Skip to content

JavaScript script ​

Runs your own JavaScript (Google V8 engine) for every message that reaches the component. Use it for transformations and small integrations when you prefer JavaScript over C#; async/await, fetch, setTimeout / setInterval and console are available.

Pins ​

PinKindTypesDescription
InputinputanyThe data from the previous component. Its type is the type of the input connection.
OutputoutputanyWhatever the script passes to services.Next. The template creates an object of the output connection's type.

Configuration ​

FieldTypeDefaultDescription
Scriptcode (JavaScript)emptyThe user script: an async function execute(metaData, inputData) plus any functions and classes you need. Empty means the generated template is used.

Behaviour ​

  • Before your code the component generates a JavaScript class for every type of the flow (same class and property names as in the flow, e.g. inputData.Name), the classes FlowMetaData, NoInputData, AnyInputData and ViaFileInfo, and the global services. This part is hidden in the editor; your code starts after the /*viaducta.javascript.code*/ marker, and the line numbers of errors and of the debugger are the lines of your script.

  • The script is loaded once when the flow starts (top-level code runs then); for every message the component calls execute(metaData, inputData), where inputData already is an instance of the input connection's class (NoInputData when nothing is connected). Global variables keep their values between messages. Messages are processed one at a time per component.

  • The generated template:

    javascript
    async function execute(metaData, inputData) {
    	const outputData = new CustomerDto();
    	services.Next(metaData, outputData);
    }

    CustomerDto is the output connection's type; without an output connection the template calls services.Next(metaData, inputData).

  • services is the component's flow service (IFlowService) with the C# member names, the same services a C# or Python script has: services.Log, services.Messaging, services.Settings, services.Component, services.Flow, services.Profiler, services.Batches, services.Storage, services.Repository and the rest. .NET methods that return a Task return a promise (await it).

  • services.Next(metaData, obj) sends obj to the components connected to Output; obj must be an instance of one of the generated flow classes (plain JavaScript objects can't be sent). It can be called zero, one or many times. services.Next(metaData, obj, connectionId) sends to one connection only; await services.NextAsync(metaData, obj) waits for the delivery.

  • Frequently used services:

    • services.Log.Information("...") (also Debug, Warning, Error, Fatal) writes to the flow log.
    • services.Messaging.Information("...") (also Warning, Error, Trace) writes to the designer console; UpdateStatusMessage("..."), UpdateProgress(0.5) (0 to 1), StopProgress() and Clear() update the component.
    • services.Settings.Get("Package", "Name") reads an expression setting.
    • services.Component.Id / .Name; services.Profiler.UpdateRecordsProduced(services.Component.Id, 1).
    • services.Batches.Begin(metaData, "label") opens a batch: send the records with batch.MetaData, call batch.Complete() and batch.Dispose() in a finally block; disposing without Complete() fails the batch (see the example).
    • await services.HandleExceptionAsync(metaData, inputData, error) hands a caught error to the flow's error handling and carries on.
  • List and array properties are JavaScript arrays: index, length, for...of, map/filter, push, pop, splice, x[i] = v, length = 0 and assigning a whole array (order.Lines = [...]) are written through to the .NET list. Items of flow types are instances of their generated class. Date properties are returned as JavaScript Date objects. Decimal properties are numbers (about 17 significant digits); assign a string (order.Amount = "12345678901234.123") to set an exact decimal.

  • console.log / info / debug / table write information messages, console.warn warnings, console.error errors, console.trace trace messages to the designer console; alert(...) is an information message.

  • The component waits until execute is done. For timer-based scripts call wait() to keep the message open and end() when you are done.

  • fetch(url, { method, headers, body }) works like in a browser: the response has status, ok, statusText, url, redirected, headers.get(name), text(), json(), bytes() and arrayBuffer(). An HTTP error status is a response with ok == false; only network errors reject. A string body is sent as application/json unless a Content-Type header says otherwise, an object body is sent as JSON, a Uint8Array / ArrayBuffer as bytes.

  • Other helpers: sleep(ms) (blocks the thread) and the host object crypto.

  • An error that leaves execute goes to the flow's error handling as JavaScriptScriptException: the message is TypeError: ..., the stack trace lists the functions of your script with their lines, and the error line is marked in the editor.

  • In debug mode breakpoints and stepping work in the script editor: the debugger stops on the breakpoint lines and shows the local variables. Stepping into the generated classes or the runtime is skipped.

Notes ​

  • There is no Node.js / browser runtime: require, import, DOM, Blob, FormData are not available.
  • The template is generated from the connections that exist when it is created; connect first, then open the editor (or clear the script).
  • Flow objects the script creates are released when the JavaScript garbage collector collects them.

Example ​

Input connection type Customer, output connection type CustomerDto:

javascript
/*viaducta.javascript.code*/
let counter = 0;

async function execute(metaData, inputData) {
	if (!inputData.Email) return;          // filter: nothing is sent on
	const outputData = new CustomerDto();
	outputData.Name = inputData.Name.toUpperCase();
	outputData.Email = inputData.Email.toLowerCase();
	outputData.Tags = inputData.Tags.filter(tag => tag !== "");
	counter++;
	services.Messaging.UpdateStatusMessage(`Mapped customer ${counter}: ${outputData.Email}`);
	services.Next(metaData, outputData);
}

A batch per file:

javascript
async function execute(metaData, inputData) {
	const batch = services.Batches.Begin(metaData, inputData.FileName);
	try {
		for (const line of inputData.Lines) {
			const row = new Row();
			row.Text = line;
			services.Next(batch.MetaData, row);
		}
		batch.Complete();
	} finally {
		batch.Dispose();
	}
}

Http listener → Json deserializer → JavaScript script → Json serializer → ...

Viaducta documentation