Skip to content

Commit 8a74aca

Browse files
Merge branch 'feature/LF-3531/merge-third-party-MR' into 'master'
Implement missing features from the latest version of the fhirpath spec See merge request lfor/fhirpath.js!62
2 parents b0459a7 + 5c0746f commit 8a74aca

37 files changed

Lines changed: 6584 additions & 2192 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ src/scratch.js
55
build
66
.DS_Store
77
.editorconfig
8+
.codex
89
Makefile
910
test/benchmark/prev-fhirpath
1011
test/benchmark/prev-src

AGENTS.md

Lines changed: 112 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,131 @@
11
# AGENTS.md
22

33
## Project map (read this first)
4-
- This project implements the FHIRPath language in JavaScript and supports DSTU2, STU3, R4, and R5 model contexts.
5-
- Entry point is `src/fhirpath.js`: public API is `parse`, `compile`, `evaluate`, `resolveInternalTypes`, `types`.
6-
- `src/fhirpath.js` also exports `version`, `FP_Decimal`, `ucumUtils`, and `util` (used by external callers and custom function implementations).
7-
- Evaluation flow is `parse()` -> AST -> `_compile()` -> `applyParsedPath()` -> `engine.doEval()`; see `src/fhirpath.js` and `src/parser/index.js`.
8-
- Core behavior is table-driven: `engine.invocationTable` in `src/fhirpath.js` maps function/operator names to handlers and arity checks.
9-
- Most function families live in separate modules (e.g. `src/aggregate.js`, `src/filtering.js`, `src/math.js`) and export an `engine` object.
10-
- FHIR version-specific typing/model metadata is in `fhir-context/{dstu2,stu3,r4,r5}` and is required for choice-type/type resolution.
11-
- CLI entry is `bin/fhirpath`; behavior is covered by `test/bin_fhirpath.test.js`.
4+
5+
- This project implements the FHIRPath language in JavaScript and supports
6+
DSTU2, STU3, R4, and R5 model contexts.
7+
- Entry point is `src/fhirpath.js`: public API is `parse`, `compile`,
8+
`evaluate`, `resolveInternalTypes`, `types`.
9+
- `src/fhirpath.js` also exports `version`, `FP_Decimal`, `ucumUtils`, and
10+
`util` (used by external callers and custom function implementations).
11+
- Evaluation flow is `parse()` -> AST -> `_compile()` -> `applyParsedPath()` ->
12+
`engine.doEval()`; see `src/fhirpath.js` and `src/parser/index.js`.
13+
- Core behavior is table-driven: `engine.invocationTable` in `src/fhirpath.js`
14+
maps function/operator names to handlers and arity checks.
15+
- Most function families live in separate modules (e.g. `src/aggregate.js`,
16+
`src/filtering.js`, `src/math.js`) and export an `engine` object.
17+
- FHIR version-specific typing/model metadata is in
18+
`fhir-context/{dstu2,stu3,r4,r5}` and is required for choice-type/type
19+
resolution.
20+
- CLI entry is `bin/fhirpath`; behavior is covered by
21+
`test/bin_fhirpath.test.js`.
1222

1323
## Architecture patterns that matter
14-
- Internal values are wrapped as `ResourceNode` / `FP_Type`; final conversion happens in `prepareEvalResult()` (`src/fhirpath.js`).
15-
- `evaluate()` mutates input resources by attaching hidden `__path__` metadata (documented in `README.md`); do not assume input objects stay pristine.
16-
- Base-path evaluation for partial resources uses `{base, expression}` and model normalization via `model.pathsDefinedElsewhere`/`model.path2Type` in `_compile()`.
17-
- Async operations (`resolve`, `memberOf`, `%terminologies.*`) require `options.async`; guards are enforced via `util.checkAllowAsync` (see `src/additional.js`, `src/terminologies.js`).
18-
- `%factory.*` is implemented as a class with its own invocation table in `src/factory.js` and injected as `%factory` in eval context.
24+
25+
- Internal values are wrapped as `ResourceNode` / `FP_Type`; final conversion
26+
happens in `prepareEvalResult()` (`src/fhirpath.js`).
27+
- `evaluate()` mutates input resources by attaching hidden `__path__` metadata (
28+
documented in `README.md`); do not assume input objects stay pristine.
29+
- Base-path evaluation for partial resources uses `{base, expression}` and model
30+
normalization via `model.pathsDefinedElsewhere`/`model.path2Type` in
31+
`_compile()`.
32+
- Async operations (`resolve`, `memberOf`, `%terminologies.*`) require
33+
`options.async`; guards are enforced via `util.checkAllowAsync` (see
34+
`src/additional.js`, `src/terminologies.js`).
35+
- For mixed sync/async flows, `util.checkAllowAsync` belongs in the function
36+
that creates an async promise; do not add it to pure Promise combiners such
37+
as `sort` using `Promise.all(...)`.
38+
- `evaluate()`/`compile()` accept `options.debugger` for step-level tracing;
39+
the callback receives `(ctx, parentData, result, node)` after each eval step
40+
(see `engine.doEvalSync()` in `src/fhirpath.js`).
41+
- With `resolveInternalTypes: false`, `prepareEvalResult()` keeps
42+
`ResourceNode` wrappers in output (instead of flattening to plain values), so
43+
downstream evaluations can retain type/path metadata.
44+
- `%factory.*` is implemented as a class with its own invocation table in
45+
`src/factory.js` and injected as `%factory` in eval context.
1946

2047
## Testing workflow (project-specific)
21-
- Spec-style unit tests are YAML-driven: `test/fhirpath.test.js` loads `test/cases/*.yaml` and generates Jest tests dynamically.
22-
- `test/cases/fhir-r4.yaml` and `test/cases/fhir-r5.yaml` are generated from `converter/dataset`; refresh them with `node converter/bin/index.js -s` (skip download, use local dataset files).
23-
- Additional Jest suites in `test/*.test.js` cover APIs and behaviors outside YAML cases (e.g. `test/async-functions.test.js`, `test/user-invocation-table.test.js`, `test/bin_fhirpath.test.js`).
24-
- Many tests run in both math modes automatically (`preciseMath` true/false) unless a case sets `preciseMath` explicitly.
25-
- `npm run test:unit` runs Jest three times across time zones (`default`, `America/New_York`, `Europe/Paris`) to catch datetime regressions.
26-
- Test helpers in `test/test_utils.js` auto-load models (`r5`, `r4`, `stu3`, `dstu2`) and deep-clone input resources to avoid cross-test pollution.
27-
- Add new spec-style cases in `test/cases/*.yaml`; use keys like `model`, `inputfile`, `variables`, `context`, `error`, `result`.
28-
- Type declaration checks use `npm run test:tsd` with tests in `test/typescript/fhirpath.test-d.ts`.
29-
- End-to-end browser checks run via Cypress under `test/cypress/` (script: `npm run test:e2e`, which builds the demo first).
48+
49+
- Spec-style unit tests are YAML-driven: `test/fhirpath.test.js` loads
50+
`test/cases/*.yaml` and generates Jest tests dynamically.
51+
- `test/cases/fhir-r4.yaml` and `test/cases/fhir-r5.yaml` are generated from
52+
`converter/dataset`; refresh them with `node converter/bin/index.js -s` (skip
53+
download, use local dataset files).
54+
- Additional Jest suites in `test/*.test.js` cover APIs and behaviors outside
55+
YAML cases (e.g. `test/async-functions.test.js`,
56+
`test/user-invocation-table.test.js`, `test/bin_fhirpath.test.js`).
57+
- Many tests run in both math modes automatically (`preciseMath` true/false)
58+
unless a case sets `preciseMath` explicitly.
59+
- `npm run test:unit` runs Jest three times across time zones (`default`,
60+
`America/New_York`, `Europe/Paris`) to catch datetime regressions.
61+
- Test helpers in `test/test_utils.js` auto-load models (`r5`, `r4`, `stu3`,
62+
`dstu2`) and deep-clone input resources to avoid cross-test pollution.
63+
- Add new spec-style cases in `test/cases/*.yaml`; use keys like `model`,
64+
`inputfile`, `variables`, `context`, `error`, `result`.
65+
- Type declaration checks use `npm run test:tsd` with tests in
66+
`test/typescript/fhirpath.test-d.ts`.
67+
- End-to-end browser checks run via Cypress under `test/cypress/` (script:
68+
`npm run test:e2e`, which builds the demo first).
3069

3170
## Build/dev commands you will actually use
32-
- Install: `npm install` (if `node` is missing in this environment, run `source bashrc.fhirpath` and retry).
33-
- Lint: `npm run lint` (targets `src/parser/index.js`, `src/*.js`, `converter/`).
71+
72+
- Install: `npm install` (if `node` is missing in this environment, run
73+
`source bashrc.fhirpath` and retry).
74+
- Lint: `npm run lint` (targets `src/parser/index.js`, `src/*.js`,
75+
`converter/`).
3476
- Unit tests: `npm run test:unit`; debugger mode: `npm run test:unit:debug`.
3577
- Type tests: `npm run test:tsd`.
3678
- Demo build: `npm run build:demo` (`npm run build` + `demo` webpack build).
3779
- E2E tests: `npm run test:e2e` (builds demo + runs Cypress).
3880
- Full CI-like local run: `npm test` (lint + tsd + unit + e2e).
39-
- Parser regeneration: `npm run generateParser` (uses `src/parser/FHIRPath.g4`, `antlr-4.9.3-complete.jar`, then `scripts/fix-parser-imports.js`).
40-
- Browser artifacts: `npm run build` (webpack outputs in `browser-build/`, plus zipped distributable).
81+
- Parser regeneration: `npm run generateParser` (uses `src/parser/FHIRPath.g4`,
82+
`antlr-4.9.3-complete.jar`, then `scripts/fix-parser-imports.js`).
83+
- Browser artifacts: `npm run build` (webpack outputs in `browser-build/`, plus
84+
zipped distributable).
4185
- Performance comparison: `npm run compare-performance` (`test/benchmark.js`).
86+
Use `-- -r <git-ref>` to compare against a local baseline commit and check the
87+
generated report at `test/benchmark/results/compare-performance-report.html`.
4288

4389
## Sandbox and escalation
44-
- If a command that is important to the task fails due to sandbox restrictions (for example EPERM/spawn permission errors), rerun it with escalated permissions and include a brief justification request.
90+
91+
- If a command that is important to the task fails due to sandbox restrictions (
92+
for example EPERM/spawn permission errors), rerun it with escalated
93+
permissions and include a brief justification request.
4594

4695
## Conventions to preserve when editing
47-
- Performance is a top priority: prefer lower-allocation, lower-overhead designs, and treat measurable performance regressions as blockers unless explicitly approved.
48-
- Use CommonJS (`require`/`module.exports`) and 2-space indentation (see `eslint.config.js`).
49-
- Use modern JavaScript syntax (`const`/`let`, arrow functions, destructuring) where it matches existing files.
50-
- In AI chat responses, prefer project-relative file references in `path:line` format (e.g. `src/fhirpath.js:19`).
51-
- Keep two blank lines between declarations and above JSDoc blocks.
52-
- Treat JSDoc as part of a declaration: keep two blank lines above the JSDoc block, and no blank line between JSDoc and the declaration.
53-
- Keep project layout conventions: core logic in `src/`, FHIR-version-specific metadata in `fhir-context/`, tests in `test/`, and docs in `docs/` or `README.md`.
54-
- New FHIRPath functions are typically added as module functions plus registration in `engine.invocationTable` (`src/fhirpath.js`).
55-
- For feature work, update tests together with implementation (`test/cases/*.yaml` for spec-style behavior, plus `test/*.test.js` when behavior is API-specific).
56-
- For custom function hooks, follow `userInvocationTable` conventions in `_compile()`; use `internalStructures: true` only if handler needs raw `ResourceNode` access.
57-
- For async server calls, thread options via `terminologyUrl`, `fhirServerUrl`, `httpHeaders`, and optional `signal` (see `docs/auth.md`, `docs/abort.md`).
58-
- If public API signatures or exports change, update `src/fhirpath.d.ts` and validate with `npm run test:tsd` (`test/typescript/fhirpath.test-d.ts`).
59-
- Keep behavior standards-compliant with FHIRPath and aligned with the selected FHIR model version when model-aware behavior is involved.
60-
- Contributors **MUST NOT** hand-edit `test/cases/fhir-r4.yaml` or `test/cases/fhir-r5.yaml` (these files are generated).
96+
97+
- Performance is a top priority: prefer lower-allocation, lower-overhead
98+
designs, and treat measurable performance regressions as blockers unless
99+
explicitly approved.
100+
- Use CommonJS (`require`/`module.exports`) and 2-space indentation (see
101+
`eslint.config.js`).
102+
- When generating code, keep lines to no more than 80 characters when practical.
103+
- Use modern JavaScript syntax (`const`/`let`, arrow functions, destructuring)
104+
where it matches existing files.
105+
- In AI chat responses, prefer project-relative file references in `path:line`
106+
format (e.g. `src/fhirpath.js:19`).
107+
- Blank lines: keep two between declarations; for JSDoc, keep two above the
108+
block and none between the block and its declaration.
109+
- In authored `test/*.test.js` files, keep two blank lines before and after
110+
`describe(...)` blocks and between `it(...)` blocks; inside each
111+
`describe(...)`, keep one blank line before the first `it(...)` and after
112+
the last `it(...)`.
113+
- Keep project layout conventions: core logic in `src/`, FHIR-version-specific
114+
metadata in `fhir-context/`, tests in `test/`, and docs in `docs/` or
115+
`README.md`.
116+
- New FHIRPath functions are typically added as module functions plus
117+
registration in `engine.invocationTable` (`src/fhirpath.js`).
118+
- For feature work, update tests together with implementation (
119+
`test/cases/*.yaml` for spec-style behavior, plus `test/*.test.js` when
120+
behavior is API-specific).
121+
- For custom function hooks, follow `userInvocationTable` conventions in
122+
`_compile()`; use `internalStructures: true` only if handler needs raw
123+
`ResourceNode` access.
124+
- For async server calls, thread options via `terminologyUrl`, `fhirServerUrl`,
125+
`httpHeaders`, and optional `signal` (see `docs/auth.md`, `docs/abort.md`).
126+
- If public API signatures or exports change, update `src/fhirpath.d.ts` and
127+
validate with `npm run test:tsd` (`test/typescript/fhirpath.test-d.ts`).
128+
- Keep behavior standards-compliant with FHIRPath and aligned with the selected
129+
FHIR model version when model-aware behavior is involved.
130+
- Contributors **MUST NOT** hand-edit `test/cases/fhir-r4.yaml` or
131+
`test/cases/fhir-r5.yaml` (these files are generated).

CHANGELOG.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,29 @@
33
This log documents significant changes for each release. This project follows
44
[Semantic Versioning](http://semver.org/).
55

6+
## [4.10.0] - 2026-04-21
7+
### Added
8+
- Added `sort()` support with optional multi-key sort arguments and
9+
`asc`/`desc` direction controls, including async sort expressions.
10+
- Added variadic `coalesce(...)` with short-circuit evaluation for both
11+
sync and async arguments.
12+
- Added `pathname([short])` for returning element paths within the input
13+
resource.
14+
- Added string functions `lastIndexOf()`, `matchesFull()`, `escape()`, and
15+
`unescape()`, and added optional flags support to `matches()`.
16+
- Added date/time component helpers: `yearOf()`, `monthOf()`, `dayOf()`,
17+
`hourOf()`, `minuteOf()`, `secondOf()`, `millisecondOf()`,
18+
`timezoneOffsetOf()`, `dateOf()`, and `timeOf()`.
19+
20+
### Changed
21+
- Updated parser grammar and generated parser artifacts for the latest
22+
FHIRPath syntax additions used by these features.
23+
- Updated the unit test scripts to call Jest via a Windows-compatible path.
24+
25+
### Fixed
26+
- Fixed a rare error that occurred during unit tests when the time value was
27+
missing milliseconds.
28+
629
## [4.9.3] - 2026-03-31
730
### Changed
831
- Updated package repository metadata in `package.json` to an explicit git

README.md

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,32 @@ implementation (e.g. `userInvocationTable.pow.fn`) can be an array of
479479
value as is, you can use the `fhirpath.util.valData` or
480480
`fhirpath.util.valDataConverted` function (see src/utilities.js).
481481
482+
483+
If a user-defined function performs an asynchronous operation (returns a
484+
`Promise`), it should verify that async evaluation is allowed by calling
485+
`fhirpath.util.checkAllowAsync`.
486+
487+
```js
488+
const userInvocationTable = {
489+
asyncIdentity: {
490+
fn: function(inputs) {
491+
fhirpath.util.checkAllowAsync(this, 'asyncIdentity');
492+
return Promise.resolve(inputs);
493+
},
494+
arity: {0: []}
495+
}
496+
};
497+
498+
// Throws without { async: true }.
499+
const resultPromise = fhirpath.evaluate(
500+
{ value: [3, 2, 1] },
501+
"value.sort(asyncIdentity())",
502+
{},
503+
null,
504+
{ async: true, userInvocationTable }
505+
);
506+
```
507+
482508
## fhirpath CLI
483509
484510
bin/fhirpath is a command-line tool for experimenting with FHIRPath.
@@ -652,33 +678,38 @@ open browser on localhost:8080
652678
### Updating the FHIR module on a FHIR release
653679
* Download the FHIR StructureDefinitions (into the `fhir-context` directory - *don't check these in*)
654680
```
655-
> wget http://hl7.org/fhir/profiles-types.json -O profiles-types.json
656-
> wget http://hl7.org/fhir/profiles-others.json -O profiles-others.json
657-
> wget http://hl7.org/fhir/profiles-resources.json -O profiles-resources.json
658-
> wget https://hl7.org/fhir/search-parameters.json -O search-parameters.json
681+
> wget 'http://hl7.org/fhir/6.0.0-ballot3/profiles-types.json' -O profiles-types.json
682+
> wget 'http://hl7.org/fhir/6.0.0-ballot3/profiles-others.json' -O profiles-others.json
683+
> wget 'http://hl7.org/fhir/6.0.0-ballot3/profiles-resources.json' -O profiles-resources.json
684+
> wget 'https://hl7.org/fhir/6.0.0-ballot3/search-parameters.json' -O search-parameters.json
659685
```
660686
* Create the new folder for the version you are importing
661687
```
662-
> mkdir r5
688+
> mkdir r6
663689
```
664-
* Run the script `` with NodeJS
690+
* Run the script `extract-model-info.js` with NodeJS
665691
```
666-
> node ./extract-model-info.js --outputDir r5 --fhirDefDir .
692+
> node ./extract-model-info.js --outputDir r6 --fhirDefDir .
667693
```
668694
* Compare the output files in the new folder to those of the last release
669695
(looking for issues that might be due to changes in the StructureDefinition format)
670696
* Copy the `index.js` file from the last release into the new folder
671697
```
672-
> cp ../r4/index.js r5
698+
> cp ../r5/index.js r6
673699
```
674-
* Update the `/index.d.ts` file to include the new module as an export
700+
* Update the `/index.d.ts` file to include the new module as an export (can copy from previous version)
675701
``` js
676-
declare module "fhirpath/fhir-context/r5" {
702+
declare module "fhirpath/fhir-context/r6" {
677703
export const {
704+
version,
705+
score,
678706
choiceTypePaths,
679707
pathsDefinedElsewhere,
680708
type2Parent,
681-
path2Type
709+
path2Type,
710+
resourcesWithUrlParam,
711+
path2TypeWithoutElements,
712+
path2RefType
682713
}: Model;
683714
}
684715
```

0 commit comments

Comments
 (0)