diff --git a/Directory.Packages.props b/Directory.Packages.props
index 5629d6d81..6f4c89e3b 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -132,7 +132,7 @@
-
+
diff --git a/src/modules/Elsa.Expressions.JavaScript/ObjectConverters/JsonElementConverter.cs b/src/modules/Elsa.Expressions.JavaScript/ObjectConverters/JsonElementConverter.cs
index 121fb2648..a8cc336e6 100644
--- a/src/modules/Elsa.Expressions.JavaScript/ObjectConverters/JsonElementConverter.cs
+++ b/src/modules/Elsa.Expressions.JavaScript/ObjectConverters/JsonElementConverter.cs
@@ -26,7 +26,11 @@ internal class JsonElementConverter : IObjectConverter
{
JsonValueKind.Object => JsValue.FromObject(engine, JsonObject.Create(element)),
JsonValueKind.Array => JsValue.FromObject(engine, JsonArray.Create(element)),
- JsonValueKind.String => JsValue.FromObject(engine, element.GetString()),
+ // JsString.Create is the counterpart of the JsNumber.Create and JsBoolean uses below: it produces the
+ // string value directly, where JsValue.FromObject would re-enter the whole conversion pipeline — the
+ // registered object converters, this one included, followed by the default converter's type switch —
+ // to arrive at the same call. It became public in Jint 4.15.3.
+ JsonValueKind.String => JsString.Create(element.GetString()!),
JsonValueKind.Number => element.TryGetInt32(out var intValue) ? JsNumber.Create(intValue) : JsNumber.Create(element.GetDouble()),
JsonValueKind.True => JsBoolean.True,
JsonValueKind.False => JsBoolean.False,
diff --git a/src/modules/Elsa.Expressions.JavaScript/Services/JintJavaScriptEvaluator.cs b/src/modules/Elsa.Expressions.JavaScript/Services/JintJavaScriptEvaluator.cs
index 475dad827..bbc395fc6 100644
--- a/src/modules/Elsa.Expressions.JavaScript/Services/JintJavaScriptEvaluator.cs
+++ b/src/modules/Elsa.Expressions.JavaScript/Services/JintJavaScriptEvaluator.cs
@@ -38,7 +38,7 @@ public class JintJavaScriptEvaluator(IConfiguration configuration, INotification
{
var engine = await GetConfiguredEngine(configureEngine, context, options, cancellationToken);
await mediator.SendAsync(new EvaluatingJavaScript(engine, context, expression), cancellationToken);
- var result = ExecuteExpressionAndGetResult(engine, expression);
+ var result = await ExecuteExpressionAndGetResultAsync(engine, expression, cancellationToken);
await mediator.SendAsync(new EvaluatedJavaScript(engine, context, expression, result), cancellationToken);
return result.ConvertTo(returnType);
@@ -53,6 +53,12 @@ public class JintJavaScriptEvaluator(IConfiguration configuration, INotification
ExperimentalFeatures = ExperimentalFeature.TaskInterop
};
+ // Jint 4.14 changed this default to LiveView, which exposes a CLR array to script as a live view over
+ // the original array rather than as a copy. Keeping the copy semantics means a script that mutates an
+ // array does not reach back into the workflow's own data, and that an array survives a round trip as
+ // object[] the way it always has. Hosts that want the live view can opt in via ConfigureEngineOptions.
+ engineOptions.Interop.ArrayConversion = ArrayConversionMode.Copy;
+
ConfigureClrAccess(engineOptions);
ConfigureObjectWrapper(engineOptions);
ConfigureObjectConverters(engineOptions);
@@ -106,11 +112,14 @@ public class JintJavaScriptEvaluator(IConfiguration configuration, INotification
engine.SetValue("getConfig", (Func)(name => configuration.GetSection(name).Value));
}
- private object? ExecuteExpressionAndGetResult(Engine engine, string expression)
+ private async Task