Skip to content

v2.4.0

Choose a tag to compare

@mechanik-daniel mechanik-daniel released this 29 Sep 21:47
· 16 commits to main since this release
3c1481c

fumifier 2.4.0

This release adds new higher-order functions, date/time formatting options, and epoch-millis support for date-like FHIR primitives. It also includes FLASH parser and evaluator fixes, parser and regex hardening, more accurate diagnostic metadata, and redaction of HTTP/client error details from diagnostics.

No public API signatures changed. See Upgrade Notes for behavior changes that integrators should review.

Highlights

  • New higher-order functions: $safe, $all, $any, and $memoize.
  • $search(..., { fetchAll: true, transform }) now accepts inline fumifier functions as transform.
  • $rightNow() can now format its output, and $now(), $fromMillis(), and $rightNow() accept picture aliases (date, dateTime, instant, time).
  • Numeric (epoch-millis) values assigned to FHIR date, dateTime, and instant primitives are now converted to canonical UTC output.
  • Nested HTTP/client errors are no longer serialized into verbose diagnostics or wrapped runtime errors.
  • Several FLASH parser/evaluator fixes, plus parser recovery and regex hardening.
  • More accurate source locations and execution IDs in diagnostics.

New Features

Higher-Order Functions: $safe, $all, $any, $memoize (Issues #86, #87, #88, #96, #97)

fumifier now adds four public higher-order functions: $safe, $all, $any, and $memoize. With them, expressions can wrap helpers that might fail without throwing, check collection predicates with sequential sync/async short-circuiting, and reuse repeated results within a single evaluation.

  • $safe() returns structured success/error result objects with sanitized public error fields.
  • $all() and $any() support sync and async predicates and short-circuit in a deterministic order.
  • $memoize() caches fulfilled results for one evaluation and shares in-flight async work between duplicate calls. Failed entries are dropped so that later retries run again.

$search(..., { fetchAll: true, transform }) now also works from the public expression surface when transform is an inline fumifier function. Previously only native JavaScript callbacks worked.

Wrapped mapping parse failures now keep their informative parser messages instead of degrading to [object Object].

Compatibility notes:

  • $memoize() rejects argument trees that contain function or symbol values.
  • $search transform follows the @outburn/fhir-client contract and is only valid with fetchAll: true.
  • maxResults still applies to the raw fetched resources before transform filtering.

Picture Aliases and Formatted $rightNow()

$rightNow() can now format the current wall-clock time using the same picture pipeline as $now() and $fromMillis(). This release also adds a small set of exact picture aliases for the date/time formatting helpers.

  • $rightNow() stays backward-compatible: called with no arguments, it still returns epoch milliseconds.
  • $rightNow(picture) and $rightNow(picture, timezone) now return formatted strings, so callers no longer need to format the result separately.
  • $now() and $fromMillis() now accept the same exact picture aliases:
    • date -> YYYY-MM-DD
    • dateTime -> second-precision timestamp with timezone
    • instant -> millisecond-precision timestamp with timezone
    • time -> HH:MM:SS.sss

Examples:

  • $rightNow('date') returns a calendar date such as 2026-07-14.
  • $rightNow('instant') returns a full timestamp such as 2026-07-14T12:34:56.789Z.
  • $fromMillis(0, 'dateTime') returns 1970-01-01T00:00:00Z.
  • $fromMillis(0, 'dateTime', '-0500') returns 1969-12-31T19:00:00-05:00.

Notes for integrators:

  • Alias matching is exact and case-sensitive. Only the known keywords are rewritten.
  • Raw picture strings are unchanged and go through the same formatter as before.
  • Aliases apply to formatting only. $toMillis() parsing behavior is unchanged.
  • Timezone handling is unchanged. Aliases honor the optional timezone argument the same way raw pictures do.

Epoch-Millis Support for Date-Like FHIR Primitives

fumifier now converts runtime numeric values assigned to FHIR date, dateTime, and instant primitives to canonical form as UTC epoch-millis values. Previously these values fell back to generic string conversion.

  • Numeric inputs for date, dateTime, and instant now go through the date-like conversion path instead of being output as stringified numbers.
  • Valid finite epoch-millis values now produce canonical UTC FHIR output:
    • date -> YYYY-MM-DD
    • dateTime -> YYYY-MM-DDTHH:mm:ss.SSSZ
    • instant -> YYYY-MM-DDTHH:mm:ss.SSSZ
  • Invalid numeric values now raise the existing F5111 validation error instead of appearing in the output as plain strings. This includes non-finite numbers and epoch-millis values outside the range supported by JavaScript Date.

Examples:

  • Assigning 0 to Patient.birthDate now yields 1970-01-01.
  • Assigning 0 to Patient.deceasedDateTime now yields 1970-01-01T00:00:00.000Z.
  • Assigning 0 to Observation.issued now yields 1970-01-01T00:00:00.000Z.
  • Oversized numeric literals that were previously stringified into the output are now rejected with F5111.

Notes for integrators:

  • This change only affects runtime JavaScript number values assigned to FHIR date, dateTime, and instant primitives.
  • String-based date-like behavior is unchanged, including precision preservation for year-only and year-month inputs.

Security and Privacy

HTTP Error Redaction (Issue #89)

fumifier now prevents raw nested HTTP/client errors from being serialized into verbose diagnostics and wrapped runtime errors. This applies to the FHIR helper, $useFhirServer, $eval, and mapping-wrapper paths.

Previously, auth config, authorization headers, and other nested request details could be exposed by accident. These are now removed. Safe diagnostic fields are kept when available: error code, message, source message, HTTP status, and safe request metadata.

This is a bug fix for the package. The public verbose contract was always meant to return sanitized diagnostics rather than raw client internals. In addition, normalized 404 handling no longer produces malformed "resource not found" diagnostics when a search-style failure has no concrete resource id.

Consumers that read serialized nested error payloads such as sourceError, error, or cause should switch to the stable safe fields on diagnostics and wrapped errors: code, message, sourceMessage, sourceErrorCode, status, request, and operationOutcome where present.


Bug Fixes

FLASH Wildcard Variable Parsing (Issue #84)

This fixes a FLASH parser issue with wildcard variable assignments inside FLASH blocks.

  • FLASH now accepts bare wildcard variable assignments such as $value := *; wherever FLASH already expects a value expression.
  • The unwrapped form now behaves the same as the parenthesized workaround $value := (*);.
  • Existing protections against malformed rules remain in place for empty * rules and malformed double-star rules.
  • Fixed cases where $value := *; could be rejected with F1024 (Malformed FLASH rule: Rule is empty) even though the parenthesized form parsed successfully.

Compatibility notes: Parenthesized expressions still work. Users who adopted the parenthesized workaround do not need to change their expressions.

Nested Array Pattern Injection (Issue #85)

This fixes a FLASH evaluation issue with profile-driven pattern injection for nested array-typed elements.

  • FLASH no longer double-wraps array-valued pattern content when a slice injects a complex value such as Identifier.type.coding.
  • Profile-driven identifier slices such as identifier[ppn] now produce the same flat coding structure as the equivalent explicit FLASH assignments.
  • Explicit child assignments on the same CodeableConcept still merge normally without reintroducing nested arrays.

Fixed cases where evaluating identifier[ppn].type could return:

{
  "coding": [
    [
      {
        "code": "PPN",
        "system": "http://terminology.hl7.org/CodeSystem/v2-0203"
      }
    ]
  ]
}

The same expression now returns the correct flat array:

{
  "coding": [
    {
      "code": "PPN",
      "display": "Passport number",
      "system": "http://terminology.hl7.org/CodeSystem/v2-0203"
    }
  ]
}

Compatibility notes: Explicit FLASH assignments for Identifier.type and similar structures work as before. The fix is internal and only removes an incorrect extra array layer from auto-injected pattern values.

Nested Block Comments (Issue #63)

fumifier now skips nested /* ... */ block comments correctly. Inner comment markers no longer cause the rest of the outer comment to be parsed as live code.

Previously, expressions with nested block comments could produce misleading parser or evaluator errors because trailing comment text was tokenized as real code. Expressions that are valid after the comment now evaluate normally. Expressions that are still invalid after the comment is removed now report the real parser error instead.

Expression semantics are unchanged apart from the corrected comment handling. Unterminated block comments still report the existing S0106 error.

Parser and Regex Hardening

This release improves parser recovery, regex handling, and the token metadata returned by the browser build for FLASH/FUME expressions.

  • After an earlier syntax error, parser recovery now keeps later / characters as division operators instead of misreporting them as unterminated regex literals.
  • Missing-right-hand-side errors now point directly at the offending operator in cases like 1/, (1/), and $var := ;.
  • Browser tokenization now returns stable regex token coordinates and explicit regex flags.
  • Exported ASTs now use serializable regex metadata instead of live compiled RegExp objects. This improves JSON round-tripping and AST mobility workflows.
  • Regex literals are now compiled once per evaluation context and reused within that evaluation, which reduces repeated regex setup in per-item flows such as filters.

Fixes:

  • Fixed misleading S0302 regex errors that could appear after unrelated earlier syntax errors.
  • Added a more actionable missing-right-hand-side diagnostic: S0218.
  • Stabilized regex node and token coordinates for downstream editor and tooling consumers.

Notes for integrators: Regex entries in exported ASTs and browser token arrays no longer expose live RegExp instances. Consumers should read regex metadata from value and flags instead.

Function Error Spans

fumifier now keeps the full function call-head span for runtime function-validation diagnostics. This covers both contextual signature failures and direct argument-type validation failures.

Previously, function-call errors such as T0411 and T0410 could report start at the opening parenthesis instead of the function name. They could also drop start: 0 entirely when the call was at the start of an expression.

Verbose diagnostics for function calls now consistently point to the callee span. This makes it easier to highlight and trace invalid calls such as $search(...) and other registered helper or user-defined function calls.

Error codes, signature validation behavior, and function execution semantics are unchanged. Only the reported source metadata for these diagnostics has been corrected.

Diagnostic Helper Metadata

fumifier now keeps stable execution IDs and helper source-location metadata in verbose diagnostics emitted by $warn, $info, and $trace.

This fixes cases where helper-generated diagnostic entries could show executionId: 'unknown', especially when the helper ran from a derived frame such as a nested function call or block scope.

Verbose helper diagnostics now include the helper call-site position, start, and line fields again. This makes it easier to trace warnings and debug entries back to the expression source that emitted them.

Helper diagnostic codes, buckets, messages, log behavior, and $trace return semantics are unchanged.


Upgrade Notes

No public API signatures changed in this release. Integrators should review these behavior changes:

  • Regex metadata in ASTs and browser tokens: exported ASTs and browser token arrays no longer contain live RegExp instances. Read value and flags instead.
  • HTTP/client error payloads: nested sourceError, error, and cause payloads are no longer serialized. Use code, message, sourceMessage, sourceErrorCode, status, request, and operationOutcome.
  • Numeric date-like values: numbers assigned to date, dateTime, and instant are now converted to canonical UTC output. Out-of-range or non-finite numbers now raise F5111 instead of being stringified.
  • $memoize() arguments: argument trees that contain function or symbol values are rejected.
  • $search transform: only valid with fetchAll: true. maxResults applies before transform filtering.
  • New diagnostic code: S0218 (missing right-hand side).

Validation Summary

  • Picture aliases / $rightNow(): added implementation coverage for alias behavior in $rightNow(), $now(), and $fromMillis(). Rebuilt the package outputs, and npx mocha test/implementation-tests.test.js --timeout=20000 passed (79 passing).
  • Epoch-millis date-like primitives: added FLASH fixture coverage for valid numeric conversion and out-of-range F5111 handling. The FLASH-only regression suite passed after rebuild. The verbose-policy regression suite passed with the new F5111 case included.
  • FLASH wildcard variable (#84): wildcard-hardening and parser recovery regression coverage passed.
  • Nested array pattern injection (#85): flash-pattern-injection regression coverage passed, including the issue reproducer, an explicit non-slice control, mixed identifier cases, and explicit child assignments. The full Mocha regression suite passed after the fix (2549 passing, 1 pending).
  • Parser and regex hardening: parser, browser, and AST validation completed, along with broader Mocha regression coverage.

What's Changed

Full Changelog: v2.3.1...v2.4.0