fhirsmith 0.9.7 → 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 +31 -0
- package/package.json +1 -1
- package/packages/package-crawler.js +123 -6
- package/packages/packages.js +104 -0
- package/publisher/publisher.js +191 -5
- package/registry/crawler.js +85 -6
- package/registry/registry.js +18 -1
- package/server.js +5 -0
- package/stats.js +26 -18
- package/translations/Messages.properties +1 -0
- package/tx/html/home-metrics.liquid +10 -10
- package/tx/library/codesystem.js +31 -0
- package/tx/library/conceptmap.js +24 -0
- package/tx/library/valueset.js +46 -0
- package/tx/operation-context.js +109 -4
- package/tx/tx-html.js +27 -0
- package/tx/tx.js +80 -17
- package/tx/workers/cache-control.js +186 -0
- package/tx/workers/{related.js → compare.js} +27 -27
- package/tx/workers/metadata.js +11 -6
- package/tx/workers/worker.js +133 -46
- package/tx/data/OperationDefinition-ValueSet-related.json +0 -133
package/tx/workers/worker.js
CHANGED
|
@@ -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
|
};
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"resourceType": "OperationDefinition",
|
|
3
|
-
"id": "ValueSet-related",
|
|
4
|
-
"url": "http://hl7.org/fhir/OperationDefinition/ValueSet-related",
|
|
5
|
-
"version": "5.0.0",
|
|
6
|
-
"name": "ValueSetRelated",
|
|
7
|
-
"title": "Value Set Related Determination",
|
|
8
|
-
"status": "active",
|
|
9
|
-
"kind": "operation",
|
|
10
|
-
"experimental": false,
|
|
11
|
-
"date": "2023-03-26T15:21:02+11:00",
|
|
12
|
-
"publisher": "FHIRsmith",
|
|
13
|
-
"description": "Determine the relationship between two value sets. Different versions of code systems are considered compatible unless versionNeeded = true for the code system",
|
|
14
|
-
"jurisdiction": [
|
|
15
|
-
{
|
|
16
|
-
"coding": [
|
|
17
|
-
{
|
|
18
|
-
"system": "http://unstats.un.org/unsd/methods/m49/m49.htm",
|
|
19
|
-
"code": "001",
|
|
20
|
-
"display": "World"
|
|
21
|
-
}
|
|
22
|
-
]
|
|
23
|
-
}
|
|
24
|
-
],
|
|
25
|
-
"affectsState": false,
|
|
26
|
-
"code": "related",
|
|
27
|
-
"comment": "An $expand will be performed internally if needed.",
|
|
28
|
-
"resource": [
|
|
29
|
-
"ValueSet"
|
|
30
|
-
],
|
|
31
|
-
"system": false,
|
|
32
|
-
"type": true,
|
|
33
|
-
"instance": false,
|
|
34
|
-
"parameter": [
|
|
35
|
-
{
|
|
36
|
-
"name": "thisUrl",
|
|
37
|
-
"use": "in",
|
|
38
|
-
"scope": [
|
|
39
|
-
"type"
|
|
40
|
-
],
|
|
41
|
-
"min": 0,
|
|
42
|
-
"max": "1",
|
|
43
|
-
"documentation": "Value set Canonical URL for the first value set of the pair (the base). The server must know the value set (e.g. it is provided as an attached resource, it is defined explicitly in the server's value sets, or it is defined implicitly by some code system known to the server",
|
|
44
|
-
"type": "uri"
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
"name": "otherUrl",
|
|
48
|
-
"use": "in",
|
|
49
|
-
"scope": [
|
|
50
|
-
"type"
|
|
51
|
-
],
|
|
52
|
-
"min": 0,
|
|
53
|
-
"max": "1",
|
|
54
|
-
"documentation": "Value set Canonical URL for the second value set of the pair (the one being compared to base). The server must know the value set (e.g. it is provided as an attached resource, it is defined explicitly in the server's value sets, or it is defined implicitly by some code system known to the server",
|
|
55
|
-
"type": "uri"
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
"name": "thisValueSet",
|
|
59
|
-
"use": "in",
|
|
60
|
-
"scope": [
|
|
61
|
-
"type"
|
|
62
|
-
],
|
|
63
|
-
"min": 0,
|
|
64
|
-
"max": "1",
|
|
65
|
-
"documentation": "The first value set is provided directly as part of the request. Servers may choose not to accept value sets in this fashion. This parameter is used when the client wants the server to expand a value set that is not stored on the server",
|
|
66
|
-
"type": "ValueSet"
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
"name": "otherValueSet",
|
|
70
|
-
"use": "in",
|
|
71
|
-
"scope": [
|
|
72
|
-
"type"
|
|
73
|
-
],
|
|
74
|
-
"min": 0,
|
|
75
|
-
"max": "1",
|
|
76
|
-
"documentation": "The other value set is provided directly as part of the request. Servers may choose not to accept value sets in this fashion. This parameter is used when the client wants the server to expand a value set that is not stored on the server",
|
|
77
|
-
"type": "ValueSet"
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
"name": "thisVersion",
|
|
81
|
-
"use": "in",
|
|
82
|
-
"scope": [
|
|
83
|
-
"type"
|
|
84
|
-
],
|
|
85
|
-
"min": 0,
|
|
86
|
-
"max": "1",
|
|
87
|
-
"documentation": "The identifier that is used to identify a specific version of the value set to be used when validating the code. This is an arbitrary value managed by the value set author and is not expected to be globally unique. For example, it might be a timestamp (e.g. yyyymmdd) if a managed version is not available.",
|
|
88
|
-
"type": "string"
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
"name": "otherVersion",
|
|
92
|
-
"use": "in",
|
|
93
|
-
"scope": [
|
|
94
|
-
"type"
|
|
95
|
-
],
|
|
96
|
-
"min": 0,
|
|
97
|
-
"max": "1",
|
|
98
|
-
"documentation": "The identifier that is used to identify a specific version of the value set to be used when validating the code. This is an arbitrary value managed by the value set author and is not expected to be globally unique. For example, it might be a timestamp (e.g. yyyymmdd) if a managed version is not available.",
|
|
99
|
-
"type": "string"
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
"name": "useSupplement",
|
|
103
|
-
"use": "in",
|
|
104
|
-
"min": 0,
|
|
105
|
-
"max": "*",
|
|
106
|
-
"documentation": "The supplement must be used when validating the code. Use of this parameter should result in $validate-code behaving the same way as if the supplements were included in the value set definition using the [http://hl7.org/fhir/StructureDefinition/valueset-supplement](http://hl7.org/fhir/extensions/StructureDefinition-valueset-supplement.html)",
|
|
107
|
-
"type": "canonical"
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
"name": "diagnostics",
|
|
111
|
-
"use": "in",
|
|
112
|
-
"min": 0,
|
|
113
|
-
"max": "1",
|
|
114
|
-
"documentation": "Whether to return information about the reasoning process"
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
"name": "result",
|
|
118
|
-
"use": "out",
|
|
119
|
-
"min": 1,
|
|
120
|
-
"max": "1",
|
|
121
|
-
"documentation": "The relationship between the ValueSets. One of: same, superset, subset, overlapping, dsjoint, empty, and indeterminate",
|
|
122
|
-
"type": "boolean"
|
|
123
|
-
},
|
|
124
|
-
{
|
|
125
|
-
"name": "message",
|
|
126
|
-
"use": "out",
|
|
127
|
-
"min": 0,
|
|
128
|
-
"max": "1",
|
|
129
|
-
"documentation": "Explanation of the code, with reason if appropriate",
|
|
130
|
-
"type": "string"
|
|
131
|
-
}
|
|
132
|
-
]
|
|
133
|
-
}
|