feat: improve nodejs bindings (#145)

This commit is contained in:
stefan-gorules
2024-04-11 11:20:22 +02:00
committed by GitHub
parent 82ff028e3a
commit 921caeb497
7 changed files with 93 additions and 29 deletions
+6 -2
View File
@@ -8,7 +8,7 @@ export interface ZenEvaluateOptions {
trace?: boolean
}
export interface ZenEngineOptions {
loader?: (key: string) => Promise<Buffer>
loader?: (key: string) => Promise<Buffer | ZenDecisionContent>
customHandler?: (request: ZenEngineHandlerRequest) => Promise<ZenEngineHandlerResponse>
}
export function evaluateExpressionSync(expression: string, context?: any | undefined | null): any
@@ -40,6 +40,10 @@ export interface DecisionNode {
kind: string
config: any
}
export class ZenDecisionContent {
constructor(content: Buffer | object)
toBuffer(): Buffer
}
export class ZenDecision {
constructor()
evaluate(context: any, opts?: ZenEvaluateOptions | undefined | null): Promise<ZenEngineResponse>
@@ -48,7 +52,7 @@ export class ZenDecision {
export class ZenEngine {
constructor(options?: ZenEngineOptions | undefined | null)
evaluate(key: string, context: any, opts?: ZenEvaluateOptions | undefined | null): Promise<ZenEngineResponse>
createDecision(content: Buffer): ZenDecision
createDecision(content: ZenDecisionContent | Buffer | object): ZenDecision
getDecision(key: string): Promise<ZenDecision>
}
export class ZenEngineHandlerRequest {
+2 -1
View File
@@ -281,8 +281,9 @@ if (!nativeBinding) {
throw new Error(`Failed to load native binding`)
}
const { ZenDecision, ZenEngine, evaluateExpressionSync, evaluateUnaryExpressionSync, renderTemplateSync, evaluateExpression, evaluateUnaryExpression, renderTemplate, ZenEngineHandlerRequest } = nativeBinding
const { ZenDecisionContent, ZenDecision, ZenEngine, evaluateExpressionSync, evaluateUnaryExpressionSync, renderTemplateSync, evaluateExpression, evaluateUnaryExpression, renderTemplate, ZenEngineHandlerRequest } = nativeBinding
module.exports.ZenDecisionContent = ZenDecisionContent
module.exports.ZenDecision = ZenDecision
module.exports.ZenEngine = ZenEngine
module.exports.evaluateExpressionSync = evaluateExpressionSync
+37
View File
@@ -0,0 +1,37 @@
use std::sync::Arc;
use napi::bindgen_prelude::Buffer;
use napi::{Either, Env, JsObject};
use napi_derive::napi;
use serde_json::Value;
use zen_engine::model::DecisionContent;
#[napi]
pub struct ZenDecisionContent {
pub(crate) inner: Arc<DecisionContent>,
}
#[napi]
impl ZenDecisionContent {
#[napi(constructor)]
pub fn new(env: Env, content: Either<Buffer, JsObject>) -> napi::Result<Self> {
let decision_content: DecisionContent = match content {
Either::A(buf) => serde_json::from_slice(buf.as_ref())?,
Either::B(obj) => {
let serde_val: Value = env.from_js_value(obj)?;
serde_json::from_value(serde_val)?
}
};
Ok(Self {
inner: Arc::new(decision_content),
})
}
#[napi]
pub fn to_buffer(&self) -> napi::Result<Buffer> {
let content_vec = serde_json::to_vec(&self.inner.as_ref())?;
Ok(Buffer::from(content_vec))
}
}
+19 -6
View File
@@ -1,10 +1,11 @@
use crate::content::ZenDecisionContent;
use crate::custom_node::CustomNode;
use crate::decision::ZenDecision;
use crate::loader::DecisionLoader;
use crate::types::ZenEngineResponse;
use napi::anyhow::{anyhow, Context};
use napi::bindgen_prelude::Buffer;
use napi::{tokio, Env, JsFunction};
use napi::bindgen_prelude::{Buffer, Either3};
use napi::{tokio, Env, JsFunction, JsObject};
use napi_derive::napi;
use serde_json::Value;
use std::sync::Arc;
@@ -33,7 +34,7 @@ impl Default for ZenEvaluateOptions {
#[napi(object)]
pub struct ZenEngineOptions {
#[napi(ts_type = "(key: string) => Promise<Buffer>")]
#[napi(ts_type = "(key: string) => Promise<Buffer | ZenDecisionContent>")]
pub loader: Option<JsFunction>,
#[napi(ts_type = "(request: ZenEngineHandlerRequest) => Promise<ZenEngineHandlerResponse>")]
@@ -99,9 +100,21 @@ impl ZenEngine {
}
#[napi]
pub fn create_decision(&self, content: Buffer) -> napi::Result<ZenDecision> {
let decision_content: DecisionContent = serde_json::from_slice(content.as_ref())?;
let decision = self.graph.create_decision(Arc::new(decision_content));
pub fn create_decision(
&self,
env: Env,
content: Either3<&ZenDecisionContent, Buffer, JsObject>,
) -> napi::Result<ZenDecision> {
let decision_content: Arc<DecisionContent> = match content {
Either3::A(c) => c.inner.clone(),
Either3::B(buffer) => Arc::new(serde_json::from_slice(buffer.as_ref())?),
Either3::C(obj) => {
let serde_val: Value = env.from_js_value(obj)?;
Arc::new(serde_json::from_value(serde_val)?)
}
};
let decision = self.graph.create_decision(decision_content);
Ok(ZenDecision::from(decision))
}
+1
View File
@@ -1,3 +1,4 @@
mod content;
mod custom_node;
mod decision;
mod engine;
+20 -19
View File
@@ -1,24 +1,21 @@
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::{Env, JsFunction};
use std::sync::Arc;
use napi::{Either, Env, JsFunction};
use zen_engine::loader::{DecisionLoader as DecisionLoaderTrait, LoaderError, LoaderResult};
use zen_engine::model::DecisionContent;
use crate::content::ZenDecisionContent;
#[derive(Default)]
pub(crate) struct DecisionLoader {
function: Option<ThreadsafeFunction<String, ErrorStrategy::Fatal>>,
}
impl Default for DecisionLoader {
fn default() -> Self {
Self { function: None }
}
}
impl DecisionLoader {
pub fn try_new(env: &mut Env, function: JsFunction) -> napi::Result<Self> {
let mut tsf =
@@ -42,14 +39,14 @@ impl DecisionLoader {
.into());
};
let promise: Promise<Option<Buffer>> = function
let promise: Promise<Option<Either<Buffer, &ZenDecisionContent>>> = function
.clone()
.call_async(key.to_string())
.await
.map_err(|e| LoaderError::Internal {
key: key.to_string(),
source: anyhow!(e.reason),
})?;
key: key.to_string(),
source: anyhow!(e.reason),
})?;
let result = promise.await.map_err(|e| LoaderError::Internal {
key: key.to_string(),
@@ -60,13 +57,17 @@ impl DecisionLoader {
return Err(LoaderError::NotFound(key.to_string()).into());
};
let decision_content =
serde_json::from_slice(buffer.as_ref()).map_err(|e| LoaderError::Internal {
key: key.to_string(),
source: e.into(),
})?;
let decision_content = match buffer {
Either::A(buf) => Arc::new(serde_json::from_slice(buf.as_ref()).map_err(|e| {
LoaderError::Internal {
key: key.to_string(),
source: e.into(),
}
})?),
Either::B(dc) => dc.inner.clone(),
};
Ok(Arc::new(decision_content))
Ok(decision_content)
}
}
+8 -1
View File
@@ -4,7 +4,7 @@ import {
evaluateUnaryExpression,
renderTemplate,
evaluateExpressionSync,
evaluateUnaryExpressionSync, renderTemplateSync
evaluateUnaryExpressionSync, renderTemplateSync, ZenDecisionContent
} from "../index";
import fs from 'fs/promises';
import path from 'path';
@@ -81,6 +81,13 @@ describe('ZenEngine', () => {
const r = await engine.evaluate('custom.json', {a: 5});
expect(r.result.data).toEqual(25);
});
it('Parses ZenDecisionContent', async () => {
const decisionContent = new ZenDecisionContent(await loader('table.json'));
expect(decisionContent).toBeDefined();
expect(decisionContent.toBuffer()).toBeDefined();
});
})
describe('Expressions', () => {