Welcome to Zuora Product Documentation

Explore our rich library of product information

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/None to 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 triggerType or eventCount may 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, or datetime objects) 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 with toISOString(). Use Intl.DateTimeFormat for display formatting.

  • Python: normalize Z to +00:00 if needed, then use datetime.fromisoformat() and zoneinfo.ZoneInfo to handle time zones. Store timestamps in UTC.

Money and numbers:

  • JS: do currency math in integer cents or with BigInt. Convert BigInt to string before JSON/state.

  • Python: use decimal.Decimal for currency. Convert Decimal values to string before saving in state or JSON.

JSON and regular expressions (regex):

  • JS: use JSON.parse()/ JSON.stringify() with try/catch, and RegExp for patterns. Pre-compile regexps if reused.

  • Python: use json.loads() / json.dumps() and re/re.compile() for patterns. Convert non-serializable types to strings before dumping.

Encoding, CSV, and binary:

  • JS: use TextEncoder / TextDecoder for text/bytes conversions. Use base64 only if you must store binary.

  • Python: use the base64 module and the csv standard 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.