feat: add nodejs dispose function (#146)

* fix: add nodejs dispose function

* fix: readme

---------

Co-authored-by: Ivan Miletic <vnmiletic@gmail.com>
This commit is contained in:
stefan-gorules
2024-04-11 17:43:34 +02:00
committed by GitHub
co-authored by Ivan Miletic
parent 64648aa4d5
commit 2a2d0d35d3
7 changed files with 71 additions and 37 deletions
+2 -2
View File
@@ -10,8 +10,8 @@ crate-type = ["cdylib"]
[dependencies]
async-trait = { workspace = true }
napi = { version = "2.16", features = ["serde-json", "error_anyhow", "tokio_rt"] }
napi-derive = "2.16"
napi = { version = "2.16.2", features = ["serde-json", "error_anyhow", "tokio_rt"] }
napi-derive = "2.16.2"
serde_json = { workspace = true }
futures = { workspace = true }
zen-engine = { path = "../../core/engine" }
+2
View File
@@ -40,6 +40,7 @@ import fs from 'fs/promises';
const decision = engine.createDecision(content);
const result = await decision.evaluate({ input: 15 });
engine.dispose();
})();
```
@@ -58,6 +59,7 @@ const loader = async (key: string) => fs.readFile(path.join(testDataRoot, key))(
const engine = new ZenEngine({ loader });
const result = await engine.evaluate('jdm_graph1.json', { input: 5 });
engine.dispose();
})();
```
+5
View File
@@ -54,6 +54,11 @@ export class ZenEngine {
evaluate(key: string, context: any, opts?: ZenEvaluateOptions | undefined | null): Promise<ZenEngineResponse>
createDecision(content: ZenDecisionContent | Buffer | object): ZenDecision
getDecision(key: string): Promise<ZenDecision>
/**
* Function used to dispose memory allocated for loaders
* In the future, it will likely be removed and made automatic
*/
dispose(): void
}
export class ZenEngineHandlerRequest {
input: any
+5 -18
View File
@@ -1,35 +1,22 @@
use napi::anyhow::anyhow;
use napi::bindgen_prelude::Promise;
use napi::threadsafe_function::{ErrorStrategy, ThreadSafeCallContext, ThreadsafeFunction};
use napi::{Env, JsFunction};
use napi::threadsafe_function::{ErrorStrategy, ThreadsafeFunction};
use zen_engine::handler::custom_node_adapter::{CustomNodeAdapter, CustomNodeRequest};
use zen_engine::handler::node::{NodeResponse, NodeResult};
use crate::types::{ZenEngineHandlerRequest, ZenEngineHandlerResponse};
#[derive(Default)]
pub(crate) struct CustomNode {
function: Option<ThreadsafeFunction<ZenEngineHandlerRequest, ErrorStrategy::Fatal>>,
}
impl Default for CustomNode {
fn default() -> Self {
Self { function: None }
}
}
impl CustomNode {
pub fn try_new(env: &mut Env, function: JsFunction) -> napi::Result<Self> {
let mut tsf = function.create_threadsafe_function(
0,
|cx: ThreadSafeCallContext<ZenEngineHandlerRequest>| Ok(vec![cx.value]),
)?;
tsf.unref(env)?;
Ok(Self {
pub fn new(tsf: ThreadsafeFunction<ZenEngineHandlerRequest, ErrorStrategy::Fatal>) -> Self {
Self {
function: Some(tsf),
})
}
}
}
+48 -7
View File
@@ -2,9 +2,10 @@ use crate::content::ZenDecisionContent;
use crate::custom_node::CustomNode;
use crate::decision::ZenDecision;
use crate::loader::DecisionLoader;
use crate::types::ZenEngineResponse;
use crate::types::{ZenEngineHandlerRequest, ZenEngineResponse};
use napi::anyhow::{anyhow, Context};
use napi::bindgen_prelude::{Buffer, Either3};
use napi::threadsafe_function::{ErrorStrategy, ThreadSafeCallContext, ThreadsafeFunction};
use napi::{tokio, Env, JsFunction, JsObject};
use napi_derive::napi;
use serde_json::Value;
@@ -15,6 +16,9 @@ use zen_engine::{DecisionEngine, EvaluationOptions};
#[napi]
pub struct ZenEngine {
graph: Arc<DecisionEngine<DecisionLoader, CustomNode>>,
loader_ref: Option<ThreadsafeFunction<String, ErrorStrategy::Fatal>>,
custom_handler_ref: Option<ThreadsafeFunction<ZenEngineHandlerRequest, ErrorStrategy::Fatal>>,
}
#[napi(object)]
@@ -44,7 +48,7 @@ pub struct ZenEngineOptions {
#[napi]
impl ZenEngine {
#[napi(constructor)]
pub fn new(mut env: Env, options: Option<ZenEngineOptions>) -> napi::Result<Self> {
pub fn new(options: Option<ZenEngineOptions>) -> napi::Result<Self> {
let Some(opts) = options else {
return Ok(Self {
graph: DecisionEngine::new(
@@ -52,21 +56,45 @@ impl ZenEngine {
CustomNode::default().into(),
)
.into(),
loader_ref: None,
custom_handler_ref: None,
});
};
let loader = match opts.loader {
None => DecisionLoader::default(),
Some(loader_fn) => DecisionLoader::try_new(&mut env, loader_fn)?,
let loader_ref = match opts.loader {
None => None,
Some(l) => Some(l.create_threadsafe_function(
0,
|cx: ThreadSafeCallContext<String>| {
cx.env.create_string(cx.value.as_str()).map(|v| vec![v])
},
)?),
};
let custom_handler = match opts.custom_handler {
let loader = match &loader_ref {
None => DecisionLoader::default(),
Some(loader_fn) => DecisionLoader::new(loader_fn.clone())?,
};
let custom_handler_ref = match opts.custom_handler {
None => None,
Some(custom_handler_fn) => Some(custom_handler_fn.create_threadsafe_function(
0,
|cx: ThreadSafeCallContext<ZenEngineHandlerRequest>| Ok(vec![cx.value]),
)?),
};
let custom_handler = match &custom_handler_ref {
None => CustomNode::default(),
Some(custom_fn) => CustomNode::try_new(&mut env, custom_fn)?,
Some(custom_fn) => CustomNode::new(custom_fn.clone()),
};
Ok(Self {
graph: DecisionEngine::new(loader.into(), custom_handler.into()).into(),
loader_ref,
custom_handler_ref,
})
}
@@ -128,4 +156,17 @@ impl ZenEngine {
Ok(ZenDecision::from(decision))
}
/// Function used to dispose memory allocated for loaders
/// In the future, it will likely be removed and made automatic
#[napi]
pub fn dispose(&self) {
if let Some(loader) = self.loader_ref.clone() {
let _ = loader.abort();
}
if let Some(loader) = self.custom_handler_ref.clone() {
let _ = loader.abort();
}
}
}
+3 -10
View File
@@ -3,8 +3,8 @@ use std::sync::Arc;
use async_trait::async_trait;
use napi::anyhow::anyhow;
use napi::bindgen_prelude::{Buffer, Promise};
use napi::threadsafe_function::{ErrorStrategy, ThreadSafeCallContext, ThreadsafeFunction};
use napi::{Either, Env, JsFunction};
use napi::threadsafe_function::{ErrorStrategy, ThreadsafeFunction};
use napi::Either;
use zen_engine::loader::{DecisionLoader as DecisionLoaderTrait, LoaderError, LoaderResult};
use zen_engine::model::DecisionContent;
@@ -17,14 +17,7 @@ pub(crate) struct DecisionLoader {
}
impl DecisionLoader {
pub fn try_new(env: &mut Env, function: JsFunction) -> napi::Result<Self> {
let mut tsf =
function.create_threadsafe_function(0, |cx: ThreadSafeCallContext<String>| {
cx.env.create_string(cx.value.as_str()).map(|v| vec![v])
})?;
tsf.unref(env)?;
pub fn new(tsf: ThreadsafeFunction<String, ErrorStrategy::Fatal>) -> napi::Result<Self> {
Ok(Self {
function: Some(tsf),
})
+6
View File
@@ -29,6 +29,8 @@ describe('ZenEngine', () => {
expect(r1.result.output).toEqual(10);
expect(r2.result.output).toEqual(0);
expect(r3.result.output).toEqual(10);
engine.dispose();
}, 10000);
it('Evaluates decisions using getDecision', async () => {
@@ -46,6 +48,8 @@ describe('ZenEngine', () => {
expect(r1.result.output).toEqual(20);
expect(r2.result.output).toEqual(0);
expect(r3.result.output).toEqual(10);
engine.dispose();
}, 10000);
it('Creates a decision from contents', async () => {
@@ -80,6 +84,8 @@ describe('ZenEngine', () => {
const r = await engine.evaluate('custom.json', {a: 5});
expect(r.result.data).toEqual(25);
engine.dispose();
});
it('Parses ZenDecisionContent', async () => {