fhirsmith 0.9.6 → 0.10.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/CHANGELOG.md +51 -0
- package/library/folder-content-loader.js +91 -0
- package/npmprojector/npmprojector.js +2 -6
- package/package.json +1 -1
- package/packages/package-crawler.js +123 -6
- package/packages/packages.js +104 -0
- package/publisher/publisher.js +290 -12
- package/registry/crawler.js +85 -6
- package/registry/registry.js +24 -7
- package/server.js +11 -2
- package/stats.js +26 -18
- package/translations/Messages.properties +2 -1
- package/tx/cs/cs-cs.js +8 -0
- package/tx/cs/cs-loinc.js +1 -0
- package/tx/cs/cs-provider-list.js +2 -1
- package/tx/cs/cs-snomed.js +142 -59
- package/tx/data/snomed-testing.cache +0 -0
- package/tx/html/home-metrics.liquid +10 -10
- package/tx/library/canonical-resource.js +4 -2
- package/tx/library/codesystem.js +31 -0
- package/tx/library/conceptmap.js +24 -0
- package/tx/library/designations.js +27 -20
- package/tx/library/renderer.js +303 -22
- package/tx/library/ucum-types.js +4 -1
- package/tx/library/valueset.js +46 -0
- package/tx/library.js +65 -21
- package/tx/operation-context.js +122 -27
- package/tx/params.js +36 -8
- package/tx/provider.js +6 -3
- package/tx/tx-html.js +34 -0
- package/tx/tx.js +92 -30
- package/tx/vs/vs-vsac.js +157 -9
- package/tx/workers/cache-control.js +186 -0
- package/tx/workers/{related.js → compare.js} +27 -27
- package/tx/workers/expand.js +100 -96
- package/tx/workers/lookup.js +6 -0
- package/tx/workers/metadata.js +11 -6
- package/tx/workers/read.js +1 -1
- package/tx/workers/translate.js +20 -29
- package/tx/workers/validate.js +18 -10
- package/tx/workers/worker.js +134 -47
- package/tx/xversion/xv-bundle.js +1 -2
- package/tx/xversion/xv-codesystem.js +5 -2
- package/tx/xversion/xv-parameters.js +4 -4
- package/tx/xversion/xv-resource.js +2 -2
- package/tx/xversion/xv-terminologyCapabilities.js +11 -6
- package/tx/xversion/xv-valueset.js +7 -7
- package/publisher/task-draft.js +0 -463
- package/tx/data/OperationDefinition-ValueSet-related.json +0 -133
package/tx/workers/validate.js
CHANGED
|
@@ -124,7 +124,7 @@ class ValueSetChecker {
|
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
126
|
} catch (error) {
|
|
127
|
-
this.log.error(error);
|
|
127
|
+
this.worker.log.error(error);
|
|
128
128
|
debugLog(error);
|
|
129
129
|
throw new Error('Exception expanding value set in order to infer system: ' + error.message);
|
|
130
130
|
}
|
|
@@ -467,7 +467,7 @@ class ValueSetChecker {
|
|
|
467
467
|
return false;
|
|
468
468
|
}
|
|
469
469
|
let cs = await this.worker.findCodeSystem(system, version, this.params, ['complete', 'fragment'], op,true, false, false, this.worker.requiredSupplements);
|
|
470
|
-
this.seeSourceProvider(cs, system);
|
|
470
|
+
this.worker.seeSourceProvider(cs, system);
|
|
471
471
|
if (cs === null) {
|
|
472
472
|
this.worker.opContext.addNote(this.valueSet, 'Didn\'t find CodeSystem "' + this.worker.renderer.displayCoded(system, version) + '"', this.indentCount);
|
|
473
473
|
result = null;
|
|
@@ -563,7 +563,7 @@ class ValueSetChecker {
|
|
|
563
563
|
}
|
|
564
564
|
let msg = await cs.incompleteValidationMessage(ctxt.context, this.params.HTTPLanguages);
|
|
565
565
|
if (msg) {
|
|
566
|
-
op.
|
|
566
|
+
op.addIssue(new Issue('information', 'informational', addToPath(path, 'code'), null, msg, 'process-note'));
|
|
567
567
|
}
|
|
568
568
|
inactive.value = await cs.isInactive(ctxt.context);
|
|
569
569
|
inactive.path = path;
|
|
@@ -824,7 +824,7 @@ class ValueSetChecker {
|
|
|
824
824
|
} else {
|
|
825
825
|
let msg = 'The code system "' + ccc.system + '" version "' + ccc.version + '" in the ValueSet expansion is different to the one in the value ("' + version + '")';
|
|
826
826
|
messages.push(msg);
|
|
827
|
-
op.
|
|
827
|
+
op.addIssue(new Issue('error', 'not-found', addToPath(path, 'version'), null, msg, 'vs-invalid'));
|
|
828
828
|
return false;
|
|
829
829
|
}
|
|
830
830
|
let cs = await this.worker.findCodeSystem(system, v, this.params, ['complete', 'fragment'], op, true, true, false, this.worker.requiredSupplements);
|
|
@@ -1122,7 +1122,7 @@ class ValueSetChecker {
|
|
|
1122
1122
|
if ((cause.value === 'not-found' && contentMode.value !== 'complete') || contentMode.value === 'example') {
|
|
1123
1123
|
let m = 'The system ' + c.system + ' was found but did not contain enough information to properly validate the code "' + c.code + '" ("' + c.display + '") (mode = ' + contentMode.value + ')';
|
|
1124
1124
|
msg(m);
|
|
1125
|
-
op.
|
|
1125
|
+
op.addIssue(new Issue('warning', 'not-found', path, null, m, 'vs-invalid'));
|
|
1126
1126
|
} else if (c.display && list.designations.length > 0) {
|
|
1127
1127
|
await this.checkDisplays(list, defLang, c, msg, op, path);
|
|
1128
1128
|
}
|
|
@@ -1350,14 +1350,18 @@ class ValueSetChecker {
|
|
|
1350
1350
|
if (vstatus.value && vstatus.value !== 'inactive') {
|
|
1351
1351
|
result.addParamCode('status', vstatus.value);
|
|
1352
1352
|
}
|
|
1353
|
+
let mpath = inactive.path;
|
|
1354
|
+
if (!mpath) {
|
|
1355
|
+
mpath = 'code';
|
|
1356
|
+
}
|
|
1353
1357
|
if (!['inactive', 'DISCOURAGED'].includes(vstatus.value)) {
|
|
1354
1358
|
let m = this.worker.i18n.translate('INACTIVE_CONCEPT_FOUND', this.params.HTTPLanguages, ['inactive', tcode]);
|
|
1355
1359
|
msg(m);
|
|
1356
|
-
op.addIssue(new Issue('warning', 'business-rule',
|
|
1360
|
+
op.addIssue(new Issue('warning', 'business-rule', mpath, 'INACTIVE_CONCEPT_FOUND', m, 'code-comment'));
|
|
1357
1361
|
}
|
|
1358
1362
|
let m = this.worker.i18n.translate('INACTIVE_CONCEPT_FOUND', this.params.HTTPLanguages, [vstatus.value, tcode]);
|
|
1359
1363
|
msg(m);
|
|
1360
|
-
op.addIssue(new Issue('warning', 'business-rule',
|
|
1364
|
+
op.addIssue(new Issue('warning', 'business-rule', mpath, 'INACTIVE_CONCEPT_FOUND', m, 'code-comment'));
|
|
1361
1365
|
} else if (vstatus.value && vstatus.value.toLowerCase() === 'deprecated') {
|
|
1362
1366
|
result.addParamCode('status', 'deprecated');
|
|
1363
1367
|
let m = this.worker.i18n.translate('DEPRECATED_CONCEPT_FOUND', this.params.HTTPLanguages, [vstatus.value, tcode]);
|
|
@@ -1414,7 +1418,11 @@ class ValueSetChecker {
|
|
|
1414
1418
|
m = this.worker.i18n.translate('NO_VALID_DISPLAY_AT_ALL', this.params.HTTPLanguages, [c.display, c.system, c.code]);
|
|
1415
1419
|
mid = 'NO_VALID_DISPLAY_AT_ALL';
|
|
1416
1420
|
} else {
|
|
1417
|
-
|
|
1421
|
+
// no displays in the requested language(s): fall back to checking against the
|
|
1422
|
+
// displays + designations in the code system's default language, not just the
|
|
1423
|
+
// single preferred display (see tests validation-simple-*-good-language-none)
|
|
1424
|
+
let hdDef = list.hasDisplay(this.params.workingLanguages(), defLang.value, c.display, false, DisplayCheckingStyle.CASE_INSENSITIVE);
|
|
1425
|
+
if (hdDef.found) {
|
|
1418
1426
|
m = this.worker.i18n.translate('NO_VALID_DISPLAY_FOUND_NONE_FOR_LANG_OK', this.params.HTTPLanguages, [c.display, c.system, c.code, this.params.langSummary(), ds]);
|
|
1419
1427
|
mid = 'NO_VALID_DISPLAY_FOUND_NONE_FOR_LANG_OK';
|
|
1420
1428
|
severity = 'information';
|
|
@@ -1518,14 +1526,14 @@ class ValueSetChecker {
|
|
|
1518
1526
|
} else if (ok === null) {
|
|
1519
1527
|
result.AddParamBool('result', false);
|
|
1520
1528
|
result.addParamStr('message', 'The system "' + system + '" is unknown so the /"' + code + '" cannot be confirmed to be in the value set ' + this.valueSet.name);
|
|
1521
|
-
op.
|
|
1529
|
+
op.addIssue(new Issue('error', cause.value, 'code', null, 'The system "' + system + '" is unknown so the /"' + code + '" cannot be confirmed to be in the value set ' + this.valueSet.name, 'not-found'));
|
|
1522
1530
|
for (let us of unknownSystems) {
|
|
1523
1531
|
result.addParamCanonical('x-caused-by-unknown-system', us);
|
|
1524
1532
|
}
|
|
1525
1533
|
} else {
|
|
1526
1534
|
result.AddParamBool('result', false);
|
|
1527
1535
|
result.addParamStr('message', 'The system/code "' + system + '"/"' + code + '" is not in the value set ' + this.valueSet.name);
|
|
1528
|
-
op.
|
|
1536
|
+
op.addIssue(new Issue('error', cause.value, 'code', null, 'The system/code "' + system + '"/"' + code + '" is not in the value set ' + this.valueSet.name, 'not-in-vs'));
|
|
1529
1537
|
if (cause.value) {
|
|
1530
1538
|
result.AddParamCode('cause', cause.value);
|
|
1531
1539
|
}
|
package/tx/workers/worker.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { TerminologyError} = require('../
|
|
1
|
+
const { TerminologyError} = require('../library/errors');
|
|
2
2
|
const { CodeSystem } = require('../library/codesystem');
|
|
3
3
|
const ValueSet = require('../library/valueset');
|
|
4
4
|
const {VersionUtilities} = require("../../library/version-utilities");
|
|
@@ -8,6 +8,12 @@ const {Languages} = require("../../library/languages");
|
|
|
8
8
|
const {ConceptMap} = require("../library/conceptmap");
|
|
9
9
|
const {Renderer} = require("../library/renderer");
|
|
10
10
|
|
|
11
|
+
// The cache-id travels as an HTTP header (not an operation parameter) so proxies /
|
|
12
|
+
// load-balancers can act on it and the server can reject it before parsing the body.
|
|
13
|
+
// Defined here (the base worker) so both the worker and cache-control share one
|
|
14
|
+
// source of truth without a circular require. Express lower-cases header names.
|
|
15
|
+
const CACHE_ID_HEADER = 'x-cache-id';
|
|
16
|
+
|
|
11
17
|
/**
|
|
12
18
|
* Custom error for terminology setup issues
|
|
13
19
|
*/
|
|
@@ -121,7 +127,11 @@ class TerminologyWorker {
|
|
|
121
127
|
latest = i;
|
|
122
128
|
}
|
|
123
129
|
}
|
|
124
|
-
|
|
130
|
+
const found = matches[latest];
|
|
131
|
+
if (this.additionalResourcesCacheId && this.log) {
|
|
132
|
+
this.log.info(`cache-id '${this.additionalResourcesCacheId}': using cached ${found.resourceType} ${found.url}${found.version ? '|' + found.version : ''} for lookup of ${url}${version ? '|' + version : ''}`);
|
|
133
|
+
}
|
|
134
|
+
return found;
|
|
125
135
|
}
|
|
126
136
|
}
|
|
127
137
|
|
|
@@ -487,50 +497,75 @@ class TerminologyWorker {
|
|
|
487
497
|
* @returns {Object} Parameters resource
|
|
488
498
|
*/
|
|
489
499
|
buildParameters(req) {
|
|
500
|
+
let params;
|
|
501
|
+
|
|
490
502
|
// If POST with Parameters resource, use directly
|
|
491
503
|
if (req.method === 'POST' && req.body && req.body.resourceType === 'Parameters') {
|
|
492
|
-
|
|
493
|
-
}
|
|
494
|
-
if (req.method === 'POST' && req.body && req.body.resourceType) {
|
|
504
|
+
params = req.body;
|
|
505
|
+
} else if (req.method === 'POST' && req.body && req.body.resourceType) {
|
|
495
506
|
let langs = this.languages.parse(req.headers['accept-language']);
|
|
496
507
|
throw new Issue('error', 'invalid', null, 'Wrong_type_for_resource_expected', this.i18n.translate('Wrong_type_for_resource_expected', langs, ["Parameters", req.body.resourceType])).handleAsOO(400);
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
};
|
|
508
|
+
} else {
|
|
509
|
+
// Convert query params or form body to Parameters
|
|
510
|
+
const source = req.method === 'POST' ? {...req.query, ...req.body} : req.query;
|
|
511
|
+
params = {
|
|
512
|
+
resourceType: 'Parameters',
|
|
513
|
+
parameter: []
|
|
514
|
+
};
|
|
505
515
|
|
|
506
|
-
|
|
507
|
-
|
|
516
|
+
for (const [name, value] of Object.entries(source)) {
|
|
517
|
+
if (value === undefined || value === null) continue;
|
|
508
518
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
519
|
+
if (Array.isArray(value)) {
|
|
520
|
+
// Repeating parameter
|
|
521
|
+
for (const v of value) {
|
|
522
|
+
params.parameter.push({name, valueString: String(v)});
|
|
523
|
+
}
|
|
524
|
+
} else if (typeof value === 'object') {
|
|
525
|
+
// Could be a resource or complex type - check resourceType
|
|
526
|
+
if (value.resourceType) {
|
|
527
|
+
params.parameter.push({name, resource: value});
|
|
528
|
+
} else {
|
|
529
|
+
// Assume it's a complex type like Coding or CodeableConcept
|
|
530
|
+
params.parameter.push(this.buildComplexParameter(name, value));
|
|
531
|
+
}
|
|
532
|
+
} else if (value == 'true') {
|
|
533
|
+
params.parameter.push({name, valueBoolean: true});
|
|
534
|
+
} else if (value == 'false') {
|
|
535
|
+
params.parameter.push({name, valueBoolean: false});
|
|
518
536
|
} else {
|
|
519
|
-
|
|
520
|
-
params.parameter.push(this.buildComplexParameter(name, value));
|
|
537
|
+
params.parameter.push({name, valueString: String(value)});
|
|
521
538
|
}
|
|
522
|
-
} else if (value == 'true') {
|
|
523
|
-
params.parameter.push({name, valueBoolean: true});
|
|
524
|
-
} else if (value == 'false') {
|
|
525
|
-
params.parameter.push({name, valueBoolean: false});
|
|
526
|
-
} else {
|
|
527
|
-
params.parameter.push({name, valueString: String(value)});
|
|
528
539
|
}
|
|
529
540
|
}
|
|
530
541
|
|
|
542
|
+
this.applyCacheIdHeader(req, params);
|
|
531
543
|
return params;
|
|
532
544
|
}
|
|
533
545
|
|
|
546
|
+
/**
|
|
547
|
+
* Normalise the cache-id from the `${CACHE_ID_HEADER}` header into a `cache-id`
|
|
548
|
+
* parameter, so all downstream code can read the cache-id the same way whether
|
|
549
|
+
* the client sent it as a header (the going-forward mechanism) or, for now, still
|
|
550
|
+
* as a parameter. If a cache-id parameter is already present it is left as-is, so
|
|
551
|
+
* an explicit parameter wins and there's no surprising override.
|
|
552
|
+
* @param {express.Request} req
|
|
553
|
+
* @param {Object} params - Parameters resource (mutated in place)
|
|
554
|
+
*/
|
|
555
|
+
applyCacheIdHeader(req, params) {
|
|
556
|
+
const headerVal = req && req.headers ? req.headers[CACHE_ID_HEADER] : null;
|
|
557
|
+
if (!headerVal) {
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
if (!params.parameter) {
|
|
561
|
+
params.parameter = [];
|
|
562
|
+
}
|
|
563
|
+
if (params.parameter.some(p => p.name === 'cache-id')) {
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
params.parameter.push({ name: 'cache-id', valueId: String(headerVal) });
|
|
567
|
+
}
|
|
568
|
+
|
|
534
569
|
/**
|
|
535
570
|
* Build a parameter for complex types
|
|
536
571
|
*/
|
|
@@ -559,36 +594,87 @@ class TerminologyWorker {
|
|
|
559
594
|
// ========== Additional Resources Handling ==========
|
|
560
595
|
|
|
561
596
|
/**
|
|
562
|
-
*
|
|
597
|
+
* Collect the resources supplied inline in a Parameters resource: the
|
|
598
|
+
* `tx-resource` parameters, and the primary `valueSet`/`codeSystem` parameters.
|
|
599
|
+
*
|
|
600
|
+
* The primary resource is collected separately because the cache-id protocol
|
|
601
|
+
* needs it retained too: fhir-core sends the main ValueSet as `valueSet` (not
|
|
602
|
+
* `tx-resource`), so if it isn't cached, a later by-reference call can't resolve
|
|
603
|
+
* it ("value set ... could not be found"). A primary resource is only usable by
|
|
604
|
+
* reference if it has a url to key on, so url-less ones are skipped.
|
|
605
|
+
*
|
|
563
606
|
* @param {Object} params - Parameters resource
|
|
607
|
+
* @returns {{txResources: Array, primaryResources: Array}} wrapped resources
|
|
564
608
|
*/
|
|
565
|
-
|
|
566
|
-
if (!params || !params.parameter) return;
|
|
567
|
-
|
|
568
|
-
// Collect tx-resource parameters (resources provided inline)
|
|
609
|
+
collectSuppliedResources(params) {
|
|
569
610
|
const txResources = [];
|
|
611
|
+
const primaryResources = [];
|
|
612
|
+
if (!params || !params.parameter) {
|
|
613
|
+
return { txResources, primaryResources };
|
|
614
|
+
}
|
|
570
615
|
for (const param of params.parameter) {
|
|
571
|
-
this.deadCheck('
|
|
616
|
+
this.deadCheck('collectSuppliedResources');
|
|
572
617
|
if (param.name === 'tx-resource' && param.resource) {
|
|
573
|
-
|
|
618
|
+
const res = this.wrapRawResource(param.resource);
|
|
574
619
|
if (res) {
|
|
575
620
|
txResources.push(res);
|
|
576
621
|
}
|
|
622
|
+
} else if ((param.name === 'valueSet' || param.name === 'codeSystem') && param.resource && param.resource.url) {
|
|
623
|
+
const res = this.wrapRawResource(param.resource);
|
|
624
|
+
if (res) {
|
|
625
|
+
primaryResources.push(res);
|
|
626
|
+
}
|
|
577
627
|
}
|
|
578
628
|
}
|
|
629
|
+
return { txResources, primaryResources };
|
|
630
|
+
}
|
|
579
631
|
|
|
580
|
-
|
|
632
|
+
/**
|
|
633
|
+
* Set up additional resources from tx-resource parameters and cache
|
|
634
|
+
* @param {Object} params - Parameters resource
|
|
635
|
+
*/
|
|
636
|
+
setupAdditionalResources(params) {
|
|
637
|
+
if (!params || !params.parameter) return;
|
|
638
|
+
|
|
639
|
+
// Collect the resources supplied inline on this request (tx-resource plus the
|
|
640
|
+
// primary valueSet/codeSystem). See collectSuppliedResources for why the
|
|
641
|
+
// primary resource is included.
|
|
642
|
+
const { txResources, primaryResources } = this.collectSuppliedResources(params);
|
|
643
|
+
|
|
644
|
+
// Check for cache-id. An explicit cache-id *parameter* wins; otherwise fall
|
|
645
|
+
// back to the cache-id the middleware lifted off the X-Cache-Id header onto
|
|
646
|
+
// the operation context. This fallback is what makes the header work on the
|
|
647
|
+
// op paths that don't route their Parameters through buildParameters
|
|
648
|
+
// (expand, related, batch-validate) or that hand setupAdditionalResources a
|
|
649
|
+
// raw req.body (lookup) - previously those silently ignored a front-loaded
|
|
650
|
+
// cache and failed to resolve by-reference resources.
|
|
581
651
|
const cacheIdParam = this.findParameter(params, 'cache-id');
|
|
582
|
-
const cacheId = cacheIdParam ? this.getParameterValue(cacheIdParam) : null
|
|
652
|
+
const cacheId = (cacheIdParam ? this.getParameterValue(cacheIdParam) : null)
|
|
653
|
+
|| (this.opContext ? this.opContext.cacheId : null)
|
|
654
|
+
|| null;
|
|
583
655
|
|
|
584
656
|
if (cacheId && this.opContext.resourceCache) {
|
|
585
|
-
//
|
|
586
|
-
|
|
587
|
-
|
|
657
|
+
// The cache must already exist: caches are created explicitly via
|
|
658
|
+
// $cache-control?mode=start, which is the only thing that mints a cache-id.
|
|
659
|
+
// A cache-id the server doesn't know is an unambiguous, server-authoritative
|
|
660
|
+
// error condition (never created, or expired / released) - report it with a
|
|
661
|
+
// specific coded issue rather than silently auto-creating a fresh cache and
|
|
662
|
+
// then failing obscurely later when a by-reference resource can't be found.
|
|
663
|
+
if (!this.opContext.resourceCache.has(cacheId)) {
|
|
664
|
+
throw new Issue('error', 'not-found', null, 'CACHE_ID_UNKNOWN',
|
|
665
|
+
this.i18n.translate('CACHE_ID_UNKNOWN', this.opContext.langs, [cacheId]),
|
|
666
|
+
'cache-id-unknown', 404);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// The cache exists: merge any resources supplied on this request into it
|
|
670
|
+
// (incremental population is allowed), then expose the full cache contents.
|
|
671
|
+
const toCache = txResources.concat(primaryResources);
|
|
672
|
+
if (toCache.length > 0) {
|
|
673
|
+
this.opContext.resourceCache.add(cacheId, toCache);
|
|
588
674
|
}
|
|
589
675
|
|
|
590
|
-
// Set additional resources to all resources for this cache-id
|
|
591
676
|
this.additionalResources = this.opContext.resourceCache.get(cacheId);
|
|
677
|
+
this.additionalResourcesCacheId = cacheId;
|
|
592
678
|
} else {
|
|
593
679
|
// No cache-id, just use the tx-resources directly
|
|
594
680
|
this.additionalResources = txResources;
|
|
@@ -708,7 +794,7 @@ class TerminologyWorker {
|
|
|
708
794
|
// Check for various value types
|
|
709
795
|
const valueTypes = [
|
|
710
796
|
'valueString', 'valueCode', 'valueUri', 'valueCanonical', 'valueUrl',
|
|
711
|
-
'valueBoolean', 'valueInteger', 'valueDecimal',
|
|
797
|
+
'valueBoolean', 'valueInteger', 'valueDecimal', 'valueId',
|
|
712
798
|
'valueDateTime', 'valueDate', 'valueTime',
|
|
713
799
|
'valueCoding', 'valueCodeableConcept',
|
|
714
800
|
'valueIdentifier', 'valueQuantity'
|
|
@@ -934,5 +1020,6 @@ class TerminologyWorker {
|
|
|
934
1020
|
|
|
935
1021
|
module.exports = {
|
|
936
1022
|
TerminologyWorker,
|
|
937
|
-
TerminologySetupError
|
|
1023
|
+
TerminologySetupError,
|
|
1024
|
+
CACHE_ID_HEADER
|
|
938
1025
|
};
|
package/tx/xversion/xv-bundle.js
CHANGED
|
@@ -139,9 +139,12 @@ function codeSystemR5ToR3(r5Obj) {
|
|
|
139
139
|
* @private
|
|
140
140
|
*/
|
|
141
141
|
function isR5OnlyFilterOperator(operator) {
|
|
142
|
+
// Operators added in R5 that are not valid in R4 or earlier.
|
|
143
|
+
// NOTE: 'generalizes' is NOT R5-only — it is a valid R4 operator
|
|
144
|
+
// (filter-operator value set, 4.0.1), so it must not be stripped here.
|
|
142
145
|
const r5OnlyOperators = [
|
|
143
|
-
'
|
|
144
|
-
//
|
|
146
|
+
'child-of', // Added in R5
|
|
147
|
+
'descendent-leaf', // Added in R5
|
|
145
148
|
];
|
|
146
149
|
return r5OnlyOperators.includes(operator);
|
|
147
150
|
}
|
|
@@ -18,7 +18,7 @@ function parametersToR5(jsonObj, sourceVersion) {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
const {convertResourceToR5} = require("./xv-resource");
|
|
21
|
-
for (let p of jsonObj.parameter) {
|
|
21
|
+
for (let p of jsonObj.parameter || []) {
|
|
22
22
|
if (p.resource) {
|
|
23
23
|
p.resource = convertResourceToR5(p.resource, sourceVersion);
|
|
24
24
|
}
|
|
@@ -59,7 +59,7 @@ function parametersFromR5(r5Obj, targetVersion) {
|
|
|
59
59
|
function parametersR5ToR4(r5Obj) {
|
|
60
60
|
const {convertResourceFromR5} = require("./xv-resource");
|
|
61
61
|
|
|
62
|
-
for (let p of r5Obj.parameter) {
|
|
62
|
+
for (let p of r5Obj.parameter || []) {
|
|
63
63
|
if (p.resource) {
|
|
64
64
|
p.resource = convertResourceFromR5(p.resource, "R4");
|
|
65
65
|
}
|
|
@@ -71,7 +71,7 @@ function parametersR5ToR4(r5Obj) {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
function convertResourceWithinR5(r5Obj) {
|
|
74
|
-
for (let p of r5Obj.parameter) {
|
|
74
|
+
for (let p of r5Obj.parameter || []) {
|
|
75
75
|
if (p.name == 'match') {
|
|
76
76
|
fixMatchParameterfor5(p);
|
|
77
77
|
}
|
|
@@ -135,7 +135,7 @@ function convertParameterR5ToR3(p) {
|
|
|
135
135
|
function parametersR5ToR3(r5Obj) {
|
|
136
136
|
const {convertResourceFromR5} = require("./xv-resource");
|
|
137
137
|
|
|
138
|
-
for (let p of r5Obj.parameter) {
|
|
138
|
+
for (let p of r5Obj.parameter || []) {
|
|
139
139
|
if (p.resource) {
|
|
140
140
|
p.resource = convertResourceFromR5(p.resource, "R3");
|
|
141
141
|
}
|
|
@@ -9,7 +9,7 @@ const {bundleFromR5, bundleToR5} = require("./xv-bundle");
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
function convertResourceToR5(data, sourceVersion) {
|
|
12
|
-
if (sourceVersion == "5.0" || !data.resourceType) {
|
|
12
|
+
if (!data || sourceVersion == "5.0" || !data.resourceType) {
|
|
13
13
|
return data;
|
|
14
14
|
}
|
|
15
15
|
switch (data.resourceType) {
|
|
@@ -26,7 +26,7 @@ function convertResourceToR5(data, sourceVersion) {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
function convertResourceFromR5(data, targetVersion) {
|
|
29
|
-
if (targetVersion == "5.0" || !data.resourceType) {
|
|
29
|
+
if (!data || targetVersion == "5.0" || !data.resourceType) {
|
|
30
30
|
return data;
|
|
31
31
|
}
|
|
32
32
|
switch (data.resourceType) {
|
|
@@ -15,17 +15,22 @@ function terminologyCapabilitiesToR5(jsonObj, sourceVersion) {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
if (VersionUtilities.isR4Ver(sourceVersion)) {
|
|
18
|
+
const contentExtUrl = "http://hl7.org/fhir/5.0/StructureDefinition/extension-TerminologyCapabilities.codeSystem.content";
|
|
18
19
|
for (const cs of jsonObj.codeSystem || []) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
// In R4 the content is carried only by the content extension (there is no
|
|
21
|
+
// native codeSystem.content), so read it from the extension directly.
|
|
22
|
+
const cnt = Extensions.readString(cs, contentExtUrl);
|
|
23
|
+
if (cnt) {
|
|
24
|
+
cs.content = cnt;
|
|
25
|
+
// Remove the now-redundant source extension (the field is `extension`,
|
|
26
|
+
// singular). Filter so any unrelated extensions are preserved.
|
|
27
|
+
cs.extension = (cs.extension || []).filter(e => e.url !== contentExtUrl);
|
|
28
|
+
if (cs.extension.length === 0) {
|
|
29
|
+
delete cs.extension;
|
|
24
30
|
}
|
|
25
31
|
}
|
|
26
32
|
}
|
|
27
33
|
|
|
28
|
-
|
|
29
34
|
return jsonObj;
|
|
30
35
|
}
|
|
31
36
|
|
|
@@ -14,10 +14,10 @@ function valueSetToR5(jsonObj, sourceVersion) {
|
|
|
14
14
|
if (VersionUtilities.isR5Ver(sourceVersion)) {
|
|
15
15
|
return jsonObj; // No conversion needed
|
|
16
16
|
}
|
|
17
|
-
for (const inc of jsonObj.compose
|
|
17
|
+
for (const inc of jsonObj.compose?.include || []) {
|
|
18
18
|
valueSetIncludeToR5(inc);
|
|
19
19
|
}
|
|
20
|
-
for (const inc of jsonObj.compose
|
|
20
|
+
for (const inc of jsonObj.compose?.exclude || []) {
|
|
21
21
|
valueSetIncludeToR5(inc);
|
|
22
22
|
}
|
|
23
23
|
if (VersionUtilities.isR4Ver(sourceVersion)) {
|
|
@@ -90,7 +90,7 @@ function valueSetR5ToR4(r5Obj) {
|
|
|
90
90
|
if (include.filter && Array.isArray(include.filter)) {
|
|
91
91
|
include.filter = include.filter.map(filter => {
|
|
92
92
|
if (filter.op && isR5OnlyFilterOperator(filter.op)) {
|
|
93
|
-
filter._op = { "extension": "http://hl7.org/fhir/5.0/StructureDefinition/extension-ValueSet.compose.include.filter.op", "valueCode": filter.op}
|
|
93
|
+
filter._op = { "extension": [{ url: "http://hl7.org/fhir/5.0/StructureDefinition/extension-ValueSet.compose.include.filter.op", "valueCode": filter.op}]};
|
|
94
94
|
delete filter.op;
|
|
95
95
|
}
|
|
96
96
|
return filter;
|
|
@@ -105,7 +105,7 @@ function valueSetR5ToR4(r5Obj) {
|
|
|
105
105
|
if (exclude.filter && Array.isArray(exclude.filter)) {
|
|
106
106
|
exclude.filter = exclude.filter.map(filter => {
|
|
107
107
|
if (filter.op && isR5OnlyFilterOperator(filter.op)) {
|
|
108
|
-
filter._op = { "extension": "http://hl7.org/fhir/5.0/StructureDefinition/extension-ValueSet.compose.include.filter.op", "valueCode": filter.op}
|
|
108
|
+
filter._op = { "extension": [{ url: "http://hl7.org/fhir/5.0/StructureDefinition/extension-ValueSet.compose.include.filter.op", "valueCode": filter.op}]};
|
|
109
109
|
delete filter.op;
|
|
110
110
|
}
|
|
111
111
|
return filter;
|
|
@@ -155,7 +155,7 @@ function valueSetR5ToR3(r5Obj) {
|
|
|
155
155
|
if (include.filter && Array.isArray(include.filter)) {
|
|
156
156
|
include.filter = include.filter.map(filter => {
|
|
157
157
|
if (filter.op && !isR3CompatibleFilterOperator(filter.op)) {
|
|
158
|
-
filter._op = { "extension": "http://hl7.org/fhir/5.0/StructureDefinition/extension-ValueSet.compose.include.filter.op", "valueCode": filter.op}
|
|
158
|
+
filter._op = { "extension": [{ url: "http://hl7.org/fhir/5.0/StructureDefinition/extension-ValueSet.compose.include.filter.op", "valueCode": filter.op}]};
|
|
159
159
|
delete filter.op;
|
|
160
160
|
}
|
|
161
161
|
return filter;
|
|
@@ -170,7 +170,7 @@ function valueSetR5ToR3(r5Obj) {
|
|
|
170
170
|
if (exclude.filter && Array.isArray(exclude.filter)) {
|
|
171
171
|
exclude.filter = exclude.filter.map(filter => {
|
|
172
172
|
if (filter.op && !isR3CompatibleFilterOperator(filter.op)) {
|
|
173
|
-
filter._op = { "extension": "http://hl7.org/fhir/5.0/StructureDefinition/extension-ValueSet.compose.include.filter.op", "valueCode": filter.op}
|
|
173
|
+
filter._op = { "extension": [{ url: "http://hl7.org/fhir/5.0/StructureDefinition/extension-ValueSet.compose.include.filter.op", "valueCode": filter.op}]};
|
|
174
174
|
delete filter.op;
|
|
175
175
|
}
|
|
176
176
|
return filter;
|
|
@@ -223,7 +223,7 @@ function convertContainsPropertyR5ToR4(containsList) {
|
|
|
223
223
|
*/
|
|
224
224
|
function isR5OnlyFilterOperator(operator) {
|
|
225
225
|
const r5OnlyOperators = [
|
|
226
|
-
'child-of', '
|
|
226
|
+
'child-of', 'descendent-leaf' // Added in R5
|
|
227
227
|
];
|
|
228
228
|
return r5OnlyOperators.includes(operator);
|
|
229
229
|
}
|