fhirpath-ts 0.0.0-bootstrap.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +391 -0
- package/THIRD-PARTY-NOTICES.md +296 -0
- package/dist/analyzer/analyze-dto.d.ts +45 -0
- package/dist/analyzer/analyze-dto.d.ts.map +1 -0
- package/dist/analyzer/analyze-dto.js +147 -0
- package/dist/analyzer/analyze-dto.js.map +1 -0
- package/dist/analyzer/analyze.d.ts +98 -0
- package/dist/analyzer/analyze.d.ts.map +1 -0
- package/dist/analyzer/analyze.js +925 -0
- package/dist/analyzer/analyze.js.map +1 -0
- package/dist/analyzer/expression-policy.d.ts +210 -0
- package/dist/analyzer/expression-policy.d.ts.map +1 -0
- package/dist/analyzer/expression-policy.js +254 -0
- package/dist/analyzer/expression-policy.js.map +1 -0
- package/dist/analyzer/index.d.ts +3 -0
- package/dist/analyzer/index.d.ts.map +1 -0
- package/dist/analyzer/index.js +6 -0
- package/dist/analyzer/index.js.map +1 -0
- package/dist/analyzer/regex-safety.d.ts +10 -0
- package/dist/analyzer/regex-safety.d.ts.map +1 -0
- package/dist/analyzer/regex-safety.js +84 -0
- package/dist/analyzer/regex-safety.js.map +1 -0
- package/dist/analyzer/signatures.d.ts +72 -0
- package/dist/analyzer/signatures.d.ts.map +1 -0
- package/dist/analyzer/signatures.js +195 -0
- package/dist/analyzer/signatures.js.map +1 -0
- package/dist/api/bundle.d.ts +29 -0
- package/dist/api/bundle.d.ts.map +1 -0
- package/dist/api/bundle.js +108 -0
- package/dist/api/bundle.js.map +1 -0
- package/dist/api/cache.d.ts +10 -0
- package/dist/api/cache.d.ts.map +1 -0
- package/dist/api/cache.js +38 -0
- package/dist/api/cache.js.map +1 -0
- package/dist/api/column-signature.d.ts +53 -0
- package/dist/api/column-signature.d.ts.map +1 -0
- package/dist/api/column-signature.js +43 -0
- package/dist/api/column-signature.js.map +1 -0
- package/dist/api/compile.d.ts +120 -0
- package/dist/api/compile.d.ts.map +1 -0
- package/dist/api/compile.js +167 -0
- package/dist/api/compile.js.map +1 -0
- package/dist/api/constraints.d.ts +48 -0
- package/dist/api/constraints.d.ts.map +1 -0
- package/dist/api/constraints.js +52 -0
- package/dist/api/constraints.js.map +1 -0
- package/dist/api/dto.d.ts +134 -0
- package/dist/api/dto.d.ts.map +1 -0
- package/dist/api/dto.js +295 -0
- package/dist/api/dto.js.map +1 -0
- package/dist/api/engine.d.ts +136 -0
- package/dist/api/engine.d.ts.map +1 -0
- package/dist/api/engine.js +183 -0
- package/dist/api/engine.js.map +1 -0
- package/dist/api/evaluate.d.ts +7 -0
- package/dist/api/evaluate.d.ts.map +1 -0
- package/dist/api/evaluate.js +10 -0
- package/dist/api/evaluate.js.map +1 -0
- package/dist/api/project.d.ts +89 -0
- package/dist/api/project.d.ts.map +1 -0
- package/dist/api/project.js +150 -0
- package/dist/api/project.js.map +1 -0
- package/dist/api/tagged.d.ts +14 -0
- package/dist/api/tagged.d.ts.map +1 -0
- package/dist/api/tagged.js +13 -0
- package/dist/api/tagged.js.map +1 -0
- package/dist/cli/dto-check.d.ts +25 -0
- package/dist/cli/dto-check.d.ts.map +1 -0
- package/dist/cli/dto-check.js +77 -0
- package/dist/cli/dto-check.js.map +1 -0
- package/dist/cli/fhirpath-check.d.ts +3 -0
- package/dist/cli/fhirpath-check.d.ts.map +1 -0
- package/dist/cli/fhirpath-check.js +149 -0
- package/dist/cli/fhirpath-check.js.map +1 -0
- package/dist/cli/ts-loader.d.mts +3 -0
- package/dist/cli/ts-loader.d.mts.map +1 -0
- package/dist/cli/ts-loader.mjs +48 -0
- package/dist/cli/ts-loader.mjs.map +1 -0
- package/dist/engine/context.d.ts +173 -0
- package/dist/engine/context.d.ts.map +1 -0
- package/dist/engine/context.js +118 -0
- package/dist/engine/context.js.map +1 -0
- package/dist/engine/evaluator.d.ts +7 -0
- package/dist/engine/evaluator.d.ts.map +1 -0
- package/dist/engine/evaluator.js +196 -0
- package/dist/engine/evaluator.js.map +1 -0
- package/dist/engine/navigation.d.ts +11 -0
- package/dist/engine/navigation.d.ts.map +1 -0
- package/dist/engine/navigation.js +102 -0
- package/dist/engine/navigation.js.map +1 -0
- package/dist/engine/operators/collections.d.ts +12 -0
- package/dist/engine/operators/collections.d.ts.map +1 -0
- package/dist/engine/operators/collections.js +31 -0
- package/dist/engine/operators/collections.js.map +1 -0
- package/dist/engine/operators/comparison.d.ts +13 -0
- package/dist/engine/operators/comparison.d.ts.map +1 -0
- package/dist/engine/operators/comparison.js +77 -0
- package/dist/engine/operators/comparison.js.map +1 -0
- package/dist/engine/operators/equality.d.ts +19 -0
- package/dist/engine/operators/equality.d.ts.map +1 -0
- package/dist/engine/operators/equality.js +203 -0
- package/dist/engine/operators/equality.js.map +1 -0
- package/dist/engine/operators/index.d.ts +18 -0
- package/dist/engine/operators/index.d.ts.map +1 -0
- package/dist/engine/operators/index.js +30 -0
- package/dist/engine/operators/index.js.map +1 -0
- package/dist/engine/operators/logic.d.ts +8 -0
- package/dist/engine/operators/logic.d.ts.map +1 -0
- package/dist/engine/operators/logic.js +38 -0
- package/dist/engine/operators/logic.js.map +1 -0
- package/dist/engine/operators/math.d.ts +13 -0
- package/dist/engine/operators/math.d.ts.map +1 -0
- package/dist/engine/operators/math.js +198 -0
- package/dist/engine/operators/math.js.map +1 -0
- package/dist/engine/operators/strings.d.ts +8 -0
- package/dist/engine/operators/strings.d.ts.map +1 -0
- package/dist/engine/operators/strings.js +22 -0
- package/dist/engine/operators/strings.js.map +1 -0
- package/dist/engine/operators/types.d.ts +3 -0
- package/dist/engine/operators/types.d.ts.map +1 -0
- package/dist/engine/operators/types.js +15 -0
- package/dist/engine/operators/types.js.map +1 -0
- package/dist/engine/type-matching.d.ts +20 -0
- package/dist/engine/type-matching.d.ts.map +1 -0
- package/dist/engine/type-matching.js +122 -0
- package/dist/engine/type-matching.js.map +1 -0
- package/dist/errors.d.ts +25 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +21 -0
- package/dist/errors.js.map +1 -0
- package/dist/eslint/index.d.ts +12 -0
- package/dist/eslint/index.d.ts.map +1 -0
- package/dist/eslint/index.js +316 -0
- package/dist/eslint/index.js.map +1 -0
- package/dist/fhir/html-checks.d.ts +7 -0
- package/dist/fhir/html-checks.d.ts.map +1 -0
- package/dist/fhir/html-checks.js +256 -0
- package/dist/fhir/html-checks.js.map +1 -0
- package/dist/fhir/model-navigation.d.ts +11 -0
- package/dist/fhir/model-navigation.d.ts.map +1 -0
- package/dist/fhir/model-navigation.js +142 -0
- package/dist/fhir/model-navigation.js.map +1 -0
- package/dist/functions/aggregate.d.ts +2 -0
- package/dist/functions/aggregate.d.ts.map +1 -0
- package/dist/functions/aggregate.js +127 -0
- package/dist/functions/aggregate.js.map +1 -0
- package/dist/functions/boundary.d.ts +2 -0
- package/dist/functions/boundary.d.ts.map +1 -0
- package/dist/functions/boundary.js +189 -0
- package/dist/functions/boundary.js.map +1 -0
- package/dist/functions/combining.d.ts +2 -0
- package/dist/functions/combining.d.ts.map +1 -0
- package/dist/functions/combining.js +13 -0
- package/dist/functions/combining.js.map +1 -0
- package/dist/functions/control.d.ts +2 -0
- package/dist/functions/control.d.ts.map +1 -0
- package/dist/functions/control.js +25 -0
- package/dist/functions/control.js.map +1 -0
- package/dist/functions/conversion.d.ts +3 -0
- package/dist/functions/conversion.d.ts.map +1 -0
- package/dist/functions/conversion.js +316 -0
- package/dist/functions/conversion.js.map +1 -0
- package/dist/functions/date-components.d.ts +2 -0
- package/dist/functions/date-components.d.ts.map +1 -0
- package/dist/functions/date-components.js +81 -0
- package/dist/functions/date-components.js.map +1 -0
- package/dist/functions/existence.d.ts +4 -0
- package/dist/functions/existence.d.ts.map +1 -0
- package/dist/functions/existence.js +97 -0
- package/dist/functions/existence.js.map +1 -0
- package/dist/functions/fhir.d.ts +2 -0
- package/dist/functions/fhir.d.ts.map +1 -0
- package/dist/functions/fhir.js +222 -0
- package/dist/functions/fhir.js.map +1 -0
- package/dist/functions/filtering.d.ts +2 -0
- package/dist/functions/filtering.d.ts.map +1 -0
- package/dist/functions/filtering.js +112 -0
- package/dist/functions/filtering.js.map +1 -0
- package/dist/functions/install.d.ts +18 -0
- package/dist/functions/install.d.ts.map +1 -0
- package/dist/functions/install.js +20 -0
- package/dist/functions/install.js.map +1 -0
- package/dist/functions/iteration.d.ts +7 -0
- package/dist/functions/iteration.d.ts.map +1 -0
- package/dist/functions/iteration.js +9 -0
- package/dist/functions/iteration.js.map +1 -0
- package/dist/functions/math.d.ts +2 -0
- package/dist/functions/math.d.ts.map +1 -0
- package/dist/functions/math.js +152 -0
- package/dist/functions/math.js.map +1 -0
- package/dist/functions/reflection.d.ts +2 -0
- package/dist/functions/reflection.d.ts.map +1 -0
- package/dist/functions/reflection.js +23 -0
- package/dist/functions/reflection.js.map +1 -0
- package/dist/functions/registry.d.ts +28 -0
- package/dist/functions/registry.d.ts.map +1 -0
- package/dist/functions/registry.js +46 -0
- package/dist/functions/registry.js.map +1 -0
- package/dist/functions/sort.d.ts +2 -0
- package/dist/functions/sort.d.ts.map +1 -0
- package/dist/functions/sort.js +48 -0
- package/dist/functions/sort.js.map +1 -0
- package/dist/functions/string.d.ts +2 -0
- package/dist/functions/string.d.ts.map +1 -0
- package/dist/functions/string.js +353 -0
- package/dist/functions/string.js.map +1 -0
- package/dist/functions/subsetting.d.ts +2 -0
- package/dist/functions/subsetting.d.ts.map +1 -0
- package/dist/functions/subsetting.js +72 -0
- package/dist/functions/subsetting.js.map +1 -0
- package/dist/functions/tree.d.ts +8 -0
- package/dist/functions/tree.d.ts.map +1 -0
- package/dist/functions/tree.js +109 -0
- package/dist/functions/tree.js.map +1 -0
- package/dist/functions/type-specifier.d.ts +8 -0
- package/dist/functions/type-specifier.d.ts.map +1 -0
- package/dist/functions/type-specifier.js +27 -0
- package/dist/functions/type-specifier.js.map +1 -0
- package/dist/functions/utility.d.ts +2 -0
- package/dist/functions/utility.d.ts.map +1 -0
- package/dist/functions/utility.js +76 -0
- package/dist/functions/utility.js.map +1 -0
- package/dist/functions/variables.d.ts +2 -0
- package/dist/functions/variables.d.ts.map +1 -0
- package/dist/functions/variables.js +35 -0
- package/dist/functions/variables.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/lexer/lexer.d.ts +4 -0
- package/dist/lexer/lexer.d.ts.map +1 -0
- package/dist/lexer/lexer.js +318 -0
- package/dist/lexer/lexer.js.map +1 -0
- package/dist/lexer/tokens.d.ts +23 -0
- package/dist/lexer/tokens.d.ts.map +1 -0
- package/dist/lexer/tokens.js +40 -0
- package/dist/lexer/tokens.js.map +1 -0
- package/dist/model/provider.d.ts +36 -0
- package/dist/model/provider.d.ts.map +1 -0
- package/dist/model/provider.js +2 -0
- package/dist/model/provider.js.map +1 -0
- package/dist/parser/ast.d.ts +104 -0
- package/dist/parser/ast.d.ts.map +1 -0
- package/dist/parser/ast.js +2 -0
- package/dist/parser/ast.js.map +1 -0
- package/dist/parser/parser.d.ts +4 -0
- package/dist/parser/parser.d.ts.map +1 -0
- package/dist/parser/parser.js +299 -0
- package/dist/parser/parser.js.map +1 -0
- package/dist/parser/precedence.d.ts +24 -0
- package/dist/parser/precedence.d.ts.map +1 -0
- package/dist/parser/precedence.js +52 -0
- package/dist/parser/precedence.js.map +1 -0
- package/dist/parser/printer.d.ts +4 -0
- package/dist/parser/printer.d.ts.map +1 -0
- package/dist/parser/printer.js +112 -0
- package/dist/parser/printer.js.map +1 -0
- package/dist/r4/generated/resources-data.d.ts +3 -0
- package/dist/r4/generated/resources-data.d.ts.map +1 -0
- package/dist/r4/generated/resources-data.js +28628 -0
- package/dist/r4/generated/resources-data.js.map +1 -0
- package/dist/r4/generated/type-maps.d.ts +43153 -0
- package/dist/r4/generated/type-maps.d.ts.map +1 -0
- package/dist/r4/generated/type-maps.js +4 -0
- package/dist/r4/generated/type-maps.js.map +1 -0
- package/dist/r4/generated/types-data.d.ts +3 -0
- package/dist/r4/generated/types-data.d.ts.map +1 -0
- package/dist/r4/generated/types-data.js +2205 -0
- package/dist/r4/generated/types-data.js.map +1 -0
- package/dist/r4/index.d.ts +9 -0
- package/dist/r4/index.d.ts.map +1 -0
- package/dist/r4/index.js +94 -0
- package/dist/r4/index.js.map +1 -0
- package/dist/r4/model-data.d.ts +17 -0
- package/dist/r4/model-data.d.ts.map +1 -0
- package/dist/r4/model-data.js +2 -0
- package/dist/r4/model-data.js.map +1 -0
- package/dist/sites/index.d.ts +31 -0
- package/dist/sites/index.d.ts.map +1 -0
- package/dist/sites/index.js +205 -0
- package/dist/sites/index.js.map +1 -0
- package/dist/typed/infer.d.ts +234 -0
- package/dist/typed/infer.d.ts.map +1 -0
- package/dist/typed/infer.js +85 -0
- package/dist/typed/infer.js.map +1 -0
- package/dist/typed/perf-fixture.types.d.ts +136 -0
- package/dist/typed/perf-fixture.types.d.ts.map +1 -0
- package/dist/typed/perf-fixture.types.js +2 -0
- package/dist/typed/perf-fixture.types.js.map +1 -0
- package/dist/values/collection.d.ts +24 -0
- package/dist/values/collection.d.ts.map +1 -0
- package/dist/values/collection.js +51 -0
- package/dist/values/collection.js.map +1 -0
- package/dist/values/datetime.d.ts +54 -0
- package/dist/values/datetime.d.ts.map +1 -0
- package/dist/values/datetime.js +226 -0
- package/dist/values/datetime.js.map +1 -0
- package/dist/values/decimal.d.ts +48 -0
- package/dist/values/decimal.d.ts.map +1 -0
- package/dist/values/decimal.js +188 -0
- package/dist/values/decimal.js.map +1 -0
- package/dist/values/numeric.d.ts +24 -0
- package/dist/values/numeric.d.ts.map +1 -0
- package/dist/values/numeric.js +53 -0
- package/dist/values/numeric.js.map +1 -0
- package/dist/values/quantity.d.ts +49 -0
- package/dist/values/quantity.d.ts.map +1 -0
- package/dist/values/quantity.js +297 -0
- package/dist/values/quantity.js.map +1 -0
- package/dist/values/temporal-arithmetic.d.ts +17 -0
- package/dist/values/temporal-arithmetic.d.ts.map +1 -0
- package/dist/values/temporal-arithmetic.js +183 -0
- package/dist/values/temporal-arithmetic.js.map +1 -0
- package/dist/values/temporal-compare.d.ts +9 -0
- package/dist/values/temporal-compare.d.ts.map +1 -0
- package/dist/values/temporal-compare.js +59 -0
- package/dist/values/temporal-compare.js.map +1 -0
- package/dist/values/type-compat.d.ts +47 -0
- package/dist/values/type-compat.d.ts.map +1 -0
- package/dist/values/type-compat.js +100 -0
- package/dist/values/type-compat.js.map +1 -0
- package/dist/values/typed-value.d.ts +48 -0
- package/dist/values/typed-value.d.ts.map +1 -0
- package/dist/values/typed-value.js +117 -0
- package/dist/values/typed-value.js.map +1 -0
- package/dist/values/ucum.d.ts +14 -0
- package/dist/values/ucum.d.ts.map +1 -0
- package/dist/values/ucum.js +146 -0
- package/dist/values/ucum.js.map +1 -0
- package/package.json +137 -0
- package/src/analyzer/analyze-dto.ts +195 -0
- package/src/analyzer/analyze.ts +1181 -0
- package/src/analyzer/expression-policy.ts +434 -0
- package/src/analyzer/index.ts +23 -0
- package/src/analyzer/regex-safety.ts +85 -0
- package/src/analyzer/signatures.ts +260 -0
- package/src/api/bundle.ts +132 -0
- package/src/api/cache.ts +40 -0
- package/src/api/column-signature.ts +71 -0
- package/src/api/compile.ts +308 -0
- package/src/api/constraints.ts +104 -0
- package/src/api/dto.ts +445 -0
- package/src/api/engine.ts +328 -0
- package/src/api/evaluate.ts +11 -0
- package/src/api/project.ts +255 -0
- package/src/api/tagged.ts +35 -0
- package/src/cli/dto-check.ts +94 -0
- package/src/cli/fhirpath-check.ts +168 -0
- package/src/cli/ts-loader.mjs +49 -0
- package/src/engine/context.ts +282 -0
- package/src/engine/evaluator.ts +246 -0
- package/src/engine/navigation.ts +106 -0
- package/src/engine/operators/collections.ts +34 -0
- package/src/engine/operators/comparison.ts +82 -0
- package/src/engine/operators/equality.ts +232 -0
- package/src/engine/operators/index.ts +65 -0
- package/src/engine/operators/logic.ts +53 -0
- package/src/engine/operators/math.ts +225 -0
- package/src/engine/operators/strings.ts +25 -0
- package/src/engine/operators/types.ts +16 -0
- package/src/engine/type-matching.ts +148 -0
- package/src/errors.ts +33 -0
- package/src/eslint/index.ts +388 -0
- package/src/fhir/html-checks.ts +266 -0
- package/src/fhir/model-navigation.ts +148 -0
- package/src/functions/aggregate.ts +136 -0
- package/src/functions/boundary.ts +209 -0
- package/src/functions/combining.ts +15 -0
- package/src/functions/control.ts +25 -0
- package/src/functions/conversion.ts +349 -0
- package/src/functions/date-components.ts +88 -0
- package/src/functions/existence.ts +107 -0
- package/src/functions/fhir.ts +247 -0
- package/src/functions/filtering.ts +120 -0
- package/src/functions/install.ts +19 -0
- package/src/functions/iteration.ts +21 -0
- package/src/functions/math.ts +167 -0
- package/src/functions/reflection.ts +24 -0
- package/src/functions/registry.ts +72 -0
- package/src/functions/sort.ts +68 -0
- package/src/functions/string.ts +413 -0
- package/src/functions/subsetting.ts +89 -0
- package/src/functions/tree.ts +112 -0
- package/src/functions/type-specifier.ts +29 -0
- package/src/functions/utility.ts +82 -0
- package/src/functions/variables.ts +36 -0
- package/src/index.ts +30 -0
- package/src/lexer/lexer.ts +341 -0
- package/src/lexer/tokens.ts +69 -0
- package/src/model/provider.ts +36 -0
- package/src/parser/ast.ts +164 -0
- package/src/parser/parser.ts +321 -0
- package/src/parser/precedence.ts +52 -0
- package/src/parser/printer.ts +114 -0
- package/src/r4/generated/resources-data.ts +28631 -0
- package/src/r4/generated/type-maps.ts +22145 -0
- package/src/r4/generated/types-data.ts +2208 -0
- package/src/r4/index.ts +107 -0
- package/src/r4/model-data.ts +17 -0
- package/src/sites/index.ts +285 -0
- package/src/typed/infer.ts +433 -0
- package/src/typed/perf-fixture.types.ts +143 -0
- package/src/values/collection.ts +57 -0
- package/src/values/datetime.ts +285 -0
- package/src/values/decimal.ts +213 -0
- package/src/values/numeric.ts +63 -0
- package/src/values/quantity.ts +358 -0
- package/src/values/temporal-arithmetic.ts +231 -0
- package/src/values/temporal-compare.ts +65 -0
- package/src/values/type-compat.ts +133 -0
- package/src/values/typed-value.ts +151 -0
- package/src/values/ucum.ts +181 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import { mergeEnvKeys } from '../engine/context.ts'
|
|
2
|
+
import type { R4TypeOf } from '../r4/generated/type-maps.ts'
|
|
3
|
+
import type { FhirpathInput, FhirpathResult } from '../typed/infer.ts'
|
|
4
|
+
import { criteriaBoolean } from '../values/collection.ts'
|
|
5
|
+
import type { TypedValue } from '../values/typed-value.ts'
|
|
6
|
+
import { type BundleLike, isBundle, normalizeInput, toSubjects } from './bundle.ts'
|
|
7
|
+
import {
|
|
8
|
+
type AnyExpression,
|
|
9
|
+
CompiledExpression,
|
|
10
|
+
type Compiler,
|
|
11
|
+
createCachedCompiler,
|
|
12
|
+
type CustomFunction,
|
|
13
|
+
type EvaluateOptions,
|
|
14
|
+
type SingleCustomFunction,
|
|
15
|
+
} from './compile.ts'
|
|
16
|
+
import { type ConstraintCheckResult, evaluateConstraints, type FhirConstraint } from './constraints.ts'
|
|
17
|
+
import { assertInputMatchesDto, dtoCallOptions, type DtoClass, dtoDefinition, withDtos } from './dto.ts'
|
|
18
|
+
import { type Projection, type ProjectionColumns, projectRows } from './project.ts'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* What engine methods accept as input: one resource, an array of resources, or a
|
|
22
|
+
* Bundle — a Bundle behaves as its entry resources unless the expression
|
|
23
|
+
* references `Bundle` in root position (then it addresses the bundle itself).
|
|
24
|
+
* An expression that starts at a bare Bundle element (`entry.count()`, `type`)
|
|
25
|
+
* is ambiguous and throws. Wrap a Bundle in an array (`[bundle]`) to force
|
|
26
|
+
* treating it as one resource.
|
|
27
|
+
*/
|
|
28
|
+
export type EngineInput<Expr extends string = string> = FhirpathInput<Expr> | readonly unknown[] | BundleLike
|
|
29
|
+
|
|
30
|
+
/** Per-call options that declare a result type when inference returns `unknown`. Runtime code ignores `type`. */
|
|
31
|
+
export type TypedEvaluateOptions<T extends keyof R4TypeOf> = EvaluateOptions & { type: T }
|
|
32
|
+
|
|
33
|
+
/** Engines created during the current recording session. */
|
|
34
|
+
let session: FhirPathEngine[] | undefined
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Records engines created while project modules are imported. Call the returned
|
|
38
|
+
* function to close the session and read its engines. Only one session is active
|
|
39
|
+
* at a time, and closing it releases the recorded engine references.
|
|
40
|
+
*/
|
|
41
|
+
export function recordEngines(): () => readonly FhirPathEngine[] {
|
|
42
|
+
const open: FhirPathEngine[] = []
|
|
43
|
+
session = open
|
|
44
|
+
return () => {
|
|
45
|
+
if (session === open) {
|
|
46
|
+
session = undefined
|
|
47
|
+
}
|
|
48
|
+
return open
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function recordEngine(engine: FhirPathEngine): void {
|
|
53
|
+
session?.push(engine)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* What a `FhirPathEngine` takes at construction: the `EvaluateOptions` it binds
|
|
58
|
+
* as per-call defaults, plus settings that belong to the engine itself.
|
|
59
|
+
*/
|
|
60
|
+
export interface EngineOptions extends EvaluateOptions {
|
|
61
|
+
/**
|
|
62
|
+
* Max number of distinct expression texts this engine keeps parsed, in its own
|
|
63
|
+
* LRU (not shared with other engines or the free `evaluate()`). Defaults to 500;
|
|
64
|
+
* 0 disables reuse. Expressions passed via `compile()` bypass it.
|
|
65
|
+
*
|
|
66
|
+
* Read once, at construction: unlike the `EvaluateOptions` fields, this is not
|
|
67
|
+
* part of `defaults` and a per-call `options` argument cannot change it.
|
|
68
|
+
*/
|
|
69
|
+
cacheSize?: number
|
|
70
|
+
/**
|
|
71
|
+
* DTOs whose columns become typed FHIRPath functions. Registration requires a
|
|
72
|
+
* model and publishes every column name, but it does not publish DTO `env` or
|
|
73
|
+
* `vars`. Same-name columns are allowed only when their input types do not
|
|
74
|
+
* overlap. See `docs/api.md#registering-dto-columns-as-functions`.
|
|
75
|
+
*/
|
|
76
|
+
resourceDtos?: readonly DtoClass[]
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* A FHIRPath engine with shared model and evaluation defaults. Per-call values
|
|
81
|
+
* replace defaults, while `env`, `vars`, and `functions` merge by name. Keep an
|
|
82
|
+
* engine for reuse because its parse cache is private to that instance.
|
|
83
|
+
*/
|
|
84
|
+
export class FhirPathEngine {
|
|
85
|
+
/** The per-call options bound at construction; engine-only settings are not part of them. */
|
|
86
|
+
readonly defaults: EvaluateOptions
|
|
87
|
+
/**
|
|
88
|
+
* The DTO classes registered at construction (EngineOptions.resourceDtos), in
|
|
89
|
+
* order. Kept so tooling can check them against this engine's own model,
|
|
90
|
+
* functions and env — see `analyzeEngineDtos` in `fhirpath-ts/analyzer`.
|
|
91
|
+
*/
|
|
92
|
+
readonly dtos: readonly DtoClass[]
|
|
93
|
+
private readonly compileCached: Compiler
|
|
94
|
+
|
|
95
|
+
constructor({ cacheSize, resourceDtos, ...defaults }: EngineOptions = {}) {
|
|
96
|
+
this.compileCached = createCachedCompiler(cacheSize)
|
|
97
|
+
this.dtos = resourceDtos ?? []
|
|
98
|
+
this.defaults = this.precompiled(withDtos(defaults, this.dtos, this.compileCached))
|
|
99
|
+
recordEngine(this)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Compile (LRU-cached by expression text) and evaluate in one call; typed like `compile().evaluate()`. */
|
|
103
|
+
evaluate<const Expr extends string, T extends keyof R4TypeOf>(
|
|
104
|
+
expression: Expr | CompiledExpression<Expr>,
|
|
105
|
+
input: EngineInput<Expr> | undefined,
|
|
106
|
+
options: TypedEvaluateOptions<T>
|
|
107
|
+
): R4TypeOf[T][]
|
|
108
|
+
evaluate<const Expr extends string>(
|
|
109
|
+
expression: Expr | CompiledExpression<Expr>,
|
|
110
|
+
input?: EngineInput<Expr>,
|
|
111
|
+
options?: EvaluateOptions
|
|
112
|
+
): FhirpathResult<Expr>
|
|
113
|
+
evaluate(expression: AnyExpression, input?: unknown, options?: EvaluateOptions): unknown[] {
|
|
114
|
+
const compiled = this.compileCached(expression)
|
|
115
|
+
const merged = this.merged(options)
|
|
116
|
+
return compiled.evaluate(normalizeInput(input, compiled.ast, merged.model), merged)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Like `evaluate()`, keeping the internal typed representation (types, Decimal, Temporal). */
|
|
120
|
+
evaluateTyped(expression: AnyExpression, input?: unknown, options?: EvaluateOptions): TypedValue[] {
|
|
121
|
+
const compiled = this.compileCached(expression)
|
|
122
|
+
const merged = this.merged(options)
|
|
123
|
+
return compiled.evaluateTyped(normalizeInput(input, compiled.ast, merged.model), merged)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Parse once for reuse, with this engine's defaults bound. Does not touch the parse cache. */
|
|
127
|
+
compile<const Expr extends string>(expression: Expr): BoundExpression<Expr> {
|
|
128
|
+
return new BoundExpression(this, new CompiledExpression(expression))
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** The first result, or undefined when the expression comes up empty. */
|
|
132
|
+
first<const Expr extends string, T extends keyof R4TypeOf>(
|
|
133
|
+
expression: Expr | CompiledExpression<Expr>,
|
|
134
|
+
input: EngineInput<Expr> | undefined,
|
|
135
|
+
options: TypedEvaluateOptions<T>
|
|
136
|
+
): R4TypeOf[T] | undefined
|
|
137
|
+
first<const Expr extends string>(
|
|
138
|
+
expression: Expr | CompiledExpression<Expr>,
|
|
139
|
+
input?: EngineInput<Expr>,
|
|
140
|
+
options?: EvaluateOptions
|
|
141
|
+
): FhirpathResult<Expr>[number] | undefined
|
|
142
|
+
first(expression: AnyExpression, input?: unknown, options?: EvaluateOptions): unknown {
|
|
143
|
+
return this.evaluate(expression, input, options)[0]
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Boolean criteria evaluation, the semantics FHIR invariants, Subscription
|
|
148
|
+
* criteria, and `enableWhen` share. A single boolean returns itself, a single
|
|
149
|
+
* non-boolean item returns true, and more than one item is an error, which is
|
|
150
|
+
* spec §4.5 singleton evaluation. Empty returns false, which is the criteria
|
|
151
|
+
* convention layered on top of it; see `criteriaBoolean`.
|
|
152
|
+
*/
|
|
153
|
+
test(input: unknown, expression: AnyExpression, options?: EvaluateOptions): boolean {
|
|
154
|
+
return criteriaBoolean(this.evaluateTyped(expression, input, options))
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* The items (or Bundle entry resources) whose criteria hold, by `test()`
|
|
159
|
+
* semantics. Criteria run against each item directly — not via `test()` — so
|
|
160
|
+
* an item that is itself a Bundle is not unwrapped again.
|
|
161
|
+
*/
|
|
162
|
+
filter<T>(input: readonly T[], expression: AnyExpression, options?: EvaluateOptions): T[]
|
|
163
|
+
filter(input: BundleLike, expression: AnyExpression, options?: EvaluateOptions): unknown[]
|
|
164
|
+
filter(input: readonly unknown[] | BundleLike, expression: AnyExpression, options?: EvaluateOptions): unknown[] {
|
|
165
|
+
const compiled = this.compileCached(expression)
|
|
166
|
+
const merged = this.merged(options)
|
|
167
|
+
return toSubjects(input)
|
|
168
|
+
.map(subject => subject.value)
|
|
169
|
+
.filter(value => criteriaBoolean(compiled.evaluateTyped(value, merged)))
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Projects each resource into a flat row. Columns return one optional value
|
|
174
|
+
* unless `collection: true` is set. `%rowIndex` and `%rowTotal` are available
|
|
175
|
+
* in every column. All columns compile before any row is read.
|
|
176
|
+
*/
|
|
177
|
+
project<C extends DtoClass>(
|
|
178
|
+
input: readonly unknown[] | BundleLike,
|
|
179
|
+
dto: C,
|
|
180
|
+
options?: EvaluateOptions
|
|
181
|
+
): InstanceType<C>[]
|
|
182
|
+
project<C extends DtoClass>(input: unknown, dto: C, options?: EvaluateOptions): InstanceType<C>
|
|
183
|
+
project<const Columns extends ProjectionColumns>(
|
|
184
|
+
input: readonly unknown[] | BundleLike,
|
|
185
|
+
columns: Columns,
|
|
186
|
+
options?: EvaluateOptions
|
|
187
|
+
): Projection<Columns>[]
|
|
188
|
+
project<const Columns extends ProjectionColumns>(
|
|
189
|
+
input: unknown,
|
|
190
|
+
columns: Columns,
|
|
191
|
+
options?: EvaluateOptions
|
|
192
|
+
): Projection<Columns>
|
|
193
|
+
project(input: unknown, columns: ProjectionColumns | DtoClass, options?: EvaluateOptions): unknown {
|
|
194
|
+
if (typeof columns === 'function') {
|
|
195
|
+
assertInputMatchesDto(input, columns)
|
|
196
|
+
}
|
|
197
|
+
const rows =
|
|
198
|
+
typeof columns === 'function'
|
|
199
|
+
? projectRows(
|
|
200
|
+
input,
|
|
201
|
+
dtoDefinition(columns).columns,
|
|
202
|
+
this.merged(dtoCallOptions(columns, options)),
|
|
203
|
+
this.compileCached
|
|
204
|
+
)
|
|
205
|
+
// Materialize each row as a class instance, so the DTO's own methods
|
|
206
|
+
// and getters see the projected values.
|
|
207
|
+
.map(row => Object.assign(new columns(), row))
|
|
208
|
+
: projectRows(input, columns, this.merged(options), this.compileCached)
|
|
209
|
+
return Array.isArray(input) || isBundle(input) ? rows : rows[0]
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Evaluates FHIR constraint expressions. Arrays and Bundles add the resource
|
|
214
|
+
* index to each issue. Expression errors become failed issues. This is not full
|
|
215
|
+
* profile validation.
|
|
216
|
+
*/
|
|
217
|
+
checkConstraints(
|
|
218
|
+
input: unknown,
|
|
219
|
+
constraints: readonly FhirConstraint[],
|
|
220
|
+
options?: EvaluateOptions
|
|
221
|
+
): ConstraintCheckResult {
|
|
222
|
+
return evaluateConstraints(input, constraints, this.merged(options), this.compileCached)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Replaces scalar defaults and merges `env`, `vars`, and `functions` by name.
|
|
227
|
+
* Per-call entries win. Environment keys are normalized with or without `%`.
|
|
228
|
+
*/
|
|
229
|
+
private merged(options?: EvaluateOptions): EvaluateOptions {
|
|
230
|
+
if (!options) {
|
|
231
|
+
return this.defaults
|
|
232
|
+
}
|
|
233
|
+
const precompiled = this.precompiled(options)
|
|
234
|
+
const merged = { ...this.defaults, ...precompiled }
|
|
235
|
+
if (this.defaults.env && precompiled.env) {
|
|
236
|
+
merged.env = mergeEnvKeys(this.defaults.env, precompiled.env)
|
|
237
|
+
}
|
|
238
|
+
if (this.defaults.vars && precompiled.vars) {
|
|
239
|
+
merged.vars = mergeEnvKeys(this.defaults.vars, precompiled.vars)
|
|
240
|
+
}
|
|
241
|
+
if (this.defaults.functions && precompiled.functions) {
|
|
242
|
+
merged.functions = { ...this.defaults.functions, ...precompiled.functions }
|
|
243
|
+
}
|
|
244
|
+
return merged
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* `vars` expressions and expression-function bodies given as strings parse
|
|
249
|
+
* through this engine's LRU here, once per call at most — so per-row
|
|
250
|
+
* evaluation inside project() never re-parses them.
|
|
251
|
+
*/
|
|
252
|
+
private precompiled(options: EvaluateOptions): EvaluateOptions {
|
|
253
|
+
const out = { ...options }
|
|
254
|
+
if (options.vars) {
|
|
255
|
+
out.vars = Object.fromEntries(
|
|
256
|
+
Object.entries(options.vars).map(([name, value]) => [
|
|
257
|
+
name,
|
|
258
|
+
typeof value === 'string' ? this.compileCached(value) : value,
|
|
259
|
+
])
|
|
260
|
+
)
|
|
261
|
+
}
|
|
262
|
+
if (options.functions) {
|
|
263
|
+
out.functions = Object.fromEntries(
|
|
264
|
+
Object.entries(options.functions).map(([name, fn]) => [name, this.precompiledFunction(fn)])
|
|
265
|
+
)
|
|
266
|
+
}
|
|
267
|
+
return out
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** One entry of `functions`, with any string body parsed — reaching into an overload set for each member. */
|
|
271
|
+
private precompiledFunction(fn: CustomFunction): CustomFunction {
|
|
272
|
+
return 'overloads' in fn
|
|
273
|
+
? { overloads: fn.overloads.map(overload => this.precompiledBody(overload)) }
|
|
274
|
+
: this.precompiledBody(fn)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
private precompiledBody(fn: SingleCustomFunction): SingleCustomFunction {
|
|
278
|
+
return 'expression' in fn && typeof fn.expression === 'string'
|
|
279
|
+
? { ...fn, expression: this.compileCached(fn.expression) }
|
|
280
|
+
: fn
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** A compiled expression carrying an engine's defaults, so `evaluate(input)` needs nothing else. */
|
|
285
|
+
export class BoundExpression<Expr extends string = string> {
|
|
286
|
+
readonly expression: CompiledExpression<Expr>
|
|
287
|
+
private readonly engine: FhirPathEngine
|
|
288
|
+
|
|
289
|
+
constructor(engine: FhirPathEngine, expression: CompiledExpression<Expr>) {
|
|
290
|
+
this.engine = engine
|
|
291
|
+
this.expression = expression
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
get source(): string {
|
|
295
|
+
return this.expression.source
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
evaluate<T extends keyof R4TypeOf>(
|
|
299
|
+
input: EngineInput<Expr> | undefined,
|
|
300
|
+
options: TypedEvaluateOptions<T>
|
|
301
|
+
): R4TypeOf[T][]
|
|
302
|
+
evaluate(input?: EngineInput<Expr>, options?: EvaluateOptions): FhirpathResult<Expr>
|
|
303
|
+
evaluate(input?: EngineInput<Expr>, options?: EvaluateOptions): unknown {
|
|
304
|
+
return this.engine.evaluate(this.expression, input, options)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
evaluateTyped(input?: unknown, options?: EvaluateOptions): TypedValue[] {
|
|
308
|
+
return this.engine.evaluateTyped(this.expression, input, options)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
first<T extends keyof R4TypeOf>(
|
|
312
|
+
input: EngineInput<Expr> | undefined,
|
|
313
|
+
options: TypedEvaluateOptions<T>
|
|
314
|
+
): R4TypeOf[T] | undefined
|
|
315
|
+
first(input?: EngineInput<Expr>, options?: EvaluateOptions): FhirpathResult<Expr>[number] | undefined
|
|
316
|
+
first(input?: EngineInput<Expr>, options?: EvaluateOptions): unknown {
|
|
317
|
+
return this.engine.first(this.expression, input, options)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
test(input: unknown, options?: EvaluateOptions): boolean {
|
|
321
|
+
return this.engine.test(input, this.expression, options)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** The canonical form of the expression. */
|
|
325
|
+
toString(): string {
|
|
326
|
+
return this.expression.toString()
|
|
327
|
+
}
|
|
328
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type AnyExpression, createCachedCompiler, type EvaluateOptions } from './compile.ts'
|
|
2
|
+
|
|
3
|
+
const compileCached = createCachedCompiler()
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Evaluate a FHIRPath expression against an input and return plain JS values.
|
|
7
|
+
* Parsed expressions are kept in a module-level LRU keyed by the expression text.
|
|
8
|
+
*/
|
|
9
|
+
export function evaluate(expression: AnyExpression, input?: unknown, options?: EvaluateOptions): unknown[] {
|
|
10
|
+
return compileCached(expression).evaluate(input, options)
|
|
11
|
+
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { type EvaluationContext, forkVariables } from '../engine/context.ts'
|
|
2
|
+
import { evaluateNode } from '../engine/evaluator.ts'
|
|
3
|
+
import { FhirPathRuntimeError } from '../errors.ts'
|
|
4
|
+
import type { R4TypeOf } from '../r4/generated/type-maps.ts'
|
|
5
|
+
import type { FhirpathResultIn } from '../typed/infer.ts'
|
|
6
|
+
import { criteriaBoolean } from '../values/collection.ts'
|
|
7
|
+
import { toCollection, type TypedValue, unwrap } from '../values/typed-value.ts'
|
|
8
|
+
import { toSubjects } from './bundle.ts'
|
|
9
|
+
import { type Compiler, contextFactory, type EvaluateOptions } from './compile.ts'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* One `project()` column. A string or `{ path }` returns one optional value.
|
|
13
|
+
* `collection` keeps every value, `default` fills an empty result, and `test`
|
|
14
|
+
* returns one criteria boolean. `as`, `choices`, and `enum` are alternative
|
|
15
|
+
* value conversions. `type` declares a result type for TypeScript and is not
|
|
16
|
+
* checked at runtime. See `docs/api.md#project` for all options.
|
|
17
|
+
*/
|
|
18
|
+
export type ProjectionColumn = string | ({ path: string } & ColumnOptions) | { test: string }
|
|
19
|
+
|
|
20
|
+
/** Options shared by plain project columns and DTO `@column` fields. */
|
|
21
|
+
export type ColumnOptions = {
|
|
22
|
+
collection?: boolean
|
|
23
|
+
type?: keyof R4TypeOf
|
|
24
|
+
default?: unknown
|
|
25
|
+
} & (
|
|
26
|
+
| { as?: 'Date' | ((value: unknown) => unknown); choices?: never; pick?: never; enum?: never }
|
|
27
|
+
| { choices: Readonly<Record<string, unknown>>; as?: never; pick?: never; enum?: never }
|
|
28
|
+
| { choices: readonly { code: string }[]; pick?: string; as?: never; enum?: never }
|
|
29
|
+
| { enum: readonly string[]; as?: never; choices?: never; pick?: never }
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
export type ProjectionColumns = Record<string, ProjectionColumn>
|
|
33
|
+
|
|
34
|
+
type ColumnPath<Column> = Column extends string
|
|
35
|
+
? Column
|
|
36
|
+
: Column extends { path: infer Path extends string }
|
|
37
|
+
? Path
|
|
38
|
+
: never
|
|
39
|
+
|
|
40
|
+
/** Infers the values after the selected conversion, or from the path when no conversion is set. */
|
|
41
|
+
type ColumnValues<Column, Root extends string> = Column extends { as: (value: never) => infer R }
|
|
42
|
+
? R[]
|
|
43
|
+
: Column extends { as: 'Date' }
|
|
44
|
+
? Date[]
|
|
45
|
+
: Column extends { choices: readonly (infer Row extends { code: string })[] }
|
|
46
|
+
? Column extends { pick: infer Key extends keyof Row }
|
|
47
|
+
? Row[Key][]
|
|
48
|
+
: Row[]
|
|
49
|
+
: Column extends { choices: infer M extends Readonly<Record<string, unknown>> }
|
|
50
|
+
? M[keyof M][]
|
|
51
|
+
: Column extends { enum: readonly (infer V extends string)[] }
|
|
52
|
+
? V[]
|
|
53
|
+
: Column extends { type: infer T extends keyof R4TypeOf }
|
|
54
|
+
? R4TypeOf[T][]
|
|
55
|
+
: FhirpathResultIn<ColumnPath<Column>, Root>
|
|
56
|
+
|
|
57
|
+
/** A column's output type. A default replaces `undefined`; a collection returns every value. */
|
|
58
|
+
export type ColumnResult<
|
|
59
|
+
Column extends string | { path: string } | { test: string },
|
|
60
|
+
Root extends string = 'opaque',
|
|
61
|
+
> = Column extends {
|
|
62
|
+
test: string
|
|
63
|
+
}
|
|
64
|
+
? boolean
|
|
65
|
+
: Column extends { collection: true }
|
|
66
|
+
? ColumnValues<Column, Root>
|
|
67
|
+
: Column extends { default: infer D }
|
|
68
|
+
? ColumnValues<Column, Root>[number] | D
|
|
69
|
+
: ColumnValues<Column, Root>[number] | undefined
|
|
70
|
+
|
|
71
|
+
/** The row shape `project()` produces: each column's type inferred from its expression. */
|
|
72
|
+
export type Projection<Columns extends ProjectionColumns, Root extends string = 'opaque'> = {
|
|
73
|
+
-readonly [K in keyof Columns]: ColumnResult<Columns[K], Root>
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The `as: 'Date'` coercion; a value that is not a parseable date string becomes empty. */
|
|
77
|
+
function toJsDates(values: unknown[]): Date[] {
|
|
78
|
+
return values.flatMap(value => {
|
|
79
|
+
if (typeof value !== 'string') {
|
|
80
|
+
return []
|
|
81
|
+
}
|
|
82
|
+
const date = new Date(value)
|
|
83
|
+
return Number.isNaN(date.getTime()) ? [] : [date]
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** The string key `choices` looks up: primitives via String(), anything else never matches. */
|
|
88
|
+
function mapKey(value: unknown): string | undefined {
|
|
89
|
+
return typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean'
|
|
90
|
+
? String(value)
|
|
91
|
+
: undefined
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** The `choices` lookup over a plain Record: own keys only; a miss becomes empty. */
|
|
95
|
+
function recordLookup(choices: Readonly<Record<string, unknown>>): (values: unknown[]) => unknown[] {
|
|
96
|
+
return values =>
|
|
97
|
+
values.flatMap(value => {
|
|
98
|
+
const key = mapKey(value)
|
|
99
|
+
return key !== undefined && Object.hasOwn(choices, key) ? [choices[key]] : []
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Looks up display rows by `code`. The first row wins; a missing row or field returns empty. */
|
|
104
|
+
function tableLookup(rows: readonly { code: string }[], pick: string | undefined): (values: unknown[]) => unknown[] {
|
|
105
|
+
const byCode = new Map<string, { code: string }>()
|
|
106
|
+
for (const row of rows) {
|
|
107
|
+
if (!byCode.has(row.code)) {
|
|
108
|
+
byCode.set(row.code, row)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return values =>
|
|
112
|
+
values.flatMap(value => {
|
|
113
|
+
const key = mapKey(value)
|
|
114
|
+
const row = key === undefined ? undefined : byCode.get(key)
|
|
115
|
+
if (row === undefined) {
|
|
116
|
+
return []
|
|
117
|
+
}
|
|
118
|
+
if (pick === undefined) {
|
|
119
|
+
return [row]
|
|
120
|
+
}
|
|
121
|
+
const field = (row as Record<string, unknown>)[pick]
|
|
122
|
+
return field === undefined ? [] : [field]
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** The `enum` check: values outside the listed strings become empty. */
|
|
127
|
+
function enumLookup(allowed: readonly string[]): (values: unknown[]) => unknown[] {
|
|
128
|
+
const members = new Set<unknown>(allowed)
|
|
129
|
+
return values => values.filter(value => members.has(value))
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** The table form of `choices`; Array.isArray alone cannot exclude the Record form for the checker. */
|
|
133
|
+
function isTable(
|
|
134
|
+
choices: Readonly<Record<string, unknown>> | readonly { code: string }[]
|
|
135
|
+
): choices is readonly { code: string }[] {
|
|
136
|
+
return Array.isArray(choices)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Resolve a column's `as`/`choices`/`enum` option into its values mapping. */
|
|
140
|
+
function coercion(spec: Extract<ProjectionColumn, { path: string }>): (values: unknown[]) => unknown[] {
|
|
141
|
+
if (spec.choices !== undefined) {
|
|
142
|
+
return isTable(spec.choices) ? tableLookup(spec.choices, spec.pick) : recordLookup(spec.choices)
|
|
143
|
+
}
|
|
144
|
+
if (spec.enum !== undefined) {
|
|
145
|
+
return enumLookup(spec.enum)
|
|
146
|
+
}
|
|
147
|
+
if (spec.as === 'Date') {
|
|
148
|
+
return toJsDates
|
|
149
|
+
}
|
|
150
|
+
if (typeof spec.as === 'function') {
|
|
151
|
+
const as = spec.as
|
|
152
|
+
return values => values.map(as)
|
|
153
|
+
}
|
|
154
|
+
return values => values
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Checks conversion options for JavaScript callers and transpile-only code. */
|
|
158
|
+
function assertShaperOptions(name: string, spec: Extract<ProjectionColumn, { path: string }>): void {
|
|
159
|
+
const shapers = [spec.as, spec.choices, spec.enum].filter(option => option !== undefined).length
|
|
160
|
+
if (shapers > 1) {
|
|
161
|
+
throw new FhirPathRuntimeError(
|
|
162
|
+
`project(): column '${name}' declares more than one of 'as', 'choices', 'enum'; use one`
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
if (spec.pick !== undefined && !Array.isArray(spec.choices)) {
|
|
166
|
+
throw new FhirPathRuntimeError(
|
|
167
|
+
`project(): column '${name}' has 'pick' without a table 'choices' (an array of { code, … } rows)`
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
if (spec.pick !== undefined && Array.isArray(spec.choices) && spec.choices.length > 0) {
|
|
171
|
+
const pick = spec.pick
|
|
172
|
+
// A field no row carries is a typo, not a sparse table — fail at plan time.
|
|
173
|
+
if (!spec.choices.some(row => Object.hasOwn(row as object, pick))) {
|
|
174
|
+
throw new FhirPathRuntimeError(`project(): column '${name}' picks '${pick}', which no row of its table has`)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* A column resolved against one compiler, ready to run once per row-context.
|
|
181
|
+
* Each read forks the context's variables, so one column's defineVariable()
|
|
182
|
+
* never leaks into the next. `at` locates the row for an error message.
|
|
183
|
+
*/
|
|
184
|
+
type ColumnReader = (root: TypedValue[], context: EvaluationContext, at: RowPosition) => unknown
|
|
185
|
+
|
|
186
|
+
/** Where a row sits in the batch, for the errors that need to point at one of many. */
|
|
187
|
+
interface RowPosition {
|
|
188
|
+
index: number
|
|
189
|
+
total: number
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Which row an error came from, when there is more than one to choose between.
|
|
194
|
+
* A single-resource projection has no position worth reporting, so it says
|
|
195
|
+
* nothing rather than "row 0".
|
|
196
|
+
*/
|
|
197
|
+
function inRow({ index, total }: RowPosition): string {
|
|
198
|
+
return total > 1 ? ` in row ${index}` : ''
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Take the column union apart once, at plan time; rows only run the result. */
|
|
202
|
+
function planColumn(name: string, column: ProjectionColumn, compile: Compiler): ColumnReader {
|
|
203
|
+
if (typeof column !== 'string' && 'test' in column) {
|
|
204
|
+
const criteria = compile(column.test)
|
|
205
|
+
return (root, context) => criteriaBoolean(evaluateNode(criteria.ast, forkVariables(context), root))
|
|
206
|
+
}
|
|
207
|
+
const spec: Extract<ProjectionColumn, { path: string }> = typeof column === 'string' ? { path: column } : column
|
|
208
|
+
assertShaperOptions(name, spec)
|
|
209
|
+
const expression = compile(spec.path)
|
|
210
|
+
const applyAs = coercion(spec)
|
|
211
|
+
const empty = 'default' in spec ? spec.default : undefined
|
|
212
|
+
return (root, context, at) => {
|
|
213
|
+
const values = evaluateNode(expression.ast, forkVariables(context), root).map(unwrap)
|
|
214
|
+
if (spec.collection === true) {
|
|
215
|
+
return applyAs(values)
|
|
216
|
+
}
|
|
217
|
+
// The scalar-column rule counts the expression's values, before any `as`
|
|
218
|
+
// coercion or `choices` miss drops them. One row of a batch can be the only
|
|
219
|
+
// one that breaks it, so the message says which — `%rowIndex` numbering,
|
|
220
|
+
// the same the columns see.
|
|
221
|
+
if (values.length > 1) {
|
|
222
|
+
throw new FhirPathRuntimeError(
|
|
223
|
+
`project(): column '${name}' yielded ${values.length} values${inRow(at)}; append first() or set collection: true`
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
const coerced = applyAs(values)
|
|
227
|
+
return coerced.length > 0 ? coerced[0] : empty
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Projects one row per input subject. Each row gets one shared context, so
|
|
233
|
+
* variables bind once and every column reads the same values. Runtime
|
|
234
|
+
* `%rowIndex` and `%rowTotal` replace matching environment entries.
|
|
235
|
+
*/
|
|
236
|
+
export function projectRows(
|
|
237
|
+
input: unknown,
|
|
238
|
+
columns: ProjectionColumns,
|
|
239
|
+
options: EvaluateOptions,
|
|
240
|
+
compile: Compiler
|
|
241
|
+
): Record<string, unknown>[] {
|
|
242
|
+
const readers = Object.entries(columns).map(([name, column]) => [name, planColumn(name, column, compile)] as const)
|
|
243
|
+
const makeContext = contextFactory(options)
|
|
244
|
+
const subjects = toSubjects(input)
|
|
245
|
+
return subjects.map((subject, index) => {
|
|
246
|
+
const root = toCollection(subject.value)
|
|
247
|
+
const at: RowPosition = { index, total: subjects.length }
|
|
248
|
+
const context = makeContext(root, { rowIndex: index, rowTotal: subjects.length })
|
|
249
|
+
const row: Record<string, unknown> = {}
|
|
250
|
+
for (const [name, read] of readers) {
|
|
251
|
+
row[name] = read(root, context, at)
|
|
252
|
+
}
|
|
253
|
+
return row
|
|
254
|
+
})
|
|
255
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { FhirPathError } from '../errors.ts'
|
|
2
|
+
import type { R4TypeOf } from '../r4/generated/type-maps.ts'
|
|
3
|
+
import type { FhirpathInput, FhirpathResult, FhirpathResultIn, FhirTypeName } from '../typed/infer.ts'
|
|
4
|
+
import { CompiledExpression } from './compile.ts'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Compiles a FHIRPath expression. The call form infers supported literal
|
|
8
|
+
* expressions. Its optional input type gives relative paths a type context for
|
|
9
|
+
* inference and source checks. The tagged form returns `unknown[]` because
|
|
10
|
+
* TypeScript does not preserve the literal type. Tags reject interpolation; use
|
|
11
|
+
* environment variables for data.
|
|
12
|
+
*/
|
|
13
|
+
export function fhirpath<
|
|
14
|
+
const Expr extends string,
|
|
15
|
+
const Root extends FhirTypeName,
|
|
16
|
+
TResult extends unknown[] = FhirpathResultIn<Expr, Root>,
|
|
17
|
+
>(expression: Expr, inputType: Root): CompiledExpression<Expr, R4TypeOf[Root], TResult>
|
|
18
|
+
export function fhirpath<
|
|
19
|
+
const Expr extends string,
|
|
20
|
+
TInput = FhirpathInput<Expr>,
|
|
21
|
+
TResult extends unknown[] = FhirpathResult<Expr>,
|
|
22
|
+
>(expression: Expr): CompiledExpression<Expr, TInput, TResult>
|
|
23
|
+
export function fhirpath(strings: TemplateStringsArray, ...substitutions: never[]): CompiledExpression
|
|
24
|
+
export function fhirpath(input: string | TemplateStringsArray, ...rest: unknown[]): CompiledExpression {
|
|
25
|
+
if (typeof input === 'string') {
|
|
26
|
+
// The optional input type is used by TypeScript and source checks only.
|
|
27
|
+
return new CompiledExpression(input)
|
|
28
|
+
}
|
|
29
|
+
if (rest.length > 0) {
|
|
30
|
+
throw new FhirPathError(
|
|
31
|
+
'fhirpath`...` does not support interpolation; pass values as environment variables instead'
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
return new CompiledExpression(input[0] as string)
|
|
35
|
+
}
|