From d90111ff3ff343b1a679c75c0d01b36b2cd0c6f0 Mon Sep 17 00:00:00 2001
From: Kevin Eady <8634912+KevinEady@users.noreply.github.com>
Date: Sat, 25 May 2024 21:26:45 +0200
Subject: [PATCH 01/19] src: introduce `NogcEnv`; support for nogc finalizers

---
 napi-inl.h                    | 262 ++++++++++++++++++++++------------
 napi.h                        |  99 +++++++++----
 test/array_buffer.cc          |   4 +-
 test/buffer.cc                |  15 +-
 test/buffer_new_or_copy-inl.h |  15 +-
 test/external.cc              |  28 +++-
 test/movable_callbacks.cc     |  11 +-
 test/object/finalizer.cc      |   8 ++
 test/objectwrap.cc            |  25 +++-
 9 files changed, 323 insertions(+), 144 deletions(-)

diff --git a/napi-inl.h b/napi-inl.h
index eb520a022..4b071ce5e 100644
--- a/napi-inl.h
+++ b/napi-inl.h
@@ -34,25 +34,10 @@ namespace details {
 // Node.js releases. Only necessary when they are used in napi.h and napi-inl.h.
 constexpr int napi_no_external_buffers_allowed = 22;
 
-#if (defined(NAPI_EXPERIMENTAL) &&                                             \
-     defined(NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER))
-template <napi_finalize finalizer>
-inline void PostFinalizerWrapper(node_api_nogc_env nogc_env,
-                                 void* data,
-                                 void* hint) {
-  napi_status status = node_api_post_finalizer(nogc_env, finalizer, data, hint);
-  NAPI_FATAL_IF_FAILED(
-      status, "PostFinalizerWrapper", "node_api_post_finalizer failed");
-}
-#else
-template <napi_finalize finalizer>
-inline void PostFinalizerWrapper(napi_env env, void* data, void* hint) {
-  finalizer(env, data, hint);
-}
-#endif
-
 template <typename FreeType>
-inline void default_finalizer(napi_env /*env*/, void* data, void* /*hint*/) {
+inline void default_finalizer(NODE_ADDON_API_NOGC_ENV /*env*/,
+                              void* data,
+                              void* /*hint*/) {
   delete static_cast<FreeType*>(data);
 }
 
@@ -61,7 +46,7 @@ inline void default_finalizer(napi_env /*env*/, void* data, void* /*hint*/) {
 // TODO: Replace this code with `napi_add_finalizer()` whenever it becomes
 // available on all supported versions of Node.js.
 template <typename FreeType,
-          napi_finalize finalizer = default_finalizer<FreeType>>
+          NODE_ADDON_API_NOGC_FINALIZER finalizer = default_finalizer<FreeType>>
 inline napi_status AttachData(napi_env env,
                               napi_value obj,
                               FreeType* data,
@@ -85,8 +70,7 @@ inline napi_status AttachData(napi_env env,
     }
   }
 #else  // NAPI_VERSION >= 5
-  status = napi_add_finalizer(
-      env, obj, data, details::PostFinalizerWrapper<finalizer>, hint, nullptr);
+  status = napi_add_finalizer(env, obj, data, finalizer, hint, nullptr);
 #endif
   return status;
 }
@@ -206,23 +190,52 @@ napi_value TemplatedInstanceVoidCallback(napi_env env, napi_callback_info info)
 
 template <typename T, typename Finalizer, typename Hint = void>
 struct FinalizeData {
-  static inline void Wrapper(napi_env env,
+  static inline void Wrapper(NODE_ADDON_API_NOGC_ENV env,
                              void* data,
                              void* finalizeHint) NAPI_NOEXCEPT {
     WrapVoidCallback([&] {
       FinalizeData* finalizeData = static_cast<FinalizeData*>(finalizeHint);
-      finalizeData->callback(Env(env), static_cast<T*>(data));
+      finalizeData->callback(env, static_cast<T*>(data));
       delete finalizeData;
     });
   }
 
-  static inline void WrapperWithHint(napi_env env,
+  static inline void WrapperWithHint(NODE_ADDON_API_NOGC_ENV env,
                                      void* data,
                                      void* finalizeHint) NAPI_NOEXCEPT {
     WrapVoidCallback([&] {
       FinalizeData* finalizeData = static_cast<FinalizeData*>(finalizeHint);
-      finalizeData->callback(
-          Env(env), static_cast<T*>(data), finalizeData->hint);
+      finalizeData->callback(env, static_cast<T*>(data), finalizeData->hint);
+      delete finalizeData;
+    });
+  }
+
+  static inline void WrapperGCWithoutData(napi_env env,
+                                          void* /*data*/,
+                                          void* finalizeHint) NAPI_NOEXCEPT {
+    WrapVoidCallback([&] {
+      FinalizeData* finalizeData = static_cast<FinalizeData*>(finalizeHint);
+      finalizeData->callback(env);
+      delete finalizeData;
+    });
+  }
+
+  static inline void WrapperGC(napi_env env,
+                               void* data,
+                               void* finalizeHint) NAPI_NOEXCEPT {
+    WrapVoidCallback([&] {
+      FinalizeData* finalizeData = static_cast<FinalizeData*>(finalizeHint);
+      finalizeData->callback(env, static_cast<T*>(data));
+      delete finalizeData;
+    });
+  }
+
+  static inline void WrapperGCWithHint(napi_env env,
+                                       void* data,
+                                       void* finalizeHint) NAPI_NOEXCEPT {
+    WrapVoidCallback([&] {
+      FinalizeData* finalizeData = static_cast<FinalizeData*>(finalizeHint);
+      finalizeData->callback(env, static_cast<T*>(data), finalizeData->hint);
       delete finalizeData;
     });
   }
@@ -482,15 +495,21 @@ inline Maybe<T> Just(const T& t) {
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-// Env class
+// NogcEnv / Env class
 ////////////////////////////////////////////////////////////////////////////////
 
-inline Env::Env(napi_env env) : _env(env) {}
+inline NogcEnv::NogcEnv(NODE_ADDON_API_NOGC_ENV env) : _env(env) {}
 
-inline Env::operator napi_env() const {
+inline NogcEnv::operator NODE_ADDON_API_NOGC_ENV() const {
   return _env;
 }
 
+inline Env::Env(napi_env env) : NogcEnv(env) {}
+
+inline Env::operator napi_env() const {
+  return const_cast<napi_env>(_env);
+}
+
 inline Object Env::Global() const {
   napi_value value;
   napi_status status = napi_get_global(*this, &value);
@@ -514,7 +533,7 @@ inline Value Env::Null() const {
 
 inline bool Env::IsExceptionPending() const {
   bool result;
-  napi_status status = napi_is_exception_pending(_env, &result);
+  napi_status status = napi_is_exception_pending(*this, &result);
   if (status != napi_ok)
     result = false;  // Checking for a pending exception shouldn't throw.
   return result;
@@ -522,16 +541,16 @@ inline bool Env::IsExceptionPending() const {
 
 inline Error Env::GetAndClearPendingException() const {
   napi_value value;
-  napi_status status = napi_get_and_clear_last_exception(_env, &value);
+  napi_status status = napi_get_and_clear_last_exception(*this, &value);
   if (status != napi_ok) {
     // Don't throw another exception when failing to get the exception!
     return Error();
   }
-  return Error(_env, value);
+  return Error(*this, value);
 }
 
 inline MaybeOrValue<Value> Env::RunScript(const char* utf8script) const {
-  String script = String::New(_env, utf8script);
+  String script = String::New(*this, utf8script);
   return RunScript(script);
 }
 
@@ -541,25 +560,25 @@ inline MaybeOrValue<Value> Env::RunScript(const std::string& utf8script) const {
 
 inline MaybeOrValue<Value> Env::RunScript(String script) const {
   napi_value result;
-  napi_status status = napi_run_script(_env, script, &result);
+  napi_status status = napi_run_script(*this, script, &result);
   NAPI_RETURN_OR_THROW_IF_FAILED(
-      _env, status, Napi::Value(_env, result), Napi::Value);
+      *this, status, Napi::Value(*this, result), Napi::Value);
 }
 
 #if NAPI_VERSION > 2
 template <typename Hook, typename Arg>
-void Env::CleanupHook<Hook, Arg>::Wrapper(void* data) NAPI_NOEXCEPT {
+void NogcEnv::CleanupHook<Hook, Arg>::Wrapper(void* data) NAPI_NOEXCEPT {
   auto* cleanupData =
-      static_cast<typename Napi::Env::CleanupHook<Hook, Arg>::CleanupData*>(
+      static_cast<typename Napi::NogcEnv::CleanupHook<Hook, Arg>::CleanupData*>(
           data);
   cleanupData->hook();
   delete cleanupData;
 }
 
 template <typename Hook, typename Arg>
-void Env::CleanupHook<Hook, Arg>::WrapperWithArg(void* data) NAPI_NOEXCEPT {
+void NogcEnv::CleanupHook<Hook, Arg>::WrapperWithArg(void* data) NAPI_NOEXCEPT {
   auto* cleanupData =
-      static_cast<typename Napi::Env::CleanupHook<Hook, Arg>::CleanupData*>(
+      static_cast<typename Napi::NogcEnv::CleanupHook<Hook, Arg>::CleanupData*>(
           data);
   cleanupData->hook(static_cast<Arg*>(cleanupData->arg));
   delete cleanupData;
@@ -567,20 +586,22 @@ void Env::CleanupHook<Hook, Arg>::WrapperWithArg(void* data) NAPI_NOEXCEPT {
 #endif  // NAPI_VERSION > 2
 
 #if NAPI_VERSION > 5
-template <typename T, Env::Finalizer<T> fini>
-inline void Env::SetInstanceData(T* data) const {
+template <typename T, NogcEnv::GcFinalizer<T> gc_fini>
+inline void NogcEnv::SetInstanceData(T* data) const {
   napi_status status = napi_set_instance_data(
       _env,
       data,
-      [](napi_env env, void* data, void*) { fini(env, static_cast<T*>(data)); },
+      [](napi_env env, void* data, void*) {
+        gc_fini(env, static_cast<T*>(data));
+      },
       nullptr);
-  NAPI_THROW_IF_FAILED_VOID(_env, status);
+  NAPI_FATAL_IF_FAILED(status, "NogcEnv::SetInstanceData", "invalid arguments");
 }
 
 template <typename DataType,
           typename HintType,
-          Napi::Env::FinalizerWithHint<DataType, HintType> fini>
-inline void Env::SetInstanceData(DataType* data, HintType* hint) const {
+          Napi::NogcEnv::FinalizerWithHint<DataType, HintType> fini>
+inline void NogcEnv::SetInstanceData(DataType* data, HintType* hint) const {
   napi_status status = napi_set_instance_data(
       _env,
       data,
@@ -588,35 +609,36 @@ inline void Env::SetInstanceData(DataType* data, HintType* hint) const {
         fini(env, static_cast<DataType*>(data), static_cast<HintType*>(hint));
       },
       hint);
-  NAPI_THROW_IF_FAILED_VOID(_env, status);
+  NAPI_FATAL_IF_FAILED(status, "NogcEnv::SetInstanceData", "invalid arguments");
 }
 
 template <typename T>
-inline T* Env::GetInstanceData() const {
+inline T* NogcEnv::GetInstanceData() const {
   void* data = nullptr;
 
   napi_status status = napi_get_instance_data(_env, &data);
-  NAPI_THROW_IF_FAILED(_env, status, nullptr);
+  NAPI_FATAL_IF_FAILED(status, "NogcEnv::GetInstanceData", "invalid arguments");
 
   return static_cast<T*>(data);
 }
 
 template <typename T>
-void Env::DefaultFini(Env, T* data) {
+void NogcEnv::DefaultGcFini(Env, T* data) {
   delete data;
 }
 
 template <typename DataType, typename HintType>
-void Env::DefaultFiniWithHint(Env, DataType* data, HintType*) {
+void NogcEnv::DefaultGcFiniWithHint(Env, DataType* data, HintType*) {
   delete data;
 }
 #endif  // NAPI_VERSION > 5
 
 #if NAPI_VERSION > 8
-inline const char* Env::GetModuleFileName() const {
+inline const char* NogcEnv::GetModuleFileName() const {
   const char* result;
   napi_status status = node_api_get_module_file_name(_env, &result);
-  NAPI_THROW_IF_FAILED(*this, status, nullptr);
+  NAPI_FATAL_IF_FAILED(
+      status, "NogcEnv::GetModuleFileName", "invalid arguments");
   return result;
 }
 #endif  // NAPI_VERSION > 8
@@ -1805,8 +1827,7 @@ inline External<T> External<T>::New(napi_env env,
   napi_status status =
       napi_create_external(env,
                            data,
-                           details::PostFinalizerWrapper<
-                               details::FinalizeData<T, Finalizer>::Wrapper>,
+                           details::FinalizeData<T, Finalizer>::Wrapper,
                            finalizeData,
                            &value);
   if (status != napi_ok) {
@@ -1829,8 +1850,7 @@ inline External<T> External<T>::New(napi_env env,
   napi_status status = napi_create_external(
       env,
       data,
-      details::PostFinalizerWrapper<
-          details::FinalizeData<T, Finalizer, Hint>::WrapperWithHint>,
+      details::FinalizeData<T, Finalizer, Hint>::WrapperWithHint,
       finalizeData,
       &value);
   if (status != napi_ok) {
@@ -1941,8 +1961,7 @@ inline ArrayBuffer ArrayBuffer::New(napi_env env,
       env,
       externalData,
       byteLength,
-      details::PostFinalizerWrapper<
-          details::FinalizeData<void, Finalizer>::Wrapper>,
+      details::FinalizeData<void, Finalizer>::Wrapper,
       finalizeData,
       &value);
   if (status != napi_ok) {
@@ -1967,8 +1986,7 @@ inline ArrayBuffer ArrayBuffer::New(napi_env env,
       env,
       externalData,
       byteLength,
-      details::PostFinalizerWrapper<
-          details::FinalizeData<void, Finalizer, Hint>::WrapperWithHint>,
+      details::FinalizeData<void, Finalizer, Hint>::WrapperWithHint,
       finalizeData,
       &value);
   if (status != napi_ok) {
@@ -2684,14 +2702,13 @@ inline Buffer<T> Buffer<T>::New(napi_env env,
   details::FinalizeData<T, Finalizer>* finalizeData =
       new details::FinalizeData<T, Finalizer>(
           {std::move(finalizeCallback), nullptr});
-  napi_status status = napi_create_external_buffer(
-      env,
-      length * sizeof(T),
-      data,
-      details::PostFinalizerWrapper<
-          details::FinalizeData<T, Finalizer>::Wrapper>,
-      finalizeData,
-      &value);
+  napi_status status =
+      napi_create_external_buffer(env,
+                                  length * sizeof(T),
+                                  data,
+                                  details::FinalizeData<T, Finalizer>::Wrapper,
+                                  finalizeData,
+                                  &value);
   if (status != napi_ok) {
     delete finalizeData;
     NAPI_THROW_IF_FAILED(env, status, Buffer());
@@ -2714,8 +2731,7 @@ inline Buffer<T> Buffer<T>::New(napi_env env,
       env,
       length * sizeof(T),
       data,
-      details::PostFinalizerWrapper<
-          details::FinalizeData<T, Finalizer, Hint>::WrapperWithHint>,
+      details::FinalizeData<T, Finalizer, Hint>::WrapperWithHint,
       finalizeData,
       &value);
   if (status != napi_ok) {
@@ -2754,14 +2770,13 @@ inline Buffer<T> Buffer<T>::NewOrCopy(napi_env env,
           {std::move(finalizeCallback), nullptr});
 #ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED
   napi_value value;
-  napi_status status = napi_create_external_buffer(
-      env,
-      length * sizeof(T),
-      data,
-      details::PostFinalizerWrapper<
-          details::FinalizeData<T, Finalizer>::Wrapper>,
-      finalizeData,
-      &value);
+  napi_status status =
+      napi_create_external_buffer(env,
+                                  length * sizeof(T),
+                                  data,
+                                  details::FinalizeData<T, Finalizer>::Wrapper,
+                                  finalizeData,
+                                  &value);
   if (status == details::napi_no_external_buffers_allowed) {
 #endif  // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED
     // If we can't create an external buffer, we'll just copy the data.
@@ -2794,8 +2809,7 @@ inline Buffer<T> Buffer<T>::NewOrCopy(napi_env env,
       env,
       length * sizeof(T),
       data,
-      details::PostFinalizerWrapper<
-          details::FinalizeData<T, Finalizer, Hint>::WrapperWithHint>,
+      details::FinalizeData<T, Finalizer, Hint>::WrapperWithHint,
       finalizeData,
       &value);
   if (status == details::napi_no_external_buffers_allowed) {
@@ -3230,6 +3244,7 @@ inline Reference<T>::Reference(napi_env env, napi_ref ref)
 
 template <typename T>
 inline Reference<T>::~Reference() {
+#ifndef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
   if (_ref != nullptr) {
     if (!_suppressDestruct) {
       napi_delete_reference(_env, _ref);
@@ -3237,6 +3252,7 @@ inline Reference<T>::~Reference() {
 
     _ref = nullptr;
   }
+#endif  // NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
 }
 
 template <typename T>
@@ -4469,12 +4485,7 @@ inline ObjectWrap<T>::ObjectWrap(const Napi::CallbackInfo& callbackInfo) {
   napi_status status;
   napi_ref ref;
   T* instance = static_cast<T*>(this);
-  status = napi_wrap(env,
-                     wrapper,
-                     instance,
-                     details::PostFinalizerWrapper<FinalizeCallback>,
-                     nullptr,
-                     &ref);
+  status = napi_wrap(env, wrapper, instance, FinalizeCallback, nullptr, &ref);
   NAPI_THROW_IF_FAILED_VOID(env, status);
 
   Reference<Object>* instanceRef = instance;
@@ -4835,7 +4846,7 @@ inline Value ObjectWrap<T>::OnCalledAsFunction(
 }
 
 template <typename T>
-inline void ObjectWrap<T>::Finalize(Napi::Env /*env*/) {}
+inline void ObjectWrap<T>::Finalize(NODE_ADDON_API_NOGC_ENV_CLASS /*env*/) {}
 
 template <typename T>
 inline napi_value ObjectWrap<T>::ConstructorCallbackWrapper(
@@ -4922,12 +4933,19 @@ inline napi_value ObjectWrap<T>::StaticSetterCallbackWrapper(
 }
 
 template <typename T>
-inline void ObjectWrap<T>::FinalizeCallback(napi_env env,
+inline void ObjectWrap<T>::FinalizeCallback(NODE_ADDON_API_NOGC_ENV env,
                                             void* data,
                                             void* /*hint*/) {
+#ifndef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
   HandleScope scope(env);
+#endif
+
   T* instance = static_cast<T*>(data);
-  instance->Finalize(Napi::Env(env));
+  instance->Finalize(env);
+
+  // Prevent ~ObjectWrap from calling napi_remove_wrap
+  instance->_ref = nullptr;
+
   delete instance;
 }
 
@@ -6605,12 +6623,12 @@ inline Napi::Object Addon<T>::DefineProperties(
 
 #if NAPI_VERSION > 2
 template <typename Hook, typename Arg>
-Env::CleanupHook<Hook, Arg> Env::AddCleanupHook(Hook hook, Arg* arg) {
+Env::CleanupHook<Hook, Arg> NogcEnv::AddCleanupHook(Hook hook, Arg* arg) {
   return CleanupHook<Hook, Arg>(*this, hook, arg);
 }
 
 template <typename Hook>
-Env::CleanupHook<Hook> Env::AddCleanupHook(Hook hook) {
+Env::CleanupHook<Hook> NogcEnv::AddCleanupHook(Hook hook) {
   return CleanupHook<Hook>(*this, hook);
 }
 
@@ -6620,7 +6638,7 @@ Env::CleanupHook<Hook, Arg>::CleanupHook() {
 }
 
 template <typename Hook, typename Arg>
-Env::CleanupHook<Hook, Arg>::CleanupHook(Napi::Env env, Hook hook)
+Env::CleanupHook<Hook, Arg>::CleanupHook(Napi::NogcEnv env, Hook hook)
     : wrapper(Env::CleanupHook<Hook, Arg>::Wrapper) {
   data = new CleanupData{std::move(hook), nullptr};
   napi_status status = napi_add_env_cleanup_hook(env, wrapper, data);
@@ -6631,7 +6649,7 @@ Env::CleanupHook<Hook, Arg>::CleanupHook(Napi::Env env, Hook hook)
 }
 
 template <typename Hook, typename Arg>
-Env::CleanupHook<Hook, Arg>::CleanupHook(Napi::Env env, Hook hook, Arg* arg)
+Env::CleanupHook<Hook, Arg>::CleanupHook(Napi::NogcEnv env, Hook hook, Arg* arg)
     : wrapper(Env::CleanupHook<Hook, Arg>::WrapperWithArg) {
   data = new CleanupData{std::move(hook), arg};
   napi_status status = napi_add_env_cleanup_hook(env, wrapper, data);
@@ -6642,7 +6660,7 @@ Env::CleanupHook<Hook, Arg>::CleanupHook(Napi::Env env, Hook hook, Arg* arg)
 }
 
 template <class Hook, class Arg>
-bool Env::CleanupHook<Hook, Arg>::Remove(Env env) {
+bool Env::CleanupHook<Hook, Arg>::Remove(NogcEnv env) {
   napi_status status = napi_remove_env_cleanup_hook(env, wrapper, data);
   delete data;
   data = nullptr;
@@ -6655,6 +6673,62 @@ bool Env::CleanupHook<Hook, Arg>::IsEmpty() const {
 }
 #endif  // NAPI_VERSION > 2
 
+#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
+template <typename Finalizer>
+inline void NogcEnv::AddPostFinalizer(Finalizer finalizeCallback) const {
+  using T = void*;
+  details::FinalizeData<T, Finalizer>* finalizeData =
+      new details::FinalizeData<T, Finalizer>(
+          {std::move(finalizeCallback), nullptr});
+
+  napi_status status = node_api_post_finalizer(
+      _env,
+      details::FinalizeData<T, Finalizer>::WrapperGCWithoutData,
+      static_cast<void*>(nullptr),
+      finalizeData);
+  if (status != napi_ok) {
+    delete finalizeData;
+    NAPI_FATAL_IF_FAILED(
+        status, "NogcEnv::AddPostFinalizer", "invalid arguments");
+  }
+}
+
+template <typename Finalizer, typename T>
+inline void NogcEnv::AddPostFinalizer(Finalizer finalizeCallback,
+                                      T* data) const {
+  details::FinalizeData<T, Finalizer>* finalizeData =
+      new details::FinalizeData<T, Finalizer>(
+          {std::move(finalizeCallback), nullptr});
+
+  napi_status status = node_api_post_finalizer(
+      _env, details::FinalizeData<T, Finalizer>::WrapperGC, data, finalizeData);
+  if (status != napi_ok) {
+    delete finalizeData;
+    NAPI_FATAL_IF_FAILED(
+        status, "NogcEnv::AddPostFinalizer", "invalid arguments");
+  }
+}
+
+template <typename Finalizer, typename T, typename Hint>
+inline void NogcEnv::AddPostFinalizer(Finalizer finalizeCallback,
+                                      T* data,
+                                      Hint* finalizeHint) const {
+  details::FinalizeData<T, Finalizer, Hint>* finalizeData =
+      new details::FinalizeData<T, Finalizer, Hint>(
+          {std::move(finalizeCallback), finalizeHint});
+  napi_status status = node_api_post_finalizer(
+      _env,
+      details::FinalizeData<T, Finalizer, Hint>::WrapperGCWithHint,
+      data,
+      finalizeData);
+  if (status != napi_ok) {
+    delete finalizeData;
+    NAPI_FATAL_IF_FAILED(
+        status, "NogcEnv::AddPostFinalizer", "invalid arguments");
+  }
+}
+#endif  // NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
+
 #ifdef NAPI_CPP_CUSTOM_NAMESPACE
 }  // namespace NAPI_CPP_CUSTOM_NAMESPACE
 #endif
diff --git a/napi.h b/napi.h
index 24298ad9a..d68486fc4 100644
--- a/napi.h
+++ b/napi.h
@@ -187,6 +187,7 @@ namespace NAPI_CPP_CUSTOM_NAMESPACE {
 #endif
 
 // Forward declarations
+class NogcEnv;
 class Env;
 class Value;
 class Boolean;
@@ -299,6 +300,16 @@ template <typename T>
 using MaybeOrValue = T;
 #endif
 
+#if defined(NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER)
+#define NODE_ADDON_API_NOGC_ENV node_api_nogc_env
+#define NODE_ADDON_API_NOGC_ENV_CLASS Napi::NogcEnv
+#define NODE_ADDON_API_NOGC_FINALIZER node_api_nogc_finalize
+#else
+#define NODE_ADDON_API_NOGC_ENV napi_env
+#define NODE_ADDON_API_NOGC_ENV_CLASS Napi::Env
+#define NODE_ADDON_API_NOGC_FINALIZER napi_finalize
+#endif
+
 /// Environment for Node-API values and operations.
 ///
 /// All Node-API values and operations must be associated with an environment.
@@ -312,30 +323,31 @@ using MaybeOrValue = T;
 ///
 /// In the V8 JavaScript engine, a Node-API environment approximately
 /// corresponds to an Isolate.
-class Env {
- private:
-  napi_env _env;
+class NogcEnv {
+ protected:
+  NODE_ADDON_API_NOGC_ENV _env;
 #if NAPI_VERSION > 5
   template <typename T>
-  static void DefaultFini(Env, T* data);
+  static void DefaultGcFini(Env, T* data);
   template <typename DataType, typename HintType>
-  static void DefaultFiniWithHint(Env, DataType* data, HintType* hint);
+  static void DefaultGcFiniWithHint(Env, DataType* data, HintType* hint);
 #endif  // NAPI_VERSION > 5
  public:
-  Env(napi_env env);
-
-  operator napi_env() const;
-
-  Object Global() const;
-  Value Undefined() const;
-  Value Null() const;
-
-  bool IsExceptionPending() const;
-  Error GetAndClearPendingException() const;
-
-  MaybeOrValue<Value> RunScript(const char* utf8script) const;
-  MaybeOrValue<Value> RunScript(const std::string& utf8script) const;
-  MaybeOrValue<Value> RunScript(String script) const;
+  NogcEnv(NODE_ADDON_API_NOGC_ENV env);
+  operator NODE_ADDON_API_NOGC_ENV() const;
+
+  // Without these operator overloads, the error:
+  //
+  //    Use of overloaded operator '==' is ambiguous (with operand types
+  //    'Napi::Env' and 'Napi::Env')
+  //
+  // ... occurs when comparing foo.Env() == bar.Env() or foo.Env() == nullptr
+  bool operator==(const NogcEnv& other) const {
+    return _env == other._env;
+  };
+  bool operator==(const std::nullptr_t& /*other*/) const {
+    return _env == nullptr;
+  };
 
 #if NAPI_VERSION > 2
   template <typename Hook, typename Arg = void>
@@ -353,8 +365,8 @@ class Env {
   T* GetInstanceData() const;
 
   template <typename T>
-  using Finalizer = void (*)(Env, T*);
-  template <typename T, Finalizer<T> fini = Env::DefaultFini<T>>
+  using GcFinalizer = void (*)(Env, T*);
+  template <typename T, GcFinalizer<T> gc_fini = NogcEnv::DefaultGcFini<T>>
   void SetInstanceData(T* data) const;
 
   template <typename DataType, typename HintType>
@@ -362,7 +374,7 @@ class Env {
   template <typename DataType,
             typename HintType,
             FinalizerWithHint<DataType, HintType> fini =
-                Env::DefaultFiniWithHint<DataType, HintType>>
+                NogcEnv::DefaultGcFiniWithHint<DataType, HintType>>
   void SetInstanceData(DataType* data, HintType* hint) const;
 #endif  // NAPI_VERSION > 5
 
@@ -371,9 +383,9 @@ class Env {
   class CleanupHook {
    public:
     CleanupHook();
-    CleanupHook(Env env, Hook hook, Arg* arg);
-    CleanupHook(Env env, Hook hook);
-    bool Remove(Env env);
+    CleanupHook(NogcEnv env, Hook hook, Arg* arg);
+    CleanupHook(NogcEnv env, Hook hook);
+    bool Remove(NogcEnv env);
     bool IsEmpty() const;
 
    private:
@@ -391,6 +403,37 @@ class Env {
 #if NAPI_VERSION > 8
   const char* GetModuleFileName() const;
 #endif  // NAPI_VERSION > 8
+
+#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
+  template <typename Finalizer>
+  inline void AddPostFinalizer(Finalizer finalizeCallback) const;
+
+  template <typename Finalizer, typename T>
+  inline void AddPostFinalizer(Finalizer finalizeCallback, T* data) const;
+
+  template <typename Finalizer, typename T, typename Hint>
+  inline void AddPostFinalizer(Finalizer finalizeCallback,
+                               T* data,
+                               Hint* finalizeHint) const;
+#endif  // NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
+};
+
+class Env : public NogcEnv {
+ public:
+  Env(napi_env env);
+
+  operator napi_env() const;
+
+  Object Global() const;
+  Value Undefined() const;
+  Value Null() const;
+
+  bool IsExceptionPending() const;
+  Error GetAndClearPendingException() const;
+
+  MaybeOrValue<Value> RunScript(const char* utf8script) const;
+  MaybeOrValue<Value> RunScript(const std::string& utf8script) const;
+  MaybeOrValue<Value> RunScript(String script) const;
 };
 
 /// A JavaScript value of unknown type.
@@ -2414,7 +2457,7 @@ class ObjectWrap : public InstanceWrap<T>, public Reference<Object> {
       Napi::Value value,
       napi_property_attributes attributes = napi_default);
   static Napi::Value OnCalledAsFunction(const Napi::CallbackInfo& callbackInfo);
-  virtual void Finalize(Napi::Env env);
+  virtual void Finalize(NODE_ADDON_API_NOGC_ENV_CLASS env);
 
  private:
   using This = ObjectWrap<T>;
@@ -2429,7 +2472,9 @@ class ObjectWrap : public InstanceWrap<T>, public Reference<Object> {
                                                 napi_callback_info info);
   static napi_value StaticSetterCallbackWrapper(napi_env env,
                                                 napi_callback_info info);
-  static void FinalizeCallback(napi_env env, void* data, void* hint);
+  static void FinalizeCallback(NODE_ADDON_API_NOGC_ENV env,
+                               void* data,
+                               void* hint);
   static Function DefineClass(Napi::Env env,
                               const char* utf8name,
                               const size_t props_count,
diff --git a/test/array_buffer.cc b/test/array_buffer.cc
index 2b07bd250..58edd2e86 100644
--- a/test/array_buffer.cc
+++ b/test/array_buffer.cc
@@ -63,7 +63,7 @@ Value CreateExternalBufferWithFinalize(const CallbackInfo& info) {
   uint8_t* data = new uint8_t[testLength];
 
   ArrayBuffer buffer = ArrayBuffer::New(
-      info.Env(), data, testLength, [](Env /*env*/, void* finalizeData) {
+      info.Env(), data, testLength, [](NogcEnv /*env*/, void* finalizeData) {
         delete[] static_cast<uint8_t*>(finalizeData);
         finalizeCount++;
       });
@@ -94,7 +94,7 @@ Value CreateExternalBufferWithFinalizeHint(const CallbackInfo& info) {
       info.Env(),
       data,
       testLength,
-      [](Env /*env*/, void* finalizeData, char* /*finalizeHint*/) {
+      [](NogcEnv /*env*/, void* finalizeData, char* /*finalizeHint*/) {
         delete[] static_cast<uint8_t*>(finalizeData);
         finalizeCount++;
       },
diff --git a/test/buffer.cc b/test/buffer.cc
index c10300dc7..405fcdec0 100644
--- a/test/buffer.cc
+++ b/test/buffer.cc
@@ -51,11 +51,14 @@ Value CreateExternalBufferWithFinalize(const CallbackInfo& info) {
 
   uint16_t* data = new uint16_t[testLength];
 
-  Buffer<uint16_t> buffer = Buffer<uint16_t>::New(
-      info.Env(), data, testLength, [](Env /*env*/, uint16_t* finalizeData) {
-        delete[] finalizeData;
-        finalizeCount++;
-      });
+  Buffer<uint16_t> buffer =
+      Buffer<uint16_t>::New(info.Env(),
+                            data,
+                            testLength,
+                            [](NogcEnv /*env*/, uint16_t* finalizeData) {
+                              delete[] finalizeData;
+                              finalizeCount++;
+                            });
 
   if (buffer.Length() != testLength) {
     Error::New(info.Env(), "Incorrect buffer length.")
@@ -83,7 +86,7 @@ Value CreateExternalBufferWithFinalizeHint(const CallbackInfo& info) {
       info.Env(),
       data,
       testLength,
-      [](Env /*env*/, uint16_t* finalizeData, char* /*finalizeHint*/) {
+      [](NogcEnv /*env*/, uint16_t* finalizeData, char* /*finalizeHint*/) {
         delete[] finalizeData;
         finalizeCount++;
       },
diff --git a/test/buffer_new_or_copy-inl.h b/test/buffer_new_or_copy-inl.h
index 4d68fbc91..60e7a112e 100644
--- a/test/buffer_new_or_copy-inl.h
+++ b/test/buffer_new_or_copy-inl.h
@@ -24,11 +24,14 @@ Value CreateOrCopyExternalBufferWithFinalize(const CallbackInfo& info) {
   uint16_t* data = new uint16_t[testLength];
   InitData(data, testLength);
 
-  Buffer<uint16_t> buffer = Buffer<uint16_t>::NewOrCopy(
-      info.Env(), data, testLength, [](Env /*env*/, uint16_t* finalizeData) {
-        delete[] finalizeData;
-        finalizeCount++;
-      });
+  Buffer<uint16_t> buffer =
+      Buffer<uint16_t>::NewOrCopy(info.Env(),
+                                  data,
+                                  testLength,
+                                  [](NogcEnv /*env*/, uint16_t* finalizeData) {
+                                    delete[] finalizeData;
+                                    finalizeCount++;
+                                  });
 
   if (buffer.Length() != testLength) {
     Error::New(info.Env(), "Incorrect buffer length.")
@@ -51,7 +54,7 @@ Value CreateOrCopyExternalBufferWithFinalizeHint(const CallbackInfo& info) {
       info.Env(),
       data,
       testLength,
-      [](Env /*env*/, uint16_t* finalizeData, char* /*finalizeHint*/) {
+      [](NogcEnv /*env*/, uint16_t* finalizeData, char* /*finalizeHint*/) {
         delete[] finalizeData;
         finalizeCount++;
       },
diff --git a/test/external.cc b/test/external.cc
index 255b17f19..a67469e86 100644
--- a/test/external.cc
+++ b/test/external.cc
@@ -14,10 +14,11 @@ Value CreateExternal(const CallbackInfo& info) {
 
 Value CreateExternalWithFinalize(const CallbackInfo& info) {
   finalizeCount = 0;
-  return External<int>::New(info.Env(), new int(1), [](Env /*env*/, int* data) {
-    delete data;
-    finalizeCount++;
-  });
+  return External<int>::New(
+      info.Env(), new int(1), [](NogcEnv /*env*/, int* data) {
+        delete data;
+        finalizeCount++;
+      });
 }
 
 Value CreateExternalWithFinalizeHint(const CallbackInfo& info) {
@@ -26,7 +27,7 @@ Value CreateExternalWithFinalizeHint(const CallbackInfo& info) {
   return External<int>::New(
       info.Env(),
       new int(1),
-      [](Env /*env*/, int* data, char* /*hint*/) {
+      [](NogcEnv /*env*/, int* data, char* /*hint*/) {
         delete data;
         finalizeCount++;
       },
@@ -55,6 +56,21 @@ Value GetFinalizeCount(const CallbackInfo& info) {
 }
 
 Value CreateExternalWithFinalizeException(const CallbackInfo& info) {
+#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
+  auto o = External<int>::New(
+      info.Env(), new int(1), [](NogcEnv /*env*/, int* data) { delete data; });
+
+  info.Env().AddPostFinalizer([](Env env) {
+    Error error = Error::New(env, "Finalizer exception");
+#ifdef NAPI_CPP_EXCEPTIONS
+    throw error;
+#else
+    error.ThrowAsJavaScriptException();
+#endif
+  });
+
+  return o;
+#else
   return External<int>::New(info.Env(), new int(1), [](Env env, int* data) {
     Error error = Error::New(env, "Finalizer exception");
     delete data;
@@ -64,8 +80,8 @@ Value CreateExternalWithFinalizeException(const CallbackInfo& info) {
       error.ThrowAsJavaScriptException();
 #endif
   });
+#endif
 }
-
 }  // end anonymous namespace
 
 Object InitExternal(Env env) {
diff --git a/test/movable_callbacks.cc b/test/movable_callbacks.cc
index 9959d4d38..be66fed21 100644
--- a/test/movable_callbacks.cc
+++ b/test/movable_callbacks.cc
@@ -1,16 +1,25 @@
+#include <iostream>
 #include "napi.h"
 
 using namespace Napi;
 
 Value createExternal(const CallbackInfo& info) {
   FunctionReference ref = Reference<Function>::New(info[0].As<Function>(), 1);
+
+#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
+  auto env = info.Env();
+  auto ret = External<char>::New(env, nullptr);
+
+  env.AddPostFinalizer(
+      [ref = std::move(ref)](Napi::Env /*env*/) { ref.Call({}); });
+#else
   auto ret = External<char>::New(
       info.Env(),
       nullptr,
       [ref = std::move(ref)](Napi::Env /*env*/, char* /*data*/) {
         ref.Call({});
       });
-
+#endif
   return ret;
 }
 
diff --git a/test/object/finalizer.cc b/test/object/finalizer.cc
index 6122e5eb9..89e3e2af6 100644
--- a/test/object/finalizer.cc
+++ b/test/object/finalizer.cc
@@ -7,7 +7,11 @@ static int dummy;
 Value AddFinalizer(const CallbackInfo& info) {
   ObjectReference* ref = new ObjectReference;
   *ref = Persistent(Object::New(info.Env()));
+#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
+  info.Env().AddPostFinalizer(
+#else
   info[0].As<Object>().AddFinalizer(
+#endif
       [](Napi::Env /*env*/, ObjectReference* ref) {
         ref->Set("finalizerCalled", true);
         delete ref;
@@ -19,7 +23,11 @@ Value AddFinalizer(const CallbackInfo& info) {
 Value AddFinalizerWithHint(const CallbackInfo& info) {
   ObjectReference* ref = new ObjectReference;
   *ref = Persistent(Object::New(info.Env()));
+#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
+  info.Env().AddPostFinalizer(
+#else
   info[0].As<Object>().AddFinalizer(
+#endif
       [](Napi::Env /*env*/, ObjectReference* ref, int* dummy_p) {
         ref->Set("finalizerCalledWithCorrectHint", dummy_p == &dummy);
         delete ref;
diff --git a/test/objectwrap.cc b/test/objectwrap.cc
index 1ee0c9ad7..9f01e089a 100644
--- a/test/objectwrap.cc
+++ b/test/objectwrap.cc
@@ -30,7 +30,22 @@ class Test : public Napi::ObjectWrap<Test> {
  public:
   Test(const Napi::CallbackInfo& info) : Napi::ObjectWrap<Test>(info) {
     if (info.Length() > 0) {
+#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
+      info.Env().AddPostFinalizer(
+          [](Napi::Env env, Napi::FunctionReference* finalizeCb) {
+            if (finalizeCb->IsEmpty()) {
+              return;
+            }
+
+            finalizeCb->Call(env.Global(), {Napi::Boolean::New(env, true)});
+            finalizeCb->Unref();
+            delete finalizeCb;
+          },
+          new Napi::FunctionReference(
+              Napi::Persistent(info[0].As<Napi::Function>())));
+#else
       finalizeCb_ = Napi::Persistent(info[0].As<Napi::Function>());
+#endif
     }
     // Create an own instance property.
     info.This().As<Napi::Object>().DefineProperty(
@@ -50,7 +65,7 @@ class Test : public Napi::ObjectWrap<Test> {
         Env(),
         static_cast<uint8_t*>(malloc(1)),
         1,
-        [](Napi::Env, uint8_t* bufaddr) { free(bufaddr); }));
+        [](Napi::NogcEnv, uint8_t* bufaddr) { free(bufaddr); }));
   }
 
   static Napi::Value OwnPropertyGetter(const Napi::CallbackInfo& info) {
@@ -265,7 +280,12 @@ class Test : public Napi::ObjectWrap<Test> {
             }));
   }
 
-  void Finalize(Napi::Env env) {
+#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
+  void Finalize(NODE_ADDON_API_NOGC_ENV_CLASS /*env*/) override {
+    // Intentionally blank to ensure `override` works correctly.
+  }
+#else
+  void Finalize(NODE_ADDON_API_NOGC_ENV_CLASS env) override {
     if (finalizeCb_.IsEmpty()) {
       return;
     }
@@ -273,6 +293,7 @@ class Test : public Napi::ObjectWrap<Test> {
     finalizeCb_.Call(env.Global(), {Napi::Boolean::New(env, true)});
     finalizeCb_.Unref();
   }
+#endif
 
  private:
   std::string value_;

From 1ddf8f08ca395a597145a5074db261f48617bb78 Mon Sep 17 00:00:00 2001
From: Kevin Eady <8634912+KevinEady@users.noreply.github.com>
Date: Sat, 8 Jun 2024 11:39:47 +0200
Subject: [PATCH 02/19] Address comments in 8 Jun 2024 Node-API meeting: - Use
 post finalizer to delete reference in `~Reference` - Remove const ref from
 comparison operator in `NogcEnv` - Remove leftover debugging statements

---
 napi-inl.h                | 8 ++++++--
 napi.h                    | 2 +-
 test/movable_callbacks.cc | 1 -
 3 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/napi-inl.h b/napi-inl.h
index 4b071ce5e..f95e4dfcc 100644
--- a/napi-inl.h
+++ b/napi-inl.h
@@ -3244,15 +3244,19 @@ inline Reference<T>::Reference(napi_env env, napi_ref ref)
 
 template <typename T>
 inline Reference<T>::~Reference() {
-#ifndef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
   if (_ref != nullptr) {
     if (!_suppressDestruct) {
+#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
+      Env().AddPostFinalizer(
+          [](Napi::Env env, napi_ref ref) { napi_delete_reference(env, ref); },
+          _ref);
+#else
       napi_delete_reference(_env, _ref);
+#endif
     }
 
     _ref = nullptr;
   }
-#endif  // NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
 }
 
 template <typename T>
diff --git a/napi.h b/napi.h
index d68486fc4..7f6472820 100644
--- a/napi.h
+++ b/napi.h
@@ -345,7 +345,7 @@ class NogcEnv {
   bool operator==(const NogcEnv& other) const {
     return _env == other._env;
   };
-  bool operator==(const std::nullptr_t& /*other*/) const {
+  bool operator==(std::nullptr_t /*other*/) const {
     return _env == nullptr;
   };
 
diff --git a/test/movable_callbacks.cc b/test/movable_callbacks.cc
index be66fed21..20edbb8d8 100644
--- a/test/movable_callbacks.cc
+++ b/test/movable_callbacks.cc
@@ -1,4 +1,3 @@
-#include <iostream>
 #include "napi.h"
 
 using namespace Napi;

From 812d27cc1a01598c8a5bf19b703a6b277be9b253 Mon Sep 17 00:00:00 2001
From: Kevin Eady <8634912+KevinEady@users.noreply.github.com>
Date: Sat, 15 Jun 2024 18:31:44 +0200
Subject: [PATCH 03/19] sfinae FinalizeData::Wrapper

---
 napi-inl.h | 40 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 40 insertions(+)

diff --git a/napi-inl.h b/napi-inl.h
index f95e4dfcc..1cc4a5e5d 100644
--- a/napi-inl.h
+++ b/napi-inl.h
@@ -190,6 +190,11 @@ napi_value TemplatedInstanceVoidCallback(napi_env env, napi_callback_info info)
 
 template <typename T, typename Finalizer, typename Hint = void>
 struct FinalizeData {
+#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
+  template <typename F = Finalizer,
+            typename =
+                std::enable_if_t<std::is_invocable_v<F, node_api_nogc_env, T*>>>
+#endif
   static inline void Wrapper(NODE_ADDON_API_NOGC_ENV env,
                              void* data,
                              void* finalizeHint) NAPI_NOEXCEPT {
@@ -200,6 +205,26 @@ struct FinalizeData {
     });
   }
 
+#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
+  template <typename F = Finalizer,
+            typename = std::enable_if_t<
+                !std::is_invocable_v<F, node_api_nogc_env, T*>>,
+            typename = void>
+  static inline void Wrapper(NODE_ADDON_API_NOGC_ENV env,
+                             void* data,
+                             void* finalizeHint) NAPI_NOEXCEPT {
+    napi_status status =
+        node_api_post_finalizer(env, WrapperGC, data, finalizeHint);
+    NAPI_FATAL_IF_FAILED(
+        status, "PostFinalizerWrapper", "node_api_post_finalizer failed");
+  }
+#endif
+
+#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
+  template <typename F = Finalizer,
+            typename = std::enable_if_t<
+                std::is_invocable_v<F, node_api_nogc_env, T*, Hint*>>>
+#endif
   static inline void WrapperWithHint(NODE_ADDON_API_NOGC_ENV env,
                                      void* data,
                                      void* finalizeHint) NAPI_NOEXCEPT {
@@ -210,6 +235,21 @@ struct FinalizeData {
     });
   }
 
+#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
+  template <typename F = Finalizer,
+            typename = std::enable_if_t<
+                !std::is_invocable_v<F, node_api_nogc_env, T*, Hint*>>,
+            typename = void>
+  static inline void WrapperWithHint(NODE_ADDON_API_NOGC_ENV env,
+                                     void* data,
+                                     void* finalizeHint) NAPI_NOEXCEPT {
+    napi_status status =
+        node_api_post_finalizer(env, WrapperGCWithHint, data, finalizeHint);
+    NAPI_FATAL_IF_FAILED(
+        status, "PostFinalizerWrapper", "node_api_post_finalizer failed");
+  }
+#endif
+
   static inline void WrapperGCWithoutData(napi_env env,
                                           void* /*data*/,
                                           void* finalizeHint) NAPI_NOEXCEPT {

From 0ad6fcde6224416c59fdc2ffd02d8c74f92816f1 Mon Sep 17 00:00:00 2001
From: Kevin Eady <8634912+KevinEady@users.noreply.github.com>
Date: Sat, 15 Jun 2024 18:35:27 +0200
Subject: [PATCH 04/19] fix test/addon_build for experimental

---
 test/addon_build/tpl/binding.gyp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/addon_build/tpl/binding.gyp b/test/addon_build/tpl/binding.gyp
index 448fb9465..5b4f9f8ad 100644
--- a/test/addon_build/tpl/binding.gyp
+++ b/test/addon_build/tpl/binding.gyp
@@ -4,7 +4,7 @@
         "<!(node -p \"require('node-addon-api').include_dir\")"
     ],
     'variables': {
-      'NAPI_VERSION%': "<!(node -p \"process.versions.napi\")",
+      'NAPI_VERSION%': "<!(node -p \"process.env.NAPI_VERSION || process.versions.napi\")",
       'disable_deprecated': "<!(node -p \"process.env['npm_config_disable_deprecated']\")"
     },
     'conditions': [

From 64392b1a1b6dd6c0a25462d28d0fb03cbcca82bf Mon Sep 17 00:00:00 2001
From: Kevin Eady <8634912+KevinEady@users.noreply.github.com>
Date: Sat, 15 Jun 2024 20:26:10 +0200
Subject: [PATCH 05/19] sfinae ObjectWrap::Finalize

---
 napi-inl.h | 72 ++++++++++++++++++++++++++++++++++++++++++++++++------
 napi.h     |  8 +++---
 2 files changed, 69 insertions(+), 11 deletions(-)

diff --git a/napi-inl.h b/napi-inl.h
index 1cc4a5e5d..a77be9508 100644
--- a/napi-inl.h
+++ b/napi-inl.h
@@ -426,6 +426,34 @@ inline std::string StringFormat(const char* format, ...) {
   return result;
 }
 
+template <typename T>
+class HasGcFinalize {
+ private:
+  template <typename U, void (U::*)(Napi::Env)>
+  struct SFINAE {};
+  template <typename U>
+  static char test(SFINAE<U, &U::Finalize>*);
+  template <typename U>
+  static int test(...);
+
+ public:
+  static constexpr bool value = sizeof(test<T>(0)) == sizeof(char);
+};
+
+template <typename T>
+class HasNogcFinalize {
+ private:
+  template <typename U, void (U::*)(Napi::NogcEnv)>
+  struct SFINAE {};
+  template <typename U>
+  static char test(SFINAE<U, &U::Finalize>*);
+  template <typename U>
+  static int test(...);
+
+ public:
+  static constexpr bool value = sizeof(test<T>(0)) == sizeof(char);
+};
+
 }  // namespace details
 
 #ifndef NODE_ADDON_API_DISABLE_DEPRECATED
@@ -2821,7 +2849,7 @@ inline Buffer<T> Buffer<T>::NewOrCopy(napi_env env,
 #endif  // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED
     // If we can't create an external buffer, we'll just copy the data.
     Buffer<T> ret = Buffer<T>::Copy(env, data, length);
-    details::FinalizeData<T, Finalizer>::Wrapper(env, data, finalizeData);
+    details::FinalizeData<T, Finalizer>::WrapperGC(env, data, finalizeData);
     return ret;
 #ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED
   }
@@ -2856,7 +2884,7 @@ inline Buffer<T> Buffer<T>::NewOrCopy(napi_env env,
 #endif
     // If we can't create an external buffer, we'll just copy the data.
     Buffer<T> ret = Buffer<T>::Copy(env, data, length);
-    details::FinalizeData<T, Finalizer, Hint>::WrapperWithHint(
+    details::FinalizeData<T, Finalizer, Hint>::WrapperGCWithHint(
         env, data, finalizeData);
     return ret;
 #ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED
@@ -4890,7 +4918,10 @@ inline Value ObjectWrap<T>::OnCalledAsFunction(
 }
 
 template <typename T>
-inline void ObjectWrap<T>::Finalize(NODE_ADDON_API_NOGC_ENV_CLASS /*env*/) {}
+inline void ObjectWrap<T>::Finalize(Napi::Env /*env*/) {}
+
+template <typename T>
+inline void ObjectWrap<T>::Finalize(NogcEnv /*env*/) {}
 
 template <typename T>
 inline napi_value ObjectWrap<T>::ConstructorCallbackWrapper(
@@ -4980,16 +5011,41 @@ template <typename T>
 inline void ObjectWrap<T>::FinalizeCallback(NODE_ADDON_API_NOGC_ENV env,
                                             void* data,
                                             void* /*hint*/) {
-#ifndef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
-  HandleScope scope(env);
-#endif
-
   T* instance = static_cast<T*>(data);
-  instance->Finalize(env);
 
   // Prevent ~ObjectWrap from calling napi_remove_wrap
   instance->_ref = nullptr;
 
+  if constexpr (details::HasNogcFinalize<T>::value) {
+#ifndef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
+    HandleScope scope(env);
+#endif
+
+    instance->Finalize(Napi::NogcEnv(env));
+  }
+
+  if constexpr (details::HasGcFinalize<T>::value) {
+#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
+    napi_status status =
+        node_api_post_finalizer(env, PostFinalizeCallback, data, nullptr);
+    NAPI_FATAL_IF_FAILED(status,
+                         "ObjectWrap<T>::FinalizeCallback",
+                         "node_api_post_finalizer failed");
+#else
+    HandleScope scope(env);
+    PostFinalizeCallback(env, data, static_cast<void*>(nullptr));
+#endif
+  } else {
+    delete instance;
+  }
+}
+
+template <typename T>
+inline void ObjectWrap<T>::PostFinalizeCallback(napi_env env,
+                                                void* data,
+                                                void* /*hint*/) {
+  T* instance = static_cast<T*>(data);
+  instance->Finalize(Napi::Env(env));
   delete instance;
 }
 
diff --git a/napi.h b/napi.h
index 7f6472820..54e57452e 100644
--- a/napi.h
+++ b/napi.h
@@ -302,11 +302,9 @@ using MaybeOrValue = T;
 
 #if defined(NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER)
 #define NODE_ADDON_API_NOGC_ENV node_api_nogc_env
-#define NODE_ADDON_API_NOGC_ENV_CLASS Napi::NogcEnv
 #define NODE_ADDON_API_NOGC_FINALIZER node_api_nogc_finalize
 #else
 #define NODE_ADDON_API_NOGC_ENV napi_env
-#define NODE_ADDON_API_NOGC_ENV_CLASS Napi::Env
 #define NODE_ADDON_API_NOGC_FINALIZER napi_finalize
 #endif
 
@@ -2457,7 +2455,8 @@ class ObjectWrap : public InstanceWrap<T>, public Reference<Object> {
       Napi::Value value,
       napi_property_attributes attributes = napi_default);
   static Napi::Value OnCalledAsFunction(const Napi::CallbackInfo& callbackInfo);
-  virtual void Finalize(NODE_ADDON_API_NOGC_ENV_CLASS env);
+  virtual void Finalize(Napi::Env env);
+  virtual void Finalize(NogcEnv env);
 
  private:
   using This = ObjectWrap<T>;
@@ -2475,6 +2474,9 @@ class ObjectWrap : public InstanceWrap<T>, public Reference<Object> {
   static void FinalizeCallback(NODE_ADDON_API_NOGC_ENV env,
                                void* data,
                                void* hint);
+
+  static void PostFinalizeCallback(napi_env env, void* data, void* hint);
+
   static Function DefineClass(Napi::Env env,
                               const char* utf8name,
                               const size_t props_count,

From 18495a47f215566743910f0efcfc47cad8c00df4 Mon Sep 17 00:00:00 2001
From: Kevin Eady <8634912+KevinEady@users.noreply.github.com>
Date: Sat, 15 Jun 2024 20:27:10 +0200
Subject: [PATCH 06/19] revert tests

---
 test/array_buffer.cc          |  4 ++--
 test/buffer.cc                | 15 ++++++---------
 test/buffer_new_or_copy-inl.h | 15 ++++++---------
 test/external.cc              | 28 ++++++----------------------
 test/movable_callbacks.cc     | 10 +---------
 test/object/finalizer.cc      |  8 --------
 test/objectwrap.cc            | 25 ++-----------------------
 7 files changed, 23 insertions(+), 82 deletions(-)

diff --git a/test/array_buffer.cc b/test/array_buffer.cc
index 58edd2e86..2b07bd250 100644
--- a/test/array_buffer.cc
+++ b/test/array_buffer.cc
@@ -63,7 +63,7 @@ Value CreateExternalBufferWithFinalize(const CallbackInfo& info) {
   uint8_t* data = new uint8_t[testLength];
 
   ArrayBuffer buffer = ArrayBuffer::New(
-      info.Env(), data, testLength, [](NogcEnv /*env*/, void* finalizeData) {
+      info.Env(), data, testLength, [](Env /*env*/, void* finalizeData) {
         delete[] static_cast<uint8_t*>(finalizeData);
         finalizeCount++;
       });
@@ -94,7 +94,7 @@ Value CreateExternalBufferWithFinalizeHint(const CallbackInfo& info) {
       info.Env(),
       data,
       testLength,
-      [](NogcEnv /*env*/, void* finalizeData, char* /*finalizeHint*/) {
+      [](Env /*env*/, void* finalizeData, char* /*finalizeHint*/) {
         delete[] static_cast<uint8_t*>(finalizeData);
         finalizeCount++;
       },
diff --git a/test/buffer.cc b/test/buffer.cc
index 405fcdec0..c10300dc7 100644
--- a/test/buffer.cc
+++ b/test/buffer.cc
@@ -51,14 +51,11 @@ Value CreateExternalBufferWithFinalize(const CallbackInfo& info) {
 
   uint16_t* data = new uint16_t[testLength];
 
-  Buffer<uint16_t> buffer =
-      Buffer<uint16_t>::New(info.Env(),
-                            data,
-                            testLength,
-                            [](NogcEnv /*env*/, uint16_t* finalizeData) {
-                              delete[] finalizeData;
-                              finalizeCount++;
-                            });
+  Buffer<uint16_t> buffer = Buffer<uint16_t>::New(
+      info.Env(), data, testLength, [](Env /*env*/, uint16_t* finalizeData) {
+        delete[] finalizeData;
+        finalizeCount++;
+      });
 
   if (buffer.Length() != testLength) {
     Error::New(info.Env(), "Incorrect buffer length.")
@@ -86,7 +83,7 @@ Value CreateExternalBufferWithFinalizeHint(const CallbackInfo& info) {
       info.Env(),
       data,
       testLength,
-      [](NogcEnv /*env*/, uint16_t* finalizeData, char* /*finalizeHint*/) {
+      [](Env /*env*/, uint16_t* finalizeData, char* /*finalizeHint*/) {
         delete[] finalizeData;
         finalizeCount++;
       },
diff --git a/test/buffer_new_or_copy-inl.h b/test/buffer_new_or_copy-inl.h
index 60e7a112e..4d68fbc91 100644
--- a/test/buffer_new_or_copy-inl.h
+++ b/test/buffer_new_or_copy-inl.h
@@ -24,14 +24,11 @@ Value CreateOrCopyExternalBufferWithFinalize(const CallbackInfo& info) {
   uint16_t* data = new uint16_t[testLength];
   InitData(data, testLength);
 
-  Buffer<uint16_t> buffer =
-      Buffer<uint16_t>::NewOrCopy(info.Env(),
-                                  data,
-                                  testLength,
-                                  [](NogcEnv /*env*/, uint16_t* finalizeData) {
-                                    delete[] finalizeData;
-                                    finalizeCount++;
-                                  });
+  Buffer<uint16_t> buffer = Buffer<uint16_t>::NewOrCopy(
+      info.Env(), data, testLength, [](Env /*env*/, uint16_t* finalizeData) {
+        delete[] finalizeData;
+        finalizeCount++;
+      });
 
   if (buffer.Length() != testLength) {
     Error::New(info.Env(), "Incorrect buffer length.")
@@ -54,7 +51,7 @@ Value CreateOrCopyExternalBufferWithFinalizeHint(const CallbackInfo& info) {
       info.Env(),
       data,
       testLength,
-      [](NogcEnv /*env*/, uint16_t* finalizeData, char* /*finalizeHint*/) {
+      [](Env /*env*/, uint16_t* finalizeData, char* /*finalizeHint*/) {
         delete[] finalizeData;
         finalizeCount++;
       },
diff --git a/test/external.cc b/test/external.cc
index a67469e86..255b17f19 100644
--- a/test/external.cc
+++ b/test/external.cc
@@ -14,11 +14,10 @@ Value CreateExternal(const CallbackInfo& info) {
 
 Value CreateExternalWithFinalize(const CallbackInfo& info) {
   finalizeCount = 0;
-  return External<int>::New(
-      info.Env(), new int(1), [](NogcEnv /*env*/, int* data) {
-        delete data;
-        finalizeCount++;
-      });
+  return External<int>::New(info.Env(), new int(1), [](Env /*env*/, int* data) {
+    delete data;
+    finalizeCount++;
+  });
 }
 
 Value CreateExternalWithFinalizeHint(const CallbackInfo& info) {
@@ -27,7 +26,7 @@ Value CreateExternalWithFinalizeHint(const CallbackInfo& info) {
   return External<int>::New(
       info.Env(),
       new int(1),
-      [](NogcEnv /*env*/, int* data, char* /*hint*/) {
+      [](Env /*env*/, int* data, char* /*hint*/) {
         delete data;
         finalizeCount++;
       },
@@ -56,21 +55,6 @@ Value GetFinalizeCount(const CallbackInfo& info) {
 }
 
 Value CreateExternalWithFinalizeException(const CallbackInfo& info) {
-#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
-  auto o = External<int>::New(
-      info.Env(), new int(1), [](NogcEnv /*env*/, int* data) { delete data; });
-
-  info.Env().AddPostFinalizer([](Env env) {
-    Error error = Error::New(env, "Finalizer exception");
-#ifdef NAPI_CPP_EXCEPTIONS
-    throw error;
-#else
-    error.ThrowAsJavaScriptException();
-#endif
-  });
-
-  return o;
-#else
   return External<int>::New(info.Env(), new int(1), [](Env env, int* data) {
     Error error = Error::New(env, "Finalizer exception");
     delete data;
@@ -80,8 +64,8 @@ Value CreateExternalWithFinalizeException(const CallbackInfo& info) {
       error.ThrowAsJavaScriptException();
 #endif
   });
-#endif
 }
+
 }  // end anonymous namespace
 
 Object InitExternal(Env env) {
diff --git a/test/movable_callbacks.cc b/test/movable_callbacks.cc
index 20edbb8d8..9959d4d38 100644
--- a/test/movable_callbacks.cc
+++ b/test/movable_callbacks.cc
@@ -4,21 +4,13 @@ using namespace Napi;
 
 Value createExternal(const CallbackInfo& info) {
   FunctionReference ref = Reference<Function>::New(info[0].As<Function>(), 1);
-
-#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
-  auto env = info.Env();
-  auto ret = External<char>::New(env, nullptr);
-
-  env.AddPostFinalizer(
-      [ref = std::move(ref)](Napi::Env /*env*/) { ref.Call({}); });
-#else
   auto ret = External<char>::New(
       info.Env(),
       nullptr,
       [ref = std::move(ref)](Napi::Env /*env*/, char* /*data*/) {
         ref.Call({});
       });
-#endif
+
   return ret;
 }
 
diff --git a/test/object/finalizer.cc b/test/object/finalizer.cc
index 89e3e2af6..6122e5eb9 100644
--- a/test/object/finalizer.cc
+++ b/test/object/finalizer.cc
@@ -7,11 +7,7 @@ static int dummy;
 Value AddFinalizer(const CallbackInfo& info) {
   ObjectReference* ref = new ObjectReference;
   *ref = Persistent(Object::New(info.Env()));
-#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
-  info.Env().AddPostFinalizer(
-#else
   info[0].As<Object>().AddFinalizer(
-#endif
       [](Napi::Env /*env*/, ObjectReference* ref) {
         ref->Set("finalizerCalled", true);
         delete ref;
@@ -23,11 +19,7 @@ Value AddFinalizer(const CallbackInfo& info) {
 Value AddFinalizerWithHint(const CallbackInfo& info) {
   ObjectReference* ref = new ObjectReference;
   *ref = Persistent(Object::New(info.Env()));
-#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
-  info.Env().AddPostFinalizer(
-#else
   info[0].As<Object>().AddFinalizer(
-#endif
       [](Napi::Env /*env*/, ObjectReference* ref, int* dummy_p) {
         ref->Set("finalizerCalledWithCorrectHint", dummy_p == &dummy);
         delete ref;
diff --git a/test/objectwrap.cc b/test/objectwrap.cc
index 9f01e089a..1ee0c9ad7 100644
--- a/test/objectwrap.cc
+++ b/test/objectwrap.cc
@@ -30,22 +30,7 @@ class Test : public Napi::ObjectWrap<Test> {
  public:
   Test(const Napi::CallbackInfo& info) : Napi::ObjectWrap<Test>(info) {
     if (info.Length() > 0) {
-#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
-      info.Env().AddPostFinalizer(
-          [](Napi::Env env, Napi::FunctionReference* finalizeCb) {
-            if (finalizeCb->IsEmpty()) {
-              return;
-            }
-
-            finalizeCb->Call(env.Global(), {Napi::Boolean::New(env, true)});
-            finalizeCb->Unref();
-            delete finalizeCb;
-          },
-          new Napi::FunctionReference(
-              Napi::Persistent(info[0].As<Napi::Function>())));
-#else
       finalizeCb_ = Napi::Persistent(info[0].As<Napi::Function>());
-#endif
     }
     // Create an own instance property.
     info.This().As<Napi::Object>().DefineProperty(
@@ -65,7 +50,7 @@ class Test : public Napi::ObjectWrap<Test> {
         Env(),
         static_cast<uint8_t*>(malloc(1)),
         1,
-        [](Napi::NogcEnv, uint8_t* bufaddr) { free(bufaddr); }));
+        [](Napi::Env, uint8_t* bufaddr) { free(bufaddr); }));
   }
 
   static Napi::Value OwnPropertyGetter(const Napi::CallbackInfo& info) {
@@ -280,12 +265,7 @@ class Test : public Napi::ObjectWrap<Test> {
             }));
   }
 
-#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
-  void Finalize(NODE_ADDON_API_NOGC_ENV_CLASS /*env*/) override {
-    // Intentionally blank to ensure `override` works correctly.
-  }
-#else
-  void Finalize(NODE_ADDON_API_NOGC_ENV_CLASS env) override {
+  void Finalize(Napi::Env env) {
     if (finalizeCb_.IsEmpty()) {
       return;
     }
@@ -293,7 +273,6 @@ class Test : public Napi::ObjectWrap<Test> {
     finalizeCb_.Call(env.Global(), {Napi::Boolean::New(env, true)});
     finalizeCb_.Unref();
   }
-#endif
 
  private:
   std::string value_;

From 70a8302d65649d37cd4354392a2e9757367daec1 Mon Sep 17 00:00:00 2001
From: Kevin Eady <8634912+KevinEady@users.noreply.github.com>
Date: Sat, 15 Jun 2024 22:55:20 +0200
Subject: [PATCH 07/19] add finalizer_order test

---
 test/binding.cc         |  8 ++++
 test/binding.gyp        |  1 +
 test/finalizer_order.cc | 99 +++++++++++++++++++++++++++++++++++++++++
 test/finalizer_order.js | 66 +++++++++++++++++++++++++++
 4 files changed, 174 insertions(+)
 create mode 100644 test/finalizer_order.cc
 create mode 100644 test/finalizer_order.js

diff --git a/test/binding.cc b/test/binding.cc
index 0415c69bc..9e5aaaaa6 100644
--- a/test/binding.cc
+++ b/test/binding.cc
@@ -86,6 +86,7 @@ Object InitEnvMiscellaneous(Env env);
 #if defined(NODE_ADDON_API_ENABLE_MAYBE)
 Object InitMaybeCheck(Env env);
 #endif
+Object InitFinalizerOrder(Env env);
 
 Object Init(Env env, Object exports) {
 #if (NAPI_VERSION > 5)
@@ -186,6 +187,13 @@ Object Init(Env env, Object exports) {
 #if defined(NODE_ADDON_API_ENABLE_MAYBE)
   exports.Set("maybe_check", InitMaybeCheck(env));
 #endif
+
+  exports.Set("finalizer_order", InitFinalizerOrder(env));
+
+  exports.Set(
+      "isExperimental",
+      Napi::Boolean::New(env, NAPI_VERSION == NAPI_VERSION_EXPERIMENTAL));
+
   return exports;
 }
 
diff --git a/test/binding.gyp b/test/binding.gyp
index e7bf253d0..28de3fe96 100644
--- a/test/binding.gyp
+++ b/test/binding.gyp
@@ -30,6 +30,7 @@
         'error.cc',
         'error_handling_for_primitives.cc',
         'external.cc',
+        'finalizer_order.cc',
         'function.cc',
         'function_reference.cc',
         'handlescope.cc',
diff --git a/test/finalizer_order.cc b/test/finalizer_order.cc
new file mode 100644
index 000000000..e06a6cfe1
--- /dev/null
+++ b/test/finalizer_order.cc
@@ -0,0 +1,99 @@
+#include <napi.h>
+
+namespace {
+class Test : public Napi::ObjectWrap<Test> {
+ public:
+  Test(const Napi::CallbackInfo& info) : Napi::ObjectWrap<Test>(info) {
+    nogcFinalizerCalled = false;
+    gcFinalizerCalled = false;
+
+    if (info.Length() > 0) {
+      finalizeCb_ = Napi::Persistent(info[0].As<Napi::Function>());
+    }
+  }
+
+  static void Initialize(Napi::Env env, Napi::Object exports) {
+    exports.Set("Test",
+                DefineClass(env,
+                            "Test",
+                            {
+                                StaticAccessor("isNogcFinalizerCalled",
+                                               &IsNogcFinalizerCalled,
+                                               nullptr,
+                                               napi_default),
+                                StaticAccessor("isGcFinalizerCalled",
+                                               &IsGcFinalizerCalled,
+                                               nullptr,
+                                               napi_default),
+                            }));
+  }
+
+  void Finalize(Napi::NogcEnv /*env*/) { nogcFinalizerCalled = true; }
+
+  void Finalize(Napi::Env /*env*/) {
+    gcFinalizerCalled = true;
+    if (!finalizeCb_.IsEmpty()) {
+      finalizeCb_.Call({});
+    }
+  }
+
+  static Napi::Value IsNogcFinalizerCalled(const Napi::CallbackInfo& info) {
+    return Napi::Boolean::New(info.Env(), nogcFinalizerCalled);
+  }
+
+  static Napi::Value IsGcFinalizerCalled(const Napi::CallbackInfo& info) {
+    return Napi::Boolean::New(info.Env(), gcFinalizerCalled);
+  }
+
+ private:
+  Napi::FunctionReference finalizeCb_;
+
+  static bool nogcFinalizerCalled;
+  static bool gcFinalizerCalled;
+};
+
+bool Test::nogcFinalizerCalled = false;
+bool Test::gcFinalizerCalled = false;
+
+bool externalNogcFinalizerCalled = false;
+bool externalGcFinalizerCalled = false;
+
+Napi::Value CreateExternalNogc(const Napi::CallbackInfo& info) {
+  externalNogcFinalizerCalled = false;
+  return Napi::External<int>::New(
+      info.Env(), new int(1), [](Napi::NogcEnv /*env*/, int* data) {
+        externalNogcFinalizerCalled = true;
+        delete data;
+      });
+}
+
+Napi::Value CreateExternalGc(const Napi::CallbackInfo& info) {
+  externalGcFinalizerCalled = false;
+  return Napi::External<int>::New(
+      info.Env(), new int(1), [](Napi::Env /*env*/, int* data) {
+        externalGcFinalizerCalled = true;
+        delete data;
+      });
+}
+
+Napi::Value IsExternalNogcFinalizerCalled(const Napi::CallbackInfo& info) {
+  return Napi::Boolean::New(info.Env(), externalNogcFinalizerCalled);
+}
+
+Napi::Value IsExternalGcFinalizerCalled(const Napi::CallbackInfo& info) {
+  return Napi::Boolean::New(info.Env(), externalGcFinalizerCalled);
+}
+
+}  // namespace
+
+Napi::Object InitFinalizerOrder(Napi::Env env) {
+  Napi::Object exports = Napi::Object::New(env);
+  Test::Initialize(env, exports);
+  exports["CreateExternalNogc"] = Napi::Function::New(env, CreateExternalNogc);
+  exports["CreateExternalGc"] = Napi::Function::New(env, CreateExternalGc);
+  exports["isExternalNogcFinalizerCalled"] =
+      Napi::Function::New(env, IsExternalNogcFinalizerCalled);
+  exports["isExternalGcFinalizerCalled"] =
+      Napi::Function::New(env, IsExternalGcFinalizerCalled);
+  return exports;
+}
diff --git a/test/finalizer_order.js b/test/finalizer_order.js
new file mode 100644
index 000000000..91b66bf36
--- /dev/null
+++ b/test/finalizer_order.js
@@ -0,0 +1,66 @@
+'use strict';
+
+/* eslint-disable no-unused-vars */
+
+const assert = require('assert');
+const testUtil = require('./testUtil');
+
+module.exports = require('./common').runTest(test);
+
+function test (binding) {
+  const { isExperimental } = binding;
+
+  let isCallbackCalled = false;
+
+  return testUtil.runGCTests([
+    'Finalizer Order - ObjectWrap',
+    () => {
+      let test = new binding.finalizer_order.Test(() => { isCallbackCalled = true; });
+      test = null;
+
+      global.gc();
+
+      if (isExperimental) {
+        assert.strictEqual(binding.finalizer_order.Test.isNogcFinalizerCalled, true, 'Expected nogc finalizer to be called [before ticking]');
+        assert.strictEqual(binding.finalizer_order.Test.isGcFinalizerCalled, false, 'Expected gc finalizer to not be called [before ticking]');
+        assert.strictEqual(isCallbackCalled, false, 'Expected callback not be called [before ticking]');
+      } else {
+        assert.strictEqual(binding.finalizer_order.Test.isNogcFinalizerCalled, false, 'Expected nogc finalizer to not be called [before ticking]');
+        assert.strictEqual(binding.finalizer_order.Test.isGcFinalizerCalled, false, 'Expected gc finalizer to not be called [before ticking]');
+        assert.strictEqual(isCallbackCalled, false, 'Expected callback not be called [before ticking]');
+      }
+    },
+    () => {
+      assert.strictEqual(binding.finalizer_order.Test.isNogcFinalizerCalled, true, 'Expected nogc finalizer to be called [after ticking]');
+      assert.strictEqual(binding.finalizer_order.Test.isGcFinalizerCalled, true, 'Expected gc finalizer to be called [after ticking]');
+      assert.strictEqual(isCallbackCalled, true, 'Expected callback to be called [after ticking]');
+    },
+
+    'Finalizer Order - External with Nogc Finalizer',
+    () => {
+      let ext = new binding.finalizer_order.CreateExternalNogc();
+      ext = null;
+      global.gc();
+
+      if (isExperimental) {
+        assert.strictEqual(binding.finalizer_order.isExternalNogcFinalizerCalled(), true, 'Expected External nogc finalizer to be called [before ticking]');
+      } else {
+        assert.strictEqual(binding.finalizer_order.isExternalNogcFinalizerCalled(), false, 'Expected External nogc finalizer to not be called [before ticking]');
+      }
+    },
+    () => {
+      assert.strictEqual(binding.finalizer_order.isExternalNogcFinalizerCalled(), true, 'Expected External nogc finalizer to be called [after ticking]');
+    },
+
+    'Finalizer Order - External with Gc Finalizer',
+    () => {
+      let ext = new binding.finalizer_order.CreateExternalGc();
+      ext = null;
+      global.gc();
+      assert.strictEqual(binding.finalizer_order.isExternalGcFinalizerCalled(), false, 'Expected External gc finalizer to not be called [before ticking]');
+    },
+    () => {
+      assert.strictEqual(binding.finalizer_order.isExternalGcFinalizerCalled(), true, 'Expected External gc finalizer to be called [after ticking]');
+    }
+  ]);
+}

From 26972bfdc941a84d1eb09bac6f810e06d66cb05e Mon Sep 17 00:00:00 2001
From: Kevin Eady <8634912+KevinEady@users.noreply.github.com>
Date: Sun, 16 Jun 2024 14:17:23 +0200
Subject: [PATCH 08/19] remove unnecessary defines

---
 napi-inl.h | 24 ++++++++++++------------
 napi.h     | 22 ++++++----------------
 2 files changed, 18 insertions(+), 28 deletions(-)

diff --git a/napi-inl.h b/napi-inl.h
index a77be9508..930046d19 100644
--- a/napi-inl.h
+++ b/napi-inl.h
@@ -35,7 +35,7 @@ namespace details {
 constexpr int napi_no_external_buffers_allowed = 22;
 
 template <typename FreeType>
-inline void default_finalizer(NODE_ADDON_API_NOGC_ENV /*env*/,
+inline void default_finalizer(node_api_nogc_env /*env*/,
                               void* data,
                               void* /*hint*/) {
   delete static_cast<FreeType*>(data);
@@ -46,7 +46,7 @@ inline void default_finalizer(NODE_ADDON_API_NOGC_ENV /*env*/,
 // TODO: Replace this code with `napi_add_finalizer()` whenever it becomes
 // available on all supported versions of Node.js.
 template <typename FreeType,
-          NODE_ADDON_API_NOGC_FINALIZER finalizer = default_finalizer<FreeType>>
+          node_api_nogc_finalize finalizer = default_finalizer<FreeType>>
 inline napi_status AttachData(napi_env env,
                               napi_value obj,
                               FreeType* data,
@@ -195,7 +195,7 @@ struct FinalizeData {
             typename =
                 std::enable_if_t<std::is_invocable_v<F, node_api_nogc_env, T*>>>
 #endif
-  static inline void Wrapper(NODE_ADDON_API_NOGC_ENV env,
+  static inline void Wrapper(node_api_nogc_env env,
                              void* data,
                              void* finalizeHint) NAPI_NOEXCEPT {
     WrapVoidCallback([&] {
@@ -210,13 +210,13 @@ struct FinalizeData {
             typename = std::enable_if_t<
                 !std::is_invocable_v<F, node_api_nogc_env, T*>>,
             typename = void>
-  static inline void Wrapper(NODE_ADDON_API_NOGC_ENV env,
+  static inline void Wrapper(node_api_nogc_env env,
                              void* data,
                              void* finalizeHint) NAPI_NOEXCEPT {
     napi_status status =
         node_api_post_finalizer(env, WrapperGC, data, finalizeHint);
     NAPI_FATAL_IF_FAILED(
-        status, "PostFinalizerWrapper", "node_api_post_finalizer failed");
+        status, "FinalizeData::Wrapper", "node_api_post_finalizer failed");
   }
 #endif
 
@@ -225,7 +225,7 @@ struct FinalizeData {
             typename = std::enable_if_t<
                 std::is_invocable_v<F, node_api_nogc_env, T*, Hint*>>>
 #endif
-  static inline void WrapperWithHint(NODE_ADDON_API_NOGC_ENV env,
+  static inline void WrapperWithHint(node_api_nogc_env env,
                                      void* data,
                                      void* finalizeHint) NAPI_NOEXCEPT {
     WrapVoidCallback([&] {
@@ -240,13 +240,13 @@ struct FinalizeData {
             typename = std::enable_if_t<
                 !std::is_invocable_v<F, node_api_nogc_env, T*, Hint*>>,
             typename = void>
-  static inline void WrapperWithHint(NODE_ADDON_API_NOGC_ENV env,
+  static inline void WrapperWithHint(node_api_nogc_env env,
                                      void* data,
                                      void* finalizeHint) NAPI_NOEXCEPT {
     napi_status status =
         node_api_post_finalizer(env, WrapperGCWithHint, data, finalizeHint);
     NAPI_FATAL_IF_FAILED(
-        status, "PostFinalizerWrapper", "node_api_post_finalizer failed");
+        status, "FinalizeData::Wrapper", "node_api_post_finalizer failed");
   }
 #endif
 
@@ -566,9 +566,9 @@ inline Maybe<T> Just(const T& t) {
 // NogcEnv / Env class
 ////////////////////////////////////////////////////////////////////////////////
 
-inline NogcEnv::NogcEnv(NODE_ADDON_API_NOGC_ENV env) : _env(env) {}
+inline NogcEnv::NogcEnv(node_api_nogc_env env) : _env(env) {}
 
-inline NogcEnv::operator NODE_ADDON_API_NOGC_ENV() const {
+inline NogcEnv::operator node_api_nogc_env() const {
   return _env;
 }
 
@@ -668,7 +668,7 @@ inline void NogcEnv::SetInstanceData(T* data) const {
 
 template <typename DataType,
           typename HintType,
-          Napi::NogcEnv::FinalizerWithHint<DataType, HintType> fini>
+          Napi::NogcEnv::GcFinalizerWithHint<DataType, HintType> fini>
 inline void NogcEnv::SetInstanceData(DataType* data, HintType* hint) const {
   napi_status status = napi_set_instance_data(
       _env,
@@ -5008,7 +5008,7 @@ inline napi_value ObjectWrap<T>::StaticSetterCallbackWrapper(
 }
 
 template <typename T>
-inline void ObjectWrap<T>::FinalizeCallback(NODE_ADDON_API_NOGC_ENV env,
+inline void ObjectWrap<T>::FinalizeCallback(node_api_nogc_env env,
                                             void* data,
                                             void* /*hint*/) {
   T* instance = static_cast<T*>(data);
diff --git a/napi.h b/napi.h
index 54e57452e..60ca1bcb2 100644
--- a/napi.h
+++ b/napi.h
@@ -300,14 +300,6 @@ template <typename T>
 using MaybeOrValue = T;
 #endif
 
-#if defined(NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER)
-#define NODE_ADDON_API_NOGC_ENV node_api_nogc_env
-#define NODE_ADDON_API_NOGC_FINALIZER node_api_nogc_finalize
-#else
-#define NODE_ADDON_API_NOGC_ENV napi_env
-#define NODE_ADDON_API_NOGC_FINALIZER napi_finalize
-#endif
-
 /// Environment for Node-API values and operations.
 ///
 /// All Node-API values and operations must be associated with an environment.
@@ -323,7 +315,7 @@ using MaybeOrValue = T;
 /// corresponds to an Isolate.
 class NogcEnv {
  protected:
-  NODE_ADDON_API_NOGC_ENV _env;
+  node_api_nogc_env _env;
 #if NAPI_VERSION > 5
   template <typename T>
   static void DefaultGcFini(Env, T* data);
@@ -331,8 +323,8 @@ class NogcEnv {
   static void DefaultGcFiniWithHint(Env, DataType* data, HintType* hint);
 #endif  // NAPI_VERSION > 5
  public:
-  NogcEnv(NODE_ADDON_API_NOGC_ENV env);
-  operator NODE_ADDON_API_NOGC_ENV() const;
+  NogcEnv(node_api_nogc_env env);
+  operator node_api_nogc_env() const;
 
   // Without these operator overloads, the error:
   //
@@ -368,10 +360,10 @@ class NogcEnv {
   void SetInstanceData(T* data) const;
 
   template <typename DataType, typename HintType>
-  using FinalizerWithHint = void (*)(Env, DataType*, HintType*);
+  using GcFinalizerWithHint = void (*)(Env, DataType*, HintType*);
   template <typename DataType,
             typename HintType,
-            FinalizerWithHint<DataType, HintType> fini =
+            GcFinalizerWithHint<DataType, HintType> fini =
                 NogcEnv::DefaultGcFiniWithHint<DataType, HintType>>
   void SetInstanceData(DataType* data, HintType* hint) const;
 #endif  // NAPI_VERSION > 5
@@ -2471,9 +2463,7 @@ class ObjectWrap : public InstanceWrap<T>, public Reference<Object> {
                                                 napi_callback_info info);
   static napi_value StaticSetterCallbackWrapper(napi_env env,
                                                 napi_callback_info info);
-  static void FinalizeCallback(NODE_ADDON_API_NOGC_ENV env,
-                               void* data,
-                               void* hint);
+  static void FinalizeCallback(node_api_nogc_env env, void* data, void* hint);
 
   static void PostFinalizeCallback(napi_env env, void* data, void* hint);
 

From 5d93fc3c5462bcc070cdf2b828ab7ea2bc81f0f2 Mon Sep 17 00:00:00 2001
From: Kevin Eady <8634912+KevinEady@users.noreply.github.com>
Date: Thu, 20 Jun 2024 16:32:11 +0200
Subject: [PATCH 09/19] add AddPostFinalizer test

---
 test/finalizer_order.cc | 50 +++++++++++++++++++++++++++++++++++++++++
 test/finalizer_order.js | 36 +++++++++++++++++++++++++++--
 2 files changed, 84 insertions(+), 2 deletions(-)

diff --git a/test/finalizer_order.cc b/test/finalizer_order.cc
index e06a6cfe1..81f176f2b 100644
--- a/test/finalizer_order.cc
+++ b/test/finalizer_order.cc
@@ -84,6 +84,48 @@ Napi::Value IsExternalGcFinalizerCalled(const Napi::CallbackInfo& info) {
   return Napi::Boolean::New(info.Env(), externalGcFinalizerCalled);
 }
 
+#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
+Napi::Value AddPostFinalizer(const Napi::CallbackInfo& info) {
+  auto env = info.Env();
+
+  env.AddPostFinalizer(
+      [callback = Napi::Persistent(info[0].As<Napi::Function>())](
+          Napi::Env /*env*/) { callback.Call({}); });
+
+  return env.Undefined();
+}
+
+Napi::Value AddPostFinalizerWithData(const Napi::CallbackInfo& info) {
+  auto env = info.Env();
+
+  env.AddPostFinalizer(
+      [callback = Napi::Persistent(info[0].As<Napi::Function>())](
+          Napi::Env /*env*/, Napi::Reference<Napi::Value>* data) {
+        callback.Call({data->Value()});
+        delete data;
+      },
+      new Napi::Reference<Napi::Value>(Napi::Persistent(info[1])));
+  return env.Undefined();
+}
+
+Napi::Value AddPostFinalizerWithDataAndHint(const Napi::CallbackInfo& info) {
+  auto env = info.Env();
+
+  env.AddPostFinalizer(
+      [callback = Napi::Persistent(info[0].As<Napi::Function>())](
+          Napi::Env /*env*/,
+          Napi::Reference<Napi::Value>* data,
+          Napi::Reference<Napi::Value>* hint) {
+        callback.Call({data->Value(), hint->Value()});
+        delete data;
+        delete hint;
+      },
+      new Napi::Reference<Napi::Value>(Napi::Persistent(info[1])),
+      new Napi::Reference<Napi::Value>(Napi::Persistent(info[2])));
+  return env.Undefined();
+}
+#endif
+
 }  // namespace
 
 Napi::Object InitFinalizerOrder(Napi::Env env) {
@@ -95,5 +137,13 @@ Napi::Object InitFinalizerOrder(Napi::Env env) {
       Napi::Function::New(env, IsExternalNogcFinalizerCalled);
   exports["isExternalGcFinalizerCalled"] =
       Napi::Function::New(env, IsExternalGcFinalizerCalled);
+
+#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
+  exports["AddPostFinalizer"] = Napi::Function::New(env, AddPostFinalizer);
+  exports["AddPostFinalizerWithData"] =
+      Napi::Function::New(env, AddPostFinalizerWithData);
+  exports["AddPostFinalizerWithDataAndHint"] =
+      Napi::Function::New(env, AddPostFinalizerWithDataAndHint);
+#endif
   return exports;
 }
diff --git a/test/finalizer_order.js b/test/finalizer_order.js
index 91b66bf36..a8e622e6c 100644
--- a/test/finalizer_order.js
+++ b/test/finalizer_order.js
@@ -3,6 +3,7 @@
 /* eslint-disable no-unused-vars */
 
 const assert = require('assert');
+const common = require('./common');
 const testUtil = require('./testUtil');
 
 module.exports = require('./common').runTest(test);
@@ -12,7 +13,7 @@ function test (binding) {
 
   let isCallbackCalled = false;
 
-  return testUtil.runGCTests([
+  const tests = [
     'Finalizer Order - ObjectWrap',
     () => {
       let test = new binding.finalizer_order.Test(() => { isCallbackCalled = true; });
@@ -62,5 +63,36 @@ function test (binding) {
     () => {
       assert.strictEqual(binding.finalizer_order.isExternalGcFinalizerCalled(), true, 'Expected External gc finalizer to be called [after ticking]');
     }
-  ]);
+  ];
+
+  if (binding.isExperimental) {
+    tests.push(...[
+      'AddPostFinalizer',
+      () => {
+        binding.finalizer_order.AddPostFinalizer(common.mustCall());
+      },
+
+      'AddPostFinalizerWithData',
+      () => {
+        const data = {};
+        const callback = (callbackData) => {
+          assert.strictEqual(callbackData, data);
+        };
+        binding.finalizer_order.AddPostFinalizerWithData(common.mustCall(callback), data);
+      },
+
+      'AddPostFinalizerWithDataAndHint',
+      () => {
+        const data = {};
+        const hint = {};
+        const callback = (callbackData, callbackHint) => {
+          assert.strictEqual(callbackData, data);
+          assert.strictEqual(callbackHint, hint);
+        };
+        binding.finalizer_order.AddPostFinalizerWithDataAndHint(common.mustCall(callback), data, hint);
+      }
+    ]);
+  }
+
+  return testUtil.runGCTests(tests);
 }

From 94bbe5838ab89131fafd37450440b639d70fbc4f Mon Sep 17 00:00:00 2001
From: Kevin Eady <8634912+KevinEady@users.noreply.github.com>
Date: Wed, 3 Jul 2024 19:38:27 +0200
Subject: [PATCH 10/19] renaming ideas

rename NoGcEnv to BasicEnv, AddPostFinalizer to PostFinalizer, NoGc/GcFinalizer to Sync/AsyncFinalizer
---
 napi-inl.h              | 112 +++++++++++++++++++++++-----------------
 napi.h                  |  47 +++++++++--------
 test/finalizer_order.cc |  99 ++++++++++++++++++-----------------
 test/finalizer_order.js |  47 ++++++++---------
 4 files changed, 164 insertions(+), 141 deletions(-)

diff --git a/napi-inl.h b/napi-inl.h
index 930046d19..d02c4df3f 100644
--- a/napi-inl.h
+++ b/napi-inl.h
@@ -427,7 +427,7 @@ inline std::string StringFormat(const char* format, ...) {
 }
 
 template <typename T>
-class HasGcFinalize {
+class HasAsyncFinalizer {
  private:
   template <typename U, void (U::*)(Napi::Env)>
   struct SFINAE {};
@@ -441,9 +441,9 @@ class HasGcFinalize {
 };
 
 template <typename T>
-class HasNogcFinalize {
+class HasSyncFinalizer {
  private:
-  template <typename U, void (U::*)(Napi::NogcEnv)>
+  template <typename U, void (U::*)(Napi::BasicEnv)>
   struct SFINAE {};
   template <typename U>
   static char test(SFINAE<U, &U::Finalize>*);
@@ -563,16 +563,16 @@ inline Maybe<T> Just(const T& t) {
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-// NogcEnv / Env class
+// BasicEnv / Env class
 ////////////////////////////////////////////////////////////////////////////////
 
-inline NogcEnv::NogcEnv(node_api_nogc_env env) : _env(env) {}
+inline BasicEnv::BasicEnv(node_api_nogc_env env) : _env(env) {}
 
-inline NogcEnv::operator node_api_nogc_env() const {
+inline BasicEnv::operator node_api_nogc_env() const {
   return _env;
 }
 
-inline Env::Env(napi_env env) : NogcEnv(env) {}
+inline Env::Env(napi_env env) : BasicEnv(env) {}
 
 inline Env::operator napi_env() const {
   return const_cast<napi_env>(_env);
@@ -635,41 +635,41 @@ inline MaybeOrValue<Value> Env::RunScript(String script) const {
 
 #if NAPI_VERSION > 2
 template <typename Hook, typename Arg>
-void NogcEnv::CleanupHook<Hook, Arg>::Wrapper(void* data) NAPI_NOEXCEPT {
-  auto* cleanupData =
-      static_cast<typename Napi::NogcEnv::CleanupHook<Hook, Arg>::CleanupData*>(
-          data);
+void BasicEnv::CleanupHook<Hook, Arg>::Wrapper(void* data) NAPI_NOEXCEPT {
+  auto* cleanupData = static_cast<
+      typename Napi::BasicEnv::CleanupHook<Hook, Arg>::CleanupData*>(data);
   cleanupData->hook();
   delete cleanupData;
 }
 
 template <typename Hook, typename Arg>
-void NogcEnv::CleanupHook<Hook, Arg>::WrapperWithArg(void* data) NAPI_NOEXCEPT {
-  auto* cleanupData =
-      static_cast<typename Napi::NogcEnv::CleanupHook<Hook, Arg>::CleanupData*>(
-          data);
+void BasicEnv::CleanupHook<Hook, Arg>::WrapperWithArg(void* data)
+    NAPI_NOEXCEPT {
+  auto* cleanupData = static_cast<
+      typename Napi::BasicEnv::CleanupHook<Hook, Arg>::CleanupData*>(data);
   cleanupData->hook(static_cast<Arg*>(cleanupData->arg));
   delete cleanupData;
 }
 #endif  // NAPI_VERSION > 2
 
 #if NAPI_VERSION > 5
-template <typename T, NogcEnv::GcFinalizer<T> gc_fini>
-inline void NogcEnv::SetInstanceData(T* data) const {
+template <typename T, BasicEnv::AsyncFinalizer<T> async_fini>
+inline void BasicEnv::SetInstanceData(T* data) const {
   napi_status status = napi_set_instance_data(
       _env,
       data,
       [](napi_env env, void* data, void*) {
-        gc_fini(env, static_cast<T*>(data));
+        async_fini(env, static_cast<T*>(data));
       },
       nullptr);
-  NAPI_FATAL_IF_FAILED(status, "NogcEnv::SetInstanceData", "invalid arguments");
+  NAPI_FATAL_IF_FAILED(
+      status, "BasicEnv::SetInstanceData", "invalid arguments");
 }
 
 template <typename DataType,
           typename HintType,
-          Napi::NogcEnv::GcFinalizerWithHint<DataType, HintType> fini>
-inline void NogcEnv::SetInstanceData(DataType* data, HintType* hint) const {
+          Napi::BasicEnv::AsyncFinalizerWithHint<DataType, HintType> fini>
+inline void BasicEnv::SetInstanceData(DataType* data, HintType* hint) const {
   napi_status status = napi_set_instance_data(
       _env,
       data,
@@ -677,36 +677,38 @@ inline void NogcEnv::SetInstanceData(DataType* data, HintType* hint) const {
         fini(env, static_cast<DataType*>(data), static_cast<HintType*>(hint));
       },
       hint);
-  NAPI_FATAL_IF_FAILED(status, "NogcEnv::SetInstanceData", "invalid arguments");
+  NAPI_FATAL_IF_FAILED(
+      status, "BasicEnv::SetInstanceData", "invalid arguments");
 }
 
 template <typename T>
-inline T* NogcEnv::GetInstanceData() const {
+inline T* BasicEnv::GetInstanceData() const {
   void* data = nullptr;
 
   napi_status status = napi_get_instance_data(_env, &data);
-  NAPI_FATAL_IF_FAILED(status, "NogcEnv::GetInstanceData", "invalid arguments");
+  NAPI_FATAL_IF_FAILED(
+      status, "BasicEnv::GetInstanceData", "invalid arguments");
 
   return static_cast<T*>(data);
 }
 
 template <typename T>
-void NogcEnv::DefaultGcFini(Env, T* data) {
+void BasicEnv::DefaultAsyncFini(Env, T* data) {
   delete data;
 }
 
 template <typename DataType, typename HintType>
-void NogcEnv::DefaultGcFiniWithHint(Env, DataType* data, HintType*) {
+void BasicEnv::DefaultAsyncFiniWithHint(Env, DataType* data, HintType*) {
   delete data;
 }
 #endif  // NAPI_VERSION > 5
 
 #if NAPI_VERSION > 8
-inline const char* NogcEnv::GetModuleFileName() const {
+inline const char* BasicEnv::GetModuleFileName() const {
   const char* result;
   napi_status status = node_api_get_module_file_name(_env, &result);
   NAPI_FATAL_IF_FAILED(
-      status, "NogcEnv::GetModuleFileName", "invalid arguments");
+      status, "BasicEnv::GetModuleFileName", "invalid arguments");
   return result;
 }
 #endif  // NAPI_VERSION > 8
@@ -3315,7 +3317,7 @@ inline Reference<T>::~Reference() {
   if (_ref != nullptr) {
     if (!_suppressDestruct) {
 #ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
-      Env().AddPostFinalizer(
+      Env().PostFinalizer(
           [](Napi::Env env, napi_ref ref) { napi_delete_reference(env, ref); },
           _ref);
 #else
@@ -4921,7 +4923,7 @@ template <typename T>
 inline void ObjectWrap<T>::Finalize(Napi::Env /*env*/) {}
 
 template <typename T>
-inline void ObjectWrap<T>::Finalize(NogcEnv /*env*/) {}
+inline void ObjectWrap<T>::Finalize(BasicEnv /*env*/) {}
 
 template <typename T>
 inline napi_value ObjectWrap<T>::ConstructorCallbackWrapper(
@@ -5016,26 +5018,39 @@ inline void ObjectWrap<T>::FinalizeCallback(node_api_nogc_env env,
   // Prevent ~ObjectWrap from calling napi_remove_wrap
   instance->_ref = nullptr;
 
-  if constexpr (details::HasNogcFinalize<T>::value) {
+  // If class overrides the synchronous finalizer, execute it.
+  if constexpr (details::HasSyncFinalizer<T>::value) {
 #ifndef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
     HandleScope scope(env);
 #endif
 
-    instance->Finalize(Napi::NogcEnv(env));
+    instance->Finalize(Napi::BasicEnv(env));
   }
 
-  if constexpr (details::HasGcFinalize<T>::value) {
+  // If class overrides the asynchronous finalizer, either schedule it or
+  // execute it immediately (depending on experimental features enabled).
+  if constexpr (details::HasAsyncFinalizer<T>::value) {
 #ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
+    // In experimental, attach via node_api_post_finalizer.
+    // `PostFinalizeCallback` is responsible for deleting the `T* instance`,
+    // after calling the user-provided finalizer.
     napi_status status =
         node_api_post_finalizer(env, PostFinalizeCallback, data, nullptr);
     NAPI_FATAL_IF_FAILED(status,
                          "ObjectWrap<T>::FinalizeCallback",
                          "node_api_post_finalizer failed");
 #else
+    // In non-experimental, this `FinalizeCallback` already executes from
+    // outside the garbage collector. Execute the override directly.
+    // `PostFinalizeCallback` is responsible for deleting the `T* instance`,
+    // after calling the user-provided finalizer.
     HandleScope scope(env);
     PostFinalizeCallback(env, data, static_cast<void*>(nullptr));
 #endif
-  } else {
+  }
+  // If the instance does _not_ have an asynchronous finalizer, delete the `T*
+  // instance` immediately.
+  else {
     delete instance;
   }
 }
@@ -6723,12 +6738,12 @@ inline Napi::Object Addon<T>::DefineProperties(
 
 #if NAPI_VERSION > 2
 template <typename Hook, typename Arg>
-Env::CleanupHook<Hook, Arg> NogcEnv::AddCleanupHook(Hook hook, Arg* arg) {
+Env::CleanupHook<Hook, Arg> BasicEnv::AddCleanupHook(Hook hook, Arg* arg) {
   return CleanupHook<Hook, Arg>(*this, hook, arg);
 }
 
 template <typename Hook>
-Env::CleanupHook<Hook> NogcEnv::AddCleanupHook(Hook hook) {
+Env::CleanupHook<Hook> BasicEnv::AddCleanupHook(Hook hook) {
   return CleanupHook<Hook>(*this, hook);
 }
 
@@ -6738,7 +6753,7 @@ Env::CleanupHook<Hook, Arg>::CleanupHook() {
 }
 
 template <typename Hook, typename Arg>
-Env::CleanupHook<Hook, Arg>::CleanupHook(Napi::NogcEnv env, Hook hook)
+Env::CleanupHook<Hook, Arg>::CleanupHook(Napi::BasicEnv env, Hook hook)
     : wrapper(Env::CleanupHook<Hook, Arg>::Wrapper) {
   data = new CleanupData{std::move(hook), nullptr};
   napi_status status = napi_add_env_cleanup_hook(env, wrapper, data);
@@ -6749,7 +6764,9 @@ Env::CleanupHook<Hook, Arg>::CleanupHook(Napi::NogcEnv env, Hook hook)
 }
 
 template <typename Hook, typename Arg>
-Env::CleanupHook<Hook, Arg>::CleanupHook(Napi::NogcEnv env, Hook hook, Arg* arg)
+Env::CleanupHook<Hook, Arg>::CleanupHook(Napi::BasicEnv env,
+                                         Hook hook,
+                                         Arg* arg)
     : wrapper(Env::CleanupHook<Hook, Arg>::WrapperWithArg) {
   data = new CleanupData{std::move(hook), arg};
   napi_status status = napi_add_env_cleanup_hook(env, wrapper, data);
@@ -6760,7 +6777,7 @@ Env::CleanupHook<Hook, Arg>::CleanupHook(Napi::NogcEnv env, Hook hook, Arg* arg)
 }
 
 template <class Hook, class Arg>
-bool Env::CleanupHook<Hook, Arg>::Remove(NogcEnv env) {
+bool Env::CleanupHook<Hook, Arg>::Remove(BasicEnv env) {
   napi_status status = napi_remove_env_cleanup_hook(env, wrapper, data);
   delete data;
   data = nullptr;
@@ -6775,7 +6792,7 @@ bool Env::CleanupHook<Hook, Arg>::IsEmpty() const {
 
 #ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
 template <typename Finalizer>
-inline void NogcEnv::AddPostFinalizer(Finalizer finalizeCallback) const {
+inline void BasicEnv::PostFinalizer(Finalizer finalizeCallback) const {
   using T = void*;
   details::FinalizeData<T, Finalizer>* finalizeData =
       new details::FinalizeData<T, Finalizer>(
@@ -6789,13 +6806,12 @@ inline void NogcEnv::AddPostFinalizer(Finalizer finalizeCallback) const {
   if (status != napi_ok) {
     delete finalizeData;
     NAPI_FATAL_IF_FAILED(
-        status, "NogcEnv::AddPostFinalizer", "invalid arguments");
+        status, "BasicEnv::PostFinalizer", "invalid arguments");
   }
 }
 
 template <typename Finalizer, typename T>
-inline void NogcEnv::AddPostFinalizer(Finalizer finalizeCallback,
-                                      T* data) const {
+inline void BasicEnv::PostFinalizer(Finalizer finalizeCallback, T* data) const {
   details::FinalizeData<T, Finalizer>* finalizeData =
       new details::FinalizeData<T, Finalizer>(
           {std::move(finalizeCallback), nullptr});
@@ -6805,14 +6821,14 @@ inline void NogcEnv::AddPostFinalizer(Finalizer finalizeCallback,
   if (status != napi_ok) {
     delete finalizeData;
     NAPI_FATAL_IF_FAILED(
-        status, "NogcEnv::AddPostFinalizer", "invalid arguments");
+        status, "BasicEnv::PostFinalizer", "invalid arguments");
   }
 }
 
 template <typename Finalizer, typename T, typename Hint>
-inline void NogcEnv::AddPostFinalizer(Finalizer finalizeCallback,
-                                      T* data,
-                                      Hint* finalizeHint) const {
+inline void BasicEnv::PostFinalizer(Finalizer finalizeCallback,
+                                    T* data,
+                                    Hint* finalizeHint) const {
   details::FinalizeData<T, Finalizer, Hint>* finalizeData =
       new details::FinalizeData<T, Finalizer, Hint>(
           {std::move(finalizeCallback), finalizeHint});
@@ -6824,7 +6840,7 @@ inline void NogcEnv::AddPostFinalizer(Finalizer finalizeCallback,
   if (status != napi_ok) {
     delete finalizeData;
     NAPI_FATAL_IF_FAILED(
-        status, "NogcEnv::AddPostFinalizer", "invalid arguments");
+        status, "BasicEnv::PostFinalizer", "invalid arguments");
   }
 }
 #endif  // NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
diff --git a/napi.h b/napi.h
index 60ca1bcb2..644e65f16 100644
--- a/napi.h
+++ b/napi.h
@@ -187,7 +187,7 @@ namespace NAPI_CPP_CUSTOM_NAMESPACE {
 #endif
 
 // Forward declarations
-class NogcEnv;
+class BasicEnv;
 class Env;
 class Value;
 class Boolean;
@@ -313,17 +313,17 @@ using MaybeOrValue = T;
 ///
 /// In the V8 JavaScript engine, a Node-API environment approximately
 /// corresponds to an Isolate.
-class NogcEnv {
- protected:
+class BasicEnv {
+ private:
   node_api_nogc_env _env;
 #if NAPI_VERSION > 5
   template <typename T>
-  static void DefaultGcFini(Env, T* data);
+  static void DefaultAsyncFini(Env, T* data);
   template <typename DataType, typename HintType>
-  static void DefaultGcFiniWithHint(Env, DataType* data, HintType* hint);
+  static void DefaultAsyncFiniWithHint(Env, DataType* data, HintType* hint);
 #endif  // NAPI_VERSION > 5
  public:
-  NogcEnv(node_api_nogc_env env);
+  BasicEnv(node_api_nogc_env env);
   operator node_api_nogc_env() const;
 
   // Without these operator overloads, the error:
@@ -332,7 +332,7 @@ class NogcEnv {
   //    'Napi::Env' and 'Napi::Env')
   //
   // ... occurs when comparing foo.Env() == bar.Env() or foo.Env() == nullptr
-  bool operator==(const NogcEnv& other) const {
+  bool operator==(const BasicEnv& other) const {
     return _env == other._env;
   };
   bool operator==(std::nullptr_t /*other*/) const {
@@ -355,16 +355,17 @@ class NogcEnv {
   T* GetInstanceData() const;
 
   template <typename T>
-  using GcFinalizer = void (*)(Env, T*);
-  template <typename T, GcFinalizer<T> gc_fini = NogcEnv::DefaultGcFini<T>>
+  using AsyncFinalizer = void (*)(Env, T*);
+  template <typename T,
+            AsyncFinalizer<T> async_fini = BasicEnv::DefaultAsyncFini<T>>
   void SetInstanceData(T* data) const;
 
   template <typename DataType, typename HintType>
-  using GcFinalizerWithHint = void (*)(Env, DataType*, HintType*);
+  using AsyncFinalizerWithHint = void (*)(Env, DataType*, HintType*);
   template <typename DataType,
             typename HintType,
-            GcFinalizerWithHint<DataType, HintType> fini =
-                NogcEnv::DefaultGcFiniWithHint<DataType, HintType>>
+            AsyncFinalizerWithHint<DataType, HintType> fini =
+                BasicEnv::DefaultAsyncFiniWithHint<DataType, HintType>>
   void SetInstanceData(DataType* data, HintType* hint) const;
 #endif  // NAPI_VERSION > 5
 
@@ -373,9 +374,9 @@ class NogcEnv {
   class CleanupHook {
    public:
     CleanupHook();
-    CleanupHook(NogcEnv env, Hook hook, Arg* arg);
-    CleanupHook(NogcEnv env, Hook hook);
-    bool Remove(NogcEnv env);
+    CleanupHook(BasicEnv env, Hook hook, Arg* arg);
+    CleanupHook(BasicEnv env, Hook hook);
+    bool Remove(BasicEnv env);
     bool IsEmpty() const;
 
    private:
@@ -396,19 +397,21 @@ class NogcEnv {
 
 #ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
   template <typename Finalizer>
-  inline void AddPostFinalizer(Finalizer finalizeCallback) const;
+  inline void PostFinalizer(Finalizer finalizeCallback) const;
 
   template <typename Finalizer, typename T>
-  inline void AddPostFinalizer(Finalizer finalizeCallback, T* data) const;
+  inline void PostFinalizer(Finalizer finalizeCallback, T* data) const;
 
   template <typename Finalizer, typename T, typename Hint>
-  inline void AddPostFinalizer(Finalizer finalizeCallback,
-                               T* data,
-                               Hint* finalizeHint) const;
+  inline void PostFinalizer(Finalizer finalizeCallback,
+                            T* data,
+                            Hint* finalizeHint) const;
 #endif  // NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
+
+  friend class Env;
 };
 
-class Env : public NogcEnv {
+class Env : public BasicEnv {
  public:
   Env(napi_env env);
 
@@ -2448,7 +2451,7 @@ class ObjectWrap : public InstanceWrap<T>, public Reference<Object> {
       napi_property_attributes attributes = napi_default);
   static Napi::Value OnCalledAsFunction(const Napi::CallbackInfo& callbackInfo);
   virtual void Finalize(Napi::Env env);
-  virtual void Finalize(NogcEnv env);
+  virtual void Finalize(BasicEnv env);
 
  private:
   using This = ObjectWrap<T>;
diff --git a/test/finalizer_order.cc b/test/finalizer_order.cc
index 81f176f2b..af6e3f9b3 100644
--- a/test/finalizer_order.cc
+++ b/test/finalizer_order.cc
@@ -4,8 +4,8 @@ namespace {
 class Test : public Napi::ObjectWrap<Test> {
  public:
   Test(const Napi::CallbackInfo& info) : Napi::ObjectWrap<Test>(info) {
-    nogcFinalizerCalled = false;
-    gcFinalizerCalled = false;
+    syncFinalizerCalled = false;
+    asyncFinalizerCalled = false;
 
     if (info.Length() > 0) {
       finalizeCb_ = Napi::Persistent(info[0].As<Napi::Function>());
@@ -17,101 +17,101 @@ class Test : public Napi::ObjectWrap<Test> {
                 DefineClass(env,
                             "Test",
                             {
-                                StaticAccessor("isNogcFinalizerCalled",
-                                               &IsNogcFinalizerCalled,
+                                StaticAccessor("isSyncFinalizerCalled",
+                                               &IsSyncFinalizerCalled,
                                                nullptr,
                                                napi_default),
-                                StaticAccessor("isGcFinalizerCalled",
-                                               &IsGcFinalizerCalled,
+                                StaticAccessor("isAsyncFinalizerCalled",
+                                               &IsAsyncFinalizerCalled,
                                                nullptr,
                                                napi_default),
                             }));
   }
 
-  void Finalize(Napi::NogcEnv /*env*/) { nogcFinalizerCalled = true; }
+  void Finalize(Napi::BasicEnv /*env*/) { syncFinalizerCalled = true; }
 
   void Finalize(Napi::Env /*env*/) {
-    gcFinalizerCalled = true;
+    asyncFinalizerCalled = true;
     if (!finalizeCb_.IsEmpty()) {
       finalizeCb_.Call({});
     }
   }
 
-  static Napi::Value IsNogcFinalizerCalled(const Napi::CallbackInfo& info) {
-    return Napi::Boolean::New(info.Env(), nogcFinalizerCalled);
+  static Napi::Value IsSyncFinalizerCalled(const Napi::CallbackInfo& info) {
+    return Napi::Boolean::New(info.Env(), syncFinalizerCalled);
   }
 
-  static Napi::Value IsGcFinalizerCalled(const Napi::CallbackInfo& info) {
-    return Napi::Boolean::New(info.Env(), gcFinalizerCalled);
+  static Napi::Value IsAsyncFinalizerCalled(const Napi::CallbackInfo& info) {
+    return Napi::Boolean::New(info.Env(), asyncFinalizerCalled);
   }
 
  private:
   Napi::FunctionReference finalizeCb_;
 
-  static bool nogcFinalizerCalled;
-  static bool gcFinalizerCalled;
+  static bool syncFinalizerCalled;
+  static bool asyncFinalizerCalled;
 };
 
-bool Test::nogcFinalizerCalled = false;
-bool Test::gcFinalizerCalled = false;
+bool Test::syncFinalizerCalled = false;
+bool Test::asyncFinalizerCalled = false;
 
-bool externalNogcFinalizerCalled = false;
-bool externalGcFinalizerCalled = false;
+bool externalSyncFinalizerCalled = false;
+bool externalAsyncFinalizerCalled = false;
 
-Napi::Value CreateExternalNogc(const Napi::CallbackInfo& info) {
-  externalNogcFinalizerCalled = false;
+Napi::Value CreateExternalSyncFinalizer(const Napi::CallbackInfo& info) {
+  externalSyncFinalizerCalled = false;
   return Napi::External<int>::New(
-      info.Env(), new int(1), [](Napi::NogcEnv /*env*/, int* data) {
-        externalNogcFinalizerCalled = true;
+      info.Env(), new int(1), [](Napi::BasicEnv /*env*/, int* data) {
+        externalSyncFinalizerCalled = true;
         delete data;
       });
 }
 
-Napi::Value CreateExternalGc(const Napi::CallbackInfo& info) {
-  externalGcFinalizerCalled = false;
+Napi::Value CreateExternalAsyncFinalizer(const Napi::CallbackInfo& info) {
+  externalAsyncFinalizerCalled = false;
   return Napi::External<int>::New(
       info.Env(), new int(1), [](Napi::Env /*env*/, int* data) {
-        externalGcFinalizerCalled = true;
+        externalAsyncFinalizerCalled = true;
         delete data;
       });
 }
 
-Napi::Value IsExternalNogcFinalizerCalled(const Napi::CallbackInfo& info) {
-  return Napi::Boolean::New(info.Env(), externalNogcFinalizerCalled);
+Napi::Value IsExternalSyncFinalizerCalled(const Napi::CallbackInfo& info) {
+  return Napi::Boolean::New(info.Env(), externalSyncFinalizerCalled);
 }
 
-Napi::Value IsExternalGcFinalizerCalled(const Napi::CallbackInfo& info) {
-  return Napi::Boolean::New(info.Env(), externalGcFinalizerCalled);
+Napi::Value IsExternalAsyncFinalizerCalled(const Napi::CallbackInfo& info) {
+  return Napi::Boolean::New(info.Env(), externalAsyncFinalizerCalled);
 }
 
 #ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
-Napi::Value AddPostFinalizer(const Napi::CallbackInfo& info) {
+Napi::Value PostFinalizer(const Napi::CallbackInfo& info) {
   auto env = info.Env();
 
-  env.AddPostFinalizer(
-      [callback = Napi::Persistent(info[0].As<Napi::Function>())](
-          Napi::Env /*env*/) { callback.Call({}); });
+  env.PostFinalizer([callback = Napi::Persistent(info[0].As<Napi::Function>())](
+                        Napi::Env /*env*/) { callback.Call({}); });
 
   return env.Undefined();
 }
 
-Napi::Value AddPostFinalizerWithData(const Napi::CallbackInfo& info) {
+Napi::Value PostFinalizerWithData(const Napi::CallbackInfo& info) {
   auto env = info.Env();
 
-  env.AddPostFinalizer(
+  env.PostFinalizer(
       [callback = Napi::Persistent(info[0].As<Napi::Function>())](
           Napi::Env /*env*/, Napi::Reference<Napi::Value>* data) {
         callback.Call({data->Value()});
         delete data;
       },
       new Napi::Reference<Napi::Value>(Napi::Persistent(info[1])));
+
   return env.Undefined();
 }
 
-Napi::Value AddPostFinalizerWithDataAndHint(const Napi::CallbackInfo& info) {
+Napi::Value PostFinalizerWithDataAndHint(const Napi::CallbackInfo& info) {
   auto env = info.Env();
 
-  env.AddPostFinalizer(
+  env.PostFinalizer(
       [callback = Napi::Persistent(info[0].As<Napi::Function>())](
           Napi::Env /*env*/,
           Napi::Reference<Napi::Value>* data,
@@ -122,6 +122,7 @@ Napi::Value AddPostFinalizerWithDataAndHint(const Napi::CallbackInfo& info) {
       },
       new Napi::Reference<Napi::Value>(Napi::Persistent(info[1])),
       new Napi::Reference<Napi::Value>(Napi::Persistent(info[2])));
+
   return env.Undefined();
 }
 #endif
@@ -131,19 +132,21 @@ Napi::Value AddPostFinalizerWithDataAndHint(const Napi::CallbackInfo& info) {
 Napi::Object InitFinalizerOrder(Napi::Env env) {
   Napi::Object exports = Napi::Object::New(env);
   Test::Initialize(env, exports);
-  exports["CreateExternalNogc"] = Napi::Function::New(env, CreateExternalNogc);
-  exports["CreateExternalGc"] = Napi::Function::New(env, CreateExternalGc);
-  exports["isExternalNogcFinalizerCalled"] =
-      Napi::Function::New(env, IsExternalNogcFinalizerCalled);
-  exports["isExternalGcFinalizerCalled"] =
-      Napi::Function::New(env, IsExternalGcFinalizerCalled);
+  exports["createExternalSyncFinalizer"] =
+      Napi::Function::New(env, CreateExternalSyncFinalizer);
+  exports["createExternalAsyncFinalizer"] =
+      Napi::Function::New(env, CreateExternalAsyncFinalizer);
+  exports["isExternalSyncFinalizerCalled"] =
+      Napi::Function::New(env, IsExternalSyncFinalizerCalled);
+  exports["isExternalAsyncFinalizerCalled"] =
+      Napi::Function::New(env, IsExternalAsyncFinalizerCalled);
 
 #ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
-  exports["AddPostFinalizer"] = Napi::Function::New(env, AddPostFinalizer);
-  exports["AddPostFinalizerWithData"] =
-      Napi::Function::New(env, AddPostFinalizerWithData);
-  exports["AddPostFinalizerWithDataAndHint"] =
-      Napi::Function::New(env, AddPostFinalizerWithDataAndHint);
+  exports["PostFinalizer"] = Napi::Function::New(env, PostFinalizer);
+  exports["PostFinalizerWithData"] =
+      Napi::Function::New(env, PostFinalizerWithData);
+  exports["PostFinalizerWithDataAndHint"] =
+      Napi::Function::New(env, PostFinalizerWithDataAndHint);
 #endif
   return exports;
 }
diff --git a/test/finalizer_order.js b/test/finalizer_order.js
index a8e622e6c..36ea68f2a 100644
--- a/test/finalizer_order.js
+++ b/test/finalizer_order.js
@@ -22,66 +22,67 @@ function test (binding) {
       global.gc();
 
       if (isExperimental) {
-        assert.strictEqual(binding.finalizer_order.Test.isNogcFinalizerCalled, true, 'Expected nogc finalizer to be called [before ticking]');
-        assert.strictEqual(binding.finalizer_order.Test.isGcFinalizerCalled, false, 'Expected gc finalizer to not be called [before ticking]');
-        assert.strictEqual(isCallbackCalled, false, 'Expected callback not be called [before ticking]');
+        assert.strictEqual(binding.finalizer_order.Test.isSyncFinalizerCalled, true, 'Expected sync finalizer to be called [before ticking]');
+        assert.strictEqual(binding.finalizer_order.Test.isAsyncFinalizerCalled, false, 'Expected async finalizer to not be called [before ticking]');
+        assert.strictEqual(isCallbackCalled, false, 'Expected callback to not be called [before ticking]');
       } else {
-        assert.strictEqual(binding.finalizer_order.Test.isNogcFinalizerCalled, false, 'Expected nogc finalizer to not be called [before ticking]');
-        assert.strictEqual(binding.finalizer_order.Test.isGcFinalizerCalled, false, 'Expected gc finalizer to not be called [before ticking]');
-        assert.strictEqual(isCallbackCalled, false, 'Expected callback not be called [before ticking]');
+        assert.strictEqual(binding.finalizer_order.Test.isSyncFinalizerCalled, false, 'Expected sync finalizer to not be called [before ticking]');
+        assert.strictEqual(binding.finalizer_order.Test.isAsyncFinalizerCalled, false, 'Expected async finalizer to not be called [before ticking]');
+        assert.strictEqual(isCallbackCalled, false, 'Expected callback to not be called [before ticking]');
       }
     },
     () => {
-      assert.strictEqual(binding.finalizer_order.Test.isNogcFinalizerCalled, true, 'Expected nogc finalizer to be called [after ticking]');
-      assert.strictEqual(binding.finalizer_order.Test.isGcFinalizerCalled, true, 'Expected gc finalizer to be called [after ticking]');
+      assert.strictEqual(binding.finalizer_order.Test.isSyncFinalizerCalled, true, 'Expected sync finalizer to be called [after ticking]');
+      assert.strictEqual(binding.finalizer_order.Test.isAsyncFinalizerCalled, true, 'Expected async finalizer to be called [after ticking]');
       assert.strictEqual(isCallbackCalled, true, 'Expected callback to be called [after ticking]');
     },
 
-    'Finalizer Order - External with Nogc Finalizer',
+    'Finalizer Order - External with Sync Finalizer',
     () => {
-      let ext = new binding.finalizer_order.CreateExternalNogc();
+      console.log(binding.finalizer_order);
+      let ext = binding.finalizer_order.createExternalSyncFinalizer();
       ext = null;
       global.gc();
 
       if (isExperimental) {
-        assert.strictEqual(binding.finalizer_order.isExternalNogcFinalizerCalled(), true, 'Expected External nogc finalizer to be called [before ticking]');
+        assert.strictEqual(binding.finalizer_order.isExternalSyncFinalizerCalled(), true, 'Expected External sync finalizer to be called [before ticking]');
       } else {
-        assert.strictEqual(binding.finalizer_order.isExternalNogcFinalizerCalled(), false, 'Expected External nogc finalizer to not be called [before ticking]');
+        assert.strictEqual(binding.finalizer_order.isExternalSyncFinalizerCalled(), false, 'Expected External sync finalizer to not be called [before ticking]');
       }
     },
     () => {
-      assert.strictEqual(binding.finalizer_order.isExternalNogcFinalizerCalled(), true, 'Expected External nogc finalizer to be called [after ticking]');
+      assert.strictEqual(binding.finalizer_order.isExternalSyncFinalizerCalled(), true, 'Expected External sync finalizer to be called [after ticking]');
     },
 
-    'Finalizer Order - External with Gc Finalizer',
+    'Finalizer Order - External with Async Finalizer',
     () => {
-      let ext = new binding.finalizer_order.CreateExternalGc();
+      let ext = binding.finalizer_order.createExternalAsyncFinalizer();
       ext = null;
       global.gc();
-      assert.strictEqual(binding.finalizer_order.isExternalGcFinalizerCalled(), false, 'Expected External gc finalizer to not be called [before ticking]');
+      assert.strictEqual(binding.finalizer_order.isExternalAsyncFinalizerCalled(), false, 'Expected External async finalizer to not be called [before ticking]');
     },
     () => {
-      assert.strictEqual(binding.finalizer_order.isExternalGcFinalizerCalled(), true, 'Expected External gc finalizer to be called [after ticking]');
+      assert.strictEqual(binding.finalizer_order.isExternalAsyncFinalizerCalled(), true, 'Expected External async finalizer to be called [after ticking]');
     }
   ];
 
   if (binding.isExperimental) {
     tests.push(...[
-      'AddPostFinalizer',
+      'PostFinalizer',
       () => {
-        binding.finalizer_order.AddPostFinalizer(common.mustCall());
+        binding.finalizer_order.PostFinalizer(common.mustCall());
       },
 
-      'AddPostFinalizerWithData',
+      'PostFinalizerWithData',
       () => {
         const data = {};
         const callback = (callbackData) => {
           assert.strictEqual(callbackData, data);
         };
-        binding.finalizer_order.AddPostFinalizerWithData(common.mustCall(callback), data);
+        binding.finalizer_order.PostFinalizerWithData(common.mustCall(callback), data);
       },
 
-      'AddPostFinalizerWithDataAndHint',
+      'PostFinalizerWithDataAndHint',
       () => {
         const data = {};
         const hint = {};
@@ -89,7 +90,7 @@ function test (binding) {
           assert.strictEqual(callbackData, data);
           assert.strictEqual(callbackHint, hint);
         };
-        binding.finalizer_order.AddPostFinalizerWithDataAndHint(common.mustCall(callback), data, hint);
+        binding.finalizer_order.PostFinalizerWithDataAndHint(common.mustCall(callback), data, hint);
       }
     ]);
   }

From c4e72eb0f2235ae7ba06a04c945a0b14c1c0bff6 Mon Sep 17 00:00:00 2001
From: Kevin Eady <8634912+KevinEady@users.noreply.github.com>
Date: Fri, 5 Jul 2024 14:39:00 +0200
Subject: [PATCH 11/19] wip on docs for general finalization

---
 doc/finalization.md | 170 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 170 insertions(+)
 create mode 100644 doc/finalization.md

diff --git a/doc/finalization.md b/doc/finalization.md
new file mode 100644
index 000000000..3b45e7a1e
--- /dev/null
+++ b/doc/finalization.md
@@ -0,0 +1,170 @@
+# Finalization
+
+Various node-addon-api methods accept a templated `Finalizer finalizeCallback`
+parameter. This parameter represents a callback function that runs in a
+_synchronous_ or _asynchronous_ manner in response to a garbage collection
+event. The callback executes either during the garbage collection cycle
+(_synchronously_) or after the cycle has completed (_asynchronously_).
+
+In general, it is best to use synchronous finalizers whenever a callback must
+free native data.
+
+## Synchronous Finalizers
+
+Synchronous finalizers execute during the garbage collection cycle, and
+therefore must not manipulate the engine heap. The finalizer executes in the
+current tick, providing a chance to free native memory. The callback takes
+`Napi::BasicEnv` as its first argument.
+
+### Example
+
+```cpp
+Napi::ArrayBuffer::New(
+    Env(), data, length, [](Napi::BasicEnv /*env*/, void* finalizeData) {
+      delete[] static_cast<uint8_t*>(finalizeData);
+    });
+```
+
+## Asynchronous Finalizers
+
+Asynchronous finalizers execute outside the context of garbage collection later
+in the event loop. Care must be taken when creating several objects in
+succession if the finalizer must free native memory. The engine may garbage
+collect several objects created in one tick, but any asynchronous finalizers --
+and therefore the freeing of native memory -- will not execute in the same tick.
+The callback takes `Napi::Env` as its first argument.
+
+### Example
+
+```cpp
+Napi::External<int>::New(Env(), new int(1), [](Napi::Env env, int* data) {
+  env.RunScript("console.log('Finalizer called')");
+  delete data;
+});
+```
+
+#### Caveats
+
+```js
+while (conditional()) {
+  const external = createExternal();
+  doSomethingWith(external);
+}
+```
+
+The engine may determine to run the garbage collector within the loop, freeing
+the JavaScript engine's resources for all objects created by `createExternal()`.
+However, asynchronous finalizers will not execute during this loop, and the
+native memory held in `data` will not be freed.
+
+## Scheduling Asynchronous Finalizers
+
+In addition to passing asynchronous finalizers to externals and other Node-API
+constructs, use `Napi::BasicEnv::PostFinalize(Napi::Env, Finalizer)` to schedule
+an asynchronous finalizer to run after the next garbage collection cycle
+completes.
+
+Free native data in a synchronous finalizer, while executing any JavaScript code
+in an asynchronous finalizer attached via this method. Since the associated
+native memory may already be freed by the synchronous finalizer, any additional
+data may be passed eg. via the finalizer's parameters (`T data*`, `Hint hint*`)
+or via lambda capture.
+
+### Example
+
+```cpp
+// Native Add-on
+
+#include <iostream>
+#include <memory>
+#include "napi.h"
+
+using namespace Napi;
+
+// A structure representing some data that uses a "large" amount of memory.
+class LargeData {
+ public:
+  LargeData() : id(instances++) {}
+  size_t id;
+
+  static size_t instances;
+};
+
+size_t LargeData::instances = 0;
+
+// Synchronous finalizer to free `LargeData`. Takes ownership of the pointer and
+// frees its memory after use.
+void MySyncFinalizer(Napi::BasicEnv env, LargeData* data) {
+  std::unique_ptr<LargeData> instance(data);
+  std::cout << "Synchronous finalizer for instance " << instance->id
+            << " called\n";
+
+  // Register the asynchronous callback. Since the instance will be deleted by
+  // the time this callback executes, pass the instance's `id` via lambda copy
+  // capture and _not_ a reference capture that accesses `this`.
+  env.PostFinalizer([instanceId = instance->id](Napi::Env env) {
+    std::cout << "Asynchronous finalizer for instance " << instanceId
+              << " called\n";
+  });
+
+  // Free the `LargeData` held in `data` once `instance` goes out of scope.
+}
+
+Value CreateExternal(const CallbackInfo& info) {
+  // Create a new instance of LargeData.
+  auto instance = std::make_unique<LargeData>();
+
+  // Wrap the instance in an External object, registering a synchronous
+  // finalizer that will delete the instance to free the "large" amount of
+  // memory.
+  auto ext =
+      External<LargeData>::New(info.Env(), instance.release(), MySyncFinalizer);
+
+  return ext;
+}
+
+Object Init(Napi::Env env, Object exports) {
+  exports["createExternal"] = Function::New(env, CreateExternal);
+  return exports;
+}
+
+NODE_API_MODULE(addon, Init)
+```
+
+```js
+// JavaScript
+
+const { createExternal } = require('./addon.node');
+
+for (let i = 0; i < 5; i++) {
+  const ext = createExternal();
+  doSomethingWith(ext);
+}
+
+console.log('Loop complete');
+await new Promise(resolve => setImmediate(resolve));
+console.log('Next event loop cycle');
+
+```
+
+Possible output:
+
+```
+Synchronous finalizer for instance 0 called
+Synchronous finalizer for instance 1 called
+Synchronous finalizer for instance 2 called
+Synchronous finalizer for instance 3 called
+Synchronous finalizer for instance 4 called
+Loop complete
+Asynchronous finalizer for instance 3 called
+Asynchronous finalizer for instance 4 called
+Asynchronous finalizer for instance 1 called
+Asynchronous finalizer for instance 2 called
+Asynchronous finalizer for instance 0 called
+Next event loop cycle
+```
+
+If the garbage collector runs during the loop, the synchronous finalizers
+execute and display their logging message before the loop completes. The
+asynchronous finalizers execute at some later point after the garbage collection
+cycle.
\ No newline at end of file

From 4e8b367e134b8761d6427e04deeee8ce13d1ca4c Mon Sep 17 00:00:00 2001
From: Kevin Eady <8634912+KevinEady@users.noreply.github.com>
Date: Wed, 10 Jul 2024 23:04:24 +0200
Subject: [PATCH 12/19] switch to "basic" finalizer in docs

---
 doc/finalization.md | 136 +++++++++++++++++++-------------------------
 1 file changed, 60 insertions(+), 76 deletions(-)

diff --git a/doc/finalization.md b/doc/finalization.md
index 3b45e7a1e..f4e28e6f2 100644
--- a/doc/finalization.md
+++ b/doc/finalization.md
@@ -1,38 +1,22 @@
 # Finalization
 
 Various node-addon-api methods accept a templated `Finalizer finalizeCallback`
-parameter. This parameter represents a callback function that runs in a
-_synchronous_ or _asynchronous_ manner in response to a garbage collection
-event. The callback executes either during the garbage collection cycle
-(_synchronously_) or after the cycle has completed (_asynchronously_).
+parameter. This parameter represents a native callback function that runs in
+response to a garbage collection event. A finalizer is considered a _basic_
+finalizer if the callback only utilizes a certain subset of APIs, which may
+provide optimizations, improved execution, or other benefits.
 
-In general, it is best to use synchronous finalizers whenever a callback must
-free native data.
+In general, it is best to use basic finalizers whenever possible (eg. when
+access to JavaScript is _not_ needed).
 
-## Synchronous Finalizers
+*NOTE*: Optimizations via basic finalizers will only occur if using
+`NAPI_EXPERIMENTAL` and the `NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT` define flag
+has _not_ been set. Otherwise, the engine will not differentiate between basic
+and (extended) finalizers.
 
-Synchronous finalizers execute during the garbage collection cycle, and
-therefore must not manipulate the engine heap. The finalizer executes in the
-current tick, providing a chance to free native memory. The callback takes
-`Napi::BasicEnv` as its first argument.
+## Finalizers
 
-### Example
-
-```cpp
-Napi::ArrayBuffer::New(
-    Env(), data, length, [](Napi::BasicEnv /*env*/, void* finalizeData) {
-      delete[] static_cast<uint8_t*>(finalizeData);
-    });
-```
-
-## Asynchronous Finalizers
-
-Asynchronous finalizers execute outside the context of garbage collection later
-in the event loop. Care must be taken when creating several objects in
-succession if the finalizer must free native memory. The engine may garbage
-collect several objects created in one tick, but any asynchronous finalizers --
-and therefore the freeing of native memory -- will not execute in the same tick.
-The callback takes `Napi::Env` as its first argument.
+The callback takes `Napi::Env` as its first argument:
 
 ### Example
 
@@ -43,32 +27,36 @@ Napi::External<int>::New(Env(), new int(1), [](Napi::Env env, int* data) {
 });
 ```
 
-#### Caveats
+## Basic Finalizers
 
-```js
-while (conditional()) {
-  const external = createExternal();
-  doSomethingWith(external);
-}
-```
+Use of basic finalizers may allow the engine to perform optimizations when
+scheduling or executing the callback. For example, V8 does not allow access to
+the engine heap during garbage collection. Restricting finalizers from accessing
+the engine heap allows the callback to execute during garbage collection,
+providing a chance to free native memory on the current tick.
+
+In general, APIs that access engine heap are not allowed in basic finalizers.
+
+The callback takes `Napi::BasicEnv` as its first argument:
 
-The engine may determine to run the garbage collector within the loop, freeing
-the JavaScript engine's resources for all objects created by `createExternal()`.
-However, asynchronous finalizers will not execute during this loop, and the
-native memory held in `data` will not be freed.
+### Example
 
-## Scheduling Asynchronous Finalizers
+```cpp
+Napi::ArrayBuffer::New(
+    Env(), data, length, [](Napi::BasicEnv /*env*/, void* finalizeData) {
+      delete[] static_cast<uint8_t*>(finalizeData);
+    });
+```
 
-In addition to passing asynchronous finalizers to externals and other Node-API
-constructs, use `Napi::BasicEnv::PostFinalize(Napi::Env, Finalizer)` to schedule
-an asynchronous finalizer to run after the next garbage collection cycle
-completes.
+## Scheduling Finalizers
 
-Free native data in a synchronous finalizer, while executing any JavaScript code
-in an asynchronous finalizer attached via this method. Since the associated
-native memory may already be freed by the synchronous finalizer, any additional
-data may be passed eg. via the finalizer's parameters (`T data*`, `Hint hint*`)
-or via lambda capture.
+In addition to passing finalizers to `Napi::External`s and other Node-API
+constructs, use `Napi::BasicEnv::PostFinalize(Napi::BasicEnv, Finalizer)` to
+schedule a callback to run outside of the garbage collector finalization. Since
+the associated native memory may already be freed by the basic finalizer, any
+additional data may be passed eg. via the finalizer's parameters (`T data*`,
+`Hint hint*`) or via lambda capture. This allows for freeing native data in a
+basic finalizer, while executing any JavaScript code in an additional finalizer.
 
 ### Example
 
@@ -92,19 +80,19 @@ class LargeData {
 
 size_t LargeData::instances = 0;
 
-// Synchronous finalizer to free `LargeData`. Takes ownership of the pointer and
+// Basic finalizer to free `LargeData`. Takes ownership of the pointer and
 // frees its memory after use.
-void MySyncFinalizer(Napi::BasicEnv env, LargeData* data) {
+void MyBasicFinalizer(Napi::BasicEnv env, LargeData* data) {
   std::unique_ptr<LargeData> instance(data);
-  std::cout << "Synchronous finalizer for instance " << instance->id
+  std::cout << "Basic finalizer for instance " << instance->id
             << " called\n";
 
-  // Register the asynchronous callback. Since the instance will be deleted by
+  // Register a finalizer. Since the instance will be deleted by
   // the time this callback executes, pass the instance's `id` via lambda copy
   // capture and _not_ a reference capture that accesses `this`.
   env.PostFinalizer([instanceId = instance->id](Napi::Env env) {
-    std::cout << "Asynchronous finalizer for instance " << instanceId
-              << " called\n";
+    env.RunScript("console.log('Finalizer for instance " +
+                  std::to_string(instanceId) + " called');");
   });
 
   // Free the `LargeData` held in `data` once `instance` goes out of scope.
@@ -114,13 +102,10 @@ Value CreateExternal(const CallbackInfo& info) {
   // Create a new instance of LargeData.
   auto instance = std::make_unique<LargeData>();
 
-  // Wrap the instance in an External object, registering a synchronous
+  // Wrap the instance in an External object, registering a basic
   // finalizer that will delete the instance to free the "large" amount of
   // memory.
-  auto ext =
-      External<LargeData>::New(info.Env(), instance.release(), MySyncFinalizer);
-
-  return ext;
+  return External<LargeData>::New(info.Env(), instance.release(), MyBasicFinalizer);
 }
 
 Object Init(Napi::Env env, Object exports) {
@@ -138,33 +123,32 @@ const { createExternal } = require('./addon.node');
 
 for (let i = 0; i < 5; i++) {
   const ext = createExternal();
-  doSomethingWith(ext);
+  // ... do something with `ext` ..
 }
 
 console.log('Loop complete');
 await new Promise(resolve => setImmediate(resolve));
 console.log('Next event loop cycle');
-
 ```
 
 Possible output:
 
 ```
-Synchronous finalizer for instance 0 called
-Synchronous finalizer for instance 1 called
-Synchronous finalizer for instance 2 called
-Synchronous finalizer for instance 3 called
-Synchronous finalizer for instance 4 called
+Basic finalizer for instance 0 called
+Basic finalizer for instance 1 called
+Basic finalizer for instance 2 called
+Basic finalizer for instance 3 called
+Basic finalizer for instance 4 called
 Loop complete
-Asynchronous finalizer for instance 3 called
-Asynchronous finalizer for instance 4 called
-Asynchronous finalizer for instance 1 called
-Asynchronous finalizer for instance 2 called
-Asynchronous finalizer for instance 0 called
+Finalizer for instance 3 called
+Finalizer for instance 4 called
+Finalizer for instance 1 called
+Finalizer for instance 2 called
+Finalizer for instance 0 called
 Next event loop cycle
 ```
 
-If the garbage collector runs during the loop, the synchronous finalizers
-execute and display their logging message before the loop completes. The
-asynchronous finalizers execute at some later point after the garbage collection
-cycle.
\ No newline at end of file
+If the garbage collector runs during the loop, the basic finalizers execute and
+display their logging message synchronously during the loop execution. The
+additional finalizers execute at some later point after the garbage collection
+cycle.

From de20ac4601d86cb253ea994c19a5f2e3338fa68d Mon Sep 17 00:00:00 2001
From: Kevin Eady <8634912+KevinEady@users.noreply.github.com>
Date: Thu, 25 Jul 2024 14:10:55 +0200
Subject: [PATCH 13/19] Address review comments - Remove discussions about
 NAPI-EXPERIMENTAL.

---
 doc/finalization.md | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/doc/finalization.md b/doc/finalization.md
index f4e28e6f2..b4daa03ba 100644
--- a/doc/finalization.md
+++ b/doc/finalization.md
@@ -9,11 +9,6 @@ provide optimizations, improved execution, or other benefits.
 In general, it is best to use basic finalizers whenever possible (eg. when
 access to JavaScript is _not_ needed).
 
-*NOTE*: Optimizations via basic finalizers will only occur if using
-`NAPI_EXPERIMENTAL` and the `NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT` define flag
-has _not_ been set. Otherwise, the engine will not differentiate between basic
-and (extended) finalizers.
-
 ## Finalizers
 
 The callback takes `Napi::Env` as its first argument:

From 6ee1ebf53b95f2de34d93a6ff00d6eebe770cdf7 Mon Sep 17 00:00:00 2001
From: Kevin Eady <8634912+KevinEady@users.noreply.github.com>
Date: Thu, 25 Jul 2024 15:01:41 +0200
Subject: [PATCH 14/19] finish renaming async finalizer to basic

---
 napi-inl.h              | 40 +++++++++++------------
 napi.h                  | 15 ++++-----
 test/finalizer_order.cc | 72 ++++++++++++++++++++---------------------
 test/finalizer_order.js | 31 +++++++++---------
 4 files changed, 77 insertions(+), 81 deletions(-)

diff --git a/napi-inl.h b/napi-inl.h
index d02c4df3f..fdda4dbe6 100644
--- a/napi-inl.h
+++ b/napi-inl.h
@@ -35,9 +35,9 @@ namespace details {
 constexpr int napi_no_external_buffers_allowed = 22;
 
 template <typename FreeType>
-inline void default_finalizer(node_api_nogc_env /*env*/,
-                              void* data,
-                              void* /*hint*/) {
+inline void default_basic_finalizer(node_api_nogc_env /*env*/,
+                                    void* data,
+                                    void* /*hint*/) {
   delete static_cast<FreeType*>(data);
 }
 
@@ -46,7 +46,7 @@ inline void default_finalizer(node_api_nogc_env /*env*/,
 // TODO: Replace this code with `napi_add_finalizer()` whenever it becomes
 // available on all supported versions of Node.js.
 template <typename FreeType,
-          node_api_nogc_finalize finalizer = default_finalizer<FreeType>>
+          node_api_nogc_finalize finalizer = default_basic_finalizer<FreeType>>
 inline napi_status AttachData(napi_env env,
                               napi_value obj,
                               FreeType* data,
@@ -427,7 +427,7 @@ inline std::string StringFormat(const char* format, ...) {
 }
 
 template <typename T>
-class HasAsyncFinalizer {
+class HasExtendedFinalizer {
  private:
   template <typename U, void (U::*)(Napi::Env)>
   struct SFINAE {};
@@ -441,7 +441,7 @@ class HasAsyncFinalizer {
 };
 
 template <typename T>
-class HasSyncFinalizer {
+class HasBasicFinalizer {
  private:
   template <typename U, void (U::*)(Napi::BasicEnv)>
   struct SFINAE {};
@@ -653,14 +653,12 @@ void BasicEnv::CleanupHook<Hook, Arg>::WrapperWithArg(void* data)
 #endif  // NAPI_VERSION > 2
 
 #if NAPI_VERSION > 5
-template <typename T, BasicEnv::AsyncFinalizer<T> async_fini>
+template <typename T, BasicEnv::Finalizer<T> fini>
 inline void BasicEnv::SetInstanceData(T* data) const {
   napi_status status = napi_set_instance_data(
       _env,
       data,
-      [](napi_env env, void* data, void*) {
-        async_fini(env, static_cast<T*>(data));
-      },
+      [](napi_env env, void* data, void*) { fini(env, static_cast<T*>(data)); },
       nullptr);
   NAPI_FATAL_IF_FAILED(
       status, "BasicEnv::SetInstanceData", "invalid arguments");
@@ -668,7 +666,7 @@ inline void BasicEnv::SetInstanceData(T* data) const {
 
 template <typename DataType,
           typename HintType,
-          Napi::BasicEnv::AsyncFinalizerWithHint<DataType, HintType> fini>
+          Napi::BasicEnv::FinalizerWithHint<DataType, HintType> fini>
 inline void BasicEnv::SetInstanceData(DataType* data, HintType* hint) const {
   napi_status status = napi_set_instance_data(
       _env,
@@ -693,12 +691,12 @@ inline T* BasicEnv::GetInstanceData() const {
 }
 
 template <typename T>
-void BasicEnv::DefaultAsyncFini(Env, T* data) {
+void BasicEnv::DefaultFini(Env, T* data) {
   delete data;
 }
 
 template <typename DataType, typename HintType>
-void BasicEnv::DefaultAsyncFiniWithHint(Env, DataType* data, HintType*) {
+void BasicEnv::DefaultFiniWithHint(Env, DataType* data, HintType*) {
   delete data;
 }
 #endif  // NAPI_VERSION > 5
@@ -5018,8 +5016,8 @@ inline void ObjectWrap<T>::FinalizeCallback(node_api_nogc_env env,
   // Prevent ~ObjectWrap from calling napi_remove_wrap
   instance->_ref = nullptr;
 
-  // If class overrides the synchronous finalizer, execute it.
-  if constexpr (details::HasSyncFinalizer<T>::value) {
+  // If class overrides the basic finalizer, execute it.
+  if constexpr (details::HasBasicFinalizer<T>::value) {
 #ifndef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
     HandleScope scope(env);
 #endif
@@ -5027,9 +5025,9 @@ inline void ObjectWrap<T>::FinalizeCallback(node_api_nogc_env env,
     instance->Finalize(Napi::BasicEnv(env));
   }
 
-  // If class overrides the asynchronous finalizer, either schedule it or
+  // If class overrides the (extended) finalizer, either schedule it or
   // execute it immediately (depending on experimental features enabled).
-  if constexpr (details::HasAsyncFinalizer<T>::value) {
+  if constexpr (details::HasExtendedFinalizer<T>::value) {
 #ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
     // In experimental, attach via node_api_post_finalizer.
     // `PostFinalizeCallback` is responsible for deleting the `T* instance`,
@@ -5040,16 +5038,16 @@ inline void ObjectWrap<T>::FinalizeCallback(node_api_nogc_env env,
                          "ObjectWrap<T>::FinalizeCallback",
                          "node_api_post_finalizer failed");
 #else
-    // In non-experimental, this `FinalizeCallback` already executes from
-    // outside the garbage collector. Execute the override directly.
+    // In non-experimental, this `FinalizeCallback` already executes from a
+    // non-basic environment. Execute the override directly.
     // `PostFinalizeCallback` is responsible for deleting the `T* instance`,
     // after calling the user-provided finalizer.
     HandleScope scope(env);
     PostFinalizeCallback(env, data, static_cast<void*>(nullptr));
 #endif
   }
-  // If the instance does _not_ have an asynchronous finalizer, delete the `T*
-  // instance` immediately.
+  // If the instance does _not_ override the (extended) finalizer, delete the
+  // `T* instance` immediately.
   else {
     delete instance;
   }
diff --git a/napi.h b/napi.h
index 644e65f16..e0d056376 100644
--- a/napi.h
+++ b/napi.h
@@ -318,9 +318,9 @@ class BasicEnv {
   node_api_nogc_env _env;
 #if NAPI_VERSION > 5
   template <typename T>
-  static void DefaultAsyncFini(Env, T* data);
+  static void DefaultFini(Env, T* data);
   template <typename DataType, typename HintType>
-  static void DefaultAsyncFiniWithHint(Env, DataType* data, HintType* hint);
+  static void DefaultFiniWithHint(Env, DataType* data, HintType* hint);
 #endif  // NAPI_VERSION > 5
  public:
   BasicEnv(node_api_nogc_env env);
@@ -355,17 +355,16 @@ class BasicEnv {
   T* GetInstanceData() const;
 
   template <typename T>
-  using AsyncFinalizer = void (*)(Env, T*);
-  template <typename T,
-            AsyncFinalizer<T> async_fini = BasicEnv::DefaultAsyncFini<T>>
+  using Finalizer = void (*)(Env, T*);
+  template <typename T, Finalizer<T> fini = BasicEnv::DefaultFini<T>>
   void SetInstanceData(T* data) const;
 
   template <typename DataType, typename HintType>
-  using AsyncFinalizerWithHint = void (*)(Env, DataType*, HintType*);
+  using FinalizerWithHint = void (*)(Env, DataType*, HintType*);
   template <typename DataType,
             typename HintType,
-            AsyncFinalizerWithHint<DataType, HintType> fini =
-                BasicEnv::DefaultAsyncFiniWithHint<DataType, HintType>>
+            FinalizerWithHint<DataType, HintType> fini =
+                BasicEnv::DefaultFiniWithHint<DataType, HintType>>
   void SetInstanceData(DataType* data, HintType* hint) const;
 #endif  // NAPI_VERSION > 5
 
diff --git a/test/finalizer_order.cc b/test/finalizer_order.cc
index af6e3f9b3..0767ced70 100644
--- a/test/finalizer_order.cc
+++ b/test/finalizer_order.cc
@@ -4,8 +4,8 @@ namespace {
 class Test : public Napi::ObjectWrap<Test> {
  public:
   Test(const Napi::CallbackInfo& info) : Napi::ObjectWrap<Test>(info) {
-    syncFinalizerCalled = false;
-    asyncFinalizerCalled = false;
+    basicFinalizerCalled = false;
+    finalizerCalled = false;
 
     if (info.Length() > 0) {
       finalizeCb_ = Napi::Persistent(info[0].As<Napi::Function>());
@@ -17,71 +17,71 @@ class Test : public Napi::ObjectWrap<Test> {
                 DefineClass(env,
                             "Test",
                             {
-                                StaticAccessor("isSyncFinalizerCalled",
-                                               &IsSyncFinalizerCalled,
+                                StaticAccessor("isBasicFinalizerCalled",
+                                               &IsBasicFinalizerCalled,
                                                nullptr,
                                                napi_default),
-                                StaticAccessor("isAsyncFinalizerCalled",
-                                               &IsAsyncFinalizerCalled,
+                                StaticAccessor("isFinalizerCalled",
+                                               &IsFinalizerCalled,
                                                nullptr,
                                                napi_default),
                             }));
   }
 
-  void Finalize(Napi::BasicEnv /*env*/) { syncFinalizerCalled = true; }
+  void Finalize(Napi::BasicEnv /*env*/) { basicFinalizerCalled = true; }
 
   void Finalize(Napi::Env /*env*/) {
-    asyncFinalizerCalled = true;
+    finalizerCalled = true;
     if (!finalizeCb_.IsEmpty()) {
       finalizeCb_.Call({});
     }
   }
 
-  static Napi::Value IsSyncFinalizerCalled(const Napi::CallbackInfo& info) {
-    return Napi::Boolean::New(info.Env(), syncFinalizerCalled);
+  static Napi::Value IsBasicFinalizerCalled(const Napi::CallbackInfo& info) {
+    return Napi::Boolean::New(info.Env(), basicFinalizerCalled);
   }
 
-  static Napi::Value IsAsyncFinalizerCalled(const Napi::CallbackInfo& info) {
-    return Napi::Boolean::New(info.Env(), asyncFinalizerCalled);
+  static Napi::Value IsFinalizerCalled(const Napi::CallbackInfo& info) {
+    return Napi::Boolean::New(info.Env(), finalizerCalled);
   }
 
  private:
   Napi::FunctionReference finalizeCb_;
 
-  static bool syncFinalizerCalled;
-  static bool asyncFinalizerCalled;
+  static bool basicFinalizerCalled;
+  static bool finalizerCalled;
 };
 
-bool Test::syncFinalizerCalled = false;
-bool Test::asyncFinalizerCalled = false;
+bool Test::basicFinalizerCalled = false;
+bool Test::finalizerCalled = false;
 
-bool externalSyncFinalizerCalled = false;
-bool externalAsyncFinalizerCalled = false;
+bool externalBasicFinalizerCalled = false;
+bool externalFinalizerCalled = false;
 
-Napi::Value CreateExternalSyncFinalizer(const Napi::CallbackInfo& info) {
-  externalSyncFinalizerCalled = false;
+Napi::Value CreateExternalBasicFinalizer(const Napi::CallbackInfo& info) {
+  externalBasicFinalizerCalled = false;
   return Napi::External<int>::New(
       info.Env(), new int(1), [](Napi::BasicEnv /*env*/, int* data) {
-        externalSyncFinalizerCalled = true;
+        externalBasicFinalizerCalled = true;
         delete data;
       });
 }
 
-Napi::Value CreateExternalAsyncFinalizer(const Napi::CallbackInfo& info) {
-  externalAsyncFinalizerCalled = false;
+Napi::Value CreateExternalFinalizer(const Napi::CallbackInfo& info) {
+  externalFinalizerCalled = false;
   return Napi::External<int>::New(
       info.Env(), new int(1), [](Napi::Env /*env*/, int* data) {
-        externalAsyncFinalizerCalled = true;
+        externalFinalizerCalled = true;
         delete data;
       });
 }
 
-Napi::Value IsExternalSyncFinalizerCalled(const Napi::CallbackInfo& info) {
-  return Napi::Boolean::New(info.Env(), externalSyncFinalizerCalled);
+Napi::Value isExternalBasicFinalizerCalled(const Napi::CallbackInfo& info) {
+  return Napi::Boolean::New(info.Env(), externalBasicFinalizerCalled);
 }
 
-Napi::Value IsExternalAsyncFinalizerCalled(const Napi::CallbackInfo& info) {
-  return Napi::Boolean::New(info.Env(), externalAsyncFinalizerCalled);
+Napi::Value IsExternalFinalizerCalled(const Napi::CallbackInfo& info) {
+  return Napi::Boolean::New(info.Env(), externalFinalizerCalled);
 }
 
 #ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
@@ -132,14 +132,14 @@ Napi::Value PostFinalizerWithDataAndHint(const Napi::CallbackInfo& info) {
 Napi::Object InitFinalizerOrder(Napi::Env env) {
   Napi::Object exports = Napi::Object::New(env);
   Test::Initialize(env, exports);
-  exports["createExternalSyncFinalizer"] =
-      Napi::Function::New(env, CreateExternalSyncFinalizer);
-  exports["createExternalAsyncFinalizer"] =
-      Napi::Function::New(env, CreateExternalAsyncFinalizer);
-  exports["isExternalSyncFinalizerCalled"] =
-      Napi::Function::New(env, IsExternalSyncFinalizerCalled);
-  exports["isExternalAsyncFinalizerCalled"] =
-      Napi::Function::New(env, IsExternalAsyncFinalizerCalled);
+  exports["createExternalBasicFinalizer"] =
+      Napi::Function::New(env, CreateExternalBasicFinalizer);
+  exports["createExternalFinalizer"] =
+      Napi::Function::New(env, CreateExternalFinalizer);
+  exports["isExternalBasicFinalizerCalled"] =
+      Napi::Function::New(env, isExternalBasicFinalizerCalled);
+  exports["isExternalFinalizerCalled"] =
+      Napi::Function::New(env, IsExternalFinalizerCalled);
 
 #ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
   exports["PostFinalizer"] = Napi::Function::New(env, PostFinalizer);
diff --git a/test/finalizer_order.js b/test/finalizer_order.js
index 36ea68f2a..4b267a0d0 100644
--- a/test/finalizer_order.js
+++ b/test/finalizer_order.js
@@ -22,47 +22,46 @@ function test (binding) {
       global.gc();
 
       if (isExperimental) {
-        assert.strictEqual(binding.finalizer_order.Test.isSyncFinalizerCalled, true, 'Expected sync finalizer to be called [before ticking]');
-        assert.strictEqual(binding.finalizer_order.Test.isAsyncFinalizerCalled, false, 'Expected async finalizer to not be called [before ticking]');
+        assert.strictEqual(binding.finalizer_order.Test.isBasicFinalizerCalled, true, 'Expected basic finalizer to be called [before ticking]');
+        assert.strictEqual(binding.finalizer_order.Test.isFinalizerCalled, false, 'Expected (extended) finalizer to not be called [before ticking]');
         assert.strictEqual(isCallbackCalled, false, 'Expected callback to not be called [before ticking]');
       } else {
-        assert.strictEqual(binding.finalizer_order.Test.isSyncFinalizerCalled, false, 'Expected sync finalizer to not be called [before ticking]');
-        assert.strictEqual(binding.finalizer_order.Test.isAsyncFinalizerCalled, false, 'Expected async finalizer to not be called [before ticking]');
+        assert.strictEqual(binding.finalizer_order.Test.isBasicFinalizerCalled, false, 'Expected basic finalizer to not be called [before ticking]');
+        assert.strictEqual(binding.finalizer_order.Test.isFinalizerCalled, false, 'Expected (extended) finalizer to not be called [before ticking]');
         assert.strictEqual(isCallbackCalled, false, 'Expected callback to not be called [before ticking]');
       }
     },
     () => {
-      assert.strictEqual(binding.finalizer_order.Test.isSyncFinalizerCalled, true, 'Expected sync finalizer to be called [after ticking]');
-      assert.strictEqual(binding.finalizer_order.Test.isAsyncFinalizerCalled, true, 'Expected async finalizer to be called [after ticking]');
+      assert.strictEqual(binding.finalizer_order.Test.isBasicFinalizerCalled, true, 'Expected basic finalizer to be called [after ticking]');
+      assert.strictEqual(binding.finalizer_order.Test.isFinalizerCalled, true, 'Expected (extended) finalizer to be called [after ticking]');
       assert.strictEqual(isCallbackCalled, true, 'Expected callback to be called [after ticking]');
     },
 
-    'Finalizer Order - External with Sync Finalizer',
+    'Finalizer Order - External with Basic Finalizer',
     () => {
-      console.log(binding.finalizer_order);
-      let ext = binding.finalizer_order.createExternalSyncFinalizer();
+      let ext = binding.finalizer_order.createExternalBasicFinalizer();
       ext = null;
       global.gc();
 
       if (isExperimental) {
-        assert.strictEqual(binding.finalizer_order.isExternalSyncFinalizerCalled(), true, 'Expected External sync finalizer to be called [before ticking]');
+        assert.strictEqual(binding.finalizer_order.isExternalBasicFinalizerCalled(), true, 'Expected External basic finalizer to be called [before ticking]');
       } else {
-        assert.strictEqual(binding.finalizer_order.isExternalSyncFinalizerCalled(), false, 'Expected External sync finalizer to not be called [before ticking]');
+        assert.strictEqual(binding.finalizer_order.isExternalBasicFinalizerCalled(), false, 'Expected External basic finalizer to not be called [before ticking]');
       }
     },
     () => {
-      assert.strictEqual(binding.finalizer_order.isExternalSyncFinalizerCalled(), true, 'Expected External sync finalizer to be called [after ticking]');
+      assert.strictEqual(binding.finalizer_order.isExternalBasicFinalizerCalled(), true, 'Expected External basic finalizer to be called [after ticking]');
     },
 
-    'Finalizer Order - External with Async Finalizer',
+    'Finalizer Order - External with Finalizer',
     () => {
-      let ext = binding.finalizer_order.createExternalAsyncFinalizer();
+      let ext = binding.finalizer_order.createExternalFinalizer();
       ext = null;
       global.gc();
-      assert.strictEqual(binding.finalizer_order.isExternalAsyncFinalizerCalled(), false, 'Expected External async finalizer to not be called [before ticking]');
+      assert.strictEqual(binding.finalizer_order.isExternalFinalizerCalled(), false, 'Expected External extended finalizer to not be called [before ticking]');
     },
     () => {
-      assert.strictEqual(binding.finalizer_order.isExternalAsyncFinalizerCalled(), true, 'Expected External async finalizer to be called [after ticking]');
+      assert.strictEqual(binding.finalizer_order.isExternalFinalizerCalled(), true, 'Expected External extended finalizer to be called [after ticking]');
     }
   ];
 

From 13118f747542bed02e403d4cdfbab856ed680108 Mon Sep 17 00:00:00 2001
From: Kevin Eady <8634912+KevinEady@users.noreply.github.com>
Date: Thu, 25 Jul 2024 15:27:35 +0200
Subject: [PATCH 15/19] finish docs

---
 doc/README.md       |   2 +
 doc/array_buffer.md |  30 +++----
 doc/basic_env.md    | 201 ++++++++++++++++++++++++++++++++++++++++++++
 doc/buffer.md       |  50 ++++++-----
 doc/env.md          | 138 ++----------------------------
 doc/external.md     |  17 +++-
 doc/object_wrap.md  |  22 ++++-
 7 files changed, 281 insertions(+), 179 deletions(-)
 create mode 100644 doc/basic_env.md

diff --git a/doc/README.md b/doc/README.md
index d6abb7107..bad1a5c5a 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -37,6 +37,7 @@ The following is the documentation for node-addon-api.
  - [Full Class Hierarchy](hierarchy.md)
  - [Addon Structure](addon.md)
  - Data Types:
+    - [BasicEnv](basic_env.md)
     - [Env](env.md)
     - [CallbackInfo](callbackinfo.md)
     - [Reference](reference.md)
@@ -70,6 +71,7 @@ The following is the documentation for node-addon-api.
  - [Object Lifetime Management](object_lifetime_management.md)
     - [HandleScope](handle_scope.md)
     - [EscapableHandleScope](escapable_handle_scope.md)
+    - [Finalization](finalization.md)
  - [Memory Management](memory_management.md)
  - [Async Operations](async_operations.md)
     - [AsyncWorker](async_worker.md)
diff --git a/doc/array_buffer.md b/doc/array_buffer.md
index 3be6b42a2..9bb925129 100644
--- a/doc/array_buffer.md
+++ b/doc/array_buffer.md
@@ -31,13 +31,13 @@ Wraps the provided external data into a new `Napi::ArrayBuffer` instance.
 The `Napi::ArrayBuffer` instance does not assume ownership for the data and
 expects it to be valid for the lifetime of the instance. Since the
 `Napi::ArrayBuffer` is subject to garbage collection this overload is only
-suitable for data which is static and never needs to be freed.
-This factory method will not provide the caller with an opportunity to free the
-data when the `Napi::ArrayBuffer` gets garbage-collected. If you need to free
-the data retained by the `Napi::ArrayBuffer` object please use other
-variants of the `Napi::ArrayBuffer::New` factory method that accept
-`Napi::Finalizer`, which is a function that will be invoked when the
-`Napi::ArrayBuffer` object has been destroyed.
+suitable for data which is static and never needs to be freed. This factory
+method will not provide the caller with an opportunity to free the data when the
+`Napi::ArrayBuffer` gets garbage-collected. If you need to free the data
+retained by the `Napi::ArrayBuffer` object please use other variants of the
+`Napi::ArrayBuffer::New` factory method that accept `Napi::Finalizer`, which is
+a function that will be invoked when the `Napi::ArrayBuffer` object has been
+destroyed. See [Finalization]() for more details.
 
 ```cpp
 static Napi::ArrayBuffer Napi::ArrayBuffer::New(napi_env env, void* externalData, size_t byteLength);
@@ -72,9 +72,9 @@ static Napi::ArrayBuffer Napi::ArrayBuffer::New(napi_env env,
 - `[in] env`: The environment in which to create the `Napi::ArrayBuffer` instance.
 - `[in] externalData`: The pointer to the external data to wrap.
 - `[in] byteLength`: The length of the `externalData`, in bytes.
-- `[in] finalizeCallback`: A function to be called when the `Napi::ArrayBuffer` is
-  destroyed. It must implement `operator()`, accept an Napi::Env, a `void*` (which is the
-  `externalData` pointer), and return `void`.
+- `[in] finalizeCallback`: A function called when the engine destroys the
+  `Napi::ArrayBuffer` object, implementing `operator()(Napi::BasicEnv, void*)`.
+  See [Finalization]() for more details.
 
 Returns a new `Napi::ArrayBuffer` instance.
 
@@ -102,11 +102,10 @@ static Napi::ArrayBuffer Napi::ArrayBuffer::New(napi_env env,
 - `[in] env`: The environment in which to create the `Napi::ArrayBuffer` instance.
 - `[in] externalData`: The pointer to the external data to wrap.
 - `[in] byteLength`: The length of the `externalData`, in bytes.
-- `[in] finalizeCallback`: The function to be called when the `Napi::ArrayBuffer` is
-  destroyed. It must implement `operator()`, accept an Napi::Env, a `void*` (which is the
-  `externalData` pointer) and `Hint*`, and return `void`.
-- `[in] finalizeHint`: The hint to be passed as the second parameter of the
-  finalize callback.
+- `[in] finalizeCallback`: A function called when the engine destroys the
+  `Napi::ArrayBuffer` object, implementing `operator()(Napi::BasicEnv, void*,
+  Hint*)`. See [Finalization]() for more details.
+- `[in] finalizeHint`: The hint value passed to the `finalizeCallback` function.
 
 Returns a new `Napi::ArrayBuffer` instance.
 
@@ -163,3 +162,4 @@ Returns `true` if this `ArrayBuffer` has been detached.
 
 [`Napi::Object`]: ./object.md
 [External Buffer]: ./external_buffer.md
+[Finalization]: ./finalization.md
diff --git a/doc/basic_env.md b/doc/basic_env.md
new file mode 100644
index 000000000..25668ce33
--- /dev/null
+++ b/doc/basic_env.md
@@ -0,0 +1,201 @@
+# BasicEnv
+
+The opaque data structure containing the environment in which the request is
+being run.
+
+The `Napi::BasicEnv` object is usually created and passed by the Node.js runtime
+or node-addon-api infrastructure.
+
+The `Napi::BasicEnv` object represents an environment that has a limited subset
+of APIs when compared to `Napi::Env` and can be used in basic finalizers. See
+[Finalization]() for more details.
+
+## Methods
+
+### Constructor
+
+```cpp
+Napi::BasicEnv::BasicEnv(node_api_nogc_env env);
+```
+
+- `[in] env`: The `node_api_nogc_env` environment from which to construct the
+  `Napi::BasicEnv` object.
+
+### node_api_nogc_env
+
+```cpp
+operator node_api_nogc_env() const;
+```
+
+Returns the `node_api_nogc_env` opaque data structure representing the
+environment.
+
+### GetInstanceData
+```cpp
+template <typename T> T* GetInstanceData() const;
+```
+
+Returns the instance data that was previously associated with the environment,
+or `nullptr` if none was associated.
+
+### SetInstanceData
+
+
+```cpp
+template <typename T> using Finalizer = void (*)(Env, T*);
+template <typename T, Finalizer<T> fini = Env::DefaultFini<T>>
+void SetInstanceData(T* data) const;
+```
+
+- `[template] fini`: A function to call when the instance data is to be deleted.
+Accepts a function of the form `void CleanupData(Napi::Env env, T* data)`. If
+not given, the default finalizer will be used, which simply uses the `delete`
+operator to destroy `T*` when the addon instance is unloaded.
+- `[in] data`: A pointer to data that will be associated with the instance of
+the addon for the duration of its lifecycle.
+
+Associates a data item stored at `T* data` with the current instance of the
+addon. The item will be passed to the function `fini` which gets called when an
+instance of the addon is unloaded.
+
+### SetInstanceData
+
+```cpp
+template <typename DataType, typename HintType>
+using FinalizerWithHint = void (*)(Env, DataType*, HintType*);
+template <typename DataType,
+          typename HintType,
+          FinalizerWithHint<DataType, HintType> fini =
+            Env::DefaultFiniWithHint<DataType, HintType>>
+void SetInstanceData(DataType* data, HintType* hint) const;
+```
+
+- `[template] fini`: A function to call when the instance data is to be deleted.
+Accepts a function of the form `void CleanupData(Napi::Env env, DataType* data,
+HintType* hint)`. If not given, the default finalizer will be used, which simply
+uses the `delete` operator to destroy `T*` when the addon instance is unloaded.
+- `[in] data`: A pointer to data that will be associated with the instance of
+the addon for the duration of its lifecycle.
+- `[in] hint`: A pointer to data that will be associated with the instance of
+the addon for the duration of its lifecycle and will be passed as a hint to
+`fini` when the addon instance is unloaded.
+
+Associates a data item stored at `T* data` with the current instance of the
+addon. The item will be passed to the function `fini` which gets called when an
+instance of the addon is unloaded. This overload accepts an additional hint to
+be passed to `fini`.
+
+### GetModuleFileName
+
+```cpp
+const char* Napi::Env::GetModuleFileName() const;
+```
+
+Returns a A URL containing the absolute path of the location from which the
+add-on was loaded. For a file on the local file system it will start with
+`file://`. The string is null-terminated and owned by env and must thus not be
+modified or freed. It is only valid while the add-on is loaded.
+
+### AddCleanupHook
+
+```cpp
+template <typename Hook>
+CleanupHook<Hook> AddCleanupHook(Hook hook);
+```
+
+- `[in] hook`: A function to call when the environment exits. Accepts a function
+  of the form `void ()`.
+
+Registers `hook` as a function to be run once the current Node.js environment
+exits. Unlike the underlying C-based Node-API, providing the same `hook`
+multiple times **is** allowed. The hooks will be called in reverse order, i.e.
+the most recently added one will be called first.
+
+Returns an `Env::CleanupHook` object, which can be used to remove the hook via
+its `Remove()` method.
+
+### PostFinalizer
+
+```cpp
+template <typename Finalizer>
+inline void PostFinalizer(Finalizer finalizeCallback) const;
+```
+
+- `[in] finalizeCallback`: The function to queue for execution outside of the GC
+  finalization, implementing `operator()(Napi::Env)`. See [Finalization]() for
+  more details.
+
+### PostFinalizer
+
+```cpp
+template <typename Finalizer, typename T>
+inline void PostFinalizer(Finalizer finalizeCallback, T* data) const;
+```
+
+- `[in] finalizeCallback`: The function to queue for execution outside of the GC
+  finalization, implementing `operator()(Napi::Env, T*)`. See [Finalization]()
+  for more details.
+- `[in] data`: The data to associate with the object.
+
+### PostFinalizer
+
+```cpp
+template <typename Finalizer, typename T, typename Hint>
+inline void PostFinalizer(Finalizer finalizeCallback,
+                          T* data,
+                          Hint* finalizeHint) const;
+```
+
+- `[in] finalizeCallback`: The function to queue for execution outside of the GC
+  finalization, implementing `operator()(Napi::Env, T*, Hint*)`. See
+  [Finalization]() for more details.
+- `[in] data`: The data to associate with the object.
+- `[in] finalizeHint`: The hint value passed to the `finalizeCallback` function.
+
+### AddCleanupHook
+
+```cpp
+template <typename Hook, typename Arg>
+CleanupHook<Hook, Arg> AddCleanupHook(Hook hook, Arg* arg);
+```
+
+- `[in] hook`: A function to call when the environment exits. Accepts a function
+  of the form `void (Arg* arg)`.
+- `[in] arg`: A pointer to data that will be passed as the argument to `hook`.
+
+Registers `hook` as a function to be run with the `arg` parameter once the
+current Node.js environment exits. Unlike the underlying C-based Node-API,
+providing the same `hook` and `arg` pair multiple times **is** allowed. The
+hooks will be called in reverse order, i.e. the most recently added one will be
+called first.
+
+Returns an `Env::CleanupHook` object, which can be used to remove the hook via
+its `Remove()` method.
+
+# Env::CleanupHook
+
+The `Env::CleanupHook` object allows removal of the hook added via
+`Env::AddCleanupHook()`
+
+## Methods
+
+### IsEmpty
+
+```cpp
+bool IsEmpty();
+```
+
+Returns `true` if the cleanup hook was **not** successfully registered.
+
+### Remove
+
+```cpp
+bool Remove(Env env);
+```
+
+Unregisters the hook from running once the current Node.js environment exits.
+
+Returns `true` if the hook was successfully removed from the Node.js
+environment.
+
+[Finalization]: ./finalization.md
diff --git a/doc/buffer.md b/doc/buffer.md
index 427eeee2f..2e2ac87de 100644
--- a/doc/buffer.md
+++ b/doc/buffer.md
@@ -27,16 +27,15 @@ Returns a new `Napi::Buffer` object.
 
 Wraps the provided external data into a new `Napi::Buffer` object.
 
-The `Napi::Buffer` object does not assume ownership for the data and expects it to be
-valid for the lifetime of the object. Since the `Napi::Buffer` is subject to garbage
-collection this overload is only suitable for data which is static and never
-needs to be freed.
-This factory method will not provide the caller with an opportunity to free the
-data when the `Napi::Buffer` gets garbage-collected. If you need to free the
-data retained by the `Napi::Buffer` object please use other variants of the
-`Napi::Buffer::New` factory method that accept `Napi::Finalizer`, which is a
-function that will be invoked when the `Napi::Buffer` object has been
-destroyed.
+The `Napi::Buffer` object does not assume ownership for the data and expects it
+to be valid for the lifetime of the object. Since the `Napi::Buffer` is subject
+to garbage collection this overload is only suitable for data which is static
+and never needs to be freed. This factory method will not provide the caller
+with an opportunity to free the data when the `Napi::Buffer` gets
+garbage-collected. If you need to free the data retained by the `Napi::Buffer`
+object please use other variants of the `Napi::Buffer::New` factory method that
+accept `Finalizer`, which is a function that will be invoked when the
+`Napi::Buffer` object has been destroyed. See [Finalization]() for more details.
 
 ```cpp
 static Napi::Buffer<T> Napi::Buffer::New(napi_env env, T* data, size_t length);
@@ -70,9 +69,9 @@ static Napi::Buffer<T> Napi::Buffer::New(napi_env env,
 - `[in] env`: The environment in which to create the `Napi::Buffer` object.
 - `[in] data`: The pointer to the external data to expose.
 - `[in] length`: The number of `T` elements in the external data.
-- `[in] finalizeCallback`: The function to be called when the `Napi::Buffer` is
-  destroyed. It must implement `operator()`, accept an Napi::Env, a `T*` (which is the
-  external data pointer), and return `void`.
+- `[in] finalizeCallback`: The function called when the engine destroys the
+  `Napi::Buffer` object, implementing `operator()(Napi::BasicEnv, T*)`. See
+  [Finalization]() for more details.
 
 Returns a new `Napi::Buffer` object.
 
@@ -99,11 +98,10 @@ static Napi::Buffer<T> Napi::Buffer::New(napi_env env,
 - `[in] env`: The environment in which to create the `Napi::Buffer` object.
 - `[in] data`: The pointer to the external data to expose.
 - `[in] length`: The number of `T` elements in the external data.
-- `[in] finalizeCallback`: The function to be called when the `Napi::Buffer` is
-  destroyed. It must implement `operator()`, accept an Napi::Env, a `T*` (which is the
-  external data pointer) and `Hint*`, and return `void`.
-- `[in] finalizeHint`: The hint to be passed as the second parameter of the
-  finalize callback.
+- `[in] finalizeCallback`: The function called when the engine destroys the
+  `Napi::Buffer` object, implementing `operator()(Napi::BasicEnv, T*, Hint*)`.
+  See [Finalization]() for more details.
+- `[in] finalizeHint`: The hint value passed to the `finalizeCallback` function.
 
 Returns a new `Napi::Buffer` object.
 
@@ -157,9 +155,9 @@ static Napi::Buffer<T> Napi::Buffer::NewOrCopy(napi_env env,
 - `[in] env`: The environment in which to create the `Napi::Buffer` object.
 - `[in] data`: The pointer to the external data to expose.
 - `[in] length`: The number of `T` elements in the external data.
-- `[in] finalizeCallback`: The function to be called when the `Napi::Buffer` is
-  destroyed. It must implement `operator()`, accept an Napi::Env, a `T*` (which is the
-  external data pointer), and return `void`.
+- `[in] finalizeCallback`: The function called when the engine destroys the
+  `Napi::Buffer` object, implementing `operator()(Napi::BasicEnv, T*)`. See
+  [Finalization]() for more details.
 
 Returns a new `Napi::Buffer` object.
 
@@ -186,11 +184,10 @@ static Napi::Buffer<T> Napi::Buffer::NewOrCopy(napi_env env,
 - `[in] env`: The environment in which to create the `Napi::Buffer` object.
 - `[in] data`: The pointer to the external data to expose.
 - `[in] length`: The number of `T` elements in the external data.
-- `[in] finalizeCallback`: The function to be called when the `Napi::Buffer` is
-  destroyed. It must implement `operator()`, accept an Napi::Env, a `T*` (which is the
-  external data pointer) and `Hint*`, and return `void`.
-- `[in] finalizeHint`: The hint to be passed as the second parameter of the
-  finalize callback.
+- `[in] finalizeCallback`: The function called when the engine destroys the
+  `Napi::Buffer` object, implementing `operator()(Napi::BasicEnv, T*, Hint*)`.
+  See [Finalization]() for more details.
+- `[in] finalizeHint`: The hint value passed to the `finalizeCallback` function.
 
 Returns a new `Napi::Buffer` object.
 
@@ -245,3 +242,4 @@ Returns the number of `T` elements in the external data.
 
 [`Napi::Uint8Array`]: ./typed_array_of.md
 [External Buffer]: ./external_buffer.md
+[Finalization]: ./finalization.md
diff --git a/doc/env.md b/doc/env.md
index 29aa4459e..1989ea4e2 100644
--- a/doc/env.md
+++ b/doc/env.md
@@ -1,8 +1,13 @@
 # Env
 
-The opaque data structure containing the environment in which the request is being run.
+The opaque data structure containing the environment in which the request is
+being run.
 
-The Env object is usually created and passed by the Node.js runtime or node-addon-api infrastructure.
+The `Napi::Env` object is usually created and passed by the Node.js runtime or
+node-addon-api infrastructure.
+
+The `Napi::Env` object represents an environment that has a superset of APIs
+when compared to `Napi::BasicEnv` and therefore _cannot_ be used in basic finalizers.
 
 ## Methods
 
@@ -76,132 +81,3 @@ The `script` can be any of the following types:
 - `const char *`
 - `const std::string &`
 
-### GetInstanceData
-```cpp
-template <typename T> T* GetInstanceData() const;
-```
-
-Returns the instance data that was previously associated with the environment,
-or `nullptr` if none was associated.
-
-### SetInstanceData
-
-```cpp
-template <typename T> using Finalizer = void (*)(Env, T*);
-template <typename T, Finalizer<T> fini = Env::DefaultFini<T>>
-void SetInstanceData(T* data) const;
-```
-
-- `[template] fini`: A function to call when the instance data is to be deleted.
-Accepts a function of the form `void CleanupData(Napi::Env env, T* data)`. If
-not given, the default finalizer will be used, which simply uses the `delete`
-operator to destroy `T*` when the addon instance is unloaded.
-- `[in] data`: A pointer to data that will be associated with the instance of
-the addon for the duration of its lifecycle.
-
-Associates a data item stored at `T* data` with the current instance of the
-addon. The item will be passed to the function `fini` which gets called when an
-instance of the addon is unloaded.
-
-### SetInstanceData
-
-```cpp
-template <typename DataType, typename HintType>
-using FinalizerWithHint = void (*)(Env, DataType*, HintType*);
-template <typename DataType,
-          typename HintType,
-          FinalizerWithHint<DataType, HintType> fini =
-            Env::DefaultFiniWithHint<DataType, HintType>>
-void SetInstanceData(DataType* data, HintType* hint) const;
-```
-
-- `[template] fini`: A function to call when the instance data is to be deleted.
-Accepts a function of the form
-`void CleanupData(Napi::Env env, DataType* data, HintType* hint)`. If not given,
-the default finalizer will be used, which simply uses the `delete` operator to
-destroy `T*` when the addon instance is unloaded.
-- `[in] data`: A pointer to data that will be associated with the instance of
-the addon for the duration of its lifecycle.
-- `[in] hint`: A pointer to data that will be associated with the instance of
-the addon for the duration of its lifecycle and will be passed as a hint to
-`fini` when the addon instance is unloaded.
-
-Associates a data item stored at `T* data` with the current instance of the
-addon. The item will be passed to the function `fini` which gets called when an
-instance of the addon is unloaded. This overload accepts an additional hint to
-be passed to `fini`.
-
-### GetModuleFileName
-
-```cpp
-const char* Napi::Env::GetModuleFileName() const;
-```
-
-Returns a A URL containing the absolute path of the location from which the
-add-on was loaded. For a file on the local file system it will start with
-`file://`. The string is null-terminated and owned by env and must thus not be
-modified or freed. It is only valid while the add-on is loaded.
-
-### AddCleanupHook
-
-```cpp
-template <typename Hook>
-CleanupHook<Hook> AddCleanupHook(Hook hook);
-```
-
-- `[in] hook`: A function to call when the environment exits. Accepts a
-  function of the form `void ()`.
-
-Registers `hook` as a function to be run once the current Node.js environment
-exits. Unlike the underlying C-based Node-API, providing the same `hook`
-multiple times **is** allowed. The hooks will be called in reverse order, i.e.
-the most recently added one will be called first.
-
-Returns an `Env::CleanupHook` object, which can be used to remove the hook via
-its `Remove()` method.
-
-### AddCleanupHook
-
-```cpp
-template <typename Hook, typename Arg>
-CleanupHook<Hook, Arg> AddCleanupHook(Hook hook, Arg* arg);
-```
-
-- `[in] hook`: A function to call when the environment exits. Accepts a
-  function of the form `void (Arg* arg)`.
-- `[in] arg`: A pointer to data that will be passed as the argument to `hook`.
-
-Registers `hook` as a function to be run with the `arg` parameter once the
-current Node.js environment exits. Unlike the underlying C-based Node-API,
-providing the same `hook` and `arg` pair multiple times **is** allowed. The
-hooks will be called in reverse order, i.e. the most recently added one will be
-called first.
-
-Returns an `Env::CleanupHook` object, which can be used to remove the hook via
-its `Remove()` method.
-
-# Env::CleanupHook
-
-The `Env::CleanupHook` object allows removal of the hook added via
-`Env::AddCleanupHook()`
-
-## Methods
-
-### IsEmpty
-
-```cpp
-bool IsEmpty();
-```
-
-Returns `true` if the cleanup hook was **not** successfully registered.
-
-### Remove
-
-```cpp
-bool Remove(Env env);
-```
-
-Unregisters the hook from running once the current Node.js environment exits.
-
-Returns `true` if the hook was successfully removed from the Node.js
-environment.
diff --git a/doc/external.md b/doc/external.md
index ce42e112a..962da722f 100644
--- a/doc/external.md
+++ b/doc/external.md
@@ -4,7 +4,11 @@ Class `Napi::External<T>` inherits from class [`Napi::TypeTaggable`][].
 
 The `Napi::External` template class implements the ability to create a `Napi::Value` object with arbitrary C++ data. It is the user's responsibility to manage the memory for the arbitrary C++ data.
 
-`Napi::External` objects can be created with an optional Finalizer function and optional Hint value. The Finalizer function, if specified, is called when your `Napi::External` object is released by Node's garbage collector. It gives your code the opportunity to free any dynamically created data. If you specify a Hint value, it is passed to your Finalizer function.
+`Napi::External` objects can be created with an optional Finalizer function and
+optional Hint value. The `Finalizer` function, if specified, is called when your
+`Napi::External` object is released by Node's garbage collector. It gives your
+code the opportunity to free any dynamically created data. If you specify a Hint
+value, it is passed to your `Finalizer` function. See [Finalization]() for more details.
 
 Note that `Napi::Value::IsExternal()` will return `true` for any external value.
 It does not differentiate between the templated parameter `T` in
@@ -38,7 +42,9 @@ static Napi::External Napi::External::New(napi_env env,
 
 - `[in] env`: The `napi_env` environment in which to construct the `Napi::External` object.
 - `[in] data`: The arbitrary C++ data to be held by the `Napi::External` object.
-- `[in] finalizeCallback`: A function called when the `Napi::External` object is released by the garbage collector accepting a T* and returning void.
+- `[in] finalizeCallback`: The function called when the engine destroys the
+  `Napi::External` object, implementing `operator()(Napi::BasicEnv, T*)`. See
+  [Finalization]() for more details.
 
 Returns the created `Napi::External<T>` object.
 
@@ -54,8 +60,10 @@ static Napi::External Napi::External::New(napi_env env,
 
 - `[in] env`: The `napi_env` environment in which to construct the `Napi::External` object.
 - `[in] data`: The arbitrary C++ data to be held by the `Napi::External` object.
-- `[in] finalizeCallback`: A function called when the `Napi::External` object is released by the garbage collector accepting T* and Hint* parameters and returning void.
-- `[in] finalizeHint`: A hint value passed to the `finalizeCallback` function.
+- `[in] finalizeCallback`: The function called when the engine destroys the
+  `Napi::External` object, implementing `operator()(Napi::BasicEnv, T*, Hint*)`.
+  See [Finalization]() for more details.
+- `[in] finalizeHint`: The hint value passed to the `finalizeCallback` function.
 
 Returns the created `Napi::External<T>` object.
 
@@ -67,4 +75,5 @@ T* Napi::External::Data() const;
 
 Returns a pointer to the arbitrary C++ data held by the `Napi::External` object.
 
+[Finalization]: ./finalization.md
 [`Napi::TypeTaggable`]: ./type_taggable.md
diff --git a/doc/object_wrap.md b/doc/object_wrap.md
index 43546646a..29e1eee48 100644
--- a/doc/object_wrap.md
+++ b/doc/object_wrap.md
@@ -241,9 +241,24 @@ request being made.
 
 ### Finalize
 
-Provides an opportunity to run cleanup code that requires access to the
-`Napi::Env` before the wrapped native object instance is freed.  Override to
-implement.
+Provides an opportunity to run cleanup code that only utilizes basic Node APIs, if any.
+Override to implement. See [Finalization]() for more details.
+
+```cpp
+virtual void Finalize(Napi::BasicEnv env);
+```
+
+- `[in] env`: `Napi::Env`.
+
+### Finalize
+
+Provides an opportunity to run cleanup code that utilizes non-basic Node APIs.
+Override to implement.
+
+*NOTE*: Defining this method causes the deletion of the underlying `T* data` to
+be postponed until _after_ the garbage collection cycle. Since an `Napi::Env`
+has access to non-basic Node APIs, it cannot run in the same current tick as the
+garbage collector.
 
 ```cpp
 virtual void Finalize(Napi::Env env);
@@ -586,3 +601,4 @@ Returns `Napi::PropertyDescriptor` object that represents an static value
 property of a JavaScript class
 
 [`Napi::InstanceWrap<T>`]: ./instance_wrap.md
+[Finalization]: ./finalization.md

From 1ad6eda0e41d8523086f9e832d94cbe462d837d6 Mon Sep 17 00:00:00 2001
From: Kevin Eady <8634912+KevinEady@users.noreply.github.com>
Date: Thu, 25 Jul 2024 15:36:31 +0200
Subject: [PATCH 16/19] fix compilation error with duplicate `Finalizer`
 identifier

---
 doc/basic_env.md | 29 ++++++++++++++++++-----------
 napi-inl.h       | 43 ++++++++++++++++++++++++++-----------------
 napi.h           | 18 ++++++++++--------
 3 files changed, 54 insertions(+), 36 deletions(-)

diff --git a/doc/basic_env.md b/doc/basic_env.md
index 25668ce33..bc8255478 100644
--- a/doc/basic_env.md
+++ b/doc/basic_env.md
@@ -117,8 +117,9 @@ its `Remove()` method.
 ### PostFinalizer
 
 ```cpp
-template <typename Finalizer>
-inline void PostFinalizer(Finalizer finalizeCallback) const;
+using FinalizerWithoutData = void (*)(Env);
+
+inline void PostFinalizer(FinalizerWithoutData finalizeCallback) const;
 ```
 
 - `[in] finalizeCallback`: The function to queue for execution outside of the GC
@@ -128,28 +129,34 @@ inline void PostFinalizer(Finalizer finalizeCallback) const;
 ### PostFinalizer
 
 ```cpp
-template <typename Finalizer, typename T>
-inline void PostFinalizer(Finalizer finalizeCallback, T* data) const;
+template <typename T>
+using Finalizer = void (*)(Napi::Env, T*);
+
+template <typename DataType>
+inline void PostFinalizer(Finalizer<DataType> finalizeCallback, DataType* data) const;
 ```
 
 - `[in] finalizeCallback`: The function to queue for execution outside of the GC
-  finalization, implementing `operator()(Napi::Env, T*)`. See [Finalization]()
+  finalization, implementing `operator()(Napi::Env, DataType*)`. See [Finalization]()
   for more details.
-- `[in] data`: The data to associate with the object.
+- `[in] data`: The data value passed to the `finalizeCallback` function.
 
 ### PostFinalizer
 
 ```cpp
-template <typename Finalizer, typename T, typename Hint>
-inline void PostFinalizer(Finalizer finalizeCallback,
-                          T* data,
+template <typename DataType, typename HintType>
+using FinalizerWithHint = void (*)(Napi::Env, DataType*, HintType*);
+
+template <typename DataType, typename HintType>
+inline void PostFinalizer(FinalizerWithHint<DataType, HintType> finalizeCallback,
+                          DataType* data,
                           Hint* finalizeHint) const;
 ```
 
 - `[in] finalizeCallback`: The function to queue for execution outside of the GC
-  finalization, implementing `operator()(Napi::Env, T*, Hint*)`. See
+  finalization, implementing `operator()(Napi::Env, DataType*, HintType*)`. See
   [Finalization]() for more details.
-- `[in] data`: The data to associate with the object.
+- `[in] data`: The data value passed to the `finalizeCallback` function.
 - `[in] finalizeHint`: The hint value passed to the `finalizeCallback` function.
 
 ### AddCleanupHook
diff --git a/napi-inl.h b/napi-inl.h
index fdda4dbe6..5af32b390 100644
--- a/napi-inl.h
+++ b/napi-inl.h
@@ -6789,16 +6789,16 @@ bool Env::CleanupHook<Hook, Arg>::IsEmpty() const {
 #endif  // NAPI_VERSION > 2
 
 #ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
-template <typename Finalizer>
-inline void BasicEnv::PostFinalizer(Finalizer finalizeCallback) const {
+inline void BasicEnv::PostFinalizer(
+    FinalizerWithoutData finalizeCallback) const {
   using T = void*;
-  details::FinalizeData<T, Finalizer>* finalizeData =
-      new details::FinalizeData<T, Finalizer>(
+  details::FinalizeData<T, FinalizerWithoutData>* finalizeData =
+      new details::FinalizeData<T, FinalizerWithoutData>(
           {std::move(finalizeCallback), nullptr});
 
   napi_status status = node_api_post_finalizer(
       _env,
-      details::FinalizeData<T, Finalizer>::WrapperGCWithoutData,
+      details::FinalizeData<T, FinalizerWithoutData>::WrapperGCWithoutData,
       static_cast<void*>(nullptr),
       finalizeData);
   if (status != napi_ok) {
@@ -6808,14 +6808,18 @@ inline void BasicEnv::PostFinalizer(Finalizer finalizeCallback) const {
   }
 }
 
-template <typename Finalizer, typename T>
-inline void BasicEnv::PostFinalizer(Finalizer finalizeCallback, T* data) const {
-  details::FinalizeData<T, Finalizer>* finalizeData =
-      new details::FinalizeData<T, Finalizer>(
+template <typename DataType>
+inline void BasicEnv::PostFinalizer(Finalizer<DataType> finalizeCallback,
+                                    DataType* data) const {
+  details::FinalizeData<DataType, Finalizer<DataType>>* finalizeData =
+      new details::FinalizeData<DataType, Finalizer<DataType>>(
           {std::move(finalizeCallback), nullptr});
 
   napi_status status = node_api_post_finalizer(
-      _env, details::FinalizeData<T, Finalizer>::WrapperGC, data, finalizeData);
+      _env,
+      details::FinalizeData<DataType, Finalizer<DataType>>::WrapperGC,
+      data,
+      finalizeData);
   if (status != napi_ok) {
     delete finalizeData;
     NAPI_FATAL_IF_FAILED(
@@ -6823,16 +6827,21 @@ inline void BasicEnv::PostFinalizer(Finalizer finalizeCallback, T* data) const {
   }
 }
 
-template <typename Finalizer, typename T, typename Hint>
-inline void BasicEnv::PostFinalizer(Finalizer finalizeCallback,
-                                    T* data,
-                                    Hint* finalizeHint) const {
-  details::FinalizeData<T, Finalizer, Hint>* finalizeData =
-      new details::FinalizeData<T, Finalizer, Hint>(
+template <typename DataType, typename HintType>
+inline void BasicEnv::PostFinalizer(
+    FinalizerWithHint<DataType, HintType> finalizeCallback,
+    DataType* data,
+    HintType* finalizeHint) const {
+  details::FinalizeData<DataType,
+                        FinalizerWithHint<DataType, HintType>,
+                        HintType>* finalizeData = new details::
+      FinalizeData<DataType, FinalizerWithHint<DataType, HintType>, HintType>(
           {std::move(finalizeCallback), finalizeHint});
   napi_status status = node_api_post_finalizer(
       _env,
-      details::FinalizeData<T, Finalizer, Hint>::WrapperGCWithHint,
+      details::FinalizeData<DataType,
+                            FinalizerWithHint<DataType, HintType>,
+                            HintType>::WrapperGCWithHint,
       data,
       finalizeData);
   if (status != napi_ok) {
diff --git a/napi.h b/napi.h
index e0d056376..d808e4b32 100644
--- a/napi.h
+++ b/napi.h
@@ -395,16 +395,18 @@ class BasicEnv {
 #endif  // NAPI_VERSION > 8
 
 #ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
-  template <typename Finalizer>
-  inline void PostFinalizer(Finalizer finalizeCallback) const;
+  using FinalizerWithoutData = void (*)(Env);
+  inline void PostFinalizer(FinalizerWithoutData finalizeCallback) const;
 
-  template <typename Finalizer, typename T>
-  inline void PostFinalizer(Finalizer finalizeCallback, T* data) const;
+  template <typename DataType>
+  inline void PostFinalizer(Finalizer<DataType> finalizeCallback,
+                            DataType* data) const;
 
-  template <typename Finalizer, typename T, typename Hint>
-  inline void PostFinalizer(Finalizer finalizeCallback,
-                            T* data,
-                            Hint* finalizeHint) const;
+  template <typename DataType, typename HintType>
+  inline void PostFinalizer(
+      FinalizerWithHint<DataType, HintType> finalizeCallback,
+      DataType* data,
+      HintType* finalizeHint) const;
 #endif  // NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
 
   friend class Env;

From ec9f521aeba10c9eafd45b41be68642c796a3c35 Mon Sep 17 00:00:00 2001
From: Kevin Eady <8634912+KevinEady@users.noreply.github.com>
Date: Thu, 25 Jul 2024 16:12:01 +0200
Subject: [PATCH 17/19] attempt fix compilation issues #2

---
 doc/basic_env.md | 29 +++++++++++------------------
 napi-inl.h       | 41 ++++++++++++++++++-----------------------
 napi.h           | 18 ++++++++----------
 3 files changed, 37 insertions(+), 51 deletions(-)

diff --git a/doc/basic_env.md b/doc/basic_env.md
index bc8255478..e8af172bb 100644
--- a/doc/basic_env.md
+++ b/doc/basic_env.md
@@ -117,9 +117,8 @@ its `Remove()` method.
 ### PostFinalizer
 
 ```cpp
-using FinalizerWithoutData = void (*)(Env);
-
-inline void PostFinalizer(FinalizerWithoutData finalizeCallback) const;
+template <typename FinalizerType>
+inline void PostFinalizer(FinalizerType finalizeCallback) const;
 ```
 
 - `[in] finalizeCallback`: The function to queue for execution outside of the GC
@@ -129,34 +128,28 @@ inline void PostFinalizer(FinalizerWithoutData finalizeCallback) const;
 ### PostFinalizer
 
 ```cpp
-template <typename T>
-using Finalizer = void (*)(Napi::Env, T*);
-
-template <typename DataType>
-inline void PostFinalizer(Finalizer<DataType> finalizeCallback, DataType* data) const;
+template <typename FinalizerType, typename T>
+inline void PostFinalizer(FinalizerType finalizeCallback, T* data) const;
 ```
 
 - `[in] finalizeCallback`: The function to queue for execution outside of the GC
-  finalization, implementing `operator()(Napi::Env, DataType*)`. See [Finalization]()
+  finalization, implementing `operator()(Napi::Env, T*)`. See [Finalization]()
   for more details.
-- `[in] data`: The data value passed to the `finalizeCallback` function.
+- `[in] data`: The data to associate with the object.
 
 ### PostFinalizer
 
 ```cpp
-template <typename DataType, typename HintType>
-using FinalizerWithHint = void (*)(Napi::Env, DataType*, HintType*);
-
-template <typename DataType, typename HintType>
-inline void PostFinalizer(FinalizerWithHint<DataType, HintType> finalizeCallback,
-                          DataType* data,
+template <typename FinalizerType, typename T, typename Hint>
+inline void PostFinalizer(FinalizerType finalizeCallback,
+                          T* data,
                           Hint* finalizeHint) const;
 ```
 
 - `[in] finalizeCallback`: The function to queue for execution outside of the GC
-  finalization, implementing `operator()(Napi::Env, DataType*, HintType*)`. See
+  finalization, implementing `operator()(Napi::Env, T*, Hint*)`. See
   [Finalization]() for more details.
-- `[in] data`: The data value passed to the `finalizeCallback` function.
+- `[in] data`: The data to associate with the object.
 - `[in] finalizeHint`: The hint value passed to the `finalizeCallback` function.
 
 ### AddCleanupHook
diff --git a/napi-inl.h b/napi-inl.h
index 5af32b390..a66f2cbcf 100644
--- a/napi-inl.h
+++ b/napi-inl.h
@@ -6789,16 +6789,16 @@ bool Env::CleanupHook<Hook, Arg>::IsEmpty() const {
 #endif  // NAPI_VERSION > 2
 
 #ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
-inline void BasicEnv::PostFinalizer(
-    FinalizerWithoutData finalizeCallback) const {
+template <typename FinalizerType>
+inline void BasicEnv::PostFinalizer(FinalizerType finalizeCallback) const {
   using T = void*;
-  details::FinalizeData<T, FinalizerWithoutData>* finalizeData =
-      new details::FinalizeData<T, FinalizerWithoutData>(
+  details::FinalizeData<T, FinalizerType>* finalizeData =
+      new details::FinalizeData<T, FinalizerType>(
           {std::move(finalizeCallback), nullptr});
 
   napi_status status = node_api_post_finalizer(
       _env,
-      details::FinalizeData<T, FinalizerWithoutData>::WrapperGCWithoutData,
+      details::FinalizeData<T, FinalizerType>::WrapperGCWithoutData,
       static_cast<void*>(nullptr),
       finalizeData);
   if (status != napi_ok) {
@@ -6808,16 +6808,16 @@ inline void BasicEnv::PostFinalizer(
   }
 }
 
-template <typename DataType>
-inline void BasicEnv::PostFinalizer(Finalizer<DataType> finalizeCallback,
-                                    DataType* data) const {
-  details::FinalizeData<DataType, Finalizer<DataType>>* finalizeData =
-      new details::FinalizeData<DataType, Finalizer<DataType>>(
+template <typename FinalizerType, typename T>
+inline void BasicEnv::PostFinalizer(FinalizerType finalizeCallback,
+                                    T* data) const {
+  details::FinalizeData<T, FinalizerType>* finalizeData =
+      new details::FinalizeData<T, FinalizerType>(
           {std::move(finalizeCallback), nullptr});
 
   napi_status status = node_api_post_finalizer(
       _env,
-      details::FinalizeData<DataType, Finalizer<DataType>>::WrapperGC,
+      details::FinalizeData<T, FinalizerType>::WrapperGC,
       data,
       finalizeData);
   if (status != napi_ok) {
@@ -6827,21 +6827,16 @@ inline void BasicEnv::PostFinalizer(Finalizer<DataType> finalizeCallback,
   }
 }
 
-template <typename DataType, typename HintType>
-inline void BasicEnv::PostFinalizer(
-    FinalizerWithHint<DataType, HintType> finalizeCallback,
-    DataType* data,
-    HintType* finalizeHint) const {
-  details::FinalizeData<DataType,
-                        FinalizerWithHint<DataType, HintType>,
-                        HintType>* finalizeData = new details::
-      FinalizeData<DataType, FinalizerWithHint<DataType, HintType>, HintType>(
+template <typename FinalizerType, typename T, typename Hint>
+inline void BasicEnv::PostFinalizer(FinalizerType finalizeCallback,
+                                    T* data,
+                                    Hint* finalizeHint) const {
+  details::FinalizeData<T, FinalizerType, Hint>* finalizeData =
+      new details::FinalizeData<T, FinalizerType, Hint>(
           {std::move(finalizeCallback), finalizeHint});
   napi_status status = node_api_post_finalizer(
       _env,
-      details::FinalizeData<DataType,
-                            FinalizerWithHint<DataType, HintType>,
-                            HintType>::WrapperGCWithHint,
+      details::FinalizeData<T, FinalizerType, Hint>::WrapperGCWithHint,
       data,
       finalizeData);
   if (status != napi_ok) {
diff --git a/napi.h b/napi.h
index d808e4b32..d7c897a3d 100644
--- a/napi.h
+++ b/napi.h
@@ -395,18 +395,16 @@ class BasicEnv {
 #endif  // NAPI_VERSION > 8
 
 #ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
-  using FinalizerWithoutData = void (*)(Env);
-  inline void PostFinalizer(FinalizerWithoutData finalizeCallback) const;
+  template <typename FinalizerType>
+  inline void PostFinalizer(FinalizerType finalizeCallback) const;
 
-  template <typename DataType>
-  inline void PostFinalizer(Finalizer<DataType> finalizeCallback,
-                            DataType* data) const;
+  template <typename FinalizerType, typename T>
+  inline void PostFinalizer(FinalizerType finalizeCallback, T* data) const;
 
-  template <typename DataType, typename HintType>
-  inline void PostFinalizer(
-      FinalizerWithHint<DataType, HintType> finalizeCallback,
-      DataType* data,
-      HintType* finalizeHint) const;
+  template <typename FinalizerType, typename T, typename Hint>
+  inline void PostFinalizer(FinalizerType finalizeCallback,
+                            T* data,
+                            Hint* finalizeHint) const;
 #endif  // NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
 
   friend class Env;

From a8f63809e0cc7ded1dd141fb9c74b270eca79dfd Mon Sep 17 00:00:00 2001
From: Kevin Eady <8634912+KevinEady@users.noreply.github.com>
Date: Thu, 25 Jul 2024 16:23:43 +0200
Subject: [PATCH 18/19] fix docs

---
 doc/array_buffer.md |  6 +++---
 doc/basic_env.md    |  8 ++++----
 doc/buffer.md       | 10 +++++-----
 doc/env.md          |  4 +++-
 doc/external.md     |  6 +++---
 doc/object_wrap.md  |  2 +-
 6 files changed, 19 insertions(+), 17 deletions(-)

diff --git a/doc/array_buffer.md b/doc/array_buffer.md
index 9bb925129..de05e55b3 100644
--- a/doc/array_buffer.md
+++ b/doc/array_buffer.md
@@ -37,7 +37,7 @@ method will not provide the caller with an opportunity to free the data when the
 retained by the `Napi::ArrayBuffer` object please use other variants of the
 `Napi::ArrayBuffer::New` factory method that accept `Napi::Finalizer`, which is
 a function that will be invoked when the `Napi::ArrayBuffer` object has been
-destroyed. See [Finalization]() for more details.
+destroyed. See [Finalization][] for more details.
 
 ```cpp
 static Napi::ArrayBuffer Napi::ArrayBuffer::New(napi_env env, void* externalData, size_t byteLength);
@@ -74,7 +74,7 @@ static Napi::ArrayBuffer Napi::ArrayBuffer::New(napi_env env,
 - `[in] byteLength`: The length of the `externalData`, in bytes.
 - `[in] finalizeCallback`: A function called when the engine destroys the
   `Napi::ArrayBuffer` object, implementing `operator()(Napi::BasicEnv, void*)`.
-  See [Finalization]() for more details.
+  See [Finalization][] for more details.
 
 Returns a new `Napi::ArrayBuffer` instance.
 
@@ -104,7 +104,7 @@ static Napi::ArrayBuffer Napi::ArrayBuffer::New(napi_env env,
 - `[in] byteLength`: The length of the `externalData`, in bytes.
 - `[in] finalizeCallback`: A function called when the engine destroys the
   `Napi::ArrayBuffer` object, implementing `operator()(Napi::BasicEnv, void*,
-  Hint*)`. See [Finalization]() for more details.
+  Hint*)`. See [Finalization][] for more details.
 - `[in] finalizeHint`: The hint value passed to the `finalizeCallback` function.
 
 Returns a new `Napi::ArrayBuffer` instance.
diff --git a/doc/basic_env.md b/doc/basic_env.md
index e8af172bb..5ef619d5d 100644
--- a/doc/basic_env.md
+++ b/doc/basic_env.md
@@ -8,7 +8,7 @@ or node-addon-api infrastructure.
 
 The `Napi::BasicEnv` object represents an environment that has a limited subset
 of APIs when compared to `Napi::Env` and can be used in basic finalizers. See
-[Finalization]() for more details.
+[Finalization][] for more details.
 
 ## Methods
 
@@ -122,7 +122,7 @@ inline void PostFinalizer(FinalizerType finalizeCallback) const;
 ```
 
 - `[in] finalizeCallback`: The function to queue for execution outside of the GC
-  finalization, implementing `operator()(Napi::Env)`. See [Finalization]() for
+  finalization, implementing `operator()(Napi::Env)`. See [Finalization][] for
   more details.
 
 ### PostFinalizer
@@ -133,7 +133,7 @@ inline void PostFinalizer(FinalizerType finalizeCallback, T* data) const;
 ```
 
 - `[in] finalizeCallback`: The function to queue for execution outside of the GC
-  finalization, implementing `operator()(Napi::Env, T*)`. See [Finalization]()
+  finalization, implementing `operator()(Napi::Env, T*)`. See [Finalization][]
   for more details.
 - `[in] data`: The data to associate with the object.
 
@@ -148,7 +148,7 @@ inline void PostFinalizer(FinalizerType finalizeCallback,
 
 - `[in] finalizeCallback`: The function to queue for execution outside of the GC
   finalization, implementing `operator()(Napi::Env, T*, Hint*)`. See
-  [Finalization]() for more details.
+  [Finalization][] for more details.
 - `[in] data`: The data to associate with the object.
 - `[in] finalizeHint`: The hint value passed to the `finalizeCallback` function.
 
diff --git a/doc/buffer.md b/doc/buffer.md
index 2e2ac87de..548400481 100644
--- a/doc/buffer.md
+++ b/doc/buffer.md
@@ -35,7 +35,7 @@ with an opportunity to free the data when the `Napi::Buffer` gets
 garbage-collected. If you need to free the data retained by the `Napi::Buffer`
 object please use other variants of the `Napi::Buffer::New` factory method that
 accept `Finalizer`, which is a function that will be invoked when the
-`Napi::Buffer` object has been destroyed. See [Finalization]() for more details.
+`Napi::Buffer` object has been destroyed. See [Finalization][] for more details.
 
 ```cpp
 static Napi::Buffer<T> Napi::Buffer::New(napi_env env, T* data, size_t length);
@@ -71,7 +71,7 @@ static Napi::Buffer<T> Napi::Buffer::New(napi_env env,
 - `[in] length`: The number of `T` elements in the external data.
 - `[in] finalizeCallback`: The function called when the engine destroys the
   `Napi::Buffer` object, implementing `operator()(Napi::BasicEnv, T*)`. See
-  [Finalization]() for more details.
+  [Finalization][] for more details.
 
 Returns a new `Napi::Buffer` object.
 
@@ -100,7 +100,7 @@ static Napi::Buffer<T> Napi::Buffer::New(napi_env env,
 - `[in] length`: The number of `T` elements in the external data.
 - `[in] finalizeCallback`: The function called when the engine destroys the
   `Napi::Buffer` object, implementing `operator()(Napi::BasicEnv, T*, Hint*)`.
-  See [Finalization]() for more details.
+  See [Finalization][] for more details.
 - `[in] finalizeHint`: The hint value passed to the `finalizeCallback` function.
 
 Returns a new `Napi::Buffer` object.
@@ -157,7 +157,7 @@ static Napi::Buffer<T> Napi::Buffer::NewOrCopy(napi_env env,
 - `[in] length`: The number of `T` elements in the external data.
 - `[in] finalizeCallback`: The function called when the engine destroys the
   `Napi::Buffer` object, implementing `operator()(Napi::BasicEnv, T*)`. See
-  [Finalization]() for more details.
+  [Finalization][] for more details.
 
 Returns a new `Napi::Buffer` object.
 
@@ -186,7 +186,7 @@ static Napi::Buffer<T> Napi::Buffer::NewOrCopy(napi_env env,
 - `[in] length`: The number of `T` elements in the external data.
 - `[in] finalizeCallback`: The function called when the engine destroys the
   `Napi::Buffer` object, implementing `operator()(Napi::BasicEnv, T*, Hint*)`.
-  See [Finalization]() for more details.
+  See [Finalization][] for more details.
 - `[in] finalizeHint`: The hint value passed to the `finalizeCallback` function.
 
 Returns a new `Napi::Buffer` object.
diff --git a/doc/env.md b/doc/env.md
index 1989ea4e2..e50331496 100644
--- a/doc/env.md
+++ b/doc/env.md
@@ -7,7 +7,8 @@ The `Napi::Env` object is usually created and passed by the Node.js runtime or
 node-addon-api infrastructure.
 
 The `Napi::Env` object represents an environment that has a superset of APIs
-when compared to `Napi::BasicEnv` and therefore _cannot_ be used in basic finalizers.
+when compared to `Napi::BasicEnv` and therefore _cannot_ be used in basic
+finalizers. See [Finalization][] for more details.
 
 ## Methods
 
@@ -81,3 +82,4 @@ The `script` can be any of the following types:
 - `const char *`
 - `const std::string &`
 
+[Finalization]: ./finalization.md
diff --git a/doc/external.md b/doc/external.md
index 962da722f..4b4603e8e 100644
--- a/doc/external.md
+++ b/doc/external.md
@@ -8,7 +8,7 @@ The `Napi::External` template class implements the ability to create a `Napi::Va
 optional Hint value. The `Finalizer` function, if specified, is called when your
 `Napi::External` object is released by Node's garbage collector. It gives your
 code the opportunity to free any dynamically created data. If you specify a Hint
-value, it is passed to your `Finalizer` function. See [Finalization]() for more details.
+value, it is passed to your `Finalizer` function. See [Finalization][] for more details.
 
 Note that `Napi::Value::IsExternal()` will return `true` for any external value.
 It does not differentiate between the templated parameter `T` in
@@ -44,7 +44,7 @@ static Napi::External Napi::External::New(napi_env env,
 - `[in] data`: The arbitrary C++ data to be held by the `Napi::External` object.
 - `[in] finalizeCallback`: The function called when the engine destroys the
   `Napi::External` object, implementing `operator()(Napi::BasicEnv, T*)`. See
-  [Finalization]() for more details.
+  [Finalization][] for more details.
 
 Returns the created `Napi::External<T>` object.
 
@@ -62,7 +62,7 @@ static Napi::External Napi::External::New(napi_env env,
 - `[in] data`: The arbitrary C++ data to be held by the `Napi::External` object.
 - `[in] finalizeCallback`: The function called when the engine destroys the
   `Napi::External` object, implementing `operator()(Napi::BasicEnv, T*, Hint*)`.
-  See [Finalization]() for more details.
+  See [Finalization][] for more details.
 - `[in] finalizeHint`: The hint value passed to the `finalizeCallback` function.
 
 Returns the created `Napi::External<T>` object.
diff --git a/doc/object_wrap.md b/doc/object_wrap.md
index 29e1eee48..40fb3bf12 100644
--- a/doc/object_wrap.md
+++ b/doc/object_wrap.md
@@ -242,7 +242,7 @@ request being made.
 ### Finalize
 
 Provides an opportunity to run cleanup code that only utilizes basic Node APIs, if any.
-Override to implement. See [Finalization]() for more details.
+Override to implement. See [Finalization][] for more details.
 
 ```cpp
 virtual void Finalize(Napi::BasicEnv env);

From 195ec28a7b1dcfe571c6df5f8a2e1bb991126ff1 Mon Sep 17 00:00:00 2001
From: Kevin Eady <8634912+KevinEady@users.noreply.github.com>
Date: Thu, 29 Aug 2024 22:21:42 +0200
Subject: [PATCH 19/19] Address review comments

---
 doc/basic_env.md    | 31 +++++++++++++++----------------
 doc/env.md          |  6 ++++--
 doc/finalization.md | 18 ++++++++++--------
 napi.h              |  1 -
 4 files changed, 29 insertions(+), 27 deletions(-)

diff --git a/doc/basic_env.md b/doc/basic_env.md
index 5ef619d5d..7a5b430f1 100644
--- a/doc/basic_env.md
+++ b/doc/basic_env.md
@@ -1,7 +1,6 @@
 # BasicEnv
 
-The opaque data structure containing the environment in which the request is
-being run.
+The data structure containing the environment in which the request is being run.
 
 The `Napi::BasicEnv` object is usually created and passed by the Node.js runtime
 or node-addon-api infrastructure.
@@ -50,13 +49,13 @@ void SetInstanceData(T* data) const;
 - `[template] fini`: A function to call when the instance data is to be deleted.
 Accepts a function of the form `void CleanupData(Napi::Env env, T* data)`. If
 not given, the default finalizer will be used, which simply uses the `delete`
-operator to destroy `T*` when the addon instance is unloaded.
+operator to destroy `T*` when the add-on instance is unloaded.
 - `[in] data`: A pointer to data that will be associated with the instance of
-the addon for the duration of its lifecycle.
+the add-on for the duration of its lifecycle.
 
 Associates a data item stored at `T* data` with the current instance of the
-addon. The item will be passed to the function `fini` which gets called when an
-instance of the addon is unloaded.
+add-on. The item will be passed to the function `fini` which gets called when an
+instance of the add-on is unloaded.
 
 ### SetInstanceData
 
@@ -73,16 +72,16 @@ void SetInstanceData(DataType* data, HintType* hint) const;
 - `[template] fini`: A function to call when the instance data is to be deleted.
 Accepts a function of the form `void CleanupData(Napi::Env env, DataType* data,
 HintType* hint)`. If not given, the default finalizer will be used, which simply
-uses the `delete` operator to destroy `T*` when the addon instance is unloaded.
+uses the `delete` operator to destroy `T*` when the add-on instance is unloaded.
 - `[in] data`: A pointer to data that will be associated with the instance of
-the addon for the duration of its lifecycle.
+the add-on for the duration of its lifecycle.
 - `[in] hint`: A pointer to data that will be associated with the instance of
-the addon for the duration of its lifecycle and will be passed as a hint to
-`fini` when the addon instance is unloaded.
+the add-on for the duration of its lifecycle and will be passed as a hint to
+`fini` when the add-on instance is unloaded.
 
 Associates a data item stored at `T* data` with the current instance of the
-addon. The item will be passed to the function `fini` which gets called when an
-instance of the addon is unloaded. This overload accepts an additional hint to
+add-on. The item will be passed to the function `fini` which gets called when an
+instance of the add-on is unloaded. This overload accepts an additional hint to
 be passed to `fini`.
 
 ### GetModuleFileName
@@ -91,10 +90,10 @@ be passed to `fini`.
 const char* Napi::Env::GetModuleFileName() const;
 ```
 
-Returns a A URL containing the absolute path of the location from which the
-add-on was loaded. For a file on the local file system it will start with
-`file://`. The string is null-terminated and owned by env and must thus not be
-modified or freed. It is only valid while the add-on is loaded.
+Returns a URL containing the absolute path of the location from which the add-on
+was loaded. For a file on the local file system it will start with `file://`.
+The string is null-terminated and owned by env and must thus not be modified or
+freed. It is only valid while the add-on is loaded.
 
 ### AddCleanupHook
 
diff --git a/doc/env.md b/doc/env.md
index e50331496..7773275ee 100644
--- a/doc/env.md
+++ b/doc/env.md
@@ -1,7 +1,8 @@
 # Env
 
-The opaque data structure containing the environment in which the request is
-being run.
+Class `Napi::Env` inherits from class [`Napi::BasicEnv`][].
+
+The data structure containing the environment in which the request is being run.
 
 The `Napi::Env` object is usually created and passed by the Node.js runtime or
 node-addon-api infrastructure.
@@ -82,4 +83,5 @@ The `script` can be any of the following types:
 - `const char *`
 - `const std::string &`
 
+[`Napi::BasicEnv`]: ./basic_env.md
 [Finalization]: ./finalization.md
diff --git a/doc/finalization.md b/doc/finalization.md
index b4daa03ba..825ff742a 100644
--- a/doc/finalization.md
+++ b/doc/finalization.md
@@ -4,7 +4,8 @@ Various node-addon-api methods accept a templated `Finalizer finalizeCallback`
 parameter. This parameter represents a native callback function that runs in
 response to a garbage collection event. A finalizer is considered a _basic_
 finalizer if the callback only utilizes a certain subset of APIs, which may
-provide optimizations, improved execution, or other benefits.
+provide more efficient memory management, optimizations, improved execution, or
+other benefits.
 
 In general, it is best to use basic finalizers whenever possible (eg. when
 access to JavaScript is _not_ needed).
@@ -28,7 +29,7 @@ Use of basic finalizers may allow the engine to perform optimizations when
 scheduling or executing the callback. For example, V8 does not allow access to
 the engine heap during garbage collection. Restricting finalizers from accessing
 the engine heap allows the callback to execute during garbage collection,
-providing a chance to free native memory on the current tick.
+providing a chance to free native memory eagerly.
 
 In general, APIs that access engine heap are not allowed in basic finalizers.
 
@@ -46,12 +47,13 @@ Napi::ArrayBuffer::New(
 ## Scheduling Finalizers
 
 In addition to passing finalizers to `Napi::External`s and other Node-API
-constructs, use `Napi::BasicEnv::PostFinalize(Napi::BasicEnv, Finalizer)` to
-schedule a callback to run outside of the garbage collector finalization. Since
-the associated native memory may already be freed by the basic finalizer, any
-additional data may be passed eg. via the finalizer's parameters (`T data*`,
-`Hint hint*`) or via lambda capture. This allows for freeing native data in a
-basic finalizer, while executing any JavaScript code in an additional finalizer.
+constructs, `Napi::BasicEnv::PostFinalize(Napi::BasicEnv, Finalizer)` can be
+used to schedule a callback to run outside of the garbage collector
+finalization. Since the associated native memory may already be freed by the
+basic finalizer, any additional data may be passed eg. via the finalizer's
+parameters (`T data*`, `Hint hint*`) or via lambda capture. This allows for
+freeing native data in a basic finalizer, while executing any JavaScript code in
+an additional finalizer.
 
 ### Example
 
diff --git a/napi.h b/napi.h
index d7c897a3d..edec5111e 100644
--- a/napi.h
+++ b/napi.h
@@ -187,7 +187,6 @@ namespace NAPI_CPP_CUSTOM_NAMESPACE {
 #endif
 
 // Forward declarations
-class BasicEnv;
 class Env;
 class Value;
 class Boolean;