Skip to main content
Version: Next

Scripting

Scadex runs small JavaScript scripts in a sandbox beside the gateway: to compute a tag, to react when someone uses a screen, to do scheduled work, and to try things out. This page is for engineers who write any of them. It covers what is common to all scripts; the pages linked at the end cover each kind.

Where scripts run​

KindRuns whenPage
Expression tagOne of the tags it reads changesExpression tags
Screen event scriptSomeone clicks a component or changes a value on a screenScreen scripts
Cron job scriptThe schedule fires, someone runs the job by hand, or a missed run is caught upCron scripts
Script consoleAn administrator presses Run on the Script console pageScript console
Module scriptA module calls IModuleContext.Scripts.RunAsync (module contract 1.3)Writing a module

The cron module is built on module scripts, so a cron script and a module script have the same limits and run as the same identity.

The language and the sandbox​

A script is a JavaScript function body: it uses return to hand back a value. The gateway wraps it in a strict-mode function, so "use strict" rules apply.

Scripts run in Jint, outside the gateway process, in a small pool of Scadex.ScriptWorker processes that talk to the gateway over stdin and stdout. Each run gets a fresh engine. Inside it:

  • There is no access to the .NET runtime (no CLR objects reach a script), and no eval or Function.
  • ShadowRealm, Atomics, SharedArrayBuffer and WebAssembly are removed.
  • Calls nest at most 64 deep, arrays hold at most 100,000 elements, and a source is at most 64 KB.
  • A script reads at most 256 tags, writes at most 64, and calls the gateway through system.* at most 100 times. Each system.* call sends at most 256 KB of arguments.
  • A tag path is at most 512 characters.

A worker's environment holds none of the gateway's settings or secrets. It is not a stronger security boundary than Jint itself: the worker has the gateway's user and file access.

Limits per kind​

Time is CPU time, so a host too busy to run the script, or a tag read that is slow to answer, does not count against it.

KindStatementsMemoryCPU time
Expression tag50,00016 MiB250 ms
Screen event script, script console500,00032 MiB2 s
Module script, cron job2,000,00032 MiB5 s

Other limits are the same for every kind:

  • A script's tag reads have their own limit of 5 s in all.
  • The pool keeps 2 workers ready from startup and grows to at most 4 (never more than the machine's cores), each using about 50 to 60 MiB. A worker's heap is capped at 256 MiB.
  • A script stuck where Jint cannot check its limits, such as inside a built-in, is killed one second after its limit. One that crashes its worker (a stack overflow, running out of memory) ends only that worker; the gateway starts another.
  • After a kill or a crash, that exact script is refused with the same error for 5 seconds, doubling each time it happens again, up to 5 minutes. A run that completes clears the penalty.
  • When no worker is free within 8 s, the script fails with "The gateway is too busy to run this script."
  • Module scripts take turns, one at a time across the gateway, so they never hold the workers that screens and expression tags rely on. A module script waits at most 30 s for its turn.

Who a script runs as​

A script never has more authority than the person or job that runs it. system.* calls that change something check the caller's role (see Users and roles).

KindIdentityRoleChanges
Script consoleThe administrator who presses RunTheir role (admin)Dry run by default: writes and changing calls are reported, not made; database statements run in a transaction that is rolled back. Run for real applies them
Screen event scriptThe signed-in user of the /sync sessionTheir role as it is at that momentTag writes need the operator role. Session- and user-scoped memory tags are the session's own
Cron jobcron:<job name> for a scheduled or caught-up run; the user who pressed Run now for a manual runEngineerWrites applied after the script, audited under that identity
Module scriptThe PrincipalId the module namesEngineerAs for a cron job; DryRun reports instead
Expression tagNoneNoneCannot write and cannot call the gateway: only system.tag.read, system.date and system.util

What every script can reach​

GlobalWhat it is
tag(path)The tag's live value, in engineering units. A path is a string such as "Plant.Tank1.Level"
tagsAn object of the tags the gateway read before the script started, by path
writeTag(path, value)Asks for a write. Writes are applied in order once the script has finished; a script that fails writes nothing. Not in expression tags
eventWhat triggered the script. Its fields depend on the kind; see each page
valA screen event's value (event.value). undefined elsewhere
console.log, .info, .warn, .error, .debugLogging. Kept in the console and in cron run records (at most 200 lines and 64 KB per run, 2,000 characters per line); accepted and dropped elsewhere
Math, JSON, Date, strings, arraysStandard JavaScript
system.*The gateway's API by area: tag, alarm, db, gateway, user, date, util. See The system API

Values cross as plain JSON. A tag value reaches a script as a boolean, number, string or object; a DateTime arrives as an ISO 8601 string and bytes as base64. A whole number a script returns is stored as an integer when it fits in 53 bits, any other number as a double, and an object or array as a JSON value.

Type-checked editors​

Every script editor in the dashboard checks the script as TypeScript would, against one contract: apps/dashboard/script-types/scadex-script.d.ts, plus a small file per context (expression.d.ts, screen.d.ts, cron.d.ts, console.d.ts) that declares which parts of system that context may use. A wrong name, a missing argument or a typo in a tag path shows as you type; hover a name for what it does and press Ctrl+Space for what is there.

Tag paths are typed from the gateway's tag list: the editor generates interface TagValues { "Plant.Tank1.Level": number; ... } for every tag that holds a value (folders, tag types and anything under @types are left out). Boolean tags are boolean, numeric tags number, string and date-time tags string, other kinds unknown. A path the editor cannot know, such as a relative path, one with a {param} or one built at run time, is typed any rather than flagged, so a working script is never shown as broken.

Errors and where they surface​

A script fails when it throws, when it hits a limit, or when a system.* call is refused. The error text names the cause, for example The script exceeded the 50000 statement limit. or system.alarm.acknowledge: Acknowledging alarms needs the operator role, and this script runs as viewer. Error messages are cut at 500 characters.

KindWhat happens
Expression tagThe tag goes to Bad quality with the message in qualityDetail, keeping its last value
Screen event scriptThe /sync reply is screen.event {ok: false, errors}; no write is made and no directive returned. The dashboard shows the error
Cron jobThe run record has outcome failed and the error; the job's failure tag, if it has one, is set to true
Script consoleThe Result pane shows Failed and the error under What went wrong
Module scriptModuleScriptResult.Ok is false and Error holds the text

Pages in this section​