Skip to content

Add intrinsic functions and newer Choice operators support#3

Merged
jamoy merged 9 commits into
masterfrom
claude/gifted-cannon-gfy802
Jun 22, 2026
Merged

Add intrinsic functions and newer Choice operators support#3
jamoy merged 9 commits into
masterfrom
claude/gifted-cannon-gfy802

Conversation

@jamoy

@jamoy jamoy commented Jun 22, 2026

Copy link
Copy Markdown
Owner

This PR significantly expands AWS Step Functions language support by implementing intrinsic functions and newer Choice state operators, bringing the library closer to full ASL specification compliance.

Summary

The library now supports:

  • Intrinsic functions (States.Format, States.Array, States.Hash, States.UUID, etc.) in Parameters and ResultSelector
  • Newer Choice operators (IsPresent, IsNull, IsString, IsNumeric, IsBoolean, IsTimestamp, StringMatches, and *Path variants)
  • ResultSelector for transforming task results before ResultPath/OutputPath
  • ItemProcessor/ItemSelector aliases for Map states
  • Proper handling of payload templates with recursive object resolution

Key Changes

  • Intrinsic function evaluation: Added comprehensive parsing and evaluation of intrinsic expressions with support for:

    • String formatting with States.Format
    • Array operations (Array, ArrayPartition, ArrayContains, ArrayRange, ArrayGetItem, ArrayLength, ArrayUnique)
    • String operations (StringSplit, Base64Encode, Base64Decode)
    • Cryptographic operations (Hash with MD5/SHA variants, UUID)
    • JSON operations (StringToJson, JsonToString, JsonMerge)
    • Math operations (MathAdd, MathRandom with optional seeding)
  • Payload template refactoring: Extracted Parameters handling into evaluatePayloadTemplate() and evaluatePayloadValue() methods to support recursive object resolution and reuse across Parameters, ItemSelector, and ResultSelector

  • Choice state enhancements:

    • Added type-checking operators (IsPresent, IsNull, IsString, IsNumeric, IsBoolean, IsTimestamp)
    • Added StringMatches with wildcard support (*, escaped with *)
    • Added *Path operators for comparing against values at other Reference Paths
    • Fixed Or operator logic (was checking > 1, now correctly checks > 0)
    • Proper handling of absent fields (only IsPresent is meaningful for missing values)
  • ResultSelector support: Implemented ResultSelector processing in Task, Map, and Parallel states, running before ResultPath/OutputPath per ASL data-flow pipeline

  • Retry input stability: Fixed bug where Parameters were re-applied to previous retry attempt's output; now maintains original input across retries

  • Map state improvements: Added support for ItemProcessor (newer spelling of Iterator) and ItemSelector (newer spelling of per-item Parameters)

  • Error handling: Added new ErrorState.IntrinsicFailure for intrinsic function errors

  • Dependencies: Updated asl-validator to v4.0.0 and jsonpath to v1.1.1; added crypto module usage for hash operations

  • Node.js requirement: Updated minimum Node.js version to 20 (tested on 20, 24, 26)

  • TypeScript definitions: Added index.d.ts with proper type declarations

  • Tooling: Migrated to modern ESLint config (v9), updated all dev dependencies, added Prettier formatting

Notable Implementation Details

  • Intrinsic function parsing respects single-quoted string literals and nested parentheses for proper argument tokenization
  • String escape sequences in intrinsic functions (', {, }, \) are properly handled
  • Seeded random number generation (mulberry32 PRNG) for reproducible States.MathRandom calls
  • Deep equality checking for array intrinsics using JSON serialization
  • Recursive payload template evaluation supports arbitrary nesting depth

https://claude.ai/code/session_01QznGWnSQuwvNRAcYinsZiG

jamoy and others added 9 commits June 22, 2026 05:16
Update the dependency stack and CI so the library builds and tests cleanly
on the currently supported Node.js releases (20, 24, 26):

- deps: asl-validator ^4, jsonpath ^1.1.1
- devDeps: jest 30, eslint 9 (flat config), prettier 3, husky 9, lint-staged 15;
  drop dead babel-eslint, ts-jest, pretty-quick, codecov npm package
- mark the `serverless` peer dependency optional so npm 7+ no longer pulls the
  end-of-life serverless@1 tree (75 -> 19 advisories), and widen its range
- add eslint.config.js (flat config) and .husky/pre-commit; remove .eslintrc
- engines: node >=20; CI matrix -> 20.x/24.x/26.x on actions/*@v4 + npm
- jest 30 compat: replace removed `toThrowError` with `toThrow`
- asl-validator 4 compat: update the invalid-definition assertion message
- fix Wait state mutating the shared (require-cached) state definition, which
  the stricter validator now rejects on re-validation; resolve *Path values
  into locals instead

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QznGWnSQuwvNRAcYinsZiG
Incorporates #2 by @MarcosRava. When a Task defined both
Parameters and Retry, each retry re-evaluated Parameters against the
already-transformed input, compounding the payload on every attempt. Pin the
pre-retry input via the execution context so Parameters always resolve against
the original input.

Co-authored-by: Marcos Rava <MarcosRava@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QznGWnSQuwvNRAcYinsZiG
Add the post-2020 JSONPath-era ASL features that the library was missing,
driven by tests, while keeping the existing API and behavior unchanged:

- Intrinsic functions in Parameters/ItemSelector/ResultSelector payload
  templates: States.Format (with brace/quote escaping), Array, ArrayPartition,
  ArrayContains, ArrayRange, ArrayGetItem, ArrayLength, ArrayUnique,
  StringToJson, JsonToString, Base64Encode/Decode, Hash, JsonMerge, MathAdd,
  MathRandom (seedable), StringSplit and UUID, including nested calls
- New Choice operators: StringMatches (glob with escaping), IsNull, IsPresent,
  IsBoolean, IsNumeric, IsString, IsTimestamp, and every *Path variant
- ResultSelector on Task/Map/Parallel (runs before ResultPath/OutputPath)
- Map ItemProcessor/ItemSelector accepted as aliases of Iterator/Parameters
- refactor the payload-template evaluation into a single recursive helper,
  which also fixes the previous one-level-deep nesting limitation
- fix Choice `Or` matching when only one rule matches (was requiring >1)
- fix Parallel draining its shared Branches array, which broke re-execution
- eslint: allow avoidEscape so single-quote rule agrees with prettier

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QznGWnSQuwvNRAcYinsZiG
- README: note Node >=20 (tested on 20/24/26), list the newly supported
  intrinsic functions, Choice operators, ResultSelector and Map
  ItemProcessor/ItemSelector, and call out JSONata/Variables as not yet
  supported
- add real index.d.ts typings for the public API (the package already points
  `types` at it); verified against a strict consumer with @types/node

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QznGWnSQuwvNRAcYinsZiG
Address two issues found in review of the newer-spec work:

- InputPath was discarded whenever a state also had Parameters/ItemSelector,
  because the payload template was evaluated against the pre-InputPath
  execution input. Fix the root cause of the retry-input recursion instead:
  task() now resolves its step input into a local so the closed-over input
  keeps its original value across retries, which lets inputPath evaluate the
  template against the (InputPath-narrowed) input again. PR #2's retry test
  still passes.
- A negated data-test Choice operator (e.g. `IsString: false`) matched an
  absent field. Per AWS only `IsPresent` is meaningful for a missing field;
  every other data-test now requires the field to be present.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QznGWnSQuwvNRAcYinsZiG
- add the missing States.MathRandom test (seeded determinism + bounded range)
- cover the States.Format placeholder/argument mismatch error path
- add StringMatches edge cases: both-end anchoring, `*` spanning dots,
  case-sensitivity, and prefix requirements

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QznGWnSQuwvNRAcYinsZiG
…o Map

Finish the Distributed-Map-era inline Map semantics:

- run iterations in batches that respect MaxConcurrency (0/omitted = unlimited,
  otherwise the per-execution default), preserving input order in the output
- ItemBatcher: group items into `{ Items: [...], BatchInput }` iteration inputs
- ToleratedFailureCount / ToleratedFailurePercentage: drop tolerated failures
  from the result and only fail the Map when the threshold is exceeded
  (default 0 keeps the previous "any failure fails the Map" behavior)

Also let Prettier own all formatting: drop the overlapping ESLint stylistic
rules (which conflicted with Prettier on ternary-nested calls) and enforce
formatting in CI via `prettier --check` in the lint script.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QznGWnSQuwvNRAcYinsZiG
Implement the November 2024 ASL features:

- QueryLanguage: "JSONata" at the machine or state level. `{% %}` expressions
  are evaluated with the jsonata package, binding the reserved `$states` object
  ($states.input/result/context) and in-scope variables. Wired into Pass
  (Output), Task (Arguments/Output), Choice (Condition), Map (Items/
  ItemSelector/Output) and Parallel (Output).
- Variables: `Assign` in both JSONPath and JSONata modes; `$name` references in
  later states. Map/Parallel children may read outer variables but their
  assignments are scoped and do not leak back to the parent.
- add jsonata ^2 as a dependency

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QznGWnSQuwvNRAcYinsZiG
Review of the Variables work found that a Map iteration's (or Parallel
branch's) Assign mutated a single shared variable object, so concurrent
siblings could clobber each other's variables. Give each iteration/branch its
own forked scope via AsyncLocalStorage, which keeps scopes isolated across
awaits and concurrency while still letting children read outer variables.

Also make the reserved `$states` JSONata binding win over any like-named
workflow variable. Adds a regression test for concurrent per-iteration Assign.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QznGWnSQuwvNRAcYinsZiG
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant