napi_async_local 0.2.0

Extends napi-rs with the ability to run local futures
Documentation
use napi::bindgen_prelude::FromNapiValue;
use napi::threadsafe_function::ThreadsafeFunction;
use napi::threadsafe_function::ThreadsafeFunctionCallMode;
use napi::Env;
use napi::JsFunction;
use napi::JsNumber;
use napi::JsUnknown;
use napi::NapiValue;
use once_cell::unsync::OnceCell;

use super::root::RootRef;

thread_local! {
  static GLOBAL: OnceCell<RootRef> = Default::default();
  static DROP: OnceCell<ThreadsafeFunction<i32>> = Default::default();
}

pub fn with_global<F, R>(
  env: &Env,
  func: F,
) -> napi::Result<R>
where
  F: FnOnce(&RootRef) -> napi::Result<R>,
{
  DROP.with(|drop| -> napi::Result<()> {
    if drop.get().is_some() {
      return Ok(());
    }

    let jsfn = env.create_function_from_closure::<Vec<JsUnknown>, _>("", |ctx| {
      GLOBAL.with(|global| {
        let global_ref = global.get().unwrap();
        let mut store = global_ref.into_inner(&ctx.env)?;
        let index = ctx.get::<JsNumber>(1)?;
        store.set_property(index, ctx.env.get_undefined()?)?;
        Ok(vec![])
      })
    })?;

    let mut tsfn = env.create_threadsafe_function::<i32, JsNumber, _>(&jsfn, 0, |ctx| {
      let number = ctx.env.create_int32(ctx.value)?;
      Ok(vec![number])
    })?;

    tsfn.unref(env)?;
    drop.set(tsfn).ok();
    Ok(())
  })?;

  GLOBAL.with(move |inner| -> napi::Result<R> {
    let global_ref = inner.get_or_try_init(move || RootRef::new_array(env))?;
    func(global_ref)
  })
}

pub fn set_store_value(
  env: &Env,
  value: impl NapiValue,
) -> napi::Result<i32> {
  with_global(env, |global_ref| {
    let store = global_ref.into_inner(&env)?;

    let push: JsFunction = store.get_named_property_unchecked("push")?;
    let length = JsNumber::from_unknown(push.call(Some(&store), &[value])?)?;
    Ok(length.get_int32()? - 1)
  })
}

pub fn get_store_value<R: NapiValue>(
  env: &Env,
  identifier: &i32,
) -> napi::Result<R> {
  with_global(env, |global_ref| {
    let store = global_ref.into_inner(&env)?;

    let index = env.create_int32(*identifier)?;
    store.get_property_unchecked(index)
  })
}

pub fn delete_store_value(identifier: i32) {
  DROP.with(|drop| {
    if let Some(drop) = drop.get() {
      drop.call(Ok(identifier), ThreadsafeFunctionCallMode::Blocking);
    }
  });
}