@haste-health/fhir-pointer 0.10.1
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/README.md +0 -0
- package/lib/conversions.d.ts +4 -0
- package/lib/conversions.js +82 -0
- package/lib/conversions.js.map +1 -0
- package/lib/index.d.ts +21 -0
- package/lib/index.js +75 -0
- package/lib/index.js.map +1 -0
- package/lib/types.d.ts +11 -0
- package/lib/types.js +2 -0
- package/lib/types.js.map +1 -0
- package/lib/utilities.d.ts +2 -0
- package/lib/utilities.js +8 -0
- package/lib/utilities.js.map +1 -0
- package/package.json +41 -0
- package/src/conversions.test.ts +32 -0
- package/src/conversions.ts +137 -0
- package/src/index.test.ts +130 -0
- package/src/index.ts +143 -0
- package/src/types.ts +21 -0
- package/src/utilities.ts +8 -0
package/README.md
ADDED
|
File without changes
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { AllDataTypes, Data, FHIR_VERSION } from "@haste-health/fhir-types/versions";
|
|
2
|
+
import { Loc, Parent } from "./types.js";
|
|
3
|
+
export declare function toJSONPointer<T, R, P extends Parent<T>>(loc: Loc<T, R, P>): string;
|
|
4
|
+
export declare function toFHIRPath<T extends Data<FHIR_VERSION, AllDataTypes>, R, P extends Parent<T>>(loc: Loc<T, R, P>): string;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { getMeta, getStartingMeta, resolveMeta, } from "@haste-health/meta-value/meta";
|
|
2
|
+
import { OperationError, outcomeFatal } from "@haste-health/operation-outcomes";
|
|
3
|
+
import { pathMeta } from "./index.js";
|
|
4
|
+
import { unescapeField } from "./utilities.js";
|
|
5
|
+
export function toJSONPointer(loc) {
|
|
6
|
+
const indexOfLastSlash = loc.indexOf("/");
|
|
7
|
+
if (indexOfLastSlash === -1)
|
|
8
|
+
return "";
|
|
9
|
+
return loc.substring(indexOfLastSlash, loc.length);
|
|
10
|
+
}
|
|
11
|
+
function resolveFieldToTypeChoiceName(version, meta, field) {
|
|
12
|
+
const keysToCheck = Object.keys(meta.properties ?? {}).filter((key) => field.startsWith(key));
|
|
13
|
+
for (const key of keysToCheck) {
|
|
14
|
+
const information = getMeta(version, meta, key);
|
|
15
|
+
if (!information)
|
|
16
|
+
throw new Error("Invalid meta information");
|
|
17
|
+
if (information._type_ === "typechoice") {
|
|
18
|
+
const v = Object.keys(information.fieldsToType).find((f) => f === field);
|
|
19
|
+
// Return if typechoice includes field name.
|
|
20
|
+
if (v !== undefined)
|
|
21
|
+
return key;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function toFHIRPath(loc) {
|
|
26
|
+
const indexOfLastSlash = loc.indexOf("/");
|
|
27
|
+
const { version, type } = pathMeta(loc);
|
|
28
|
+
let meta = getStartingMeta(version, type);
|
|
29
|
+
if (meta?._type_ === "fp-primitive")
|
|
30
|
+
throw new Error("Invalid meta information");
|
|
31
|
+
if (indexOfLastSlash === -1)
|
|
32
|
+
return "$this";
|
|
33
|
+
const pieces = loc.substring(indexOfLastSlash + 1).split("/");
|
|
34
|
+
let fp = "$this";
|
|
35
|
+
for (const piece of pieces) {
|
|
36
|
+
const unescapedField = unescapeField(piece);
|
|
37
|
+
const parsedNumber = parseInt(unescapedField);
|
|
38
|
+
let field = unescapedField;
|
|
39
|
+
if (isNaN(parsedNumber)) {
|
|
40
|
+
if (meta?.cardinality !== "single") {
|
|
41
|
+
throw new OperationError(outcomeFatal("invalid", "Cannot convert path to FHIRPath", [
|
|
42
|
+
toJSONPointer(loc),
|
|
43
|
+
]));
|
|
44
|
+
}
|
|
45
|
+
// Resolve typechoice to fp field name.
|
|
46
|
+
// IE valueCoding if typechoice would be just value.
|
|
47
|
+
if (!meta.properties?.[unescapedField]) {
|
|
48
|
+
const typeChoiceField = resolveFieldToTypeChoiceName(version, meta, unescapedField);
|
|
49
|
+
if (!typeChoiceField) {
|
|
50
|
+
throw new OperationError(outcomeFatal("invalid", "Cannot convert path to FHIRPath", [
|
|
51
|
+
toJSONPointer(loc),
|
|
52
|
+
]));
|
|
53
|
+
}
|
|
54
|
+
field = typeChoiceField;
|
|
55
|
+
}
|
|
56
|
+
const unresolvedMeta = getMeta(version, meta, field);
|
|
57
|
+
if (!unresolvedMeta)
|
|
58
|
+
throw new OperationError(outcomeFatal("exception", `Invalid meta information for field ${field}`));
|
|
59
|
+
const nextMeta = resolveMeta(version, unresolvedMeta,
|
|
60
|
+
// Hack to inject the typechoice field in.
|
|
61
|
+
{ [unescapedField]: {} }, field);
|
|
62
|
+
if (!nextMeta) {
|
|
63
|
+
throw new OperationError(outcomeFatal("invalid", "Cannot convert path to FHIRPath", [
|
|
64
|
+
toJSONPointer(loc),
|
|
65
|
+
]));
|
|
66
|
+
}
|
|
67
|
+
meta = nextMeta.meta;
|
|
68
|
+
fp += `.${field}`;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
if (meta?.cardinality !== "array") {
|
|
72
|
+
throw new OperationError(outcomeFatal("invalid", `Cannot convert path '${loc}' to FHIRPath`, [
|
|
73
|
+
toJSONPointer(loc),
|
|
74
|
+
]));
|
|
75
|
+
}
|
|
76
|
+
fp += `[${parsedNumber}]`;
|
|
77
|
+
meta = { ...meta, cardinality: "single" };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return fp;
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=conversions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversions.js","sourceRoot":"","sources":["../src/conversions.ts"],"names":[],"mappings":"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"}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { id } from "@haste-health/fhir-types/r4/types";
|
|
2
|
+
import { AllDataTypes, Data, DataType, FHIR_VERSION } from "@haste-health/fhir-types/versions";
|
|
3
|
+
import { Loc, NullGuard, Parent } from "./types.js";
|
|
4
|
+
export type { Loc, NullGuard } from "./types.js";
|
|
5
|
+
export * from "./conversions.js";
|
|
6
|
+
export declare function descend<T, R, P extends Parent<T>, Field extends keyof NonNullable<R>>(loc: Loc<T, R, P>, field: Field): Loc<T, NullGuard<R, Field>, typeof loc>;
|
|
7
|
+
type ReturnType<L> = L extends Loc<infer B, infer T, infer P> ? T : any;
|
|
8
|
+
export declare function ascend<T, R, P extends Parent<T>>(loc: Loc<T, R, P>): {
|
|
9
|
+
parent: NonNullable<P>;
|
|
10
|
+
field: NonNullable<keyof ReturnType<P>>;
|
|
11
|
+
} | undefined;
|
|
12
|
+
export declare function pathMeta<Version extends FHIR_VERSION, T extends Data<Version, AllDataTypes>, R, P extends Parent<T>>(loc: Loc<T, R, P>): {
|
|
13
|
+
version: FHIR_VERSION;
|
|
14
|
+
type: DataType<Version>;
|
|
15
|
+
id: id;
|
|
16
|
+
};
|
|
17
|
+
export declare function get<T, R, P extends Parent<T>>(loc: Loc<T, R, P>, v: T): R;
|
|
18
|
+
export declare function fields<T extends object, R, P extends Parent<T>>(loc: Loc<T, R, P>): (string | number | symbol)[];
|
|
19
|
+
export declare function root<Version extends FHIR_VERSION, T extends Data<Version, AllDataTypes>, R, P extends Parent<T>>(loc: Loc<T, R, P>): Loc<T, T>;
|
|
20
|
+
export declare function pointer<Version extends FHIR_VERSION, T extends DataType<Version>>(fhir_version: FHIR_VERSION, resourceType: T, resourceId: id): Loc<Data<Version, T>, Data<Version, T>>;
|
|
21
|
+
export declare function typedPointer<V, T>(): Loc<V, T, Parent<V>>;
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import jsonpointer from "jsonpointer";
|
|
3
|
+
import { R4, } from "@haste-health/fhir-types/versions";
|
|
4
|
+
import { toJSONPointer } from "./conversions.js";
|
|
5
|
+
import { escapeField, unescapeField } from "./utilities.js";
|
|
6
|
+
export * from "./conversions.js";
|
|
7
|
+
/*
|
|
8
|
+
** Descend Loc pointer with field.
|
|
9
|
+
*/
|
|
10
|
+
export function descend(loc, field) {
|
|
11
|
+
return `${loc}/${escapeField(String(field))}`;
|
|
12
|
+
}
|
|
13
|
+
/*
|
|
14
|
+
** Ascend Loc pointer to parent.
|
|
15
|
+
*/
|
|
16
|
+
export function ascend(loc) {
|
|
17
|
+
// At root so return undefined.
|
|
18
|
+
const lastIndexSlash = loc.lastIndexOf("/");
|
|
19
|
+
if (lastIndexSlash === -1)
|
|
20
|
+
return undefined;
|
|
21
|
+
const field = unescapeField(loc.substring(lastIndexSlash + 1));
|
|
22
|
+
return {
|
|
23
|
+
parent: loc.slice(0, lastIndexSlash),
|
|
24
|
+
field: Number.isNaN(parseInt(field))
|
|
25
|
+
? field
|
|
26
|
+
: parseInt(field),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export function pathMeta(loc) {
|
|
30
|
+
const indexOfLastSlash = loc.indexOf("/");
|
|
31
|
+
const [fhirVersion, type, id] = loc
|
|
32
|
+
.substring(0, indexOfLastSlash === -1 ? loc.length : indexOfLastSlash)
|
|
33
|
+
.split("|");
|
|
34
|
+
return {
|
|
35
|
+
version: fhirVersion,
|
|
36
|
+
type: type,
|
|
37
|
+
id: id,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export function get(loc, v) {
|
|
41
|
+
const pointer = toJSONPointer(loc);
|
|
42
|
+
if (typeof v !== "object" || v === null) {
|
|
43
|
+
if (pointer === "") {
|
|
44
|
+
return v;
|
|
45
|
+
}
|
|
46
|
+
throw new Error(`Invalid pointer for primitive value ${typeof v}`);
|
|
47
|
+
}
|
|
48
|
+
return jsonpointer.get(v, pointer);
|
|
49
|
+
}
|
|
50
|
+
export function fields(loc) {
|
|
51
|
+
let asc = ascend(loc);
|
|
52
|
+
const fields = [];
|
|
53
|
+
while (asc) {
|
|
54
|
+
fields.unshift(asc.field);
|
|
55
|
+
asc = ascend(asc.parent);
|
|
56
|
+
}
|
|
57
|
+
return fields;
|
|
58
|
+
}
|
|
59
|
+
export function root(loc) {
|
|
60
|
+
const { version, type, id } = pathMeta(loc);
|
|
61
|
+
return pointer(version, type, id);
|
|
62
|
+
}
|
|
63
|
+
function metaString(fhirVersion, type, id) {
|
|
64
|
+
return `${fhirVersion}|${type}|${id}`;
|
|
65
|
+
}
|
|
66
|
+
/*
|
|
67
|
+
** Creates a Loc pointer for a resource.
|
|
68
|
+
*/
|
|
69
|
+
export function pointer(fhir_version, resourceType, resourceId) {
|
|
70
|
+
return `${metaString(fhir_version, resourceType, resourceId)}`;
|
|
71
|
+
}
|
|
72
|
+
export function typedPointer() {
|
|
73
|
+
return metaString(R4, "Unknown", "unknown");
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/lib/types.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
declare const __parent: unique symbol;
|
|
2
|
+
declare const __root: unique symbol;
|
|
3
|
+
type _Loc<B, T> = {
|
|
4
|
+
[__root]: (root: B) => T;
|
|
5
|
+
};
|
|
6
|
+
export type Parent<B> = Loc<B, unknown, any> | null;
|
|
7
|
+
export type Loc<B, T, P extends Loc<B, unknown, Parent<B>> | null = null> = string & _Loc<B, T> & {
|
|
8
|
+
[__parent]: P;
|
|
9
|
+
};
|
|
10
|
+
export type NullGuard<V, Field extends keyof NonNullable<V>> = V extends NonNullable<V> ? NonNullable<V>[Field] : NonNullable<V>[Field] | undefined;
|
|
11
|
+
export {};
|
package/lib/types.js
ADDED
package/lib/types.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/lib/utilities.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// See [https://datatracker.ietf.org/doc/html/rfc6901#section-3] for reference.
|
|
2
|
+
export function escapeField(field) {
|
|
3
|
+
return field.replace("~", "~0").replace("/", "~1");
|
|
4
|
+
}
|
|
5
|
+
export function unescapeField(field) {
|
|
6
|
+
return field.replace("~1", "/").replace("~0", "~");
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=utilities.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utilities.js","sourceRoot":"","sources":["../src/utilities.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,OAAO,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;AACrD,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AACrD,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@haste-health/fhir-pointer",
|
|
3
|
+
"version": "0.10.1",
|
|
4
|
+
"homepage": "https://haste.health",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/HasteHealth/HasteHealth.git"
|
|
8
|
+
},
|
|
9
|
+
"description": "FHIR Pointer allows creation and manipulation of pointers to fhir-types.",
|
|
10
|
+
"type": "module",
|
|
11
|
+
"main": "./lib/index.js",
|
|
12
|
+
"types": "./lib/index.d.ts",
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"test": "pnpm node --experimental-vm-modules $(pnpm bin jest)",
|
|
16
|
+
"publish": "pnpm build && pnpm npm publish --access public --tolerate-republish"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [],
|
|
19
|
+
"author": "HasteHealth",
|
|
20
|
+
"license": "ISC",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": "./lib/index.js"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@haste-health/fhir-types": "workspace:^",
|
|
26
|
+
"@jest/globals": "^29.7.0",
|
|
27
|
+
"jest": "^29.7.0",
|
|
28
|
+
"ts-jest": "^29.3.2",
|
|
29
|
+
"typescript": "5.9.2"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"readme.md",
|
|
33
|
+
"lib/**",
|
|
34
|
+
"src/**"
|
|
35
|
+
],
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@haste-health/meta-value": "workspace:^",
|
|
38
|
+
"@haste-health/operation-outcomes": "workspace:^",
|
|
39
|
+
"jsonpointer": "^5.0.1"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { expect, test } from "@jest/globals";
|
|
2
|
+
|
|
3
|
+
import { id } from "@haste-health/fhir-types/lib/generated/r4/types";
|
|
4
|
+
import { R4 } from "@haste-health/fhir-types/versions";
|
|
5
|
+
|
|
6
|
+
import { Loc, descend, pointer, toFHIRPath } from "./index.js";
|
|
7
|
+
|
|
8
|
+
test("Test fhirpath conversion", async () => {
|
|
9
|
+
expect(
|
|
10
|
+
toFHIRPath(
|
|
11
|
+
descend(
|
|
12
|
+
descend(
|
|
13
|
+
descend(
|
|
14
|
+
pointer(R4, "Observation", "test" as id),
|
|
15
|
+
"valueCodeableConcept"
|
|
16
|
+
),
|
|
17
|
+
"coding"
|
|
18
|
+
),
|
|
19
|
+
0
|
|
20
|
+
)
|
|
21
|
+
)
|
|
22
|
+
).toEqual("$this.value.coding[0]");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("4.0|Patient|invalid-us-core-patient/extension/0/extension/0/valueCoding", async () => {
|
|
26
|
+
const fields = ["extension", 0, "extension", 0, "valueCoding"];
|
|
27
|
+
const pt = fields.reduce((pt: Loc<any, any, any>, field: string | number) => {
|
|
28
|
+
return descend(pt, field);
|
|
29
|
+
}, pointer(R4, "Patient", "invalid-us-core-patient" as id));
|
|
30
|
+
|
|
31
|
+
expect(toFHIRPath(pt)).toEqual("$this.extension[0].extension[0].value");
|
|
32
|
+
});
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { uri } from "@haste-health/fhir-types/lib/generated/r4/types";
|
|
2
|
+
import {
|
|
3
|
+
AllDataTypes,
|
|
4
|
+
Data,
|
|
5
|
+
FHIR_VERSION,
|
|
6
|
+
} from "@haste-health/fhir-types/versions";
|
|
7
|
+
import {
|
|
8
|
+
ElementNode,
|
|
9
|
+
getMeta,
|
|
10
|
+
getStartingMeta,
|
|
11
|
+
resolveMeta,
|
|
12
|
+
} from "@haste-health/meta-value/meta";
|
|
13
|
+
import { OperationError, outcomeFatal } from "@haste-health/operation-outcomes";
|
|
14
|
+
|
|
15
|
+
import { pathMeta } from "./index.js";
|
|
16
|
+
import { Loc, Parent } from "./types.js";
|
|
17
|
+
import { unescapeField } from "./utilities.js";
|
|
18
|
+
|
|
19
|
+
export function toJSONPointer<T, R, P extends Parent<T>>(loc: Loc<T, R, P>) {
|
|
20
|
+
const indexOfLastSlash = loc.indexOf("/");
|
|
21
|
+
if (indexOfLastSlash === -1) return "";
|
|
22
|
+
return loc.substring(indexOfLastSlash, loc.length);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function resolveFieldToTypeChoiceName(
|
|
26
|
+
version: FHIR_VERSION,
|
|
27
|
+
meta: ElementNode,
|
|
28
|
+
field: string
|
|
29
|
+
): string | undefined {
|
|
30
|
+
const keysToCheck = Object.keys(meta.properties ?? {}).filter((key) =>
|
|
31
|
+
field.startsWith(key)
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
for (const key of keysToCheck) {
|
|
35
|
+
const information = getMeta(version, meta, key);
|
|
36
|
+
if (!information) throw new Error("Invalid meta information");
|
|
37
|
+
if (information._type_ === "typechoice") {
|
|
38
|
+
const v = Object.keys(information.fieldsToType).find((f) => f === field);
|
|
39
|
+
// Return if typechoice includes field name.
|
|
40
|
+
if (v !== undefined) return key;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function toFHIRPath<
|
|
46
|
+
T extends Data<FHIR_VERSION, AllDataTypes>,
|
|
47
|
+
R,
|
|
48
|
+
P extends Parent<T>
|
|
49
|
+
>(loc: Loc<T, R, P>) {
|
|
50
|
+
const indexOfLastSlash = loc.indexOf("/");
|
|
51
|
+
|
|
52
|
+
const { version, type } = pathMeta(loc);
|
|
53
|
+
|
|
54
|
+
let meta = getStartingMeta(version, type as uri);
|
|
55
|
+
if (meta?._type_ === "fp-primitive")
|
|
56
|
+
throw new Error("Invalid meta information");
|
|
57
|
+
|
|
58
|
+
if (indexOfLastSlash === -1) return "$this";
|
|
59
|
+
const pieces = loc.substring(indexOfLastSlash + 1).split("/");
|
|
60
|
+
let fp = "$this";
|
|
61
|
+
|
|
62
|
+
for (const piece of pieces) {
|
|
63
|
+
const unescapedField = unescapeField(piece);
|
|
64
|
+
const parsedNumber = parseInt(unescapedField);
|
|
65
|
+
let field = unescapedField;
|
|
66
|
+
|
|
67
|
+
if (isNaN(parsedNumber)) {
|
|
68
|
+
if (meta?.cardinality !== "single") {
|
|
69
|
+
throw new OperationError(
|
|
70
|
+
outcomeFatal("invalid", "Cannot convert path to FHIRPath", [
|
|
71
|
+
toJSONPointer(loc),
|
|
72
|
+
])
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Resolve typechoice to fp field name.
|
|
77
|
+
// IE valueCoding if typechoice would be just value.
|
|
78
|
+
if (!meta.properties?.[unescapedField]) {
|
|
79
|
+
const typeChoiceField = resolveFieldToTypeChoiceName(
|
|
80
|
+
version,
|
|
81
|
+
meta,
|
|
82
|
+
unescapedField
|
|
83
|
+
);
|
|
84
|
+
if (!typeChoiceField) {
|
|
85
|
+
throw new OperationError(
|
|
86
|
+
outcomeFatal("invalid", "Cannot convert path to FHIRPath", [
|
|
87
|
+
toJSONPointer(loc),
|
|
88
|
+
])
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
field = typeChoiceField;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const unresolvedMeta = getMeta(version, meta, field);
|
|
95
|
+
if (!unresolvedMeta)
|
|
96
|
+
throw new OperationError(
|
|
97
|
+
outcomeFatal(
|
|
98
|
+
"exception",
|
|
99
|
+
`Invalid meta information for field ${field}`
|
|
100
|
+
)
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const nextMeta = resolveMeta(
|
|
104
|
+
version,
|
|
105
|
+
unresolvedMeta,
|
|
106
|
+
// Hack to inject the typechoice field in.
|
|
107
|
+
{ [unescapedField]: {} },
|
|
108
|
+
field
|
|
109
|
+
);
|
|
110
|
+
if (!nextMeta) {
|
|
111
|
+
throw new OperationError(
|
|
112
|
+
outcomeFatal("invalid", "Cannot convert path to FHIRPath", [
|
|
113
|
+
toJSONPointer(loc),
|
|
114
|
+
])
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
meta = nextMeta.meta;
|
|
119
|
+
|
|
120
|
+
fp += `.${field}`;
|
|
121
|
+
} else {
|
|
122
|
+
if (meta?.cardinality !== "array") {
|
|
123
|
+
throw new OperationError(
|
|
124
|
+
outcomeFatal("invalid", `Cannot convert path '${loc}' to FHIRPath`, [
|
|
125
|
+
toJSONPointer(loc),
|
|
126
|
+
])
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
fp += `[${parsedNumber}]`;
|
|
131
|
+
|
|
132
|
+
meta = { ...meta, cardinality: "single" };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return fp;
|
|
137
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { expect, test } from "@jest/globals";
|
|
2
|
+
|
|
3
|
+
import { Patient, id } from "@haste-health/fhir-types/r4/types";
|
|
4
|
+
import { R4 } from "@haste-health/fhir-types/versions";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
Loc,
|
|
8
|
+
ascend,
|
|
9
|
+
descend,
|
|
10
|
+
fields,
|
|
11
|
+
get,
|
|
12
|
+
pathMeta,
|
|
13
|
+
pointer,
|
|
14
|
+
root,
|
|
15
|
+
toJSONPointer,
|
|
16
|
+
typedPointer,
|
|
17
|
+
} from "./index";
|
|
18
|
+
|
|
19
|
+
test("pointer", () => {
|
|
20
|
+
const loc = pointer(R4, "Patient", "123" as id);
|
|
21
|
+
expect(loc).toBe("4.0|Patient|123");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("ascend pointer", () => {
|
|
25
|
+
const loc = descend(pointer(R4, "Patient", "123" as id), "name");
|
|
26
|
+
expect(ascend(loc)).toEqual({ parent: "4.0|Patient|123", field: "name" });
|
|
27
|
+
// @ts-ignore
|
|
28
|
+
expect(ascend(ascend(loc)?.parent)).toEqual(undefined);
|
|
29
|
+
|
|
30
|
+
const nestedLoc = descend(
|
|
31
|
+
descend(descend(pointer(R4, "Patient", "123" as id), "name"), 0),
|
|
32
|
+
"given"
|
|
33
|
+
);
|
|
34
|
+
expect(nestedLoc).toEqual("4.0|Patient|123/name/0/given");
|
|
35
|
+
expect(ascend(nestedLoc)).toEqual({
|
|
36
|
+
parent: "4.0|Patient|123/name/0",
|
|
37
|
+
field: "given",
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// @ts-ignore
|
|
41
|
+
expect(ascend(ascend(nestedLoc)?.parent)).toEqual({
|
|
42
|
+
parent: "4.0|Patient|123/name",
|
|
43
|
+
field: 0,
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("get function", () => {
|
|
48
|
+
const nestedLoc = descend(
|
|
49
|
+
descend(descend(pointer(R4, "Patient", "123" as id), "name"), 0),
|
|
50
|
+
"given"
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const patient: Patient = {
|
|
54
|
+
resourceType: "Patient",
|
|
55
|
+
id: "123",
|
|
56
|
+
name: [{ given: ["John"] }],
|
|
57
|
+
} as Patient;
|
|
58
|
+
|
|
59
|
+
expect(get(nestedLoc, patient)).toEqual(["John"]);
|
|
60
|
+
expect(
|
|
61
|
+
get(
|
|
62
|
+
descend(descend(pointer(R4, "Patient", "123" as id), "name"), 1),
|
|
63
|
+
patient
|
|
64
|
+
)
|
|
65
|
+
).toEqual(undefined);
|
|
66
|
+
|
|
67
|
+
expect(
|
|
68
|
+
get(
|
|
69
|
+
descend(
|
|
70
|
+
descend(descend(pointer(R4, "Patient", "123" as id), "name"), 1),
|
|
71
|
+
"given"
|
|
72
|
+
),
|
|
73
|
+
patient
|
|
74
|
+
)
|
|
75
|
+
).toEqual(undefined);
|
|
76
|
+
|
|
77
|
+
expect(
|
|
78
|
+
get(
|
|
79
|
+
descend(
|
|
80
|
+
descend(descend(pointer(R4, "Patient", "123" as id), "identifier"), 1),
|
|
81
|
+
"system"
|
|
82
|
+
),
|
|
83
|
+
patient
|
|
84
|
+
)
|
|
85
|
+
).toEqual(undefined);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("path meta", () => {
|
|
89
|
+
const nestedLoc = descend(pointer(R4, "Patient", "123" as id), "name");
|
|
90
|
+
expect(pathMeta(nestedLoc)).toEqual({
|
|
91
|
+
version: "4.0",
|
|
92
|
+
type: "Patient",
|
|
93
|
+
id: "123",
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("fields", () => {
|
|
98
|
+
const nestedLoc = descend(
|
|
99
|
+
descend(descend(pointer(R4, "Patient", "123" as id), "name"), 0),
|
|
100
|
+
"given"
|
|
101
|
+
);
|
|
102
|
+
expect(fields(nestedLoc)).toEqual(["name", 0, "given"]);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("root", () => {
|
|
106
|
+
const nestedLoc = descend(
|
|
107
|
+
descend(descend(pointer(R4, "Patient", "123" as id), "name"), 0),
|
|
108
|
+
"given"
|
|
109
|
+
);
|
|
110
|
+
expect(root(nestedLoc)).toEqual("4.0|Patient|123");
|
|
111
|
+
expect(toJSONPointer(root(nestedLoc))).toEqual("");
|
|
112
|
+
expect(root(pointer(R4, "Patient", "123" as id))).toEqual("4.0|Patient|123");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("escaping strings.", () => {
|
|
116
|
+
const loc = pointer(R4, "Patient", "asdf" as id) as Loc<any, any>;
|
|
117
|
+
const escapedLoc = descend(descend(loc, "~name"), "/test");
|
|
118
|
+
expect(escapedLoc).toEqual("4.0|Patient|asdf/~0name/~1test");
|
|
119
|
+
expect(toJSONPointer(escapedLoc)).toEqual("/~0name/~1test");
|
|
120
|
+
expect(get(escapedLoc, { "~name": { "/test": "test" } })).toEqual("test");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("typedpointer", () => {
|
|
124
|
+
const loc = typedPointer() as Loc<any, any>;
|
|
125
|
+
expect(toJSONPointer(loc)).toEqual("");
|
|
126
|
+
const escapedLoc = descend(descend(loc, "~name"), "/test");
|
|
127
|
+
expect(escapedLoc).toEqual("4.0|Unknown|unknown/~0name/~1test");
|
|
128
|
+
expect(toJSONPointer(escapedLoc)).toEqual("/~0name/~1test");
|
|
129
|
+
expect(get(escapedLoc, { "~name": { "/test": "test" } })).toEqual("test");
|
|
130
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import jsonpointer from "jsonpointer";
|
|
3
|
+
|
|
4
|
+
import { id } from "@haste-health/fhir-types/r4/types";
|
|
5
|
+
import {
|
|
6
|
+
AllDataTypes,
|
|
7
|
+
Data,
|
|
8
|
+
DataType,
|
|
9
|
+
FHIR_VERSION,
|
|
10
|
+
R4,
|
|
11
|
+
} from "@haste-health/fhir-types/versions";
|
|
12
|
+
|
|
13
|
+
import { toJSONPointer } from "./conversions.js";
|
|
14
|
+
import { Loc, NullGuard, Parent } from "./types.js";
|
|
15
|
+
import { escapeField, unescapeField } from "./utilities.js";
|
|
16
|
+
|
|
17
|
+
export type { Loc, NullGuard } from "./types.js";
|
|
18
|
+
export * from "./conversions.js";
|
|
19
|
+
|
|
20
|
+
/*
|
|
21
|
+
** Descend Loc pointer with field.
|
|
22
|
+
*/
|
|
23
|
+
export function descend<
|
|
24
|
+
T,
|
|
25
|
+
R,
|
|
26
|
+
P extends Parent<T>,
|
|
27
|
+
Field extends keyof NonNullable<R>
|
|
28
|
+
>(loc: Loc<T, R, P>, field: Field): Loc<T, NullGuard<R, Field>, typeof loc> {
|
|
29
|
+
return `${loc}/${escapeField(String(field))}` as Loc<
|
|
30
|
+
T,
|
|
31
|
+
NullGuard<R, Field>,
|
|
32
|
+
typeof loc
|
|
33
|
+
>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type ReturnType<L> = L extends Loc<infer B, infer T, infer P> ? T : any;
|
|
37
|
+
|
|
38
|
+
/*
|
|
39
|
+
** Ascend Loc pointer to parent.
|
|
40
|
+
*/
|
|
41
|
+
export function ascend<T, R, P extends Parent<T>>(
|
|
42
|
+
loc: Loc<T, R, P>
|
|
43
|
+
):
|
|
44
|
+
| { parent: NonNullable<P>; field: NonNullable<keyof ReturnType<P>> }
|
|
45
|
+
| undefined {
|
|
46
|
+
// At root so return undefined.
|
|
47
|
+
const lastIndexSlash = loc.lastIndexOf("/");
|
|
48
|
+
if (lastIndexSlash === -1) return undefined;
|
|
49
|
+
const field = unescapeField(loc.substring(lastIndexSlash + 1));
|
|
50
|
+
return {
|
|
51
|
+
parent: loc.slice(0, lastIndexSlash) as NonNullable<P>,
|
|
52
|
+
field: Number.isNaN(parseInt(field))
|
|
53
|
+
? field
|
|
54
|
+
: (parseInt(field) as keyof NonNullable<ReturnType<P>>),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function pathMeta<
|
|
59
|
+
Version extends FHIR_VERSION,
|
|
60
|
+
T extends Data<Version, AllDataTypes>,
|
|
61
|
+
R,
|
|
62
|
+
P extends Parent<T>
|
|
63
|
+
>(
|
|
64
|
+
loc: Loc<T, R, P>
|
|
65
|
+
): { version: FHIR_VERSION; type: DataType<Version>; id: id } {
|
|
66
|
+
const indexOfLastSlash = loc.indexOf("/");
|
|
67
|
+
const [fhirVersion, type, id] = loc
|
|
68
|
+
.substring(0, indexOfLastSlash === -1 ? loc.length : indexOfLastSlash)
|
|
69
|
+
.split("|");
|
|
70
|
+
return {
|
|
71
|
+
version: fhirVersion as FHIR_VERSION,
|
|
72
|
+
type: type as DataType<Version>,
|
|
73
|
+
id: id as id,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function get<T, R, P extends Parent<T>>(loc: Loc<T, R, P>, v: T): R {
|
|
78
|
+
const pointer = toJSONPointer(loc);
|
|
79
|
+
|
|
80
|
+
if (typeof v !== "object" || v === null) {
|
|
81
|
+
if (pointer === "") {
|
|
82
|
+
return v as unknown as R;
|
|
83
|
+
}
|
|
84
|
+
throw new Error(`Invalid pointer for primitive value ${typeof v}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return jsonpointer.get(v, pointer) as R;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function fields<T extends object, R, P extends Parent<T>>(
|
|
91
|
+
loc: Loc<T, R, P>
|
|
92
|
+
) {
|
|
93
|
+
let asc = ascend(loc);
|
|
94
|
+
const fields = [];
|
|
95
|
+
while (asc) {
|
|
96
|
+
fields.unshift(asc.field);
|
|
97
|
+
asc = ascend(asc.parent);
|
|
98
|
+
}
|
|
99
|
+
return fields;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function root<
|
|
103
|
+
Version extends FHIR_VERSION,
|
|
104
|
+
T extends Data<Version, AllDataTypes>,
|
|
105
|
+
R,
|
|
106
|
+
P extends Parent<T>
|
|
107
|
+
>(loc: Loc<T, R, P>): Loc<T, T> {
|
|
108
|
+
const { version, type, id } = pathMeta(loc);
|
|
109
|
+
return pointer(version, type, id) as any as Loc<T, T>;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function metaString<Version extends FHIR_VERSION>(
|
|
113
|
+
fhirVersion: FHIR_VERSION,
|
|
114
|
+
type: DataType<Version>,
|
|
115
|
+
id: id
|
|
116
|
+
) {
|
|
117
|
+
return `${fhirVersion}|${type}|${id}`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/*
|
|
121
|
+
** Creates a Loc pointer for a resource.
|
|
122
|
+
*/
|
|
123
|
+
export function pointer<
|
|
124
|
+
Version extends FHIR_VERSION,
|
|
125
|
+
T extends DataType<Version>
|
|
126
|
+
>(
|
|
127
|
+
fhir_version: FHIR_VERSION,
|
|
128
|
+
resourceType: T,
|
|
129
|
+
resourceId: id
|
|
130
|
+
): Loc<Data<Version, T>, Data<Version, T>> {
|
|
131
|
+
return `${metaString(fhir_version, resourceType, resourceId)}` as Loc<
|
|
132
|
+
Data<Version, T>,
|
|
133
|
+
Data<Version, T>
|
|
134
|
+
>;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function typedPointer<V, T>(): Loc<V, T, Parent<V>> {
|
|
138
|
+
return metaString(R4, "Unknown" as DataType<R4>, "unknown" as id) as Loc<
|
|
139
|
+
V,
|
|
140
|
+
T,
|
|
141
|
+
Parent<V>
|
|
142
|
+
>;
|
|
143
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
// Parent Loc
|
|
3
|
+
declare const __parent: unique symbol;
|
|
4
|
+
declare const __root: unique symbol;
|
|
5
|
+
type _Loc<B, T> = { [__root]: (root: B) => T };
|
|
6
|
+
export type Parent<B> = Loc<B, unknown, any> | null;
|
|
7
|
+
|
|
8
|
+
export type Loc<
|
|
9
|
+
B,
|
|
10
|
+
T,
|
|
11
|
+
P extends Loc<B, unknown, Parent<B>> | null = null,
|
|
12
|
+
> = string & _Loc<B, T> & { [__parent]: P };
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
** Access field in potentially Nullable V
|
|
16
|
+
** If V is nullable then set the return type to potentially nullable as well.
|
|
17
|
+
*/
|
|
18
|
+
export type NullGuard<V, Field extends keyof NonNullable<V>> =
|
|
19
|
+
V extends NonNullable<V>
|
|
20
|
+
? NonNullable<V>[Field]
|
|
21
|
+
: NonNullable<V>[Field] | undefined;
|
package/src/utilities.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// See [https://datatracker.ietf.org/doc/html/rfc6901#section-3] for reference.
|
|
2
|
+
export function escapeField(field: string) {
|
|
3
|
+
return field.replace("~", "~0").replace("/", "~1");
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function unescapeField(field: string) {
|
|
7
|
+
return field.replace("~1", "/").replace("~0", "~");
|
|
8
|
+
}
|