fhir-validator-wrapper 1.0.0 → 1.2.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/fhir-validator.js +451 -37
- package/package.json +3 -3
- package/readme.md +220 -35
package/fhir-validator.js
CHANGED
|
@@ -1,18 +1,300 @@
|
|
|
1
1
|
const { spawn } = require('child_process');
|
|
2
2
|
const http = require('http');
|
|
3
3
|
const https = require('https');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
4
6
|
const { URL } = require('url');
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Node.js wrapper for the FHIR Validator HTTP Service
|
|
8
10
|
*/
|
|
9
11
|
class FhirValidator {
|
|
10
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Create a new FHIR Validator instance
|
|
14
|
+
* @param {string} validatorJarPath - Path to the validator JAR file
|
|
15
|
+
* @param {Object} [logger] - Winston logger instance (optional)
|
|
16
|
+
*/
|
|
17
|
+
constructor(validatorJarPath, logger = null) {
|
|
11
18
|
this.validatorJarPath = validatorJarPath;
|
|
19
|
+
this.logger = logger;
|
|
12
20
|
this.process = null;
|
|
13
21
|
this.port = null;
|
|
14
22
|
this.baseUrl = null;
|
|
15
23
|
this.isReady = false;
|
|
24
|
+
|
|
25
|
+
// Version tracking file sits alongside the JAR
|
|
26
|
+
this.versionFilePath = validatorJarPath + '.version';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Set a logger after initialization
|
|
31
|
+
* @param {Object} logger - Winston logger instance
|
|
32
|
+
*/
|
|
33
|
+
setLogger(logger) {
|
|
34
|
+
this.logger = logger;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Log a message with the appropriate level
|
|
39
|
+
* @private
|
|
40
|
+
* @param {string} level - Log level ('info', 'error', 'warn')
|
|
41
|
+
* @param {string} message - Message to log
|
|
42
|
+
* @param {Object} [meta] - Optional metadata
|
|
43
|
+
*/
|
|
44
|
+
log(level, message, meta = {}) {
|
|
45
|
+
if (this.logger) {
|
|
46
|
+
this.logger[level](message, meta);
|
|
47
|
+
} else {
|
|
48
|
+
if (level === 'error') {
|
|
49
|
+
console.error(message, meta);
|
|
50
|
+
} else if (level === 'warn') {
|
|
51
|
+
console.warn(message, meta);
|
|
52
|
+
} else {
|
|
53
|
+
console.log(message, meta);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get the latest release information from GitHub
|
|
60
|
+
* @returns {Promise<{version: string, downloadUrl: string}>}
|
|
61
|
+
*/
|
|
62
|
+
async getLatestRelease() {
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
const options = {
|
|
65
|
+
hostname: 'api.github.com',
|
|
66
|
+
path: '/repos/hapifhir/org.hl7.fhir.core/releases/latest',
|
|
67
|
+
method: 'GET',
|
|
68
|
+
headers: {
|
|
69
|
+
'User-Agent': 'fhir-validator-node',
|
|
70
|
+
'Accept': 'application/vnd.github.v3+json'
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const req = https.request(options, (res) => {
|
|
75
|
+
let data = '';
|
|
76
|
+
|
|
77
|
+
res.on('data', (chunk) => {
|
|
78
|
+
data += chunk;
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
res.on('end', () => {
|
|
82
|
+
if (res.statusCode !== 200) {
|
|
83
|
+
reject(new Error(`GitHub API returned status ${res.statusCode}: ${data}`));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const release = JSON.parse(data);
|
|
89
|
+
const version = release.tag_name;
|
|
90
|
+
|
|
91
|
+
// Find the validator_cli.jar asset
|
|
92
|
+
const asset = release.assets.find(a => a.name === 'validator_cli.jar');
|
|
93
|
+
if (!asset) {
|
|
94
|
+
reject(new Error('validator_cli.jar not found in latest release assets'));
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
resolve({
|
|
99
|
+
version,
|
|
100
|
+
downloadUrl: asset.browser_download_url,
|
|
101
|
+
publishedAt: release.published_at
|
|
102
|
+
});
|
|
103
|
+
} catch (error) {
|
|
104
|
+
reject(new Error(`Failed to parse GitHub response: ${error.message}`));
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
req.on('error', reject);
|
|
110
|
+
req.setTimeout(30000, () => {
|
|
111
|
+
req.destroy();
|
|
112
|
+
reject(new Error('GitHub API request timeout'));
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
req.end();
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Get the currently installed version
|
|
121
|
+
* @returns {string|null} - The installed version or null if not installed
|
|
122
|
+
*/
|
|
123
|
+
getInstalledVersion() {
|
|
124
|
+
try {
|
|
125
|
+
if (fs.existsSync(this.versionFilePath)) {
|
|
126
|
+
const versionInfo = JSON.parse(fs.readFileSync(this.versionFilePath, 'utf8'));
|
|
127
|
+
return versionInfo.version;
|
|
128
|
+
}
|
|
129
|
+
} catch (error) {
|
|
130
|
+
this.log('warn', `Failed to read version file: ${error.message}`);
|
|
131
|
+
}
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Save version information
|
|
137
|
+
* @param {string} version - The version string
|
|
138
|
+
* @param {string} downloadUrl - The URL it was downloaded from
|
|
139
|
+
*/
|
|
140
|
+
saveVersionInfo(version, downloadUrl) {
|
|
141
|
+
const versionInfo = {
|
|
142
|
+
version,
|
|
143
|
+
downloadUrl,
|
|
144
|
+
downloadedAt: new Date().toISOString()
|
|
145
|
+
};
|
|
146
|
+
fs.writeFileSync(this.versionFilePath, JSON.stringify(versionInfo, null, 2));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Download a file from a URL, following redirects
|
|
151
|
+
* @param {string} url - The URL to download from
|
|
152
|
+
* @param {string} destPath - The destination file path
|
|
153
|
+
* @returns {Promise<void>}
|
|
154
|
+
*/
|
|
155
|
+
async downloadFile(url, destPath) {
|
|
156
|
+
return new Promise((resolve, reject) => {
|
|
157
|
+
const downloadWithRedirects = (downloadUrl, redirectCount = 0) => {
|
|
158
|
+
if (redirectCount > 5) {
|
|
159
|
+
reject(new Error('Too many redirects'));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const parsedUrl = new URL(downloadUrl);
|
|
164
|
+
const protocol = parsedUrl.protocol === 'https:' ? https : http;
|
|
165
|
+
|
|
166
|
+
const req = protocol.get(downloadUrl, {
|
|
167
|
+
headers: {
|
|
168
|
+
'User-Agent': 'fhir-validator-node'
|
|
169
|
+
}
|
|
170
|
+
}, (res) => {
|
|
171
|
+
// Handle redirects
|
|
172
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
173
|
+
this.log('info', `Following redirect to ${res.headers.location}`);
|
|
174
|
+
downloadWithRedirects(res.headers.location, redirectCount + 1);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (res.statusCode !== 200) {
|
|
179
|
+
reject(new Error(`Download failed with status ${res.statusCode}`));
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Ensure directory exists
|
|
184
|
+
const dir = path.dirname(destPath);
|
|
185
|
+
if (!fs.existsSync(dir)) {
|
|
186
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Download to a temp file first, then rename
|
|
190
|
+
const tempPath = destPath + '.download';
|
|
191
|
+
const fileStream = fs.createWriteStream(tempPath);
|
|
192
|
+
|
|
193
|
+
const contentLength = parseInt(res.headers['content-length'], 10);
|
|
194
|
+
let downloadedBytes = 0;
|
|
195
|
+
let lastLoggedPercent = 0;
|
|
196
|
+
|
|
197
|
+
res.on('data', (chunk) => {
|
|
198
|
+
downloadedBytes += chunk.length;
|
|
199
|
+
if (contentLength) {
|
|
200
|
+
const percent = Math.floor((downloadedBytes / contentLength) * 100);
|
|
201
|
+
if (percent >= lastLoggedPercent + 10) {
|
|
202
|
+
this.log('info', `Download progress: ${percent}% (${Math.round(downloadedBytes / 1024 / 1024)}MB / ${Math.round(contentLength / 1024 / 1024)}MB)`);
|
|
203
|
+
lastLoggedPercent = percent;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
res.pipe(fileStream);
|
|
209
|
+
|
|
210
|
+
fileStream.on('finish', () => {
|
|
211
|
+
fileStream.close(() => {
|
|
212
|
+
// Rename temp file to final destination
|
|
213
|
+
fs.renameSync(tempPath, destPath);
|
|
214
|
+
resolve();
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
fileStream.on('error', (err) => {
|
|
219
|
+
// Clean up temp file on error
|
|
220
|
+
if (fs.existsSync(tempPath)) {
|
|
221
|
+
fs.unlinkSync(tempPath);
|
|
222
|
+
}
|
|
223
|
+
reject(err);
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
req.on('error', reject);
|
|
228
|
+
req.setTimeout(300000, () => { // 5 minute timeout for large file
|
|
229
|
+
req.destroy();
|
|
230
|
+
reject(new Error('Download timeout'));
|
|
231
|
+
});
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
downloadWithRedirects(url);
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Ensure the validator JAR is downloaded and up to date
|
|
240
|
+
* @param {Object} [options] - Options for the update check
|
|
241
|
+
* @param {boolean} [options.force=false] - Force download even if current version is up to date
|
|
242
|
+
* @param {boolean} [options.skipUpdateCheck=false] - Skip checking for updates if JAR exists
|
|
243
|
+
* @returns {Promise<{version: string, updated: boolean, downloaded: boolean}>}
|
|
244
|
+
*/
|
|
245
|
+
async ensureValidator(options = {}) {
|
|
246
|
+
const { force = false, skipUpdateCheck = false } = options;
|
|
247
|
+
|
|
248
|
+
const jarExists = fs.existsSync(this.validatorJarPath);
|
|
249
|
+
const installedVersion = this.getInstalledVersion();
|
|
250
|
+
|
|
251
|
+
// If JAR exists and we're skipping update checks, we're done
|
|
252
|
+
if (jarExists && skipUpdateCheck && !force) {
|
|
253
|
+
this.log('info', `Using existing validator JAR (version check skipped)`);
|
|
254
|
+
return {
|
|
255
|
+
version: installedVersion || 'unknown',
|
|
256
|
+
updated: false,
|
|
257
|
+
downloaded: false
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Check for latest version
|
|
262
|
+
this.log('info', 'Checking for latest FHIR validator release...');
|
|
263
|
+
const latest = await this.getLatestRelease();
|
|
264
|
+
this.log('info', `Latest version: ${latest.version}`);
|
|
265
|
+
|
|
266
|
+
// Determine if we need to download
|
|
267
|
+
const needsDownload = force ||
|
|
268
|
+
!jarExists ||
|
|
269
|
+
!installedVersion ||
|
|
270
|
+
installedVersion !== latest.version;
|
|
271
|
+
|
|
272
|
+
if (!needsDownload) {
|
|
273
|
+
this.log('info', `Validator is up to date (${installedVersion})`);
|
|
274
|
+
return {
|
|
275
|
+
version: installedVersion,
|
|
276
|
+
updated: false,
|
|
277
|
+
downloaded: false
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Download the JAR
|
|
282
|
+
if (installedVersion && jarExists) {
|
|
283
|
+
this.log('info', `Updating validator from ${installedVersion} to ${latest.version}...`);
|
|
284
|
+
} else {
|
|
285
|
+
this.log('info', `Downloading validator ${latest.version}...`);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
await this.downloadFile(latest.downloadUrl, this.validatorJarPath);
|
|
289
|
+
this.saveVersionInfo(latest.version, latest.downloadUrl);
|
|
290
|
+
|
|
291
|
+
this.log('info', `Validator ${latest.version} downloaded successfully`);
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
version: latest.version,
|
|
295
|
+
updated: installedVersion !== null && installedVersion !== latest.version,
|
|
296
|
+
downloaded: true
|
|
297
|
+
};
|
|
16
298
|
}
|
|
17
299
|
|
|
18
300
|
/**
|
|
@@ -24,6 +306,8 @@ class FhirValidator {
|
|
|
24
306
|
* @param {string[]} [config.igs] - Array of implementation guide packages (e.g., ["hl7.fhir.us.core#6.0.0"])
|
|
25
307
|
* @param {number} [config.port=8080] - Port to run the service on
|
|
26
308
|
* @param {number} [config.timeout=30000] - Timeout in ms to wait for service to be ready
|
|
309
|
+
* @param {boolean} [config.autoDownload=true] - Automatically download/update validator JAR
|
|
310
|
+
* @param {boolean} [config.skipUpdateCheck=false] - Skip checking for updates if JAR exists
|
|
27
311
|
* @returns {Promise<void>}
|
|
28
312
|
*/
|
|
29
313
|
async start(config) {
|
|
@@ -31,12 +315,28 @@ class FhirValidator {
|
|
|
31
315
|
throw new Error('Validator service is already running');
|
|
32
316
|
}
|
|
33
317
|
|
|
34
|
-
const {
|
|
318
|
+
const {
|
|
319
|
+
version,
|
|
320
|
+
txServer,
|
|
321
|
+
txLog,
|
|
322
|
+
igs = [],
|
|
323
|
+
port = 8080,
|
|
324
|
+
timeout = 30000,
|
|
325
|
+
autoDownload = true,
|
|
326
|
+
skipUpdateCheck = false
|
|
327
|
+
} = config;
|
|
35
328
|
|
|
36
329
|
if (!version || !txServer || !txLog) {
|
|
37
330
|
throw new Error('version, txServer, and txLog are required');
|
|
38
331
|
}
|
|
39
332
|
|
|
333
|
+
// Ensure validator is downloaded if autoDownload is enabled
|
|
334
|
+
if (autoDownload) {
|
|
335
|
+
await this.ensureValidator({ skipUpdateCheck });
|
|
336
|
+
} else if (!fs.existsSync(this.validatorJarPath)) {
|
|
337
|
+
throw new Error(`Validator JAR not found at ${this.validatorJarPath}. Set autoDownload: true or download manually.`);
|
|
338
|
+
}
|
|
339
|
+
|
|
40
340
|
this.port = port;
|
|
41
341
|
this.baseUrl = `http://localhost:${port}`;
|
|
42
342
|
|
|
@@ -45,7 +345,7 @@ class FhirValidator {
|
|
|
45
345
|
'-jar', this.validatorJarPath,
|
|
46
346
|
'-server', port.toString(),
|
|
47
347
|
'-tx', txServer,
|
|
48
|
-
'-
|
|
348
|
+
'-txLog', txLog,
|
|
49
349
|
'-version', version
|
|
50
350
|
];
|
|
51
351
|
|
|
@@ -54,7 +354,7 @@ class FhirValidator {
|
|
|
54
354
|
args.push('-ig', ig);
|
|
55
355
|
}
|
|
56
356
|
|
|
57
|
-
|
|
357
|
+
this.log('info', `Starting FHIR validator with command: java ${args.join(' ')}`);
|
|
58
358
|
|
|
59
359
|
// Spawn the Java process
|
|
60
360
|
this.process = spawn('java', args, {
|
|
@@ -63,12 +363,12 @@ class FhirValidator {
|
|
|
63
363
|
|
|
64
364
|
// Handle process events
|
|
65
365
|
this.process.on('error', (error) => {
|
|
66
|
-
|
|
366
|
+
this.log('error', 'Failed to start validator process:', error);
|
|
67
367
|
throw error;
|
|
68
368
|
});
|
|
69
369
|
|
|
70
370
|
this.process.on('exit', (code, signal) => {
|
|
71
|
-
|
|
371
|
+
this.log('info', `Validator process exited with code ${code} and signal ${signal}`);
|
|
72
372
|
this.cleanup();
|
|
73
373
|
});
|
|
74
374
|
|
|
@@ -78,19 +378,19 @@ class FhirValidator {
|
|
|
78
378
|
lines.forEach(line => {
|
|
79
379
|
// Remove ANSI escape sequences (color codes, etc.)
|
|
80
380
|
const cleanLine = line.replace(/\u001b\[[0-9;]*m/g, '').trim();
|
|
81
|
-
if (cleanLine.length > 1) {
|
|
82
|
-
|
|
381
|
+
if (cleanLine.length > 1) {
|
|
382
|
+
this.log('info', `Validator: ${cleanLine}`);
|
|
83
383
|
}
|
|
84
384
|
});
|
|
85
385
|
});
|
|
86
386
|
|
|
87
387
|
this.process.stderr.on('data', (data) => {
|
|
88
|
-
|
|
388
|
+
this.log('error', `Validator-err: ${data}`);
|
|
89
389
|
});
|
|
90
390
|
|
|
91
391
|
// Wait for the service to be ready
|
|
92
392
|
await this.waitForReady(timeout);
|
|
93
|
-
|
|
393
|
+
this.log('info', 'FHIR validator service is ready');
|
|
94
394
|
}
|
|
95
395
|
|
|
96
396
|
/**
|
|
@@ -100,7 +400,7 @@ class FhirValidator {
|
|
|
100
400
|
*/
|
|
101
401
|
async waitForReady(timeout) {
|
|
102
402
|
const startTime = Date.now();
|
|
103
|
-
|
|
403
|
+
|
|
104
404
|
while (Date.now() - startTime < timeout) {
|
|
105
405
|
try {
|
|
106
406
|
await this.healthCheck();
|
|
@@ -111,7 +411,7 @@ class FhirValidator {
|
|
|
111
411
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
112
412
|
}
|
|
113
413
|
}
|
|
114
|
-
|
|
414
|
+
|
|
115
415
|
throw new Error(`Validator service did not become ready within ${timeout}ms`);
|
|
116
416
|
}
|
|
117
417
|
|
|
@@ -181,7 +481,7 @@ class FhirValidator {
|
|
|
181
481
|
|
|
182
482
|
// Build query parameters
|
|
183
483
|
const queryParams = new URLSearchParams();
|
|
184
|
-
|
|
484
|
+
|
|
185
485
|
if (options.profiles && options.profiles.length > 0) {
|
|
186
486
|
queryParams.set('profiles', options.profiles.join(','));
|
|
187
487
|
}
|
|
@@ -216,11 +516,11 @@ class FhirValidator {
|
|
|
216
516
|
|
|
217
517
|
const req = http.request(requestOptions, (res) => {
|
|
218
518
|
let data = '';
|
|
219
|
-
|
|
519
|
+
|
|
220
520
|
res.on('data', (chunk) => {
|
|
221
521
|
data += chunk;
|
|
222
522
|
});
|
|
223
|
-
|
|
523
|
+
|
|
224
524
|
res.on('end', () => {
|
|
225
525
|
try {
|
|
226
526
|
const result = JSON.parse(data);
|
|
@@ -232,7 +532,7 @@ class FhirValidator {
|
|
|
232
532
|
});
|
|
233
533
|
|
|
234
534
|
req.on('error', reject);
|
|
235
|
-
|
|
535
|
+
|
|
236
536
|
req.setTimeout(30000, () => {
|
|
237
537
|
req.destroy();
|
|
238
538
|
reject(new Error('Validation request timeout'));
|
|
@@ -254,7 +554,7 @@ class FhirValidator {
|
|
|
254
554
|
if (!Buffer.isBuffer(resourceBytes)) {
|
|
255
555
|
throw new Error('resourceBytes must be a Buffer');
|
|
256
556
|
}
|
|
257
|
-
|
|
557
|
+
|
|
258
558
|
return this.validate(resourceBytes, options);
|
|
259
559
|
}
|
|
260
560
|
|
|
@@ -268,7 +568,7 @@ class FhirValidator {
|
|
|
268
568
|
if (typeof resourceObject !== 'object' || resourceObject === null) {
|
|
269
569
|
throw new Error('resourceObject must be an object');
|
|
270
570
|
}
|
|
271
|
-
|
|
571
|
+
|
|
272
572
|
return this.validate(resourceObject, options);
|
|
273
573
|
}
|
|
274
574
|
|
|
@@ -303,11 +603,11 @@ class FhirValidator {
|
|
|
303
603
|
|
|
304
604
|
const req = http.request(requestOptions, (res) => {
|
|
305
605
|
let data = '';
|
|
306
|
-
|
|
606
|
+
|
|
307
607
|
res.on('data', (chunk) => {
|
|
308
608
|
data += chunk;
|
|
309
609
|
});
|
|
310
|
-
|
|
610
|
+
|
|
311
611
|
res.on('end', () => {
|
|
312
612
|
try {
|
|
313
613
|
const result = JSON.parse(data);
|
|
@@ -319,7 +619,7 @@ class FhirValidator {
|
|
|
319
619
|
});
|
|
320
620
|
|
|
321
621
|
req.on('error', reject);
|
|
322
|
-
|
|
622
|
+
|
|
323
623
|
req.setTimeout(30000, () => {
|
|
324
624
|
req.destroy();
|
|
325
625
|
reject(new Error('Load IG request timeout'));
|
|
@@ -329,6 +629,132 @@ class FhirValidator {
|
|
|
329
629
|
});
|
|
330
630
|
}
|
|
331
631
|
|
|
632
|
+
/**
|
|
633
|
+
* Run a terminology server test
|
|
634
|
+
* @param {Object} params - Test parameters
|
|
635
|
+
* @param {string} params.server - The address of the terminology server to test
|
|
636
|
+
* @param {string} params.suiteName - The suite name that contains the test to run
|
|
637
|
+
* @param {string} params.testName - The test name to run
|
|
638
|
+
* @param {string} params.version - What FHIR version to use for the test
|
|
639
|
+
* @param {string} [params.externalFile] - Optional name of messages file
|
|
640
|
+
* @param {string} [params.modes] - Optional comma delimited string of modes
|
|
641
|
+
* @returns {Promise<{result: boolean, message?: string}>}
|
|
642
|
+
*/
|
|
643
|
+
async runTxTest(params) {
|
|
644
|
+
if (!this.isReady) {
|
|
645
|
+
throw new Error('Validator service is not ready');
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
const { server, suiteName, testName, version, externalFile, modes } = params;
|
|
649
|
+
|
|
650
|
+
if (!server || !suiteName || !testName || !version) {
|
|
651
|
+
throw new Error('server, suiteName, testName, and version are required');
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// Build query parameters
|
|
655
|
+
const queryParams = new URLSearchParams();
|
|
656
|
+
queryParams.set('server', server);
|
|
657
|
+
queryParams.set('suite', suiteName);
|
|
658
|
+
queryParams.set('test', testName);
|
|
659
|
+
queryParams.set('version', version);
|
|
660
|
+
|
|
661
|
+
if (externalFile) {
|
|
662
|
+
queryParams.set('externalFile', externalFile);
|
|
663
|
+
}
|
|
664
|
+
if (modes) {
|
|
665
|
+
queryParams.set('modes', modes);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
const url = `${this.baseUrl}/txTest?${queryParams.toString()}`;
|
|
669
|
+
|
|
670
|
+
return new Promise((resolve) => {
|
|
671
|
+
const parsedUrl = new URL(url);
|
|
672
|
+
const requestOptions = {
|
|
673
|
+
hostname: parsedUrl.hostname,
|
|
674
|
+
port: parsedUrl.port,
|
|
675
|
+
path: parsedUrl.pathname + parsedUrl.search,
|
|
676
|
+
method: 'GET',
|
|
677
|
+
headers: {
|
|
678
|
+
'Accept': 'application/fhir+json'
|
|
679
|
+
}
|
|
680
|
+
};
|
|
681
|
+
|
|
682
|
+
const req = http.request(requestOptions, (res) => {
|
|
683
|
+
let data = '';
|
|
684
|
+
|
|
685
|
+
res.on('data', (chunk) => {
|
|
686
|
+
data += chunk;
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
res.on('end', () => {
|
|
690
|
+
// Handle HTTP errors
|
|
691
|
+
if (res.statusCode >= 400) {
|
|
692
|
+
resolve({
|
|
693
|
+
result: false,
|
|
694
|
+
message: `HTTP error ${res.statusCode}: ${data}`
|
|
695
|
+
});
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
try {
|
|
700
|
+
const outcome = JSON.parse(data);
|
|
701
|
+
|
|
702
|
+
// Check if it's a valid OperationOutcome
|
|
703
|
+
if (outcome.resourceType !== 'OperationOutcome') {
|
|
704
|
+
resolve({
|
|
705
|
+
result: false,
|
|
706
|
+
message: `Unexpected response type: ${outcome.resourceType || 'unknown'}`
|
|
707
|
+
});
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// No issues means success
|
|
712
|
+
if (!outcome.issue || outcome.issue.length === 0) {
|
|
713
|
+
resolve({ result: true });
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// Check for error severity issues
|
|
718
|
+
const errorIssue = outcome.issue.find(issue => issue.severity === 'error');
|
|
719
|
+
if (errorIssue) {
|
|
720
|
+
resolve({
|
|
721
|
+
result: false,
|
|
722
|
+
message: errorIssue.details?.text || errorIssue.diagnostics || 'Test failed with error'
|
|
723
|
+
});
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// No error issues, test passed
|
|
728
|
+
resolve({ result: true });
|
|
729
|
+
|
|
730
|
+
} catch (error) {
|
|
731
|
+
resolve({
|
|
732
|
+
result: false,
|
|
733
|
+
message: `Failed to parse response: ${error.message}`
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
});
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
req.on('error', (error) => {
|
|
740
|
+
resolve({
|
|
741
|
+
result: false,
|
|
742
|
+
message: `Request failed: ${error.message}`
|
|
743
|
+
});
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
req.setTimeout(60000, () => {
|
|
747
|
+
req.destroy();
|
|
748
|
+
resolve({
|
|
749
|
+
result: false,
|
|
750
|
+
message: 'Request timeout'
|
|
751
|
+
});
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
req.end();
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
|
|
332
758
|
/**
|
|
333
759
|
* Stop the validator service
|
|
334
760
|
* @returns {Promise<void>}
|
|
@@ -340,36 +766,24 @@ class FhirValidator {
|
|
|
340
766
|
|
|
341
767
|
return new Promise((resolve, reject) => {
|
|
342
768
|
const timeout = setTimeout(() => {
|
|
343
|
-
|
|
769
|
+
this.log('warn', 'Force killing validator process after timeout');
|
|
344
770
|
if (this.process && !this.process.killed) {
|
|
345
771
|
this.process.kill('SIGKILL');
|
|
346
772
|
}
|
|
347
773
|
this.cleanup();
|
|
348
774
|
resolve();
|
|
349
|
-
}, 10000);
|
|
775
|
+
}, 10000);
|
|
350
776
|
|
|
351
|
-
// Single exit handler
|
|
352
777
|
const onExit = () => {
|
|
353
778
|
clearTimeout(timeout);
|
|
354
779
|
this.cleanup();
|
|
355
780
|
resolve();
|
|
356
781
|
};
|
|
357
782
|
|
|
358
|
-
this.process.once('exit', onExit);
|
|
783
|
+
this.process.once('exit', onExit);
|
|
359
784
|
|
|
360
|
-
|
|
361
|
-
// Go straight to SIGKILL for immediate termination
|
|
362
|
-
console.log('Stopping validator process...');
|
|
785
|
+
this.log('info', 'Stopping validator process...');
|
|
363
786
|
this.process.kill('SIGKILL');
|
|
364
|
-
|
|
365
|
-
// Backup: try SIGTERM first, then SIGKILL after 2 seconds
|
|
366
|
-
// this.process.kill('SIGTERM');
|
|
367
|
-
// setTimeout(() => {
|
|
368
|
-
// if (this.process && !this.process.killed) {
|
|
369
|
-
// console.log('Escalating to SIGKILL...');
|
|
370
|
-
// this.process.kill('SIGKILL');
|
|
371
|
-
// }
|
|
372
|
-
// }, 2000);
|
|
373
787
|
});
|
|
374
788
|
}
|
|
375
789
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fhir-validator-wrapper",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Node.js wrapper for the HL7 FHIR Validator CLI",
|
|
5
5
|
"main": "fhir-validator.js",
|
|
6
6
|
"scripts": {
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
},
|
|
27
27
|
"repository": {
|
|
28
28
|
"type": "git",
|
|
29
|
-
"url": "https://github.com/FHIR/fhir-validator-wrapper.git"
|
|
29
|
+
"url": "git+https://github.com/FHIR/fhir-validator-wrapper.git"
|
|
30
30
|
},
|
|
31
31
|
"bugs": {
|
|
32
32
|
"url": "https://github.com/FHIR/fhir-validator-wrapper/issues"
|
|
@@ -41,4 +41,4 @@
|
|
|
41
41
|
"README.md",
|
|
42
42
|
"LICENSE"
|
|
43
43
|
]
|
|
44
|
-
}
|
|
44
|
+
}
|
package/readme.md
CHANGED
|
@@ -30,13 +30,22 @@ There are many ways to contribute:
|
|
|
30
30
|
|
|
31
31
|
## Overview
|
|
32
32
|
|
|
33
|
-
This library manages the lifecycle of the FHIR Validator Java service and provides a clean Node.js interface for validation operations. It handles process management, HTTP communication, and provides typed validation options.
|
|
33
|
+
This library manages the lifecycle of the FHIR Validator Java service and provides a clean Node.js interface for validation operations. It handles automatic downloading of the validator JAR, process management, HTTP communication, and provides typed validation options.
|
|
34
|
+
|
|
35
|
+
## Features
|
|
36
|
+
|
|
37
|
+
- **Automatic JAR Management**: Automatically downloads and updates the FHIR Validator CLI JAR from GitHub releases
|
|
38
|
+
- **Version Tracking**: Tracks installed version and checks for updates
|
|
39
|
+
- **Resource Validation**: Validate FHIR resources in JSON or XML format
|
|
40
|
+
- **Profile Validation**: Validate against specific FHIR profiles
|
|
41
|
+
- **Implementation Guide Support**: Load IGs at startup or runtime
|
|
42
|
+
- **Terminology Testing**: Run terminology server tests with `runTxTest`
|
|
34
43
|
|
|
35
44
|
## Prerequisites
|
|
36
45
|
|
|
37
46
|
- Node.js 12.0.0 or higher
|
|
38
47
|
- Java 8 or higher
|
|
39
|
-
-
|
|
48
|
+
- Internet connection (for automatic JAR download, or manually download from [GitHub releases](https://github.com/hapifhir/org.hl7.fhir.core/releases))
|
|
40
49
|
|
|
41
50
|
## Installation
|
|
42
51
|
|
|
@@ -46,21 +55,15 @@ npm install fhir-validator-wrapper
|
|
|
46
55
|
|
|
47
56
|
## Quick Start
|
|
48
57
|
|
|
49
|
-
```bash
|
|
50
|
-
# Run the example with your JAR file
|
|
51
|
-
FHIR_VALIDATOR_JAR_PATH=./your-validator.jar npm start
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
Or in code:
|
|
55
|
-
|
|
56
58
|
```javascript
|
|
57
59
|
const FhirValidator = require('fhir-validator-wrapper');
|
|
58
60
|
|
|
59
61
|
async function validateResource() {
|
|
62
|
+
// The JAR will be automatically downloaded if not present
|
|
60
63
|
const validator = new FhirValidator('./validator_cli.jar');
|
|
61
64
|
|
|
62
65
|
try {
|
|
63
|
-
// Start the validator service
|
|
66
|
+
// Start the validator service (auto-downloads JAR if needed)
|
|
64
67
|
await validator.start({
|
|
65
68
|
version: '5.0.0',
|
|
66
69
|
txServer: 'http://tx.fhir.org/r5',
|
|
@@ -89,14 +92,62 @@ async function validateResource() {
|
|
|
89
92
|
|
|
90
93
|
### Constructor
|
|
91
94
|
|
|
92
|
-
#### `new FhirValidator(validatorJarPath)`
|
|
95
|
+
#### `new FhirValidator(validatorJarPath, logger)`
|
|
93
96
|
|
|
94
97
|
Creates a new FHIR validator instance.
|
|
95
98
|
|
|
96
|
-
- `validatorJarPath` (string): Path to the FHIR validator CLI JAR file
|
|
99
|
+
- `validatorJarPath` (string): Path to the FHIR validator CLI JAR file (will be downloaded here if not present)
|
|
100
|
+
- `logger` (Object, optional): Winston logger instance for custom logging
|
|
97
101
|
|
|
98
102
|
### Methods
|
|
99
103
|
|
|
104
|
+
#### `ensureValidator(options)`
|
|
105
|
+
|
|
106
|
+
Checks for and downloads/updates the validator JAR as needed. This is called automatically by `start()` when `autoDownload` is enabled.
|
|
107
|
+
|
|
108
|
+
**Parameters:**
|
|
109
|
+
- `options` (Object, optional): Configuration object
|
|
110
|
+
- `force` (boolean): Force download even if current version is up to date (default: false)
|
|
111
|
+
- `skipUpdateCheck` (boolean): Skip checking for updates if JAR exists (default: false)
|
|
112
|
+
|
|
113
|
+
**Returns:** `Promise<{version: string, updated: boolean, downloaded: boolean}>`
|
|
114
|
+
|
|
115
|
+
**Example:**
|
|
116
|
+
```javascript
|
|
117
|
+
const validator = new FhirValidator('./validator_cli.jar');
|
|
118
|
+
|
|
119
|
+
// Check for updates and download if needed
|
|
120
|
+
const result = await validator.ensureValidator();
|
|
121
|
+
console.log(`Version: ${result.version}`);
|
|
122
|
+
console.log(`Downloaded: ${result.downloaded}`);
|
|
123
|
+
console.log(`Updated: ${result.updated}`);
|
|
124
|
+
|
|
125
|
+
// Force re-download
|
|
126
|
+
await validator.ensureValidator({ force: true });
|
|
127
|
+
|
|
128
|
+
// Skip update check (use existing JAR)
|
|
129
|
+
await validator.ensureValidator({ skipUpdateCheck: true });
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
#### `getLatestRelease()`
|
|
133
|
+
|
|
134
|
+
Fetches the latest release information from GitHub.
|
|
135
|
+
|
|
136
|
+
**Returns:** `Promise<{version: string, downloadUrl: string, publishedAt: string}>`
|
|
137
|
+
|
|
138
|
+
**Example:**
|
|
139
|
+
```javascript
|
|
140
|
+
const latest = await validator.getLatestRelease();
|
|
141
|
+
console.log(`Latest version: ${latest.version}`);
|
|
142
|
+
console.log(`Published: ${latest.publishedAt}`);
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
#### `getInstalledVersion()`
|
|
146
|
+
|
|
147
|
+
Gets the currently installed validator version.
|
|
148
|
+
|
|
149
|
+
**Returns:** `string|null` - The installed version or null if not installed
|
|
150
|
+
|
|
100
151
|
#### `start(config)`
|
|
101
152
|
|
|
102
153
|
Starts the FHIR validator service with the specified configuration.
|
|
@@ -109,6 +160,8 @@ Starts the FHIR validator service with the specified configuration.
|
|
|
109
160
|
- `igs` (string[], optional): Array of implementation guide packages
|
|
110
161
|
- `port` (number, optional): Port to run the service on (default: 8080)
|
|
111
162
|
- `timeout` (number, optional): Startup timeout in milliseconds (default: 30000)
|
|
163
|
+
- `autoDownload` (boolean, optional): Automatically download/update validator JAR (default: true)
|
|
164
|
+
- `skipUpdateCheck` (boolean, optional): Skip checking for updates if JAR exists (default: false)
|
|
112
165
|
|
|
113
166
|
**Returns:** `Promise<void>`
|
|
114
167
|
|
|
@@ -122,7 +175,9 @@ await validator.start({
|
|
|
122
175
|
'hl7.fhir.us.core#6.0.0',
|
|
123
176
|
'hl7.fhir.uv.sdc#3.0.0'
|
|
124
177
|
],
|
|
125
|
-
port: 8080
|
|
178
|
+
port: 8080,
|
|
179
|
+
autoDownload: true, // Download JAR if missing (default)
|
|
180
|
+
skipUpdateCheck: true // Don't check for updates every time
|
|
126
181
|
});
|
|
127
182
|
```
|
|
128
183
|
|
|
@@ -193,6 +248,47 @@ Loads an additional implementation guide at runtime.
|
|
|
193
248
|
await validator.loadIG('hl7.fhir.uv.ips', '1.1.0');
|
|
194
249
|
```
|
|
195
250
|
|
|
251
|
+
#### `runTxTest(params)`
|
|
252
|
+
|
|
253
|
+
Runs a terminology server test against a specified server.
|
|
254
|
+
|
|
255
|
+
**Parameters:**
|
|
256
|
+
- `params` (Object): Test parameters
|
|
257
|
+
- `server` (string): The address of the terminology server to test
|
|
258
|
+
- `suiteName` (string): The suite name that contains the test to run
|
|
259
|
+
- `testName` (string): The test name to run
|
|
260
|
+
- `version` (string): What FHIR version to use for the test
|
|
261
|
+
- `externalFile` (string, optional): Name of messages file
|
|
262
|
+
- `modes` (string, optional): Comma delimited string of modes
|
|
263
|
+
|
|
264
|
+
**Returns:** `Promise<{result: boolean, message?: string}>`
|
|
265
|
+
|
|
266
|
+
**Example:**
|
|
267
|
+
```javascript
|
|
268
|
+
// Run a terminology server test
|
|
269
|
+
const result = await validator.runTxTest({
|
|
270
|
+
server: 'http://tx-dev.fhir.org',
|
|
271
|
+
suiteName: 'metadata',
|
|
272
|
+
testName: 'metadata',
|
|
273
|
+
version: '5.0'
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
if (result.result) {
|
|
277
|
+
console.log('Test passed!');
|
|
278
|
+
} else {
|
|
279
|
+
console.log('Test failed:', result.message);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// With optional parameters
|
|
283
|
+
const result = await validator.runTxTest({
|
|
284
|
+
server: 'http://tx-dev.fhir.org',
|
|
285
|
+
suiteName: 'expand',
|
|
286
|
+
testName: 'expand-test-1',
|
|
287
|
+
version: '5.0',
|
|
288
|
+
modes: 'lenient,tx-resource-cache'
|
|
289
|
+
});
|
|
290
|
+
```
|
|
291
|
+
|
|
196
292
|
#### `stop()`
|
|
197
293
|
|
|
198
294
|
Stops the validator service and cleans up resources.
|
|
@@ -211,6 +307,79 @@ Performs a health check on the running service.
|
|
|
211
307
|
|
|
212
308
|
**Returns:** `Promise<void>`
|
|
213
309
|
|
|
310
|
+
## Automatic JAR Download
|
|
311
|
+
|
|
312
|
+
The library automatically manages the FHIR Validator CLI JAR file:
|
|
313
|
+
|
|
314
|
+
### Default Behavior
|
|
315
|
+
When you call `start()`, the library will:
|
|
316
|
+
1. Check if the JAR file exists at the specified path
|
|
317
|
+
2. If missing, download the latest version from GitHub releases
|
|
318
|
+
3. Track the version in a `.version` file alongside the JAR
|
|
319
|
+
|
|
320
|
+
### Version Tracking
|
|
321
|
+
Version information is stored in `{jarPath}.version`:
|
|
322
|
+
```json
|
|
323
|
+
{
|
|
324
|
+
"version": "6.3.4",
|
|
325
|
+
"downloadUrl": "https://github.com/hapifhir/org.hl7.fhir.core/releases/...",
|
|
326
|
+
"downloadedAt": "2024-01-15T10:30:00.000Z"
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Update Strategies
|
|
331
|
+
|
|
332
|
+
```javascript
|
|
333
|
+
// Always check for updates (default)
|
|
334
|
+
await validator.start({
|
|
335
|
+
version: '5.0.0',
|
|
336
|
+
txServer: 'http://tx.fhir.org/r5',
|
|
337
|
+
txLog: './txlog.txt'
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// Skip update check for faster startup
|
|
341
|
+
await validator.start({
|
|
342
|
+
version: '5.0.0',
|
|
343
|
+
txServer: 'http://tx.fhir.org/r5',
|
|
344
|
+
txLog: './txlog.txt',
|
|
345
|
+
skipUpdateCheck: true
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// Disable auto-download entirely (JAR must exist)
|
|
349
|
+
await validator.start({
|
|
350
|
+
version: '5.0.0',
|
|
351
|
+
txServer: 'http://tx.fhir.org/r5',
|
|
352
|
+
txLog: './txlog.txt',
|
|
353
|
+
autoDownload: false
|
|
354
|
+
});
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Manual Download Management
|
|
358
|
+
|
|
359
|
+
```javascript
|
|
360
|
+
const validator = new FhirValidator('./validator_cli.jar');
|
|
361
|
+
|
|
362
|
+
// Check what's available vs installed
|
|
363
|
+
const latest = await validator.getLatestRelease();
|
|
364
|
+
const installed = validator.getInstalledVersion();
|
|
365
|
+
|
|
366
|
+
console.log(`Latest: ${latest.version}`);
|
|
367
|
+
console.log(`Installed: ${installed || 'not installed'}`);
|
|
368
|
+
|
|
369
|
+
// Download/update without starting service
|
|
370
|
+
const result = await validator.ensureValidator();
|
|
371
|
+
|
|
372
|
+
// Force re-download
|
|
373
|
+
await validator.ensureValidator({ force: true });
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### Download-Only Mode
|
|
377
|
+
|
|
378
|
+
```bash
|
|
379
|
+
# Use the example script to just download/update the JAR
|
|
380
|
+
node example.js --download-only
|
|
381
|
+
```
|
|
382
|
+
|
|
214
383
|
## Implementation Guide Loading
|
|
215
384
|
|
|
216
385
|
Implementation guides can be loaded in two ways:
|
|
@@ -297,6 +466,14 @@ await validator.start({
|
|
|
297
466
|
});
|
|
298
467
|
```
|
|
299
468
|
|
|
469
|
+
5. **Skip Update Checks in CI/CD**: For faster builds, skip update checks:
|
|
470
|
+
```javascript
|
|
471
|
+
await validator.start({
|
|
472
|
+
// ... other config
|
|
473
|
+
skipUpdateCheck: true
|
|
474
|
+
});
|
|
475
|
+
```
|
|
476
|
+
|
|
300
477
|
## Testing
|
|
301
478
|
|
|
302
479
|
The library includes comprehensive tests. You can run them in different ways depending on your setup:
|
|
@@ -306,51 +483,59 @@ The library includes comprehensive tests. You can run them in different ways dep
|
|
|
306
483
|
npm run test:unit
|
|
307
484
|
```
|
|
308
485
|
|
|
309
|
-
###
|
|
486
|
+
### GitHub API Tests (requires network)
|
|
310
487
|
```bash
|
|
311
|
-
|
|
312
|
-
FHIR_VALIDATOR_JAR_PATH=./your-validator.jar npm run test:integration
|
|
488
|
+
GITHUB_API_TESTS=1 npm test
|
|
313
489
|
```
|
|
314
490
|
|
|
315
|
-
###
|
|
491
|
+
### Download Tests (downloads ~300MB JAR)
|
|
316
492
|
```bash
|
|
317
|
-
|
|
318
|
-
FHIR_VALIDATOR_JAR_PATH=./your-validator.jar npm run test:manual
|
|
493
|
+
DOWNLOAD_TESTS=1 npm test
|
|
319
494
|
```
|
|
320
495
|
|
|
321
|
-
###
|
|
322
|
-
|
|
323
|
-
The JAR file location can be configured using the `FHIR_VALIDATOR_JAR_PATH` environment variable:
|
|
324
|
-
|
|
496
|
+
### Integration Tests (requires JAR file and network)
|
|
325
497
|
```bash
|
|
326
|
-
#
|
|
327
|
-
|
|
328
|
-
npm test
|
|
498
|
+
# With auto-download
|
|
499
|
+
INTEGRATION_TESTS=1 npm test
|
|
329
500
|
|
|
330
|
-
#
|
|
331
|
-
FHIR_VALIDATOR_JAR_PATH=./
|
|
332
|
-
|
|
333
|
-
# Option 3: Use npm script helper
|
|
334
|
-
npm run test:with-jar
|
|
501
|
+
# With specific JAR path
|
|
502
|
+
FHIR_VALIDATOR_JAR_PATH=./your-validator.jar INTEGRATION_TESTS=1 npm test
|
|
335
503
|
```
|
|
336
504
|
|
|
337
|
-
|
|
505
|
+
### Manual Testing
|
|
506
|
+
```bash
|
|
507
|
+
# Quick manual test (auto-downloads JAR if needed)
|
|
508
|
+
INTEGRATION_TESTS=1 npm run test:manual
|
|
509
|
+
```
|
|
338
510
|
|
|
339
511
|
## Troubleshooting
|
|
340
512
|
|
|
341
513
|
### Common Issues
|
|
342
514
|
|
|
343
515
|
1. **Java not found**: Ensure Java is installed and available in PATH
|
|
344
|
-
2. **JAR
|
|
516
|
+
2. **JAR download fails**: Check internet connection and GitHub accessibility
|
|
345
517
|
3. **Port conflicts**: Change the port if 8080 is already in use
|
|
346
518
|
4. **Memory issues**: Add JVM options by modifying the spawn command if needed
|
|
347
519
|
5. **Network timeouts**: Increase timeout values for slow networks
|
|
520
|
+
6. **GitHub rate limits**: Use `skipUpdateCheck: true` to avoid repeated API calls
|
|
348
521
|
|
|
349
522
|
### Debug Logging
|
|
350
523
|
|
|
351
|
-
The library logs validator stdout/stderr for debugging.
|
|
524
|
+
The library logs validator stdout/stderr for debugging. You can provide a Winston logger for custom logging:
|
|
525
|
+
|
|
526
|
+
```javascript
|
|
527
|
+
const winston = require('winston');
|
|
528
|
+
const logger = winston.createLogger({
|
|
529
|
+
level: 'debug',
|
|
530
|
+
transports: [new winston.transports.Console()]
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
const validator = new FhirValidator('./validator_cli.jar', logger);
|
|
534
|
+
// Or set later:
|
|
535
|
+
validator.setLogger(logger);
|
|
536
|
+
```
|
|
352
537
|
|
|
353
538
|
## Support
|
|
354
539
|
|
|
355
540
|
For issues with this wrapper, please file a GitHub issue.
|
|
356
|
-
For FHIR validator issues, see the [official FHIR validator documentation](https://confluence.hl7.org/spaces/FHIR/pages/35718580/Using+the+FHIR+Validator).
|
|
541
|
+
For FHIR validator issues, see the [official FHIR validator documentation](https://confluence.hl7.org/spaces/FHIR/pages/35718580/Using+the+FHIR+Validator).
|