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/tx-html.js
CHANGED
|
@@ -471,6 +471,33 @@ class TxHtmlRenderer {
|
|
|
471
471
|
if (param.valueCode !== undefined) {
|
|
472
472
|
return `<code>${escape(param.valueCode)}</code>`;
|
|
473
473
|
}
|
|
474
|
+
if (param.valueId !== undefined) {
|
|
475
|
+
return escape(String(param.valueId));
|
|
476
|
+
}
|
|
477
|
+
if (param.valueOid !== undefined) {
|
|
478
|
+
return escape(String(param.valueOid));
|
|
479
|
+
}
|
|
480
|
+
if (param.valueUuid !== undefined) {
|
|
481
|
+
return escape(String(param.valueUuid));
|
|
482
|
+
}
|
|
483
|
+
if (param.valueMarkdown !== undefined) {
|
|
484
|
+
// Render markdown to HTML the same way the rest of the server does, using
|
|
485
|
+
// commonmark in safe mode (raw HTML in the markdown is escaped, so this is
|
|
486
|
+
// XSS-safe).
|
|
487
|
+
const commonmark = require('commonmark');
|
|
488
|
+
const reader = new commonmark.Parser();
|
|
489
|
+
const writer = new commonmark.HtmlRenderer({ safe: true });
|
|
490
|
+
return writer.render(reader.parse(String(param.valueMarkdown)));
|
|
491
|
+
}
|
|
492
|
+
if (param.valueInteger64 !== undefined) {
|
|
493
|
+
return escape(String(param.valueInteger64));
|
|
494
|
+
}
|
|
495
|
+
if (param.valuePositiveInt !== undefined) {
|
|
496
|
+
return escape(String(param.valuePositiveInt));
|
|
497
|
+
}
|
|
498
|
+
if (param.valueUnsignedInt !== undefined) {
|
|
499
|
+
return escape(String(param.valueUnsignedInt));
|
|
500
|
+
}
|
|
474
501
|
if (param.valueDate !== undefined) {
|
|
475
502
|
return escape(param.valueDate);
|
|
476
503
|
}
|
package/tx/tx.js
CHANGED
|
@@ -27,6 +27,7 @@ const LookupWorker = require('./workers/lookup');
|
|
|
27
27
|
const SubsumesWorker = require('./workers/subsumes');
|
|
28
28
|
const { MetadataHandler } = require('./workers/metadata');
|
|
29
29
|
const { BatchValidateWorker } = require('./workers/batch-validate');
|
|
30
|
+
const { CacheControlWorker } = require('./workers/cache-control');
|
|
30
31
|
const {CapabilityStatementXML} = require("./xml/capabilitystatement-xml");
|
|
31
32
|
const {TerminologyCapabilitiesXML} = require("./xml/terminologycapabilities-xml");
|
|
32
33
|
const {ParametersXML} = require("./xml/parameters-xml");
|
|
@@ -36,7 +37,7 @@ const {ConceptMapXML} = require("./xml/conceptmap-xml");
|
|
|
36
37
|
const {TxHtmlRenderer} = require("./tx-html");
|
|
37
38
|
const {Renderer} = require("./library/renderer");
|
|
38
39
|
const {OperationsWorker} = require("./workers/operations");
|
|
39
|
-
const {
|
|
40
|
+
const {CompareWorker} = require("./workers/compare");
|
|
40
41
|
const {codeSystemFromR5} = require("./xversion/xv-codesystem");
|
|
41
42
|
const {operationOutcomeFromR5} = require("./xversion/xv-operationoutcome");
|
|
42
43
|
const {parametersFromR5} = require("./xversion/xv-parameters");
|
|
@@ -296,6 +297,12 @@ class TXModule {
|
|
|
296
297
|
endpointInfo.resourceCache, endpointInfo.expansionCache
|
|
297
298
|
);
|
|
298
299
|
opContext.usageTracker = this.usageTracker;
|
|
300
|
+
// Normalise the cache-id from the X-Cache-Id header onto the operation
|
|
301
|
+
// context once, here, so every worker honours a front-loaded cache no
|
|
302
|
+
// matter how it later assembles its Parameters (some use buildParameters,
|
|
303
|
+
// others read req.body / query directly). An explicit cache-id parameter
|
|
304
|
+
// on the request still wins (see setupAdditionalResources).
|
|
305
|
+
opContext.cacheId = req.get('X-Cache-Id') || null;
|
|
299
306
|
|
|
300
307
|
// Attach everything to request
|
|
301
308
|
req.txProvider = endpointInfo.provider;
|
|
@@ -595,23 +602,23 @@ class TXModule {
|
|
|
595
602
|
}
|
|
596
603
|
});
|
|
597
604
|
|
|
598
|
-
// ValueSet/$
|
|
599
|
-
router.get('/ValueSet/\\$
|
|
605
|
+
// ValueSet/$compare(GET and POST)
|
|
606
|
+
router.get('/ValueSet/\\$compare', async (req, res) => {
|
|
600
607
|
const start = Date.now();
|
|
601
608
|
try {
|
|
602
|
-
let worker = new
|
|
609
|
+
let worker = new CompareWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
|
|
603
610
|
await worker.handle(req, res);
|
|
604
611
|
} finally {
|
|
605
|
-
this.countRequest('$
|
|
612
|
+
this.countRequest('$compare', Date.now() - start);
|
|
606
613
|
}
|
|
607
614
|
});
|
|
608
|
-
router.post('/ValueSet/\\$
|
|
615
|
+
router.post('/ValueSet/\\$compare', async (req, res) => {
|
|
609
616
|
const start = Date.now();
|
|
610
617
|
try {
|
|
611
|
-
let worker = new
|
|
618
|
+
let worker = new CompareWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
|
|
612
619
|
await worker.handle(req, res);
|
|
613
620
|
} finally {
|
|
614
|
-
this.countRequest('$
|
|
621
|
+
this.countRequest('$compare', Date.now() - start);
|
|
615
622
|
}
|
|
616
623
|
});
|
|
617
624
|
|
|
@@ -655,6 +662,26 @@ class TXModule {
|
|
|
655
662
|
}
|
|
656
663
|
});
|
|
657
664
|
|
|
665
|
+
// $cache-control (GET and POST) - base-level operation: mode=start|end
|
|
666
|
+
router.get('/\\$cache-control', async (req, res) => {
|
|
667
|
+
const start = Date.now();
|
|
668
|
+
try {
|
|
669
|
+
let worker = new CacheControlWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
|
|
670
|
+
await worker.handle(req, res, this.log);
|
|
671
|
+
} finally {
|
|
672
|
+
this.countRequest('$cache-control', Date.now() - start);
|
|
673
|
+
}
|
|
674
|
+
});
|
|
675
|
+
router.post('/\\$cache-control', async (req, res) => {
|
|
676
|
+
const start = Date.now();
|
|
677
|
+
try {
|
|
678
|
+
let worker = new CacheControlWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
|
|
679
|
+
await worker.handle(req, res, this.log);
|
|
680
|
+
} finally {
|
|
681
|
+
this.countRequest('$cache-control', Date.now() - start);
|
|
682
|
+
}
|
|
683
|
+
});
|
|
684
|
+
|
|
658
685
|
// ConceptMap/$translate (GET and POST)
|
|
659
686
|
router.get('/ConceptMap/\\$translate', async (req, res) => {
|
|
660
687
|
const start = Date.now();
|
|
@@ -779,23 +806,23 @@ class TXModule {
|
|
|
779
806
|
});
|
|
780
807
|
|
|
781
808
|
|
|
782
|
-
// ValueSet/[id]/$
|
|
783
|
-
router.get('/ValueSet/:id/\\$
|
|
809
|
+
// ValueSet/[id]/$compare
|
|
810
|
+
router.get('/ValueSet/:id/\\$compare', async (req, res) => {
|
|
784
811
|
const start = Date.now();
|
|
785
812
|
try {
|
|
786
|
-
let worker = new
|
|
813
|
+
let worker = new CompareWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
|
|
787
814
|
await worker.handleInstance(req, res, this.log);
|
|
788
815
|
} finally {
|
|
789
|
-
this.countRequest('$
|
|
816
|
+
this.countRequest('$compare', Date.now() - start);
|
|
790
817
|
}
|
|
791
818
|
});
|
|
792
|
-
router.post('/ValueSet/:id/\\$
|
|
819
|
+
router.post('/ValueSet/:id/\\$compare', async (req, res) => {
|
|
793
820
|
const start = Date.now();
|
|
794
821
|
try {
|
|
795
|
-
let worker = new
|
|
822
|
+
let worker = new CompareWorker(req.txOpContext, this.log, req.txProvider, this.languages, this.i18n);
|
|
796
823
|
await worker.handleInstance(req, res, this.log);
|
|
797
824
|
} finally {
|
|
798
|
-
this.countRequest('$
|
|
825
|
+
this.countRequest('$compare', Date.now() - start);
|
|
799
826
|
}
|
|
800
827
|
});
|
|
801
828
|
|
|
@@ -1177,10 +1204,46 @@ class TXModule {
|
|
|
1177
1204
|
}
|
|
1178
1205
|
}
|
|
1179
1206
|
|
|
1180
|
-
|
|
1207
|
+
// Number of entries in the expansion cache, summed across endpoints.
|
|
1208
|
+
expansionItemCount() {
|
|
1209
|
+
let count = 0;
|
|
1210
|
+
for (let ep of this.endpoints) {
|
|
1211
|
+
count = count + ep.expansionCache.size();
|
|
1212
|
+
}
|
|
1213
|
+
return count;
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
// Number of concepts held in the client (resource) cache, summed across endpoints.
|
|
1217
|
+
clientConceptCount() {
|
|
1218
|
+
let count = 0;
|
|
1219
|
+
for (let ep of this.endpoints) {
|
|
1220
|
+
count = count + ep.resourceCache.conceptCount();
|
|
1221
|
+
}
|
|
1222
|
+
return count;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
// Number of caches (cache-ids) currently held in the client cache, across endpoints.
|
|
1226
|
+
clientCacheCount() {
|
|
1227
|
+
let count = 0;
|
|
1228
|
+
for (let ep of this.endpoints) {
|
|
1229
|
+
count = count + ep.resourceCache.size();
|
|
1230
|
+
}
|
|
1231
|
+
return count;
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
// High-water marks for the client cache, summed across endpoints.
|
|
1235
|
+
maxClientCacheCount() {
|
|
1236
|
+
let count = 0;
|
|
1237
|
+
for (let ep of this.endpoints) {
|
|
1238
|
+
count = count + ep.resourceCache.maxSize();
|
|
1239
|
+
}
|
|
1240
|
+
return count;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
maxClientConceptCount() {
|
|
1181
1244
|
let count = 0;
|
|
1182
1245
|
for (let ep of this.endpoints) {
|
|
1183
|
-
count = count + ep.resourceCache.
|
|
1246
|
+
count = count + ep.resourceCache.maxConceptCount();
|
|
1184
1247
|
}
|
|
1185
1248
|
return count;
|
|
1186
1249
|
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Cache Control Worker - Handles the $cache-control operation
|
|
3
|
+
//
|
|
4
|
+
// GET /$cache-control?mode=start - create a cache, return its (server-issued) id
|
|
5
|
+
// POST /$cache-control?mode=start - as above, optionally front-loading resources
|
|
6
|
+
// GET /$cache-control?mode=end - tell the server it can release the cache now
|
|
7
|
+
// POST /$cache-control?mode=end
|
|
8
|
+
// (mode=check is reserved for later - report whether a cache is still valid + stats)
|
|
9
|
+
//
|
|
10
|
+
// This is the explicit replacement for the implicit `cache-id` parameter protocol:
|
|
11
|
+
// the server owns the cache-id namespace, so it can authoritatively reject an
|
|
12
|
+
// unknown/expired cache later instead of failing obscurely deep inside a validation.
|
|
13
|
+
//
|
|
14
|
+
// NOTE: this is scaffolding. start()/end() currently parse the request into a
|
|
15
|
+
// Parameters resource (the same way validate.js does) but do not yet create or
|
|
16
|
+
// release anything. The behaviour is filled in by later steps.
|
|
17
|
+
//
|
|
18
|
+
|
|
19
|
+
const crypto = require('crypto');
|
|
20
|
+
const { TerminologyWorker, CACHE_ID_HEADER } = require('./worker');
|
|
21
|
+
const { Parameters } = require('../library/parameters');
|
|
22
|
+
const { debugLog } = require('../operation-context');
|
|
23
|
+
|
|
24
|
+
class CacheControlWorker extends TerminologyWorker {
|
|
25
|
+
/**
|
|
26
|
+
* @param {OperationContext} opContext - Operation context
|
|
27
|
+
* @param {Logger} log - Logger instance
|
|
28
|
+
* @param {Provider} provider - Provider for code systems and resources
|
|
29
|
+
* @param {LanguageDefinitions} languages - Language definitions
|
|
30
|
+
* @param {I18nSupport} i18n - Internationalization support
|
|
31
|
+
*/
|
|
32
|
+
constructor(opContext, log, provider, languages, i18n) {
|
|
33
|
+
super(opContext, log, provider, languages, i18n);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
opName() {
|
|
37
|
+
return 'cache-control';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Not a value-set operation; the base class requires this to be implemented.
|
|
41
|
+
vsHandle() {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Express entry point for /$cache-control (GET and POST).
|
|
47
|
+
* Dispatches on the `mode` query parameter.
|
|
48
|
+
* @param {express.Request} req - Express request
|
|
49
|
+
* @param {express.Response} res - Express response
|
|
50
|
+
*/
|
|
51
|
+
async handle(req, res) {
|
|
52
|
+
try {
|
|
53
|
+
const mode = this.getMode(req);
|
|
54
|
+
// GET is accepted as well as POST, for consistency with the server's other
|
|
55
|
+
// operations (which are all browsable) and the convenience of poking
|
|
56
|
+
// $cache-control from a browser. The operation is declared affectsState=true
|
|
57
|
+
// in its OperationDefinition, so conformant clients POST; an accidental
|
|
58
|
+
// GET-created cache is an empty entry that self-expires.
|
|
59
|
+
switch (mode) {
|
|
60
|
+
case 'start':
|
|
61
|
+
return await this.start(req, res);
|
|
62
|
+
case 'end':
|
|
63
|
+
return await this.end(req, res);
|
|
64
|
+
default:
|
|
65
|
+
return res.status(400).json(this.operationOutcome('error', 'invalid',
|
|
66
|
+
`$cache-control requires a 'mode' of 'start' or 'end'` +
|
|
67
|
+
(mode ? ` (got '${mode}')` : ` (none supplied)`)));
|
|
68
|
+
}
|
|
69
|
+
} catch (error) {
|
|
70
|
+
this.log.error(error);
|
|
71
|
+
debugLog(error);
|
|
72
|
+
req.logInfo = this.usedSources.join('|') + ' - error' + (error.msgId ? ' ' + error.msgId : '');
|
|
73
|
+
const statusCode = error.statusCode || 500;
|
|
74
|
+
const issueCode = error.issueCode || 'exception';
|
|
75
|
+
return res.status(statusCode).json(this.operationOutcome('error', issueCode, error.message));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Determine the requested mode. The cache-control mode travels in the query
|
|
81
|
+
* string (?mode=start) so it survives even when a Parameters resource is POSTed
|
|
82
|
+
* as the body; a `mode` parameter in the body is accepted as a fallback.
|
|
83
|
+
* @param {express.Request} req
|
|
84
|
+
* @returns {string|null}
|
|
85
|
+
*/
|
|
86
|
+
getMode(req) {
|
|
87
|
+
if (req.query && req.query.mode) {
|
|
88
|
+
return String(req.query.mode);
|
|
89
|
+
}
|
|
90
|
+
if (req.body && req.body.resourceType === 'Parameters') {
|
|
91
|
+
const p = this.findParameter(req.body, 'mode');
|
|
92
|
+
if (p) {
|
|
93
|
+
return this.getParameterValue(p);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* mode=start: mint a server-issued cache-id, create the (per-endpoint) cache,
|
|
101
|
+
* front-load any supplied resources into it, and return the id in the body.
|
|
102
|
+
*
|
|
103
|
+
* The id is returned in the body rather than a header to keep it readable by
|
|
104
|
+
* browser clients without CORS expose-header configuration; subsequent calls
|
|
105
|
+
* carry it back as the `${CACHE_ID_HEADER}` request header.
|
|
106
|
+
*
|
|
107
|
+
* The cache is created even when no resources are front-loaded: an explicitly
|
|
108
|
+
* empty cache must still *exist* so the server can later tell "cache I issued,
|
|
109
|
+
* currently empty" from "cache-id I never issued". That's why this uses
|
|
110
|
+
* ResourceCache.set (which always creates the entry) rather than add (which
|
|
111
|
+
* ignores empty resource lists).
|
|
112
|
+
*
|
|
113
|
+
* @param {express.Request} req
|
|
114
|
+
* @param {express.Response} res
|
|
115
|
+
*/
|
|
116
|
+
async start(req, res) {
|
|
117
|
+
const cache = this.opContext.resourceCache;
|
|
118
|
+
if (!cache) {
|
|
119
|
+
return res.status(500).json(this.operationOutcome('error', 'exception',
|
|
120
|
+
'No resource cache is available on this endpoint'));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const params = new Parameters(this.buildParameters(req));
|
|
124
|
+
const { txResources, primaryResources } = this.collectSuppliedResources(params.jsonObj);
|
|
125
|
+
const resources = txResources.concat(primaryResources);
|
|
126
|
+
|
|
127
|
+
const cacheId = crypto.randomUUID();
|
|
128
|
+
cache.set(cacheId, resources);
|
|
129
|
+
|
|
130
|
+
return res.status(200).json({
|
|
131
|
+
resourceType: 'Parameters',
|
|
132
|
+
parameter: [
|
|
133
|
+
{ name: 'cache-id', valueId: cacheId }
|
|
134
|
+
]
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* mode=end: release the cache named by the `${CACHE_ID_HEADER}` header so the
|
|
140
|
+
* server can reclaim it now rather than waiting for the idle timeout.
|
|
141
|
+
*
|
|
142
|
+
* Releasing is idempotent: ending an id the server doesn't have is not an error
|
|
143
|
+
* here (the authoritative "unknown cache" signal belongs on the validation path,
|
|
144
|
+
* a later step). A missing header is a client error, though.
|
|
145
|
+
*
|
|
146
|
+
* @param {express.Request} req
|
|
147
|
+
* @param {express.Response} res
|
|
148
|
+
*/
|
|
149
|
+
async end(req, res) {
|
|
150
|
+
const cache = this.opContext.resourceCache;
|
|
151
|
+
if (!cache) {
|
|
152
|
+
return res.status(500).json(this.operationOutcome('error', 'exception',
|
|
153
|
+
'No resource cache is available on this endpoint'));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const cacheId = req.headers[CACHE_ID_HEADER];
|
|
157
|
+
if (!cacheId) {
|
|
158
|
+
return res.status(400).json(this.operationOutcome('error', 'invalid',
|
|
159
|
+
`$cache-control mode=end requires the cache-id in the '${CACHE_ID_HEADER}' header`));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
cache.clear(cacheId);
|
|
163
|
+
|
|
164
|
+
return res.status(200).json({ resourceType: 'Parameters', parameter: [] });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Build an OperationOutcome.
|
|
169
|
+
* @param {string} severity - error, warning, information
|
|
170
|
+
* @param {string} code - Issue code
|
|
171
|
+
* @param {string} message - Diagnostic message
|
|
172
|
+
* @returns {Object} OperationOutcome resource
|
|
173
|
+
*/
|
|
174
|
+
operationOutcome(severity, code, message) {
|
|
175
|
+
return {
|
|
176
|
+
resourceType: 'OperationOutcome',
|
|
177
|
+
issue: [{
|
|
178
|
+
severity,
|
|
179
|
+
code,
|
|
180
|
+
details: { text: message }
|
|
181
|
+
}]
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
module.exports = { CacheControlWorker, CACHE_ID_HEADER };
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
//
|
|
2
|
-
//
|
|
2
|
+
// Compare Worker - Handles ValueSet $compare operation
|
|
3
3
|
//
|
|
4
|
-
// GET /ValueSet/{id}/$
|
|
5
|
-
// GET /ValueSet/$
|
|
6
|
-
// POST /ValueSet/$
|
|
7
|
-
// POST /ValueSet/$
|
|
8
|
-
// POST /ValueSet/$
|
|
4
|
+
// GET /ValueSet/{id}/$compare
|
|
5
|
+
// GET /ValueSet/$compare?url=...&version=...
|
|
6
|
+
// POST /ValueSet/$compare (form body or Parameters with url)
|
|
7
|
+
// POST /ValueSet/$compare (body is ValueSet resource)
|
|
8
|
+
// POST /ValueSet/$compare (body is Parameters with valueSet parameter)
|
|
9
9
|
//
|
|
10
10
|
|
|
11
11
|
const { TerminologyWorker } = require('./worker');
|
|
@@ -18,7 +18,7 @@ const {SearchFilterText} = require("../library/designations");
|
|
|
18
18
|
const {ArrayMatcher} = require("../../library/utilities");
|
|
19
19
|
const {debugLog} = require("../operation-context");
|
|
20
20
|
|
|
21
|
-
class
|
|
21
|
+
class CompareWorker extends TerminologyWorker {
|
|
22
22
|
showLogic = false;
|
|
23
23
|
|
|
24
24
|
/**
|
|
@@ -37,18 +37,18 @@ class RelatedWorker extends TerminologyWorker {
|
|
|
37
37
|
* @returns {string}
|
|
38
38
|
*/
|
|
39
39
|
opName() {
|
|
40
|
-
return '
|
|
40
|
+
return 'compare';
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
|
-
* Handle a type-level $
|
|
45
|
-
* GET/POST /ValueSet/$
|
|
44
|
+
* Handle a type-level $compare request
|
|
45
|
+
* GET/POST /ValueSet/$compare
|
|
46
46
|
* @param {express.Request} req - Express request
|
|
47
47
|
* @param {express.Response} res - Express response
|
|
48
48
|
*/
|
|
49
49
|
async handle(req, res) {
|
|
50
50
|
try {
|
|
51
|
-
await this.
|
|
51
|
+
await this.handleTypeLevelCompare(req, res);
|
|
52
52
|
} catch (error) {
|
|
53
53
|
this.log.error(error);
|
|
54
54
|
debugLog(error);
|
|
@@ -76,14 +76,14 @@ class RelatedWorker extends TerminologyWorker {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
/**
|
|
79
|
-
* Handle an instance-level $
|
|
80
|
-
* GET/POST /ValueSet/{id}/$
|
|
79
|
+
* Handle an instance-level $compare request
|
|
80
|
+
* GET/POST /ValueSet/{id}/$compare
|
|
81
81
|
* @param {express.Request} req - Express request
|
|
82
82
|
* @param {express.Response} res - Express response
|
|
83
83
|
*/
|
|
84
84
|
async handleInstance(req, res) {
|
|
85
85
|
try {
|
|
86
|
-
await this.
|
|
86
|
+
await this.handleInstanceLevelCompare(req, res);
|
|
87
87
|
} catch (error) {
|
|
88
88
|
this.log.error(error);
|
|
89
89
|
debugLog(error);
|
|
@@ -105,11 +105,11 @@ class RelatedWorker extends TerminologyWorker {
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
/**
|
|
108
|
-
* Handle type-level $
|
|
108
|
+
* Handle type-level $compare: /ValueSet/$compare
|
|
109
109
|
* ValueSet identified by url, or provided directly in body
|
|
110
110
|
*/
|
|
111
|
-
async
|
|
112
|
-
this.deadCheck('
|
|
111
|
+
async handleTypeLevelCompare(req, res) {
|
|
112
|
+
this.deadCheck('compare-type-level');
|
|
113
113
|
|
|
114
114
|
let params = req.body;
|
|
115
115
|
this.addHttpParams(req, params);
|
|
@@ -121,16 +121,16 @@ class RelatedWorker extends TerminologyWorker {
|
|
|
121
121
|
let thisVS = await this.readValueSet(res, "this", params, txp);
|
|
122
122
|
let otherVS = await this.readValueSet(res, "other", params, txp);
|
|
123
123
|
|
|
124
|
-
const result = await this.
|
|
124
|
+
const result = await this.doCompare(txp, thisVS, otherVS);
|
|
125
125
|
return res.json(result);
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
/**
|
|
129
|
-
* Handle instance-level
|
|
129
|
+
* Handle instance-level compare: /ValueSet/{id}/$compare
|
|
130
130
|
* ValueSet identified by resource ID
|
|
131
131
|
*/
|
|
132
|
-
async
|
|
133
|
-
this.deadCheck('
|
|
132
|
+
async handleInstanceLevelCompare(req, res) {
|
|
133
|
+
this.deadCheck('compare-instance-level');
|
|
134
134
|
|
|
135
135
|
let params = req.body;
|
|
136
136
|
this.addHttpParams(req, params);
|
|
@@ -147,7 +147,7 @@ class RelatedWorker extends TerminologyWorker {
|
|
|
147
147
|
}
|
|
148
148
|
let otherVS = await this.readValueSet(res, "other", params, txp);
|
|
149
149
|
|
|
150
|
-
const result = await this.
|
|
150
|
+
const result = await this.doCompare(txp, thisVS, otherVS);
|
|
151
151
|
return res.json(result);
|
|
152
152
|
}
|
|
153
153
|
|
|
@@ -198,7 +198,7 @@ class RelatedWorker extends TerminologyWorker {
|
|
|
198
198
|
}
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
-
async
|
|
201
|
+
async doCompare(txp, thisVS, otherVS) {
|
|
202
202
|
|
|
203
203
|
// ok, we have to compare the composes. we don't care about anything else
|
|
204
204
|
const thisC = thisVS.jsonObj.compose;
|
|
@@ -206,12 +206,12 @@ class RelatedWorker extends TerminologyWorker {
|
|
|
206
206
|
if (!thisC) {
|
|
207
207
|
return this.makeOutcome("indeterminate", `The ValueSet ${thisVS.vurl} has no compose`);
|
|
208
208
|
}
|
|
209
|
-
Extensions.checkNoModifiers(thisC, '
|
|
209
|
+
Extensions.checkNoModifiers(thisC, 'CompareWorker.doCompare', 'compose', thisVS.vurl)
|
|
210
210
|
this.checkNoLockedDate(thisVS.vurl, thisC);
|
|
211
211
|
if (!otherC) {
|
|
212
212
|
return this.makeOutcome("indeterminate", `The ValueSet ${otherVS.vurl} has no compose`);
|
|
213
213
|
}
|
|
214
|
-
Extensions.checkNoModifiers(otherC, '
|
|
214
|
+
Extensions.checkNoModifiers(otherC, 'CompareWorker.doCompare', 'compose', otherVS.vurl)
|
|
215
215
|
this.checkNoLockedDate(otherVS.vurl, otherC);
|
|
216
216
|
|
|
217
217
|
let systems = new Map(); // tracks whether the comparison is version dependent or not
|
|
@@ -268,7 +268,7 @@ class RelatedWorker extends TerminologyWorker {
|
|
|
268
268
|
} else if (status.left) {
|
|
269
269
|
outcome = this.makeOutcome("superset", `The valueSet ${thisVS.vurl} is a super-set of the valueSet ${otherVS.vurl}`);
|
|
270
270
|
} else {
|
|
271
|
-
outcome = this.makeOutcome("subset", `The valueSet ${thisVS.vurl} is a
|
|
271
|
+
outcome = this.makeOutcome("subset", `The valueSet ${thisVS.vurl} is a sub-set of the valueSet ${otherVS.vurl}`);
|
|
272
272
|
}
|
|
273
273
|
if (txp.diagnostics) {
|
|
274
274
|
outcome.parameter.push({name: 'performed-expansion', valueBoolean: exp ? true : false})
|
|
@@ -712,5 +712,5 @@ class RelatedWorker extends TerminologyWorker {
|
|
|
712
712
|
}
|
|
713
713
|
|
|
714
714
|
module.exports = {
|
|
715
|
-
|
|
715
|
+
CompareWorker
|
|
716
716
|
};
|
package/tx/workers/metadata.js
CHANGED
|
@@ -233,7 +233,7 @@ class MetadataHandler {
|
|
|
233
233
|
operation: [
|
|
234
234
|
{ name: 'expand', definition: 'http://hl7.org/fhir/OperationDefinition/ValueSet-expand' },
|
|
235
235
|
{ name: 'validate-code', definition: 'http://hl7.org/fhir/OperationDefinition/ValueSet-validate-code' },
|
|
236
|
-
{ name: '
|
|
236
|
+
{ name: 'compare', definition: 'http://hl7.org/fhir/tools/OperationDefinition/ValueSet-compare' }
|
|
237
237
|
]
|
|
238
238
|
},
|
|
239
239
|
{
|
|
@@ -266,7 +266,8 @@ class MetadataHandler {
|
|
|
266
266
|
{ name: 'validate-code', definition: 'http://hl7.org/fhir/OperationDefinition/Resource-validate-code' },
|
|
267
267
|
{ name: 'translate', definition: 'http://hl7.org/fhir/OperationDefinition/ConceptMap-translate' },
|
|
268
268
|
{ name: 'closure', definition: 'http://hl7.org/fhir/OperationDefinition/ConceptMap-closure' },
|
|
269
|
-
{ name: '
|
|
269
|
+
{ name: 'compare', definition: 'http://hl7.org/fhir/tools/OperationDefinition/ValueSet-compare' },
|
|
270
|
+
{ name: 'cache-control', definition: 'http://hl7.org/fhir/tools/OperationDefinition/cache-control' },
|
|
270
271
|
{ name: 'versions', definition: 'http://hl7.org/fhir/OperationDefinition/fhir-versions' }
|
|
271
272
|
]
|
|
272
273
|
}
|
|
@@ -395,10 +396,14 @@ class MetadataHandler {
|
|
|
395
396
|
buildExpansionCapabilities() {
|
|
396
397
|
return {
|
|
397
398
|
parameter: [
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
399
|
+
// NOTE: `cache-id` is deliberately not advertised as an expansion parameter.
|
|
400
|
+
// Caching is now discovered via the $cache-control operation in the
|
|
401
|
+
// CapabilityStatement, not by advertising a cache-id parameter here. The old
|
|
402
|
+
// advertisement implied the implicit "auto-create on first sight" protocol,
|
|
403
|
+
// which has been replaced: a cache must be created explicitly with
|
|
404
|
+
// $cache-control?mode=start before its cache-id can be used. Clients that
|
|
405
|
+
// only know the old mechanism will see no cache-id parameter and simply not
|
|
406
|
+
// cache (inlining each request), which is correct, just not optimised.
|
|
402
407
|
{
|
|
403
408
|
name: 'tx-resource',
|
|
404
409
|
documentation: 'Additional valuesets needed for evaluation e.g. value sets referred to from the import statement of the value set being expanded'
|