@haste-health/fhir-patch-building 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 ADDED
@@ -0,0 +1,3 @@
1
+ # FHIR Patch Building
2
+
3
+ High level API for building JSON Patches with deeply nested FHIR Objects.
package/lib/index.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { Operation } from "fast-json-patch";
2
+ import * as fpt from "@haste-health/fhir-pointer";
3
+ import { Resource } from "@haste-health/fhir-types/r4/types";
4
+ export interface Mutation<T, R> {
5
+ path: fpt.Loc<T, R, any>;
6
+ op: "add" | "remove" | "replace";
7
+ value?: R;
8
+ }
9
+ export default function buildPatches<T extends Resource, R>(value: T, mutation: Mutation<T, R>): Operation[];
10
+ export declare function applyMutation<T extends Resource, R>(value: T, mutation: Mutation<T, R>): T;
11
+ export declare function applyMutationImmutable<T extends Resource, R>(value: T, mutation: Mutation<T, R>): T;
package/lib/index.js ADDED
@@ -0,0 +1,102 @@
1
+ import jsonpatch from "fast-json-patch";
2
+ import { produce } from "immer";
3
+ import * as fpt from "@haste-health/fhir-pointer";
4
+ import { R4 } from "@haste-health/fhir-types/versions";
5
+ function getValue(value, pointer) {
6
+ return fpt.get(pointer, value);
7
+ }
8
+ function valueExists(value, json_pointer) {
9
+ return getValue(value, json_pointer) !== undefined;
10
+ }
11
+ function deriveNextValuePlaceHolder(fields) {
12
+ if (typeof fields[1] === "number") {
13
+ return [];
14
+ }
15
+ return {};
16
+ }
17
+ function createPatchesNonExistantFields(resource, path) {
18
+ const fields = fpt.fields(path);
19
+ let patches = [];
20
+ let curValue = resource;
21
+ let curPointer = fpt.pointer(R4, resource.resourceType, resource.id);
22
+ for (let i = 0; i < fields.length; i++) {
23
+ curPointer = fpt.descend(curPointer, fields[i]);
24
+ curValue = getValue(resource, curPointer);
25
+ if (curValue === undefined) {
26
+ const nextValue = deriveNextValuePlaceHolder(fields.slice(i));
27
+ patches = [
28
+ ...patches,
29
+ { op: "add", path: fpt.toJSONPointer(curPointer), value: nextValue },
30
+ ];
31
+ }
32
+ }
33
+ return patches;
34
+ }
35
+ export default function buildPatches(value, mutation) {
36
+ // Builds patches with a given mutation to include non existant values up to the point in the path
37
+ // where the mutation occurs
38
+ switch (mutation.op) {
39
+ case "remove": {
40
+ if (valueExists(value, mutation.path)) {
41
+ return [
42
+ {
43
+ op: "remove",
44
+ path: fpt.toJSONPointer(mutation.path),
45
+ },
46
+ ];
47
+ }
48
+ return [];
49
+ }
50
+ case "add": {
51
+ const patches = createPatchesNonExistantFields(value, mutation.path);
52
+ //If last is adding value remove here as collection will only add once.
53
+ if (patches[patches.length - 1]?.op === "add" &&
54
+ patches[patches.length - 1]?.path === fpt.toJSONPointer(mutation.path)) {
55
+ return [
56
+ ...patches.slice(0, patches.length - 1),
57
+ {
58
+ op: "add",
59
+ path: fpt.toJSONPointer(mutation.path),
60
+ value: mutation.value,
61
+ },
62
+ ];
63
+ }
64
+ return [
65
+ ...patches,
66
+ {
67
+ op: "add",
68
+ path: fpt.toJSONPointer(mutation.path),
69
+ value: mutation.value,
70
+ },
71
+ ];
72
+ }
73
+ case "replace": {
74
+ if (mutation.value === undefined || mutation.value === null) {
75
+ return buildPatches(value, {
76
+ op: "remove",
77
+ path: mutation.path,
78
+ });
79
+ }
80
+ const patches = createPatchesNonExistantFields(value, mutation.path);
81
+ return [
82
+ ...patches,
83
+ {
84
+ op: "replace",
85
+ path: fpt.toJSONPointer(mutation.path),
86
+ value: mutation.value,
87
+ },
88
+ ];
89
+ }
90
+ default:
91
+ throw new Error(`Invalid operation '${mutation.op}'`);
92
+ }
93
+ }
94
+ export function applyMutation(value, mutation) {
95
+ return jsonpatch.applyPatch(value, buildPatches(value, mutation)).newDocument;
96
+ }
97
+ export function applyMutationImmutable(value, mutation) {
98
+ return produce(value, (v) => {
99
+ applyMutation(v, mutation);
100
+ });
101
+ }
102
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@haste-health/fhir-patch-building",
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": "JSON Patch building with typesafe fhir pointers.",
10
+ "type": "module",
11
+ "main": "./lib/index.js",
12
+ "types": "./lib/index.d.ts",
13
+ "scripts": {
14
+ "build": "pnpm tsc",
15
+ "test": "pnpm node --experimental-vm-modules $(pnpm bin jest)",
16
+ "publish": "pnpm build && pnpm npm publish --access public --tolerate-republish"
17
+ },
18
+ "devDependencies": {
19
+ "@haste-health/fhir-types": "workspace:^",
20
+ "@jest/globals": "^29.7.0",
21
+ "jest": "^29.7.0",
22
+ "ts-jest": "^29.3.2",
23
+ "typescript": "5.9.2"
24
+ },
25
+ "dependencies": {
26
+ "@haste-health/fhir-pointer": "workspace:^",
27
+ "fast-json-patch": "^3.1.1",
28
+ "immer": "^10.1.1"
29
+ },
30
+ "files": [
31
+ "readme.md",
32
+ "lib/**",
33
+ "src/**"
34
+ ]
35
+ }
@@ -0,0 +1,188 @@
1
+ import { expect, test } from "@jest/globals";
2
+ import jsonpatch from "fast-json-patch";
3
+
4
+ import { descend, pointer } from "@haste-health/fhir-pointer";
5
+ import { Patient, id } from "@haste-health/fhir-types/r4/types";
6
+ import { R4 } from "@haste-health/fhir-types/versions";
7
+
8
+ import buildPatches, { applyMutationImmutable } from "./index.js";
9
+
10
+ test("Adding a value.", () => {
11
+ const loc = pointer(R4, "Patient", "123" as id);
12
+ const patient: Patient = { resourceType: "Patient", id: "123" } as Patient;
13
+
14
+ descend(descend(descend(descend(loc, "name"), 0), "given"), 0);
15
+ expect(
16
+ buildPatches(patient, {
17
+ op: "add",
18
+ path: descend(descend(descend(descend(loc, "name"), 0), "given"), 0),
19
+ value: "test",
20
+ })
21
+ ).toEqual([
22
+ {
23
+ op: "add",
24
+ path: "/name",
25
+ value: [],
26
+ },
27
+ {
28
+ op: "add",
29
+ path: "/name/0",
30
+ value: {},
31
+ },
32
+ {
33
+ op: "add",
34
+ path: "/name/0/given",
35
+ value: [],
36
+ },
37
+ {
38
+ op: "add",
39
+ path: "/name/0/given/0",
40
+ value: "test",
41
+ },
42
+ ]);
43
+
44
+ expect(
45
+ buildPatches(patient, {
46
+ op: "add",
47
+ path: descend(loc, "name"),
48
+ value: [{ given: ["John"] }],
49
+ })
50
+ ).toEqual([
51
+ {
52
+ op: "add",
53
+ path: "/name",
54
+ value: [{ given: ["John"] }],
55
+ },
56
+ ]);
57
+
58
+ expect(
59
+ buildPatches(
60
+ { ...patient, name: [{ given: ["bob"] }] },
61
+ {
62
+ op: "add",
63
+ path: descend(descend(descend(descend(loc, "name"), 0), "given"), 1),
64
+ value: "Jake",
65
+ }
66
+ )
67
+ ).toEqual([
68
+ {
69
+ op: "add",
70
+ path: "/name/0/given/1",
71
+ value: "Jake",
72
+ },
73
+ ]);
74
+
75
+ expect(
76
+ jsonpatch.applyPatch(
77
+ { ...patient, name: [{ given: ["bob"] }] },
78
+ buildPatches(
79
+ { ...patient, name: [{ given: ["bob"] }] },
80
+ {
81
+ op: "add",
82
+ path: descend(descend(descend(descend(loc, "name"), 0), "given"), 1),
83
+ value: "Jake",
84
+ }
85
+ )
86
+ ).newDocument
87
+ ).toEqual({ ...patient, name: [{ given: ["bob", "Jake"] }] });
88
+
89
+ expect(
90
+ jsonpatch.applyPatch(
91
+ patient,
92
+ buildPatches(patient, {
93
+ op: "add",
94
+ path: descend(descend(descend(descend(loc, "name"), 0), "given"), 0),
95
+ value: "test",
96
+ })
97
+ ).newDocument
98
+ ).toEqual({ ...patient, name: [{ given: ["test"] }] });
99
+ });
100
+
101
+ test("replace", () => {
102
+ const loc = pointer(R4, "Patient", "123" as id);
103
+ const patient: Patient = { resourceType: "Patient", id: "123" } as Patient;
104
+ expect(
105
+ jsonpatch.applyPatch(
106
+ { ...patient, name: [{ given: ["bob"] }] },
107
+ buildPatches(
108
+ { ...patient, name: [{ given: ["bob"] }] },
109
+ {
110
+ op: "replace",
111
+ path: descend(descend(descend(descend(loc, "name"), 0), "given"), 0),
112
+ value: "Jake",
113
+ }
114
+ )
115
+ ).newDocument
116
+ ).toEqual({ ...patient, name: [{ given: ["Jake"] }] });
117
+
118
+ expect(
119
+ jsonpatch.applyPatch(
120
+ { ...patient, name: [{ given: ["bob"] }] },
121
+ buildPatches(
122
+ { ...patient, name: [{ given: ["bob"] }] },
123
+ {
124
+ op: "replace",
125
+ path: descend(descend(descend(descend(loc, "name"), 0), "given"), 1),
126
+ value: "Jake",
127
+ }
128
+ )
129
+ ).newDocument
130
+ ).toEqual({ ...patient, name: [{ given: ["bob", "Jake"] }] });
131
+ });
132
+
133
+ test("removal", () => {
134
+ const loc = pointer(R4, "Patient", "123" as id);
135
+ const patient: Patient = { resourceType: "Patient", id: "123" } as Patient;
136
+ expect(
137
+ jsonpatch.applyPatch(
138
+ { ...patient, name: [{ given: ["bob"] }] },
139
+ buildPatches(
140
+ { ...patient, name: [{ given: ["bob"] }] },
141
+ {
142
+ op: "remove",
143
+ path: descend(descend(descend(descend(loc, "name"), 0), "given"), 0),
144
+ }
145
+ )
146
+ ).newDocument
147
+ ).toEqual({ ...patient, name: [{ given: [] }] });
148
+
149
+ expect(
150
+ jsonpatch.applyPatch(
151
+ { ...patient, name: [{ given: ["bob"] }] },
152
+ buildPatches(
153
+ { ...patient, name: [{ given: ["bob"] }] },
154
+ {
155
+ op: "remove",
156
+ path: descend(descend(descend(descend(loc, "name"), 0), "given"), 1),
157
+ }
158
+ )
159
+ ).newDocument
160
+ ).toEqual({ ...patient, name: [{ given: ["bob"] }] });
161
+ expect(
162
+ jsonpatch.applyPatch(
163
+ { ...patient, name: [{ given: ["bob"] }] },
164
+ buildPatches(
165
+ { ...patient, name: [{ given: ["bob"] }] },
166
+ {
167
+ op: "remove",
168
+ path: descend(descend(descend(loc, "identifier"), 0), "system"),
169
+ }
170
+ )
171
+ ).newDocument
172
+ ).toEqual({ ...patient, name: [{ given: ["bob"] }] });
173
+ });
174
+
175
+ test("immutable Patch", () => {
176
+ const loc = pointer(R4, "Patient", "123" as id);
177
+ const patient: Patient = { resourceType: "Patient", id: "123" } as Patient;
178
+ expect(
179
+ applyMutationImmutable(
180
+ { ...patient, name: [{ given: ["bob"] }] },
181
+ {
182
+ op: "replace",
183
+ path: descend(descend(descend(descend(loc, "name"), 0), "given"), 1),
184
+ value: "Jake",
185
+ }
186
+ )
187
+ ).toEqual({ ...patient, name: [{ given: ["bob", "Jake"] }] });
188
+ });
package/src/index.ts ADDED
@@ -0,0 +1,144 @@
1
+ import jsonpatch, { Operation } from "fast-json-patch";
2
+ import { produce } from "immer";
3
+
4
+ import * as fpt from "@haste-health/fhir-pointer";
5
+ import { Resource, id } from "@haste-health/fhir-types/r4/types";
6
+ import { R4 } from "@haste-health/fhir-types/versions";
7
+
8
+ export interface Mutation<T, R> {
9
+ path: fpt.Loc<T, R, any>;
10
+ op: "add" | "remove" | "replace";
11
+ value?: R;
12
+ }
13
+
14
+ function getValue<T extends object, R>(
15
+ value: T,
16
+ pointer: fpt.Loc<T, R, any>
17
+ ): R {
18
+ return fpt.get(pointer, value);
19
+ }
20
+
21
+ function valueExists<T extends object, R>(
22
+ value: T,
23
+ json_pointer: fpt.Loc<T, R, any>
24
+ ) {
25
+ return getValue(value, json_pointer) !== undefined;
26
+ }
27
+
28
+ function deriveNextValuePlaceHolder(
29
+ fields: (string | number | symbol)[]
30
+ ): Array<unknown> | Record<string, unknown> {
31
+ if (typeof fields[1] === "number") {
32
+ return [];
33
+ }
34
+ return {};
35
+ }
36
+
37
+ function createPatchesNonExistantFields<T extends Record<string, any>, R>(
38
+ resource: T,
39
+ path: fpt.Loc<T, R, any>
40
+ ) {
41
+ const fields = fpt.fields(path);
42
+
43
+ let patches: Operation[] = [];
44
+ let curValue = resource as unknown;
45
+ let curPointer: fpt.Loc<T, any, any> = fpt.pointer(
46
+ R4,
47
+ resource.resourceType,
48
+ resource.id as id
49
+ );
50
+ for (let i = 0; i < fields.length; i++) {
51
+ curPointer = fpt.descend(curPointer, fields[i]);
52
+ curValue = getValue(resource, curPointer);
53
+ if (curValue === undefined) {
54
+ const nextValue = deriveNextValuePlaceHolder(fields.slice(i));
55
+ patches = [
56
+ ...patches,
57
+ { op: "add", path: fpt.toJSONPointer(curPointer), value: nextValue },
58
+ ];
59
+ }
60
+ }
61
+ return patches;
62
+ }
63
+
64
+ export default function buildPatches<T extends Resource, R>(
65
+ value: T,
66
+ mutation: Mutation<T, R>
67
+ ): Operation[] {
68
+ // Builds patches with a given mutation to include non existant values up to the point in the path
69
+ // where the mutation occurs
70
+
71
+ switch (mutation.op) {
72
+ case "remove": {
73
+ if (valueExists(value, mutation.path)) {
74
+ return [
75
+ {
76
+ op: "remove",
77
+ path: fpt.toJSONPointer(mutation.path),
78
+ },
79
+ ];
80
+ }
81
+ return [];
82
+ }
83
+ case "add": {
84
+ const patches = createPatchesNonExistantFields(value, mutation.path);
85
+ //If last is adding value remove here as collection will only add once.
86
+ if (
87
+ patches[patches.length - 1]?.op === "add" &&
88
+ patches[patches.length - 1]?.path === fpt.toJSONPointer(mutation.path)
89
+ ) {
90
+ return [
91
+ ...patches.slice(0, patches.length - 1),
92
+ {
93
+ op: "add",
94
+ path: fpt.toJSONPointer(mutation.path),
95
+ value: mutation.value,
96
+ },
97
+ ];
98
+ }
99
+ return [
100
+ ...patches,
101
+ {
102
+ op: "add",
103
+ path: fpt.toJSONPointer(mutation.path),
104
+ value: mutation.value,
105
+ },
106
+ ];
107
+ }
108
+ case "replace": {
109
+ if (mutation.value === undefined || mutation.value === null) {
110
+ return buildPatches(value, {
111
+ op: "remove",
112
+ path: mutation.path,
113
+ });
114
+ }
115
+ const patches = createPatchesNonExistantFields(value, mutation.path);
116
+ return [
117
+ ...patches,
118
+ {
119
+ op: "replace",
120
+ path: fpt.toJSONPointer(mutation.path),
121
+ value: mutation.value,
122
+ },
123
+ ];
124
+ }
125
+ default:
126
+ throw new Error(`Invalid operation '${mutation.op}'`);
127
+ }
128
+ }
129
+
130
+ export function applyMutation<T extends Resource, R>(
131
+ value: T,
132
+ mutation: Mutation<T, R>
133
+ ): T {
134
+ return jsonpatch.applyPatch(value, buildPatches(value, mutation)).newDocument;
135
+ }
136
+
137
+ export function applyMutationImmutable<T extends Resource, R>(
138
+ value: T,
139
+ mutation: Mutation<T, R>
140
+ ): T {
141
+ return produce(value, (v) => {
142
+ applyMutation(v as T, mutation);
143
+ });
144
+ }