fix: nodejs add dispose method (#413)

This commit is contained in:
stefan-gorules
2025-11-21 09:41:43 +01:00
committed by GitHub
parent f8e753e138
commit bb70da3447
9 changed files with 90 additions and 18 deletions
+1
View File
@@ -11,6 +11,7 @@ crate-type = ["cdylib"]
[dependencies]
ahash = { workspace = true }
napi = { version = "3", features = ["serde-json", "error_anyhow", "tokio_rt"] }
napi-sys = "3"
napi-derive = "3"
tokio-util = { workspace = true, features = ["rt"] }
serde_json = { workspace = true }
+4 -3
View File
@@ -3,7 +3,7 @@
export declare class ZenDecision {
constructor()
evaluate(context: any, opts?: ZenEvaluateOptions | undefined | null): Promise<ZenEngineResponse>
safeEvaluate(context: any, opts?: ZenEvaluateOptions | undefined | null): Promise<SafeResult<ZenEngineResponse>>
safeEvaluate(context: any, opts?: ZenEvaluateOptions | undefined | null): { success: true, data: ZenEngineResponse } | { success: false; error: any; }
validate(): void
}
@@ -17,8 +17,9 @@ export declare class ZenEngine {
evaluate(key: string, context: any, opts?: ZenEvaluateOptions | undefined | null): Promise<ZenEngineResponse>
createDecision(content: ZenDecisionContent | Buffer | object): ZenDecision
getDecision(key: string): Promise<ZenDecision>
safeEvaluate(key: string, context: any, opts?: ZenEvaluateOptions | undefined | null): Promise<SafeResult<ZenEngineResponse>>
safeGetDecision(key: string): Promise<SafeResult<ZenDecision>>
safeEvaluate(key: string, context: any, opts?: ZenEvaluateOptions | undefined | null): { success: true, data: ZenEngineResponse } | { success: false; error: any; }
safeGetDecision(key: string): { success: true, data: ZenDecision } | { success: false; error: any; }
dispose(): void
}
export declare class ZenEngineHandlerRequest {
+1 -1
View File
@@ -10,7 +10,7 @@ use zen_engine::nodes::custom::{CustomNodeAdapter, CustomNodeRequest};
use zen_engine::nodes::{NodeError, NodeResponse, NodeResult};
use zen_engine::Variable;
type CustomNodeTsfn = Arc<
pub(crate) type CustomNodeTsfn = Arc<
ThreadsafeFunction<
ZenEngineHandlerRequest,
Promise<ZenEngineHandlerResponse>,
+3 -1
View File
@@ -46,7 +46,9 @@ impl ZenDecision {
Ok(result)
}
#[napi(ts_return_type = "Promise<SafeResult<ZenEngineResponse>>")]
#[napi(
ts_return_type = "{ success: true, data: ZenEngineResponse } | { success: false; error: any; }"
)]
pub async fn safe_evaluate(
&self,
context: Value,
+24
View File
@@ -0,0 +1,24 @@
use napi::check_status;
use napi::threadsafe_function::ThreadsafeFunctionHandle;
pub trait DisposeThreadsafeHandler {
fn dispose(&self) -> napi::Result<()>;
}
impl DisposeThreadsafeHandler for ThreadsafeFunctionHandle {
fn dispose(&self) -> napi::Result<()> {
self.with_write_aborted(|mut aborted_guard| {
if !*aborted_guard {
check_status!(unsafe {
napi_sys::napi_release_threadsafe_function(
self.get_raw(),
napi_sys::ThreadsafeFunctionReleaseMode::abort,
)
})?;
*aborted_guard = true;
}
Ok(())
})
}
}
+54 -11
View File
@@ -10,21 +10,27 @@ use napi::{Env, JsValue, Unknown, ValueType};
use napi_derive::napi;
use serde_json::Value;
use zen_engine::model::DecisionContent;
use zen_engine::{DecisionEngine, EvaluationSerializedOptions, EvaluationTraceKind};
use crate::content::ZenDecisionContent;
use crate::custom_node::CustomNode;
use crate::custom_node::{CustomNode, CustomNodeTsfn};
use crate::decision::ZenDecision;
use crate::http_handler::{NodeHttpHandler, ZenHttpHandlerRequest, ZenHttpHandlerResponse};
use crate::loader::DecisionLoader;
use crate::dispose::DisposeThreadsafeHandler;
use crate::http_handler::{
HttpHandlerTsfn, NodeHttpHandler, ZenHttpHandlerRequest, ZenHttpHandlerResponse,
};
use crate::loader::{DecisionLoader, LoaderTsfn};
use crate::mt::spawn_worker;
use crate::safe_result::SafeResult;
use crate::types::{ZenEngineHandlerRequest, ZenEngineHandlerResponse};
use zen_engine::model::DecisionContent;
use zen_engine::{DecisionEngine, EvaluationSerializedOptions, EvaluationTraceKind};
#[napi]
pub struct ZenEngine {
graph: Arc<DecisionEngine>,
custom_node_tsfn: Option<CustomNodeTsfn>,
loader_tsfn: Option<LoaderTsfn>,
http_handler_tsfn: Option<HttpHandlerTsfn>,
}
#[derive(Debug, Default)]
@@ -124,9 +130,17 @@ impl ZenEngine {
Arc::new(CustomNode::default()),
)
.into(),
loader_tsfn: None,
http_handler_tsfn: None,
custom_node_tsfn: None,
});
};
let mut loader_tsfn_opt: Option<LoaderTsfn> = None;
let mut http_handler_tsfn_opt: Option<HttpHandlerTsfn> = None;
let mut custom_node_tsfn_opt: Option<CustomNodeTsfn> = None;
let loader = match opts.loader {
None => DecisionLoader::default(),
Some(l) => {
@@ -137,7 +151,9 @@ impl ZenEngine {
.weak()
.build()?;
DecisionLoader::new(Arc::new(loader_tsfn))
let arc_loader_tsfn = Arc::new(loader_tsfn);
loader_tsfn_opt = Some(arc_loader_tsfn.clone());
DecisionLoader::new(arc_loader_tsfn)
}
};
@@ -151,7 +167,9 @@ impl ZenEngine {
.weak()
.build()?;
CustomNode::new(Arc::new(custom_tfsn))
let arc_custom_node = Arc::new(custom_tfsn);
custom_node_tsfn_opt = Some(arc_custom_node.clone());
CustomNode::new(arc_custom_node)
}
};
@@ -164,12 +182,18 @@ impl ZenEngine {
.weak()
.build()?;
let arc_http_handler_tsfn = Arc::new(http_tsfn);
http_handler_tsfn_opt = Some(arc_http_handler_tsfn.clone());
decision_engine = decision_engine
.with_http_handler(Some(Arc::new(NodeHttpHandler::new(Arc::new(http_tsfn)))));
.with_http_handler(Some(Arc::new(NodeHttpHandler::new(arc_http_handler_tsfn))));
}
Ok(Self {
graph: Arc::new(decision_engine),
loader_tsfn: loader_tsfn_opt,
http_handler_tsfn: http_handler_tsfn_opt,
custom_node_tsfn: custom_node_tsfn_opt,
})
}
@@ -228,7 +252,9 @@ impl ZenEngine {
Ok(ZenDecision::from(decision))
}
#[napi(ts_return_type = "Promise<SafeResult<ZenEngineResponse>>")]
#[napi(
ts_return_type = "{ success: true, data: ZenEngineResponse } | { success: false; error: any; }"
)]
pub async fn safe_evaluate(
&self,
key: String,
@@ -238,8 +264,25 @@ impl ZenEngine {
self.evaluate(key, context, opts).await.into()
}
#[napi(ts_return_type = "Promise<SafeResult<ZenDecision>>")]
#[napi(
ts_return_type = "{ success: true, data: ZenDecision } | { success: false; error: any; }"
)]
pub async fn safe_get_decision(&self, key: String) -> SafeResult<ZenDecision> {
self.get_decision(key).await.into()
}
#[napi]
pub fn dispose(&self) {
if let Some(loader_tsfn) = &self.loader_tsfn {
let _ = loader_tsfn.handle.dispose();
}
if let Some(http_handler_tsfn) = &self.http_handler_tsfn {
let _ = http_handler_tsfn.handle.dispose();
}
if let Some(custom_node) = &self.custom_node_tsfn {
let _ = custom_node.handle.dispose();
}
}
}
+1 -1
View File
@@ -14,7 +14,7 @@ use zen_engine::nodes::http_handler::{
HttpHandlerResponse as EngineHttpHandlerResponse,
};
type HttpHandlerTsfn = Arc<
pub(crate) type HttpHandlerTsfn = Arc<
ThreadsafeFunction<
ZenHttpHandlerRequest,
Promise<ZenHttpHandlerResponse>,
+1
View File
@@ -2,6 +2,7 @@ mod config;
mod content;
mod custom_node;
mod decision;
mod dispose;
mod engine;
mod expression;
mod http_handler;
+1 -1
View File
@@ -15,7 +15,7 @@ use zen_engine::model::DecisionContent;
use crate::content::ZenDecisionContent;
type LoaderTsfn = Arc<
pub(crate) type LoaderTsfn = Arc<
ThreadsafeFunction<
String,
Promise<Option<Either<Buffer, &'static ZenDecisionContent>>>,