Skip to main content

Composition and orchestration

FUME evaluates expressions and FLASH blocks together.

  • FLASH expresses where to write in the FHIR output (FHIR target paths).
  • JSONata-based expressions compute what to write (values, control-flow, orchestration).

This page focuses on how the FUME expression layer and FLASH blocks compose.

The same expression layer also supports mappings that return direct values or custom JSON structures. This page stays focused on the places where those expressions compose with FLASH.

If you're looking for rule forms and indentation, see Syntax. If you're looking for how fan-out and append/override works, see Evaluation model.

Mental model: one language, two layers

Use this as the default "combined" mental model:

  • FLASH is the structural layer: it decides where to write in the output (FHIR paths, array fan-out, append vs override).
  • JSONata-based expressions are the expression layer: they decide what values to write (transforms, lookups, control flow, orchestration).

In practice, authors combine them: FLASH rules use JSONata on the right-hand side, and orchestration blocks can wrap multiple FLASH blocks inside (...).

Mapping recipe (end-to-end)

Most real mappings follow the same shape:

  1. Input normalization: XML/CSV/HL7v2/etc are normalized to JSON (so your expressions operate on predictable JSON structures).
  2. Orchestration (JSONata): bind ids, compute intermediate values, optionally call other saved mappings, optionally fetch/enrich from a FHIR server.
  3. Build resources (FLASH): write FHIR-shaped output using paths + nested context rules.
  4. Terminology/validation: use terminology helpers (ConceptMaps/ValueSets) and let definition-driven shaping/validation emit diagnostics.
  5. Diagnostics and tracing: keep $trace(...) breadcrumbs so verbose evaluation is explainable.

When debugging, ask: "Is this a structure problem (FLASH path/context) or a value problem (JSONata expression)?"

Where JSONata appears

Right-hand side of =

The right-hand side of an assignment rule is an expression evaluated against the current input context.

InstanceOf: Patient
* active = true
* birthDate = dateOfBirth
* name.text = firstName & " " & lastName

Instance:

Instance: is an expression used to compute the resource id.

Instance: $uuid("ExampleCare-Patient-001")
InstanceOf: Patient
* active = true

Input-context rules: (<input-expr>).<path>

Inside an input-context rule, the part in parentheses is JSONata. It is evaluated first, and if it produces an array, the nested rule set evaluates once per item.

InstanceOf: Patient
* (phones).telecom
* system = "phone"
* value = value

Variable bindings inside a FLASH block

Within a FLASH block you can bind variables using := and then use them in later rules.

InstanceOf: Patient
$fullName := firstName & " " & lastName
* name.text = $fullName

JSONata orchestration around FLASH

A complete FUME expression can be a single FLASH block, or a broader orchestration that mixes FLASH with direct expression results and returns a resource, an array, a custom JSON object, or a Bundle.

Two common building blocks are:

  • Parenthesized blocks (...) to sequence expressions
  • Semicolons ; to separate expressions inside a block

In JSONata, a parenthesized block evaluates each expression in order and returns the value of the final expression.

Pattern: build several resources and return an array

(
$patientId := $uuid("ExampleCare-Patient-001");

$patient := (
Instance: $patientId
InstanceOf: Patient
* active = true
* name
* family = "ExampleCare"
* given = "Avery"
);

$encounter := (
InstanceOf: Encounter
* status = "finished"
* subject.reference = "Patient/" & $patientId
);

[$patient, $encounter]
)

Gotchas that show up in orchestration:

  • If a FLASH block produces no writes, it can evaluate to undefined. When you later build arrays/objects in JSONata, undefined values are often dropped.
  • $warn() and $info() return undefined. They're useful as standalone statements, but if you put them inside an array/object constructor, they do not contribute a value.

Pattern: compose into a Bundle

This pattern keeps resource construction in FLASH, then assembles a collection Bundle that embeds those resources.

(
$patientId := $uuid("ExampleCare-Patient-001");

$patient := (
Instance: $patientId
InstanceOf: Patient
* active = true
* name
* family = "ExampleCare"
* given = "Avery"
);

$encounter := (
InstanceOf: Encounter
* status = "finished"
* subject.reference = "Patient/" & $patientId
);

(
InstanceOf: Bundle
* type = "collection"

* entry
* resource = $patient

* entry
* resource = $encounter
)
)

Variables and scoping

Variable bindings use :=:

(
$x := 1;
$y := $x + 1;
$y
)

Practical scoping rules for combined FUME mappings:

  • A variable is visible after it is bound.
  • Variables are scoped to the smallest enclosing JSONata block (...) (and any nested blocks can read outer variables).
  • A useful pattern is "one orchestration block" that binds shared ids and intermediate resources, then returns either a final resource, a Bundle, or an array.

If you want two FLASH blocks to share variables, place them under the same outer orchestration block.

Mapping calls (mapping repository functions)

When you run a saved mapping (for example via the server's saved-mapping endpoints), the runtime can expose other saved mappings as callable JSONata functions.

For the reader-facing server endpoint model behind those saved-mapping calls, see Server API execution patterns.

Conceptually:

  • A saved mapping named normalizePatient is available as a function call: $normalizePatient(...).
  • The function evaluates the referenced mapping using the same FUME runtime.

Calling convention

Mapping calls accept up to two arguments:

  • input (optional): the input value for the called mapping. When omitted, the called mapping uses the current input context.
  • bindings (optional): an object whose keys become variables inside the called mapping.

Example:

(
$patientId := $uuid("ExampleCare-Patient-001");

$patient := $normalizePatient($, {"patientId": $patientId});

(
InstanceOf: Bundle
* type = "collection"
* entry
* resource = $patient
)
)

Inside normalizePatient, the binding can be referenced as $patientId.

Passing bindings to control behavior

Bindings are useful for configuration-like inputs that you don't want to mix into the main source document.

Typical uses:

  • deterministic ids and reference wiring ($patientId, $encounterId)
  • reusable code systems/URIs ($mrnSystem)
  • feature flags for shaping output ($includeNarrative)

Tracing and logging helpers

Orchestration often benefits from "breadcrumb" diagnostics.

  • $warn(message) emits a warning diagnostic and returns undefined.
  • $info(message) emits an info diagnostic and returns undefined.
  • $trace(value?, label, projection?) emits a trace diagnostic and returns value (so it can be used inline).

Examples:

(
$info("Starting ExampleCare patient transform");

$patientId := $uuid("ExampleCare-Patient-001");

$patient := (
Instance: $patientId
InstanceOf: Patient
* active = true
);

$trace($patientId, "patientId");

$patient
)

These helpers surface in verbose evaluation output and are also subject to the same threshold policies described in Diagnostics.

For the detailed function signatures, see:

Mixed examples

These are intentionally small snippets that show where JSONata shows up inside FLASH, and how JSONata can wrap FLASH.

Example 1 - Deterministic ids + terminology translate + tracing

This example combines:

  • deterministic $uuid(seed) for stable ids
  • $translateCode(...) to translate an encounter status via a ConceptMap
  • $trace(...) to record intermediate values without breaking expression flow

This example uses the EncounterStatusCanonicalMap ConceptMap from FHIR R4.

(
$patientId := $uuid(patient.patientId);
$resourceStatus := $translateCode(encounter.status, "sc-encounter-status");

$trace($patientId, "patientId");
$trace($resourceStatus, "translatedEncounterStatus");

(
Instance: $patientId
InstanceOf: Patient
* active = true
* name.text = patient.name.display
* birthDate = patient.birthDate
)
)

Function refs:

Example 2 - Input-context rules change what fields are addressable

Inside an input-context rule, the expression in parentheses evaluates first, and then the nested block runs once per produced item.

InstanceOf: ExplanationOfBenefit

* (claimLines).item
* sequence = lineNumber
* productOrService.text = serviceDisplay
* adjudication
* amount.value = allowedAmount

Notice how lineNumber, serviceDisplay, and allowedAmount are now directly addressable inside the nested block: the input context is "one claim line at a time".

Example 3 - $zip for pairing parallel arrays

Sometimes your input gives parallel arrays rather than an array of objects. $zip pairs them so you can iterate over tuples.

$zip(["PROC-010", "PROC-020"], [110, 300]).{
"serviceCode": $[0],
"allowedAmount": $[1]
}

Function ref:

Example 4 - Enrichment with $search (FHIR server)

FHIR server helpers run only when a FHIR server is configured (server URL/credentials/etc). A common pattern is "query in JSONata, write results in FLASH".

(
$patientReference := $literal("Patient?identifier=urn:examplecare:member-id|" & memberId);

(
InstanceOf: Observation
* status = "final"
* subject.reference = $patientReference
)
)

Function ref: