Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions .github/workflows/node.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ jobs:
yarn build --target aarch64-unknown-linux-musl
/aarch64-linux-musl-cross/bin/aarch64-linux-musl-strip *.node

name: stable - ${{ matrix.settings.target }} - node@18
name: stable - ${{ matrix.settings.target }} - node@22
runs-on: ${{ matrix.settings.host }}
defaults:
run:
Expand All @@ -124,7 +124,7 @@ jobs:
uses: actions/setup-node@v3
if: ${{ !matrix.settings.docker }}
with:
node-version: 18
node-version: 22
check-latest: true
cache: yarn
cache-dependency-path: 'bindings/nodejs/yarn.lock'
Expand Down Expand Up @@ -191,7 +191,7 @@ jobs:
target: 'aarch64-apple-darwin'
- host: windows-latest
target: 'x86_64-pc-windows-msvc'
node: [ '18', '20', '22' ]
node: [ '20', '22' ]
runs-on: ${{ matrix.settings.host }}

steps:
Expand Down Expand Up @@ -235,7 +235,7 @@ jobs:
strategy:
fail-fast: false
matrix:
node: [ '18', '20', '22' ]
node: [ '20', '22' ]
runs-on: ubuntu-latest

steps:
Expand Down Expand Up @@ -286,7 +286,7 @@ jobs:
strategy:
fail-fast: false
matrix:
node: [ '18', '20', '22' ]
node: [ '20', '22' ]
runs-on: ubuntu-latest

steps:
Expand Down Expand Up @@ -337,7 +337,7 @@ jobs:
strategy:
fail-fast: false
matrix:
node: [ '18', '20', '22' ]
node: [ '20', '22' ]
runs-on: ubuntu-latest

steps:
Expand Down Expand Up @@ -457,7 +457,7 @@ jobs:
- name: Setup node
uses: actions/setup-node@v3
with:
node-version: 18
node-version: 22
check-latest: true
cache: yarn
cache-dependency-path: 'bindings/nodejs/yarn.lock'
Expand Down
4 changes: 2 additions & 2 deletions bindings/c/src/custom_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@ impl ZenCustomNodeResult {

if let Some(c_error) = maybe_error {
let maybe_str = c_error.to_str().unwrap_or("unknown error");
return Err(anyhow!("{maybe_str}"));
return Err(anyhow!("{maybe_str}").into());
}

if self.content.is_null() {
return Err(anyhow!("response not provided"));
return Err(anyhow!("response not provided").into());
}

let content_cstr = unsafe { CString::from_raw(self.content) };
Expand Down
4 changes: 2 additions & 2 deletions bindings/c/src/languages/go.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,11 @@ impl GoCustomNode {
impl CustomNodeAdapter for GoCustomNode {
async fn handle(&self, request: CustomNodeRequest) -> NodeResult {
let Some(handler) = self.handler else {
return Err(anyhow!("go handler not found"));
return Err(anyhow!("go handler not found").into());
};

let Ok(request_value) = serde_json::to_string(&request) else {
return Err(anyhow!("failed to serialize request json"));
return Err(anyhow!("failed to serialize request json").into());
};

let c_request = unsafe { CString::from_vec_unchecked(request_value.into_bytes()) };
Expand Down
2 changes: 1 addition & 1 deletion bindings/c/src/languages/native.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ impl NativeCustomNode {
impl CustomNodeAdapter for NativeCustomNode {
async fn handle(&self, request: CustomNodeRequest) -> NodeResult {
let Ok(request_value) = serde_json::to_string(&request) else {
return Err(anyhow!("failed to serialize request json"));
return Err(anyhow!("failed to serialize request json").into());
};

let c_request = unsafe { CString::from_vec_unchecked(request_value.into_bytes()) };
Expand Down
6 changes: 3 additions & 3 deletions bindings/c/src/result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@ impl<T> From<&IsolateError> for ZenResult<T> {
}
}

impl<T> From<&Box<LoaderError>> for ZenResult<T> {
fn from(loader_error: &Box<LoaderError>) -> Self {
match loader_error.as_ref() {
impl<T> From<&LoaderError> for ZenResult<T> {
fn from(loader_error: &LoaderError) -> Self {
match loader_error {
LoaderError::NotFound(key) => {
ZenResult::error(ZenError::LoaderKeyNotFound { key: key.clone() })
}
Expand Down
38 changes: 13 additions & 25 deletions bindings/nodejs/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,23 @@

export interface ZenConfig {
nodesInContext?: boolean
functionTimeoutMillis?: number
}
export function overrideConfig(config: ZenConfig): void
export declare function overrideConfig(config: ZenConfig): void
export interface ZenEvaluateOptions {
maxDepth?: number
trace?: boolean
trace?: boolean | 'string' | 'reference' | 'referenceString'
}
export interface ZenEngineOptions {
loader?: (key: string) => Promise<Buffer | ZenDecisionContent>
customHandler?: (request: ZenEngineHandlerRequest) => Promise<ZenEngineHandlerResponse>
}
export function evaluateExpressionSync(expression: string, context?: any | undefined | null): any
export function evaluateUnaryExpressionSync(expression: string, context: any): boolean
export function renderTemplateSync(template: string, context: any): any
export function evaluateExpression(expression: string, context?: any | undefined | null): Promise<any>
export function evaluateUnaryExpression(expression: string, context: any): Promise<boolean>
export function renderTemplate(template: string, context: any): Promise<any>
export declare function evaluateExpressionSync(expression: string, context?: any | undefined | null): any
export declare function evaluateUnaryExpressionSync(expression: string, context: any): boolean
export declare function renderTemplateSync(template: string, context: any): any
export declare function evaluateExpression(expression: string, context?: any | undefined | null): Promise<any>
export declare function evaluateUnaryExpression(expression: string, context: any): Promise<boolean>
export declare function renderTemplate(template: string, context: any): Promise<any>
export interface ZenEngineTrace {
id: string
name: string
Expand All @@ -45,17 +46,17 @@ export interface DecisionNode {
kind: string
config: any
}
export class ZenDecisionContent {
export declare class ZenDecisionContent {
constructor(content: Buffer | object)
toBuffer(): Buffer
}
export class ZenDecision {
export declare class ZenDecision {
constructor()
evaluate(context: any, opts?: ZenEvaluateOptions | undefined | null): Promise<ZenEngineResponse>
safeEvaluate(context: any, opts?: ZenEvaluateOptions | undefined | null): Promise<SafeResult<ZenEngineResponse>>
validate(): void
}
export class ZenEngine {
export declare class ZenEngine {
constructor(options?: ZenEngineOptions | undefined | null)
evaluate(key: string, context: any, opts?: ZenEvaluateOptions | undefined | null): Promise<ZenEngineResponse>
createDecision(content: ZenDecisionContent | Buffer | object): ZenDecision
Expand All @@ -68,23 +69,10 @@ export class ZenEngine {
*/
dispose(): void
}
export class ZenEngineHandlerRequest {
export declare class ZenEngineHandlerRequest {
input: any
node: DecisionNode
constructor()
getField(path: string): unknown
getFieldRaw(path: string): unknown
}

// Custom definitions
type SafeResultSuccess<T> = {
success: true;
data: T;
}

type SafeResultError = {
success: false;
error: any;
}

export type SafeResult<T> = SafeResultSuccess<T> | SafeResultError;
3 changes: 2 additions & 1 deletion bindings/nodejs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"@types/express": "^5.0.1",
"@types/node": "^22.14.1",
"babel-jest": "^29.7.0",
"cross-env": "^10.0.0",
"express": "^5.1.0",
"jest": "^29.7.0",
"lerna": "6",
Expand All @@ -78,7 +79,7 @@
"build": "napi build --dts temp.d.ts --platform --release",
"build:debug": "napi build --platform --js index.js --dts index.d.ts",
"watch": "cargo watch --ignore '{index.js,index.d.ts}' -- npm run build:debug",
"test": "jest",
"test": "cross-env __ZEN_MOCK_UTC_TIME=2025-08-19T16:55:02.078Z jest",
"artifacts": "napi artifacts -d ../../artifacts",
"prepublishOnly": "napi prepublish",
"version": "napi version"
Expand Down
8 changes: 4 additions & 4 deletions bindings/nodejs/src/custom_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ use napi::anyhow::anyhow;
use napi::bindgen_prelude::Promise;
use napi::threadsafe_function::{ErrorStrategy, ThreadsafeFunction};

use crate::types::{ZenEngineHandlerRequest, ZenEngineHandlerResponse};
use zen_engine::handler::custom_node_adapter::{CustomNodeAdapter, CustomNodeRequest};
use zen_engine::handler::node::{NodeResponse, NodeResult};

use crate::types::{ZenEngineHandlerRequest, ZenEngineHandlerResponse};
use zen_engine::Variable;

#[derive(Default)]
pub(crate) struct CustomNode {
Expand All @@ -23,7 +23,7 @@ impl CustomNode {
impl CustomNodeAdapter for CustomNode {
async fn handle(&self, request: CustomNodeRequest) -> NodeResult {
let Some(function) = &self.function else {
return Err(anyhow!("Custom function is undefined"));
return Err(anyhow!("Custom function is undefined").into());
};

let node_data = crate::types::DecisionNode::from(request.node);
Expand All @@ -41,7 +41,7 @@ impl CustomNodeAdapter for CustomNode {

Ok(NodeResponse {
output: result.output.into(),
trace_data: result.trace_data,
trace_data: result.trace_data.map(Variable::from),
})
}
}
20 changes: 8 additions & 12 deletions bindings/nodejs/src/decision.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ use crate::engine::ZenEvaluateOptions;
use crate::loader::DecisionLoader;
use crate::mt::spawn_worker;
use crate::safe_result::SafeResult;
use crate::types::ZenEngineResponse;
use napi::anyhow::anyhow;
use napi_derive::napi;
use serde_json::Value;
use std::sync::Arc;
use zen_engine::{Decision, EvaluationOptions};
use zen_engine::{Decision, EvaluationSerializedOptions};

#[napi]
pub struct ZenDecision(pub(crate) Arc<Decision<DecisionLoader, CustomNode>>);
Expand All @@ -26,34 +25,31 @@ impl ZenDecision {
Err(anyhow!("Private constructor").into())
}

#[napi]
#[napi(ts_return_type = "Promise<ZenEngineResponse>")]
pub async fn evaluate(
&self,
context: Value,
opts: Option<ZenEvaluateOptions>,
) -> napi::Result<ZenEngineResponse> {
) -> napi::Result<Value> {
let decision = self.0.clone();
let result = spawn_worker(move || {
let options = opts.unwrap_or_default();

async move {
decision
.evaluate_with_opts(
.evaluate_serialized(
context.into(),
EvaluationOptions {
EvaluationSerializedOptions {
max_depth: options.max_depth,
trace: options.trace,
trace: options.trace.unwrap_or_default().0,
},
)
.await
.map(ZenEngineResponse::from)
}
})
.await
.map_err(|_| anyhow!("Hook timed out"))?
.map_err(|e| {
anyhow!(serde_json::to_string(e.as_ref()).unwrap_or_else(|_| e.to_string()))
})?;
.map_err(|e| anyhow!(e))?;

Ok(result)
}
Expand All @@ -63,7 +59,7 @@ impl ZenDecision {
&self,
context: Value,
opts: Option<ZenEvaluateOptions>,
) -> SafeResult<ZenEngineResponse> {
) -> SafeResult<Value> {
self.evaluate(context, opts).await.into()
}

Expand Down
Loading
Loading