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,133 @@
|
|
|
1
|
+
/** Shared, conservative type compatibility for runtime dispatch and static checks. */
|
|
2
|
+
import type { ModelProvider } from '../model/provider.ts'
|
|
3
|
+
import { FHIR_PRIMITIVE_TO_SYSTEM, typeLocalName } from './typed-value.ts'
|
|
4
|
+
|
|
5
|
+
/** Behavior families used by the static checks. */
|
|
6
|
+
export type ValueKind = 'Boolean' | 'String' | 'Numeric' | 'Temporal' | 'Quantity' | 'Complex'
|
|
7
|
+
|
|
8
|
+
export function valueKindOfTypeName(canonical: string): ValueKind {
|
|
9
|
+
const system = canonical.startsWith('System.') ? canonical : FHIR_PRIMITIVE_TO_SYSTEM[typeLocalName(canonical)]
|
|
10
|
+
switch (system) {
|
|
11
|
+
case 'System.Boolean':
|
|
12
|
+
return 'Boolean'
|
|
13
|
+
case 'System.String':
|
|
14
|
+
return 'String'
|
|
15
|
+
case 'System.Integer':
|
|
16
|
+
case 'System.Long':
|
|
17
|
+
case 'System.Decimal':
|
|
18
|
+
return 'Numeric'
|
|
19
|
+
case 'System.Date':
|
|
20
|
+
case 'System.DateTime':
|
|
21
|
+
case 'System.Time':
|
|
22
|
+
return 'Temporal'
|
|
23
|
+
case 'System.Quantity':
|
|
24
|
+
return 'Quantity'
|
|
25
|
+
default:
|
|
26
|
+
return canonical === 'FHIR.Quantity' || typeLocalName(canonical) === 'Quantity' ? 'Quantity' : 'Complex'
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Resolves a focus type through its full name, then its local name. Unknown names
|
|
32
|
+
* return undefined so callers can skip claims the model cannot support.
|
|
33
|
+
*/
|
|
34
|
+
export function canonicalFocusType(model: ModelProvider, raw: string): string | undefined {
|
|
35
|
+
if (raw.startsWith('System.')) {
|
|
36
|
+
return raw
|
|
37
|
+
}
|
|
38
|
+
return model.resolveType(raw) ?? model.resolveType(typeLocalName(raw))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** True when every `type` value is a `base` value, including FHIR-primitive → System subtyping. */
|
|
42
|
+
function typeSatisfies(model: ModelProvider, type: string, base: string): boolean {
|
|
43
|
+
if (type === base || model.isSubtypeOf(type, base)) {
|
|
44
|
+
return true
|
|
45
|
+
}
|
|
46
|
+
return base.startsWith('System.') && FHIR_PRIMITIVE_TO_SYSTEM[typeLocalName(type)] === base
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Returns whether one value may have both types. Compatible value kinds and
|
|
51
|
+
* either direction of the model hierarchy count. The model check must still run
|
|
52
|
+
* when kinds differ because `SimpleQuantity` and `Quantity` use different kinds.
|
|
53
|
+
*/
|
|
54
|
+
export function typesOverlap(model: ModelProvider, a: string, b: string): boolean {
|
|
55
|
+
const kind = valueKindOfTypeName(a)
|
|
56
|
+
if (kind !== 'Complex' && kind === valueKindOfTypeName(b)) {
|
|
57
|
+
return true
|
|
58
|
+
}
|
|
59
|
+
return typeSatisfies(model, a, b) || typeSatisfies(model, b, a)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The proof that a call is on the wrong focus: `wanted` lists the declared
|
|
64
|
+
* types and `found` lists the focus types, both as canonical names with
|
|
65
|
+
* duplicates removed. Callers report those two lists and add nothing of their
|
|
66
|
+
* own.
|
|
67
|
+
*/
|
|
68
|
+
export interface UnsatisfiedInput {
|
|
69
|
+
wanted: string[]
|
|
70
|
+
found: string[]
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Returns proof only when every known focus type is incompatible with every
|
|
75
|
+
* declared input type. Missing models, unknown types, empty focus, and any
|
|
76
|
+
* matching type remain allowed. Runtime dispatch and the analyzer share this
|
|
77
|
+
* decision and report it in their own way.
|
|
78
|
+
*/
|
|
79
|
+
export function unsatisfiedInput(
|
|
80
|
+
model: ModelProvider | undefined,
|
|
81
|
+
declared: readonly string[] | undefined,
|
|
82
|
+
focus: Iterable<string>
|
|
83
|
+
): UnsatisfiedInput | undefined {
|
|
84
|
+
if (model === undefined || declared === undefined) {
|
|
85
|
+
return undefined
|
|
86
|
+
}
|
|
87
|
+
const wanted = declared
|
|
88
|
+
.map(type => canonicalFocusType(model, type))
|
|
89
|
+
.filter((type): type is string => type !== undefined)
|
|
90
|
+
if (wanted.length === 0) {
|
|
91
|
+
return undefined
|
|
92
|
+
}
|
|
93
|
+
const found = new Set<string>()
|
|
94
|
+
for (const type of focus) {
|
|
95
|
+
const canonical = canonicalFocusType(model, type)
|
|
96
|
+
if (canonical === undefined || wanted.some(want => typesOverlap(model, canonical, want))) {
|
|
97
|
+
return undefined
|
|
98
|
+
}
|
|
99
|
+
found.add(canonical)
|
|
100
|
+
}
|
|
101
|
+
return found.size === 0 ? undefined : { wanted, found: [...found] }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Either the declaration a call resolves to, or the proof that none of them fits. */
|
|
105
|
+
export type InputResolution<T> = { resolved: T } | { unsatisfied: UnsatisfiedInput }
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Selects the first same-name declaration accepted by the focus. If none fits,
|
|
109
|
+
* the result combines their expected and found types into one error. Unknown or
|
|
110
|
+
* empty focus selects the first declaration; registration prevents ambiguous
|
|
111
|
+
* known focus types.
|
|
112
|
+
*/
|
|
113
|
+
export function resolveByInput<T>(
|
|
114
|
+
model: ModelProvider | undefined,
|
|
115
|
+
candidates: readonly T[],
|
|
116
|
+
declaredTypesOf: (candidate: T) => readonly string[] | undefined,
|
|
117
|
+
focus: readonly string[]
|
|
118
|
+
): InputResolution<T> {
|
|
119
|
+
const misses: UnsatisfiedInput[] = []
|
|
120
|
+
for (const candidate of candidates) {
|
|
121
|
+
const miss = unsatisfiedInput(model, declaredTypesOf(candidate), focus)
|
|
122
|
+
if (miss === undefined) {
|
|
123
|
+
return { resolved: candidate }
|
|
124
|
+
}
|
|
125
|
+
misses.push(miss)
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
unsatisfied: {
|
|
129
|
+
wanted: [...new Set(misses.flatMap(miss => miss.wanted))],
|
|
130
|
+
found: [...new Set(misses.flatMap(miss => miss.found))],
|
|
131
|
+
},
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import type { Temporal } from './datetime.ts'
|
|
2
|
+
import { Decimal } from './decimal.ts'
|
|
3
|
+
|
|
4
|
+
export const SYSTEM_BOOLEAN = 'System.Boolean'
|
|
5
|
+
export const SYSTEM_STRING = 'System.String'
|
|
6
|
+
export const SYSTEM_INTEGER = 'System.Integer'
|
|
7
|
+
export const SYSTEM_LONG = 'System.Long'
|
|
8
|
+
export const SYSTEM_DECIMAL = 'System.Decimal'
|
|
9
|
+
export const SYSTEM_DATE = 'System.Date'
|
|
10
|
+
export const SYSTEM_DATETIME = 'System.DateTime'
|
|
11
|
+
export const SYSTEM_TIME = 'System.Time'
|
|
12
|
+
export const SYSTEM_QUANTITY = 'System.Quantity'
|
|
13
|
+
|
|
14
|
+
/** Placeholder type for plain objects when no model knows better. */
|
|
15
|
+
export const OBJECT_TYPE = 'Object'
|
|
16
|
+
|
|
17
|
+
/** A minimal Quantity value; unit algebra and UCUM conversion land in the quantity phase. */
|
|
18
|
+
export interface QuantityValue {
|
|
19
|
+
value: Decimal
|
|
20
|
+
unit: string
|
|
21
|
+
/** True for calendar duration units (`4 days`), false for UCUM strings (`4 'd'`). */
|
|
22
|
+
calendar: boolean
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A value paired with its type name, e.g. `System.Integer` or `FHIR.Patient`.
|
|
27
|
+
* Runtime representations: Boolean → boolean, String → string, Integer → number,
|
|
28
|
+
* Long → bigint, Decimal → Decimal, Date/DateTime/Time → Temporal,
|
|
29
|
+
* Quantity → QuantityValue, complex types → raw JSON.
|
|
30
|
+
*/
|
|
31
|
+
export interface TypedValue {
|
|
32
|
+
type: string
|
|
33
|
+
value: unknown
|
|
34
|
+
/** For FHIR primitives navigated with a model: the `_field` sibling (id/extension). */
|
|
35
|
+
primitiveElement?: unknown
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** FHIR primitive type names to their System twins (FHIR spec "types" page). */
|
|
39
|
+
export const FHIR_PRIMITIVE_TO_SYSTEM: Readonly<Record<string, string>> = {
|
|
40
|
+
boolean: 'System.Boolean',
|
|
41
|
+
integer: 'System.Integer',
|
|
42
|
+
positiveInt: 'System.Integer',
|
|
43
|
+
unsignedInt: 'System.Integer',
|
|
44
|
+
integer64: 'System.Long',
|
|
45
|
+
decimal: 'System.Decimal',
|
|
46
|
+
date: 'System.Date',
|
|
47
|
+
dateTime: 'System.DateTime',
|
|
48
|
+
instant: 'System.DateTime',
|
|
49
|
+
time: 'System.Time',
|
|
50
|
+
string: 'System.String',
|
|
51
|
+
code: 'System.String',
|
|
52
|
+
id: 'System.String',
|
|
53
|
+
markdown: 'System.String',
|
|
54
|
+
uri: 'System.String',
|
|
55
|
+
url: 'System.String',
|
|
56
|
+
canonical: 'System.String',
|
|
57
|
+
oid: 'System.String',
|
|
58
|
+
uuid: 'System.String',
|
|
59
|
+
base64Binary: 'System.String',
|
|
60
|
+
xhtml: 'System.String',
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The System type a value behaves as: System types answer themselves, FHIR
|
|
65
|
+
* primitives answer their twin (`FHIR.code` → `System.String`), everything else
|
|
66
|
+
* undefined. Operators dispatch on this so FHIR-typed primitives keep working.
|
|
67
|
+
*/
|
|
68
|
+
export function systemTypeOf(item: TypedValue): string | undefined {
|
|
69
|
+
if (item.type.startsWith('System.')) {
|
|
70
|
+
return item.type
|
|
71
|
+
}
|
|
72
|
+
return FHIR_PRIMITIVE_TO_SYSTEM[typeLocalName(item.type)]
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** The local part of a qualified type name: `System.Boolean` → `Boolean`. */
|
|
76
|
+
export function typeLocalName(type: string): string {
|
|
77
|
+
const separator = type.lastIndexOf('.')
|
|
78
|
+
return separator === -1 ? type : type.slice(separator + 1)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Wrap a raw JSON value, inferring System types where possible. */
|
|
82
|
+
export function toTypedValue(value: unknown): TypedValue {
|
|
83
|
+
if (typeof value === 'boolean') {
|
|
84
|
+
return { type: SYSTEM_BOOLEAN, value }
|
|
85
|
+
}
|
|
86
|
+
if (typeof value === 'string') {
|
|
87
|
+
return { type: SYSTEM_STRING, value }
|
|
88
|
+
}
|
|
89
|
+
if (typeof value === 'bigint') {
|
|
90
|
+
return { type: SYSTEM_LONG, value }
|
|
91
|
+
}
|
|
92
|
+
if (typeof value === 'number') {
|
|
93
|
+
if (Number.isSafeInteger(value)) {
|
|
94
|
+
return { type: SYSTEM_INTEGER, value }
|
|
95
|
+
}
|
|
96
|
+
const decimal = Decimal.fromNumber(value)
|
|
97
|
+
// NaN/Infinity have no FHIRPath representation; keep them opaque so operations
|
|
98
|
+
// against them fail with a clean type error instead of a raw TypeError.
|
|
99
|
+
return decimal === undefined ? { type: OBJECT_TYPE, value } : { type: SYSTEM_DECIMAL, value: decimal }
|
|
100
|
+
}
|
|
101
|
+
if (value instanceof Decimal) {
|
|
102
|
+
return { type: SYSTEM_DECIMAL, value }
|
|
103
|
+
}
|
|
104
|
+
if (isResource(value)) {
|
|
105
|
+
return { type: `FHIR.${value.resourceType}`, value }
|
|
106
|
+
}
|
|
107
|
+
return { type: OBJECT_TYPE, value }
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function isResource(value: unknown): value is { resourceType: string } {
|
|
111
|
+
return (
|
|
112
|
+
typeof value === 'object' &&
|
|
113
|
+
value !== null &&
|
|
114
|
+
typeof (value as { resourceType?: unknown }).resourceType === 'string'
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Wrap an input (single value, array, or undefined) as a collection. */
|
|
119
|
+
export function toCollection(value: unknown): TypedValue[] {
|
|
120
|
+
if (value === undefined || value === null) {
|
|
121
|
+
return []
|
|
122
|
+
}
|
|
123
|
+
if (Array.isArray(value)) {
|
|
124
|
+
return value.flatMap(item => toCollection(item))
|
|
125
|
+
}
|
|
126
|
+
return [toTypedValue(value)]
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Unwrap for the public API: internal value objects become plain JS values. */
|
|
130
|
+
export function unwrap(typed: TypedValue): unknown {
|
|
131
|
+
const value = typed.value
|
|
132
|
+
if (value instanceof Decimal) {
|
|
133
|
+
return value.toNumber()
|
|
134
|
+
}
|
|
135
|
+
if (isTemporal(value)) {
|
|
136
|
+
return value.toString()
|
|
137
|
+
}
|
|
138
|
+
if (isQuantity(typed)) {
|
|
139
|
+
const quantity = value as QuantityValue
|
|
140
|
+
return { value: quantity.value.toNumber(), unit: quantity.unit }
|
|
141
|
+
}
|
|
142
|
+
return value
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function isTemporal(value: unknown): value is Temporal {
|
|
146
|
+
return typeof value === 'object' && value !== null && 'kind' in value && 'toLiteralString' in value
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function isQuantity(typed: TypedValue): boolean {
|
|
150
|
+
return typed.type === SYSTEM_QUANTITY
|
|
151
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { Decimal } from './decimal.ts'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Minimal UCUM support: enough units for quantity comparison, conversion, and the
|
|
5
|
+
* official test suites. Factors are exact Decimals, so `1 'm' = 100 'cm'` holds
|
|
6
|
+
* without float drift. Anything outside this table still works as an opaque unit
|
|
7
|
+
* that only compares with itself. Full UCUM (@lhncbc/ucum-lhc) is the documented
|
|
8
|
+
* upgrade path if conformance ever needs it.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** Base dimensions; every known unit reduces to a product of these. */
|
|
12
|
+
type Dimension = 'g' | 'm' | 's' | 'mol' | '1'
|
|
13
|
+
|
|
14
|
+
interface UnitAtom {
|
|
15
|
+
dimension: Dimension
|
|
16
|
+
/** Factor to the base unit of the dimension, as an exact decimal string. */
|
|
17
|
+
factor: string
|
|
18
|
+
/** Prefixable per UCUM metadata; bracketed and derived units are not. */
|
|
19
|
+
prefixable: boolean
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const ATOMS: Readonly<Record<string, UnitAtom>> = {
|
|
23
|
+
m: { dimension: 'm', factor: '1', prefixable: true },
|
|
24
|
+
g: { dimension: 'g', factor: '1', prefixable: true },
|
|
25
|
+
s: { dimension: 's', factor: '1', prefixable: true },
|
|
26
|
+
mol: { dimension: 'mol', factor: '1', prefixable: true },
|
|
27
|
+
L: { dimension: 'm', factor: '0.001', prefixable: true }, // handled as m^3 via exponent bump below
|
|
28
|
+
l: { dimension: 'm', factor: '0.001', prefixable: true },
|
|
29
|
+
min: { dimension: 's', factor: '60', prefixable: false },
|
|
30
|
+
h: { dimension: 's', factor: '3600', prefixable: false },
|
|
31
|
+
d: { dimension: 's', factor: '86400', prefixable: false },
|
|
32
|
+
wk: { dimension: 's', factor: '604800', prefixable: false },
|
|
33
|
+
// UCUM's annum is the Julian year; the Julian month is a twelfth of it.
|
|
34
|
+
a: { dimension: 's', factor: '31557600', prefixable: false },
|
|
35
|
+
mo: { dimension: 's', factor: '2629800', prefixable: false },
|
|
36
|
+
'[lb_av]': { dimension: 'g', factor: '453.59237', prefixable: false },
|
|
37
|
+
'[oz_av]': { dimension: 'g', factor: '28.349523125', prefixable: false },
|
|
38
|
+
'[in_i]': { dimension: 'm', factor: '0.0254', prefixable: false },
|
|
39
|
+
'[ft_i]': { dimension: 'm', factor: '0.3048', prefixable: false },
|
|
40
|
+
'1': { dimension: '1', factor: '1', prefixable: false },
|
|
41
|
+
'%': { dimension: '1', factor: '0.01', prefixable: false },
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Units that are volumes even though their atom row reduces through meters. */
|
|
45
|
+
const VOLUME_ATOMS = new Set(['L', 'l'])
|
|
46
|
+
|
|
47
|
+
const PREFIXES: Readonly<Record<string, string>> = {
|
|
48
|
+
T: '1000000000000',
|
|
49
|
+
G: '1000000000',
|
|
50
|
+
M: '1000000',
|
|
51
|
+
k: '1000',
|
|
52
|
+
h: '100',
|
|
53
|
+
da: '10',
|
|
54
|
+
d: '0.1',
|
|
55
|
+
c: '0.01',
|
|
56
|
+
m: '0.001',
|
|
57
|
+
u: '0.000001',
|
|
58
|
+
n: '0.000000001',
|
|
59
|
+
p: '0.000000000001',
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface CanonicalUnit {
|
|
63
|
+
/** Multiplier to base units, exact. */
|
|
64
|
+
factor: Decimal
|
|
65
|
+
/** Dimension exponents, e.g. `g/m` → { g: 1, m: -1 }. Dimensionless entries are dropped. */
|
|
66
|
+
dimensions: Readonly<Record<string, number>>
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Real UCUM exponents are single-digit (m2, cm3, s2). Larger ones would make
|
|
71
|
+
* powDecimal multiply huge exact BigInts — a tiny expression could hang the
|
|
72
|
+
* process — so anything past this stays an opaque unit.
|
|
73
|
+
*/
|
|
74
|
+
const MAX_EXPONENT_MAGNITUDE = 9
|
|
75
|
+
|
|
76
|
+
/** One `atom` optionally followed by an integer exponent, e.g. `m2` or `cm3`. */
|
|
77
|
+
function parseComponent(text: string): { factor: Decimal; dimension: Dimension; exponent: number } | undefined {
|
|
78
|
+
// Whole-text atoms first, so '1' is the unity unit rather than an empty atom with exponent 1.
|
|
79
|
+
const whole = resolveAtom(text)
|
|
80
|
+
if (whole) {
|
|
81
|
+
const volumeBump = VOLUME_ATOMS.has(whole.atomKey) ? 3 : 1
|
|
82
|
+
return { factor: whole.factor, dimension: whole.atom.dimension, exponent: volumeBump }
|
|
83
|
+
}
|
|
84
|
+
const match = /^(.*?)(-?\d+)?$/.exec(text)
|
|
85
|
+
/* v8 ignore next 3 -- the regex cannot fail on any string */
|
|
86
|
+
if (!match) {
|
|
87
|
+
return undefined
|
|
88
|
+
}
|
|
89
|
+
const [, atomText = '', exponentText] = match
|
|
90
|
+
const exponent = exponentText === undefined ? 1 : Number.parseInt(exponentText, 10)
|
|
91
|
+
if (Math.abs(exponent) > MAX_EXPONENT_MAGNITUDE) {
|
|
92
|
+
return undefined
|
|
93
|
+
}
|
|
94
|
+
const resolved = resolveAtom(atomText)
|
|
95
|
+
if (!resolved) {
|
|
96
|
+
return undefined
|
|
97
|
+
}
|
|
98
|
+
const volumeBump = VOLUME_ATOMS.has(resolved.atomKey) ? 3 : 1
|
|
99
|
+
const factor = powDecimal(resolved.factor, exponent)
|
|
100
|
+
return { factor, dimension: resolved.atom.dimension, exponent: exponent * volumeBump }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function resolveAtom(text: string): { atom: UnitAtom; atomKey: string; factor: Decimal } | undefined {
|
|
104
|
+
const direct = ATOMS[text]
|
|
105
|
+
if (direct) {
|
|
106
|
+
return { atom: direct, atomKey: text, factor: Decimal.fromString(direct.factor) as Decimal }
|
|
107
|
+
}
|
|
108
|
+
for (const [prefix, prefixFactor] of Object.entries(PREFIXES)) {
|
|
109
|
+
if (text.startsWith(prefix)) {
|
|
110
|
+
const rest = text.slice(prefix.length)
|
|
111
|
+
const atom = ATOMS[rest]
|
|
112
|
+
if (atom?.prefixable) {
|
|
113
|
+
const factor = (Decimal.fromString(atom.factor) as Decimal).multiply(
|
|
114
|
+
Decimal.fromString(prefixFactor) as Decimal
|
|
115
|
+
)
|
|
116
|
+
return { atom, atomKey: rest, factor }
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return undefined
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function powDecimal(base: Decimal, exponent: number): Decimal {
|
|
124
|
+
if (exponent === 0) {
|
|
125
|
+
return new Decimal(1n, 0)
|
|
126
|
+
}
|
|
127
|
+
let result = new Decimal(1n, 0)
|
|
128
|
+
for (let i = 0; i < Math.abs(exponent); i++) {
|
|
129
|
+
result = result.multiply(base)
|
|
130
|
+
}
|
|
131
|
+
if (exponent < 0) {
|
|
132
|
+
// Factors are exact positive decimals, so the inverse always exists.
|
|
133
|
+
return new Decimal(1n, 0).divide(result) as Decimal
|
|
134
|
+
}
|
|
135
|
+
return result
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Reduce a UCUM expression (`mg`, `m2`, `g/m`, `kg.m/s2`) to base dimensions.
|
|
140
|
+
* Undefined for units outside the table — they stay opaque.
|
|
141
|
+
*/
|
|
142
|
+
export function canonicalizeUnit(unit: string): CanonicalUnit | undefined {
|
|
143
|
+
const [numerator, ...denominators] = unit.split('/')
|
|
144
|
+
let factor = new Decimal(1n, 0)
|
|
145
|
+
const dimensions: Record<string, number> = {}
|
|
146
|
+
const apply = (componentText: string, sign: 1 | -1): boolean => {
|
|
147
|
+
const component = parseComponent(componentText)
|
|
148
|
+
if (!component) {
|
|
149
|
+
return false
|
|
150
|
+
}
|
|
151
|
+
factor = sign === 1 ? factor.multiply(component.factor) : (factor.divide(component.factor) as Decimal)
|
|
152
|
+
if (component.dimension !== '1') {
|
|
153
|
+
const next = (dimensions[component.dimension] ?? 0) + sign * component.exponent
|
|
154
|
+
if (next === 0) {
|
|
155
|
+
delete dimensions[component.dimension]
|
|
156
|
+
} else {
|
|
157
|
+
dimensions[component.dimension] = next
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return true
|
|
161
|
+
}
|
|
162
|
+
for (const part of (numerator ?? '').split('.')) {
|
|
163
|
+
if (!apply(part, 1)) {
|
|
164
|
+
return undefined
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
for (const denominator of denominators) {
|
|
168
|
+
for (const part of denominator.split('.')) {
|
|
169
|
+
if (!apply(part, -1)) {
|
|
170
|
+
return undefined
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return { factor, dimensions }
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function sameDimensions(a: CanonicalUnit, b: CanonicalUnit): boolean {
|
|
178
|
+
const keysA = Object.keys(a.dimensions)
|
|
179
|
+
const keysB = Object.keys(b.dimensions)
|
|
180
|
+
return keysA.length === keysB.length && keysA.every(key => a.dimensions[key] === b.dimensions[key])
|
|
181
|
+
}
|