Mediation code editor guide
The Mediation code editor guide offers a comprehensive overview of using GraalVM 23.0.1, GraalJS, and GraalPy to execute custom logic in meters. It includes examples of transformers, accumulators, and aggregators, along with guidelines for handling dates and currency in JavaScript and Python.
Zuora Mediation code editors run on GraalVM 23.0.1, which provides a high-performance, multi-language runtime. GraalJS / GraalPy are the JavaScript and Python runtimes that Mediation's code‑based operators use to run your custom logic inside meters.
|
Version |
Language Version |
Description |
Link | |
|---|---|---|---|---|
|
GraalVM |
23.0.1 |
n/a |
Unified polyglot runtime for JS, Python | GraalVM |
|
GraalJS |
23.0.1 |
ECMAScript 2023 |
Full ES2023 (ECMA-262) compliance; ES2024 features rolling in incrementally | GraalJS – GitHub Releases |
|
GraalPy |
23.0.1 |
Python 3.11.7 |
Full Python 3.11 standard library support | GraalPy Documentation |
Example: Minimal transformer
The structure of a transformer that runs once per event:
GraalJS
// minimal transformer
exports.step = (payload) => {
const price = Number(payload.price) || 0;
const qty = Number(payload.quantity) || 0;
const totalCents = BigInt(Math.round(price * 100)) * BigInt(qty);
return { ...payload, total_cents: totalCents };
};
GraalPy
# minimal transformer
from decimal import Decimal
def step(payload):
price = Decimal(str(payload.get("price", "0")))
qty = int(payload.get("quantity") or 0)
total_cents = (price * Decimal("100") * qty).to_integral_value()
payload["total_cents"] = str(total_cents)
return payload
Example: Accumulator (windowed / batch)
An accumulator runs on a group of events collected in a time window and returns one summary. It usually does not rely on persistent state.
GraalJS
exports.step = (events, ctx) => {
if (!events || events.length === 0) return null;
let total = 0;
for (const e of events) {
total += Math.round((Number(e.price) || 0) * 100) * (Number(e.quantity) || 0);
}
return { groupKey: ctx?.groupKey, eventCount: events.length, total_cents: total };
};
GraalPy
GraalPy (example)
from decimal import Decimal
def step(events, ctx):
if not events: return None
total = sum((Decimal(str(e.get("price","0"))) * Decimal("100") * int(e.get("quantity") or 0)).to_integral_value() for e in events)
return {"groupKey": ctx.get("groupKey"), "eventCount": len(events), "total_cents": str(total)}
Aggregator (stateful per-group)
An aggregator keeps a small, persistent state per group (for example per customer) and releases records when business rules are met. The context provides state.get(key) and state.set(key, value). Use those to read and update small values that persist across calls for the same group.
Function structure
-
The entry point is usually a function named
step.-
In JavaScript export it as
exports.step = …. -
In Python define
def step(...):
-
-
A transformer receives one event (payload) and an optional context. It returns a transformed object or
null/Noneto drop the event. -
An accumulator receives a list of events for a time window and returns a single summary object or
null/None. -
An aggregator receives a list of events and a context that includes state; it may return multiple released records or
null/None. -
Always document parameter shapes (JSDoc for JS or type hints and a docstring for Python) and validate inputs.
Common context fields
state: object with.get(key),.set(key, value), and.clear(key)(present for aggregators).windowStart,windowEnd: ISO timestamps for accumulators.groupKey/groupFields: the grouping key(s) that scope state to each group.Other metadata such as
triggerTypeoreventCountmay also appear.
State rules
Call state.get(key) and state.set(key, value) to read and write state. In JS you often do const state = context.state.
State is scoped per
groupFields, so each group gets its own isolated state.Keep state small and JSON-serializable (numbers, strings, small objects or arrays).
Serialize complex values (for example
Decimal,BigInt, ordatetimeobjects) as strings or compact JSON and parse them back when you read them.Do not store large files, binary blobs, file handles, or OS resources in state.
Rules for common operations
Dates:
-
Prefer ISO-8601 strings like
2025-11-21T14:00:00Z. -
JS: parse with
new Date(iso)and get canonical form withtoISOString(). UseIntl.DateTimeFormatfor display formatting. -
Python: normalize
Zto+00:00if needed, then usedatetime.fromisoformat()andzoneinfo.ZoneInfoto handle time zones. Store timestamps in UTC.
Money and numbers:
-
JS: do currency math in integer cents or with
BigInt. ConvertBigIntto string before JSON/state. -
Python: use
decimal.Decimalfor currency. ConvertDecimalvalues to string before saving in state or JSON.
JSON and regular expressions (regex):
-
JS: use
JSON.parse()/JSON.stringify()withtry/catch, andRegExpfor patterns. Pre-compile regexps if reused. -
Python: use
json.loads()/json.dumps()andre/re.compile()for patterns. Convert non-serializable types to strings before dumping.
Encoding, CSV, and binary:
-
JS: use
TextEncoder/TextDecoderfor text/bytes conversions. Use base64 only if you must store binary. -
Python: use the
base64module and thecsvstandard library to parse CSV files. Do not store raw binary in state.
Error handling: Always validate inputs and catch parse/convert exceptions. Use Return null/None to drop bad events and log helpful context for debugging.
Other guidelines
-
Use standard libraries only: Always prefer built-in or standard libraries provided by the runtime.
-
Follow version compatibility: Write code compatible with ES2023 or Python 3.11 to ensure consistent behavior.
-
Keep it portable: Write code that does not depend on system-specific or external dependencies.
-
No external native libraries: Avoid using Node.js packages, CPython binary extensions (.pyd, .so), or OS-level native modules. They may not be compatible.
-
No browser-specific code: GraalJS is not a browser. DOM APIs (
window,document) do not exist. -
Node.js globals:
require,process, and similar globals are not supported in GraalJS. -
Do not mix unrelated language constructs: Avoid mixing language runtimes unnecessarily. Polyglot should serve a clear integration purpose.
-
No experimental flags in production: Unless explicitly approved, do not enable unstable or undocumented flags.