@healthcare-interoperability/fhir-tools 1.0.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/.babelrc ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "presets": [
3
+ ["@babel/env", {
4
+ "targets": {
5
+ "node": "current"
6
+ }
7
+ }]
8
+ ]
9
+ }
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 quicore
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,267 @@
1
+ # @healthcare-interoperability/fhir-tools
2
+
3
+ Utilities for working with **FHIR (Fast Healthcare Interoperability Resources)** in JavaScript/Node.js, focused on **safe reference assignment, deterministic ID generation, and schema-aware data shaping**.
4
+
5
+ This package is designed for **interop pipelines**, **FHIR normalization layers**, and **HL7 → FHIR transformations**, where nested structures and reference integrity matter.
6
+
7
+ ---
8
+
9
+ ## ✨ Features
10
+
11
+ * ✅ Assign FHIR references to **deeply nested object paths**
12
+ * ✅ Automatically **creates missing objects and arrays**
13
+ * ✅ Prevents **type conflicts** during assignment
14
+ * ✅ Supports **explicit array declarations** in paths
15
+ * ✅ Generates **deterministic FHIR IDs** using integration context
16
+ * ✅ Produces **clear, path-aware error messages**
17
+ * ✅ Zero runtime dependencies (except ID generator)
18
+
19
+ ---
20
+
21
+ ## 📦 Installation
22
+
23
+ ```bash
24
+ npm install @healthcare-interoperability/fhir-tools
25
+ ```
26
+
27
+ ---
28
+
29
+ ## 🔧 Dependencies
30
+
31
+ This package depends on:
32
+
33
+ ```ts
34
+ @quicore/resource-id
35
+ ```
36
+
37
+ Make sure it is resolvable in your project.
38
+
39
+ ---
40
+
41
+ ## 🚀 Quick Start
42
+
43
+ ```ts
44
+ import { FHIRReferenceAssigner } from "@healthcare-interoperability/fhir-tools";
45
+
46
+ const assigner = new FHIRReferenceAssigner("my-integration-id");
47
+
48
+ const resource = {};
49
+
50
+ assigner.assign(
51
+ resource,
52
+ ["subject"],
53
+ "Patient/123"
54
+ );
55
+
56
+ console.log(resource);
57
+ ```
58
+
59
+ ### Output
60
+
61
+ ```json
62
+ {
63
+ "subject": {
64
+ "id": "generated-fhir-id",
65
+ "type": "Patient",
66
+ "originalReference": "Patient/123"
67
+ }
68
+ }
69
+ ```
70
+
71
+ ---
72
+
73
+ ## 🧠 Core Concept
74
+
75
+ FHIR data structures are often:
76
+
77
+ * deeply nested
78
+ * partially populated
79
+ * array-heavy
80
+ * schema-sensitive
81
+
82
+ `FHIRReferenceAssigner` safely **walks a declared path**, creates intermediate structures if needed, validates types, and assigns a **normalized FHIR reference object**.
83
+
84
+ ---
85
+
86
+ ## 🏗️ Path Segments Explained
87
+
88
+ The `pathSegments` argument is an array where each segment describes **how to traverse or create the structure**.
89
+
90
+ | Segment Type | Meaning |
91
+ | ------------ | ---------------------------------------- |
92
+ | `string` | Object key |
93
+ | `number` | Array index |
94
+ | `string[]` | Explicitly declares an array at that key |
95
+
96
+ ---
97
+
98
+ ## 📌 Examples
99
+
100
+ ### 1️⃣ Assigning into Nested Objects
101
+
102
+ ```ts
103
+ assigner.assign(
104
+ resource,
105
+ ["encounter", "serviceProvider"],
106
+ "Organization/456"
107
+ );
108
+ ```
109
+
110
+ ```json
111
+ {
112
+ "encounter": {
113
+ "serviceProvider": {
114
+ "id": "generated-id",
115
+ "type": "Organization",
116
+ "originalReference": "Organization/456"
117
+ }
118
+ }
119
+ }
120
+ ```
121
+
122
+ ---
123
+
124
+ ### 2️⃣ Assigning into Arrays (Implicit)
125
+
126
+ ```ts
127
+ assigner.assign(
128
+ resource,
129
+ ["participant", 0, "individual"],
130
+ "Practitioner/789"
131
+ );
132
+ ```
133
+
134
+ Creates `participant` as an array automatically.
135
+
136
+ ---
137
+
138
+ ### 3️⃣ Explicit Array Declaration
139
+
140
+ Use `["key"]` to **force an array**, even if no numeric index is present yet.
141
+
142
+ ```ts
143
+ assigner.assign(
144
+ resource,
145
+ [["performer"], 0, "actor"],
146
+ "Practitioner/321"
147
+ );
148
+ ```
149
+
150
+ This ensures `performer` **must be an array**.
151
+
152
+ ---
153
+
154
+ ## 🧩 Reference Object Structure
155
+
156
+ Every assigned reference has the shape:
157
+
158
+ ```ts
159
+ {
160
+ id: string; // Generated FHIR ID
161
+ type: string; // Resource type (Patient, Practitioner, etc.)
162
+ originalReference: string; // Original FHIR reference (ResourceType/ID)
163
+ }
164
+ ```
165
+
166
+ This preserves traceability while enabling internal ID normalization.
167
+
168
+ ---
169
+
170
+ ## 🔐 Validation & Safety
171
+
172
+ ### ✔ Reference Format Validation
173
+
174
+ Only valid FHIR references are accepted:
175
+
176
+ ```
177
+ ResourceType/ID
178
+ ```
179
+
180
+ Invalid formats throw immediately.
181
+
182
+ ---
183
+
184
+ ### ✔ Type Conflict Detection
185
+
186
+ If an existing path segment conflicts with the expected type:
187
+
188
+ * object vs array
189
+ * array vs object
190
+
191
+ An error is thrown with **full path context**, e.g.:
192
+
193
+ ```
194
+ Path segment 'participant' at 'participant[0]' must be an array
195
+ ```
196
+
197
+ ---
198
+
199
+ ### ✔ Array Bounds Protection
200
+
201
+ Array indices are validated to avoid runaway memory usage:
202
+
203
+ ```ts
204
+ RangeError: Array index out of bounds
205
+ ```
206
+
207
+ (Max index: `10000`)
208
+
209
+ ---
210
+
211
+ ## 🧪 Error Handling
212
+
213
+ Errors are explicit and actionable:
214
+
215
+ * `TypeError` → structural conflicts
216
+ * `RangeError` → invalid array access
217
+ * `Error` → invalid references or ID generation failures
218
+
219
+ This makes the class **safe for automated pipelines and AI-driven transformations**.
220
+
221
+ ---
222
+
223
+ ## 🏥 Typical Use Cases
224
+
225
+ * HL7v2 → FHIR normalization
226
+ * FHIR ingestion pipelines
227
+ * Appointment, Encounter, and Observation consolidation
228
+ * Interop middleware and data harmonization layers
229
+ * Schema-aware AI preprocessing
230
+
231
+ ---
232
+
233
+ ## 📄 API
234
+
235
+ ### `new FHIRReferenceAssigner(integrationId: string)`
236
+
237
+ Creates a new assigner scoped to an integration.
238
+
239
+ ---
240
+
241
+ ### `assign(baseObject, pathSegments, sourceReference)`
242
+
243
+ Assigns a normalized FHIR reference to the target path.
244
+
245
+ **Parameters**
246
+
247
+ | Name | Type | Description |
248
+ | ----------------- | ---------------------------------- | ---------------------------------- |
249
+ | `baseObject` | `Object` | Root object to mutate |
250
+ | `pathSegments` | `(string \| number \| string[])[]` | Path definition |
251
+ | `sourceReference` | `string` | FHIR reference (`ResourceType/ID`) |
252
+
253
+ ---
254
+
255
+ ## 📜 License
256
+
257
+ MIT
258
+
259
+ ---
260
+
261
+ ## 🤝 Contributing
262
+
263
+ Contributions, issues, and design discussions are welcome—especially around:
264
+
265
+ * FHIR R4/R5 alignment
266
+ * Schema validation helpers
267
+ * Bulk reference assignment utilities
@@ -0,0 +1,132 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.FHIRReferenceAssigner = void 0;
7
+ var _resourceId = require("@quicore/resource-id");
8
+ /**
9
+ * FHIRReferenceAssigner
10
+ *
11
+ * This class provides utilities for assigning FHIR (Fast Healthcare Interoperability Resources)
12
+ * reference IDs to nested object paths within healthcare data records.
13
+ */
14
+ class FHIRReferenceAssigner {
15
+ /**
16
+ * Creates a new FHIRReferenceAssigner
17
+ *
18
+ * @param {string} integrationId - Integration ID used in FHIRID generation
19
+ */
20
+ constructor(integrationId) {
21
+ if (!integrationId || typeof integrationId !== 'string') {
22
+ throw new TypeError("integrationId must be a non-empty string");
23
+ }
24
+ this.integrationId = integrationId;
25
+ }
26
+
27
+ /**
28
+ * Assigns a FHIR reference to a nested object path.
29
+ *
30
+ * This method provides robust handling of nested paths, including:
31
+ * - Creating intermediate objects and arrays as needed
32
+ * - Validating existing field types to prevent conflicts
33
+ * - Supporting explicit array declarations via wrapped syntax
34
+ * - Comprehensive error reporting with full path context
35
+ *
36
+ * @param {Object} baseObject - The root object to modify
37
+ * @param {Array<string | number | string[]>} pathSegments - Array of path segments:
38
+ * - string: object key
39
+ * - number: array index
40
+ * - string[]: declares array at given key
41
+ * @param {string} sourceReference - FHIR reference string (e.g., "Patient/123")
42
+ *
43
+ * @throws {TypeError} If existing values conflict with required types
44
+ * @throws {RangeError} If array index is invalid
45
+ * @throws {Error} If sourceReference is invalid or FHIR ID generation fails
46
+ */
47
+ assign(baseObject, pathSegments, sourceReference) {
48
+ if (!sourceReference) return;
49
+ if (!FHIRReferenceAssigner.isValidFHIRReference(sourceReference)) {
50
+ throw new Error(`Invalid FHIR reference format: "${sourceReference}". Expected format: "ResourceType/ID"`);
51
+ }
52
+ let current = baseObject;
53
+ const totalSegments = pathSegments.length;
54
+ for (let i = 0; i < totalSegments - 1; i++) {
55
+ const segment = pathSegments[i];
56
+ const nextSegment = pathSegments[i + 1];
57
+ const currentPath = FHIRReferenceAssigner.buildPathString(pathSegments, i + 1);
58
+ let actualSegment = Array.isArray(segment) ? segment[0] : segment;
59
+ const isArray = Array.isArray(segment) || typeof nextSegment === 'number';
60
+ if (current[actualSegment] == null) {
61
+ current[actualSegment] = isArray ? [] : {};
62
+ } else {
63
+ const actualType = typeof current[actualSegment];
64
+ if (isArray && !Array.isArray(current[actualSegment])) {
65
+ throw new TypeError(`Path segment '${actualSegment}' at '${currentPath}' must be an array`);
66
+ }
67
+ if (!isArray && (actualType !== 'object' || Array.isArray(current[actualSegment]))) {
68
+ throw new TypeError(`Path segment '${actualSegment}' at '${currentPath}' must be an object`);
69
+ }
70
+ }
71
+ if (typeof nextSegment === 'number') {
72
+ if (nextSegment < 0 || nextSegment > 10000) {
73
+ throw new RangeError(`Array index out of bounds at '${currentPath}': ${nextSegment}`);
74
+ }
75
+ FHIRReferenceAssigner.ensureArrayLength(current[actualSegment], nextSegment);
76
+ }
77
+ current = current[actualSegment];
78
+ }
79
+ const finalSegment = Array.isArray(pathSegments[totalSegments - 1]) ? pathSegments[totalSegments - 1][0] : pathSegments[totalSegments - 1];
80
+ const [resourceType, sourceId] = sourceReference.split('/');
81
+ try {
82
+ const idGen = new _resourceId.FHIRIDGenerator({
83
+ id: sourceId,
84
+ resourceType
85
+ }).setIntegrationId(this.integrationId);
86
+ current[finalSegment] = {
87
+ id: idGen.generate(),
88
+ type: resourceType,
89
+ originalReference: sourceReference
90
+ };
91
+ } catch (err) {
92
+ throw new Error(`Failed to generate FHIR ID for reference '${sourceReference}': ${err.message}`);
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Validates that a reference string follows the FHIR reference format (ResourceType/ID)
98
+ *
99
+ * @param {string} reference - The reference string to validate
100
+ * @returns {boolean} True if valid, false otherwise
101
+ * @private
102
+ */
103
+ static isValidFHIRReference(reference) {
104
+ if (!reference || typeof reference !== 'string') return false;
105
+ const parts = reference.split('/');
106
+ return parts.length === 2 && parts[0] && parts[1];
107
+ }
108
+
109
+ /**
110
+ * Builds a human-readable path string from path segments
111
+ *
112
+ * @param {Array<string | number | string[]>} segments - Path segments
113
+ * @param {number} upTo - Index up to which path should be built
114
+ * @returns {string} Path string
115
+ * @private
116
+ */
117
+ static buildPathString(segments, upTo = segments.length) {
118
+ return segments.slice(0, upTo).map(seg => Array.isArray(seg) ? `[${seg[0]}]` : typeof seg === 'number' ? `[${seg}]` : seg).join('.');
119
+ }
120
+
121
+ /**
122
+ * Ensures an array has at least requiredIndex+1 elements
123
+ *
124
+ * @param {Array} arr - The array to extend
125
+ * @param {number} requiredIndex - Index that must be valid
126
+ * @private
127
+ */
128
+ static ensureArrayLength(arr, requiredIndex) {
129
+ while (arr.length <= requiredIndex) arr.push({});
130
+ }
131
+ }
132
+ exports.FHIRReferenceAssigner = FHIRReferenceAssigner;
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "FHIRReferenceAssigner", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _FHIRReferenceAssigner.FHIRReferenceAssigner;
10
+ }
11
+ });
12
+ var _FHIRReferenceAssigner = require("./FHIRReferenceAssigner");
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@healthcare-interoperability/fhir-tools",
3
+ "version": "1.0.0",
4
+ "description": "Helper tools for FHIR processing",
5
+ "main": "dist/index.js",
6
+ "scripts": {
7
+ "build": "babel src -d dist"
8
+ },
9
+ "keywords": [
10
+ "fhir",
11
+ "healthcare",
12
+ "quicore"
13
+ ],
14
+ "author": "quicore",
15
+ "license": "MIT",
16
+ "dependencies": {
17
+ "@quicore/resource-id": "^1.0.0"
18
+ },
19
+ "devDependencies": {
20
+ "@babel/cli": "^7.27.0",
21
+ "@babel/core": "^7.26.10",
22
+ "@babel/preset-env": "^7.26.9"
23
+ },
24
+ "engines": {
25
+ "node": ">=18.0.0"
26
+ }
27
+ }