fhirsmith 0.3.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 +42 -0
- package/FHIRsmith.png +0 -0
- package/README.md +277 -0
- package/config-template.json +144 -0
- package/library/folder-setup.js +58 -0
- package/library/html-server.js +166 -0
- package/library/html.js +835 -0
- package/library/i18nsupport.js +259 -0
- package/library/languages.js +779 -0
- package/library/logger-telnet.js +205 -0
- package/library/logger.js +279 -0
- package/library/package-manager.js +876 -0
- package/library/utilities.js +196 -0
- package/library/version-utilities.js +1056 -0
- package/npmprojector/config-example.json +13 -0
- package/npmprojector/indexer.js +394 -0
- package/npmprojector/npmprojector.js +395 -0
- package/npmprojector/readme.md +174 -0
- package/npmprojector/watcher.js +335 -0
- package/package.json +119 -0
- package/packages/package-crawler.js +846 -0
- package/packages/packages-template.html +126 -0
- package/packages/packages.js +2838 -0
- package/passwords.ini +2 -0
- package/publisher/publisher-template.html +208 -0
- package/publisher/publisher.js +2167 -0
- package/publisher/task-draft.js +458 -0
- package/registry/api.js +735 -0
- package/registry/crawler.js +637 -0
- package/registry/model.js +513 -0
- package/registry/readme.md +243 -0
- package/registry/registry-data.json +121015 -0
- package/registry/registry-template.html +126 -0
- package/registry/registry.js +1395 -0
- package/registry/test-runner.js +237 -0
- package/root-template.html +124 -0
- package/server.js +524 -0
- package/shl/private-key.pem +5 -0
- package/shl/public-key.pem +18 -0
- package/shl/shl.js +1125 -0
- package/shl/vhl.js +69 -0
- package/static/FHIRsmith128.png +0 -0
- package/static/FHIRsmith16.png +0 -0
- package/static/FHIRsmith32.png +0 -0
- package/static/FHIRsmith64.png +0 -0
- package/static/assets/css/bootstrap-fhir.css +5302 -0
- package/static/assets/css/bootstrap-glyphicons.css +2 -0
- package/static/assets/css/bootstrap.css +4097 -0
- package/static/assets/css/jquery-ui.css +523 -0
- package/static/assets/css/jquery-ui.structure.css +863 -0
- package/static/assets/css/jquery-ui.structure.min.css +5 -0
- package/static/assets/css/jquery-ui.theme.css +439 -0
- package/static/assets/css/jquery-ui.theme.min.css +5 -0
- package/static/assets/css/jquery.ui.all.css +7 -0
- package/static/assets/css/modules.css +18 -0
- package/static/assets/css/project.css +367 -0
- package/static/assets/css/pygments-manni.css +66 -0
- package/static/assets/css/tags.css +74 -0
- package/static/assets/css/xml.css +2 -0
- package/static/assets/fonts/glyphiconshalflings-regular.eot +0 -0
- package/static/assets/fonts/glyphiconshalflings-regular.otf +0 -0
- package/static/assets/fonts/glyphiconshalflings-regular.svg +175 -0
- package/static/assets/fonts/glyphiconshalflings-regular.ttf +0 -0
- package/static/assets/fonts/glyphiconshalflings-regular.woff +0 -0
- package/static/assets/ico/apple-touch-icon-114-precomposed.png +0 -0
- package/static/assets/ico/apple-touch-icon-144-precomposed.png +0 -0
- package/static/assets/ico/apple-touch-icon-57-precomposed.png +0 -0
- package/static/assets/ico/apple-touch-icon-72-precomposed.png +0 -0
- package/static/assets/ico/favicon.ico +0 -0
- package/static/assets/ico/favicon.png +0 -0
- package/static/assets/images/fhir-logo-www.png +0 -0
- package/static/assets/images/fhir-logo.png +0 -0
- package/static/assets/images/hl7-logo.png +0 -0
- package/static/assets/images/logo_ansinew.jpg +0 -0
- package/static/assets/images/search.png +0 -0
- package/static/assets/images/stripe.png +0 -0
- package/static/assets/images/target.png +0 -0
- package/static/assets/images/tx-registry-root.gif +0 -0
- package/static/assets/images/tx-registry.png +0 -0
- package/static/assets/images/tx-server.png +0 -0
- package/static/assets/images/tx-version.png +0 -0
- package/static/assets/js/bootstrap.min.js +6 -0
- package/static/assets/js/fhir-gw.js +259 -0
- package/static/assets/js/fhir.js +2 -0
- package/static/assets/js/html5shiv.js +8 -0
- package/static/assets/js/jcookie.js +96 -0
- package/static/assets/js/jquery-ui.min.js +6 -0
- package/static/assets/js/jquery.js +10716 -0
- package/static/assets/js/jquery.min.js +2 -0
- package/static/assets/js/jquery.ui.core.js +314 -0
- package/static/assets/js/jquery.ui.draggable.js +825 -0
- package/static/assets/js/jquery.ui.mouse.js +162 -0
- package/static/assets/js/jquery.ui.resizable.js +842 -0
- package/static/assets/js/jquery.ui.widget.js +268 -0
- package/static/assets/js/json2.js +487 -0
- package/static/assets/js/jtip.js +97 -0
- package/static/assets/js/respond.min.js +6 -0
- package/static/assets/js/statuspage.js +70 -0
- package/static/assets/js/xml.js +2 -0
- package/static/dist/js/bootstrap.js +1964 -0
- package/static/favicon.png +0 -0
- package/static/fhir.css +626 -0
- package/static/icon-fhir-16.png +0 -0
- package/static/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
- package/static/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
- package/static/images/ui-bg_flat_10_000000_40x100.png +0 -0
- package/static/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
- package/static/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
- package/static/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- package/static/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
- package/static/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
- package/static/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
- package/static/images/ui-icons_222222_256x240.png +0 -0
- package/static/images/ui-icons_228ef1_256x240.png +0 -0
- package/static/images/ui-icons_ef8c08_256x240.png +0 -0
- package/static/images/ui-icons_ffd27a_256x240.png +0 -0
- package/static/images/ui-icons_ffffff_256x240.png +0 -0
- package/static/js/jquery.effects.blind.js +49 -0
- package/static/js/jquery.effects.bounce.js +78 -0
- package/static/js/jquery.effects.clip.js +54 -0
- package/static/js/jquery.effects.core.js +763 -0
- package/static/js/jquery.effects.drop.js +50 -0
- package/static/js/jquery.effects.explode.js +79 -0
- package/static/js/jquery.effects.fade.js +32 -0
- package/static/js/jquery.effects.fold.js +56 -0
- package/static/js/jquery.effects.highlight.js +50 -0
- package/static/js/jquery.effects.pulsate.js +51 -0
- package/static/js/jquery.effects.scale.js +178 -0
- package/static/js/jquery.effects.shake.js +57 -0
- package/static/js/jquery.effects.slide.js +50 -0
- package/static/js/jquery.effects.transfer.js +45 -0
- package/static/js/jquery.ui.accordion.js +611 -0
- package/static/js/jquery.ui.autocomplete.js +612 -0
- package/static/js/jquery.ui.button.js +416 -0
- package/static/js/jquery.ui.datepicker.js +1823 -0
- package/static/js/jquery.ui.dialog.js +878 -0
- package/static/js/jquery.ui.droppable.js +296 -0
- package/static/js/jquery.ui.position.js +252 -0
- package/static/js/jquery.ui.progressbar.js +109 -0
- package/static/js/jquery.ui.selectable.js +266 -0
- package/static/js/jquery.ui.slider.js +666 -0
- package/static/js/jquery.ui.sortable.js +1077 -0
- package/static/js/jquery.ui.tabs.js +758 -0
- package/stats.js +80 -0
- package/test-cache/vsac/vsac-valuesets.db +0 -0
- package/token/nginx_passport_setup.md +383 -0
- package/token/security_guide.md +294 -0
- package/token/token-template.html +330 -0
- package/token/token.js +1300 -0
- package/translations/Messages.properties +1510 -0
- package/translations/Messages_ar.properties +1399 -0
- package/translations/Messages_de.properties +836 -0
- package/translations/Messages_es.properties +737 -0
- package/translations/Messages_fr.properties +1 -0
- package/translations/Messages_ja.properties +893 -0
- package/translations/Messages_nl.properties +1357 -0
- package/translations/Messages_pt.properties +1302 -0
- package/translations/Messages_ru.properties +1 -0
- package/translations/Messages_uz.properties +1 -0
- package/translations/Messages_zh.properties +1 -0
- package/translations/rendering-phrases.properties +1128 -0
- package/translations/rendering-phrases_ar.properties +1091 -0
- package/translations/rendering-phrases_de.properties +6 -0
- package/translations/rendering-phrases_es.properties +6 -0
- package/translations/rendering-phrases_fr.properties +624 -0
- package/translations/rendering-phrases_ja.properties +21 -0
- package/translations/rendering-phrases_nl.properties +970 -0
- package/translations/rendering-phrases_pt.properties +1020 -0
- package/translations/rendering-phrases_ru.properties +1094 -0
- package/translations/rendering-phrases_uz.properties +1 -0
- package/translations/rendering-phrases_zh.properties +1 -0
- package/tx/README.md +418 -0
- package/tx/cm/cm-api.js +110 -0
- package/tx/cm/cm-database.js +735 -0
- package/tx/cm/cm-package.js +325 -0
- package/tx/cs/cs-api.js +789 -0
- package/tx/cs/cs-areacode.js +615 -0
- package/tx/cs/cs-country.js +1110 -0
- package/tx/cs/cs-cpt.js +785 -0
- package/tx/cs/cs-cs.js +1579 -0
- package/tx/cs/cs-currency.js +539 -0
- package/tx/cs/cs-db.js +1321 -0
- package/tx/cs/cs-hgvs.js +329 -0
- package/tx/cs/cs-lang.js +465 -0
- package/tx/cs/cs-loinc.js +1485 -0
- package/tx/cs/cs-mimetypes.js +238 -0
- package/tx/cs/cs-ndc.js +704 -0
- package/tx/cs/cs-omop.js +1025 -0
- package/tx/cs/cs-provider-api.js +43 -0
- package/tx/cs/cs-provider-list.js +37 -0
- package/tx/cs/cs-rxnorm.js +808 -0
- package/tx/cs/cs-snomed.js +1102 -0
- package/tx/cs/cs-ucum.js +514 -0
- package/tx/cs/cs-unii.js +271 -0
- package/tx/cs/cs-uri.js +218 -0
- package/tx/cs/cs-usstates.js +305 -0
- package/tx/dev.fhir.org.yml +14 -0
- package/tx/fixtures/test-cases-setup.json +18 -0
- package/tx/fixtures/test-cases.yml +16 -0
- package/tx/html/codesystem-operations.liquid +25 -0
- package/tx/html/home-metrics.liquid +247 -0
- package/tx/html/operations-form.liquid +148 -0
- package/tx/html/search-form.liquid +62 -0
- package/tx/html/tx-template.html +133 -0
- package/tx/html/valueset-operations.liquid +54 -0
- package/tx/importers/atc-to-fhir.js +316 -0
- package/tx/importers/import-loinc.module.js +1536 -0
- package/tx/importers/import-ndc.module.js +1088 -0
- package/tx/importers/import-rxnorm.module.js +898 -0
- package/tx/importers/import-sct.module.js +2457 -0
- package/tx/importers/import-unii.module.js +601 -0
- package/tx/importers/readme.md +453 -0
- package/tx/importers/subset-loinc.module.js +1081 -0
- package/tx/importers/subset-rxnorm.module.js +938 -0
- package/tx/importers/tx-import-base.js +351 -0
- package/tx/importers/tx-import-settings.js +310 -0
- package/tx/importers/tx-import.js +357 -0
- package/tx/library/canonical-resource.js +88 -0
- package/tx/library/capabilitystatement.js +292 -0
- package/tx/library/codesystem.js +774 -0
- package/tx/library/conceptmap.js +568 -0
- package/tx/library/designations.js +932 -0
- package/tx/library/errors.js +77 -0
- package/tx/library/extensions.js +117 -0
- package/tx/library/namingsystem.js +322 -0
- package/tx/library/operation-outcome.js +127 -0
- package/tx/library/parameters.js +105 -0
- package/tx/library/renderer.js +1559 -0
- package/tx/library/terminologycapabilities.js +418 -0
- package/tx/library/ucum-parsers.js +1029 -0
- package/tx/library/ucum-service.js +370 -0
- package/tx/library/ucum-types.js +1099 -0
- package/tx/library/valueset.js +543 -0
- package/tx/library.js +676 -0
- package/tx/ocl/cm-ocl.js +106 -0
- package/tx/ocl/cs-ocl.js +39 -0
- package/tx/ocl/vs-ocl.js +105 -0
- package/tx/operation-context.js +568 -0
- package/tx/params.js +613 -0
- package/tx/provider.js +403 -0
- package/tx/sct/ecl.js +1560 -0
- package/tx/sct/expressions.js +2077 -0
- package/tx/sct/structures.js +1396 -0
- package/tx/tx-html.js +1063 -0
- package/tx/tx.fhir.org.yml +39 -0
- package/tx/tx.js +927 -0
- package/tx/vs/vs-api.js +112 -0
- package/tx/vs/vs-database.js +786 -0
- package/tx/vs/vs-package.js +358 -0
- package/tx/vs/vs-vsac.js +366 -0
- package/tx/workers/batch-validate.js +129 -0
- package/tx/workers/batch.js +361 -0
- package/tx/workers/closure.js +32 -0
- package/tx/workers/expand.js +1845 -0
- package/tx/workers/lookup.js +407 -0
- package/tx/workers/metadata.js +467 -0
- package/tx/workers/operations.js +34 -0
- package/tx/workers/read.js +164 -0
- package/tx/workers/search.js +384 -0
- package/tx/workers/subsumes.js +334 -0
- package/tx/workers/translate.js +492 -0
- package/tx/workers/validate.js +2504 -0
- package/tx/workers/worker.js +904 -0
- package/tx/xml/capabilitystatement-xml.js +63 -0
- package/tx/xml/codesystem-xml.js +62 -0
- package/tx/xml/conceptmap-xml.js +65 -0
- package/tx/xml/namingsystem-xml.js +65 -0
- package/tx/xml/operationoutcome-xml.js +127 -0
- package/tx/xml/parameters-xml.js +312 -0
- package/tx/xml/terminologycapabilities-xml.js +64 -0
- package/tx/xml/valueset-xml.js +64 -0
- package/tx/xml/xml-base.js +603 -0
- package/vcl/vcl-parser.js +1098 -0
- package/vcl/vcl.js +253 -0
- package/windows-install.js +19 -0
- package/xig/xig-template.html +124 -0
- package/xig/xig.js +3049 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Validate Worker - Handles $validate-code operations
|
|
3
|
+
//
|
|
4
|
+
// GET /CodeSystem/$validate-code?{params}
|
|
5
|
+
// POST /CodeSystem/$validate-code
|
|
6
|
+
// GET /CodeSystem/{id}/$validate-code?{params}
|
|
7
|
+
// POST /CodeSystem/{id}/$validate-code
|
|
8
|
+
// GET /ValueSet/$validate-code?{params}
|
|
9
|
+
// POST /ValueSet/$validate-code
|
|
10
|
+
// GET /ValueSet/{id}/$validate-code?{params}
|
|
11
|
+
// POST /ValueSet/{id}/$validate-code
|
|
12
|
+
//
|
|
13
|
+
|
|
14
|
+
const { TerminologyWorker } = require('./worker');
|
|
15
|
+
const {OperationOutcome, Issue} = require("../library/operation-outcome");
|
|
16
|
+
const {Parameters} = require("../library/parameters");
|
|
17
|
+
const {ValidateWorker} = require("./validate");
|
|
18
|
+
|
|
19
|
+
class BatchValidateWorker extends TerminologyWorker {
|
|
20
|
+
|
|
21
|
+
globalNames = new Set();
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {OperationContext} opContext - Operation context
|
|
25
|
+
* @param {Logger} log - Logger instance
|
|
26
|
+
* @param {Provider} provider - Provider for code systems and resources
|
|
27
|
+
* @param {LanguageDefinitions} languages - Language definitions
|
|
28
|
+
* @param {I18nSupport} i18n - Internationalization support
|
|
29
|
+
*/
|
|
30
|
+
constructor(opContext, log, provider, languages, i18n) {
|
|
31
|
+
super(opContext, log, provider, languages, i18n);
|
|
32
|
+
this.globalNames.add("tx-resource");
|
|
33
|
+
this.globalNames.add("url");
|
|
34
|
+
this.globalNames.add("valueSet");
|
|
35
|
+
this.globalNames.add("lenient-display-validation");
|
|
36
|
+
this.globalNames.add("__Accept-Language");
|
|
37
|
+
this.globalNames.add("__Content-Language");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get operation name
|
|
42
|
+
* @returns {string}
|
|
43
|
+
*/
|
|
44
|
+
opName() {
|
|
45
|
+
return 'batch-validate-code';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async handleValueSet(req, res) {
|
|
49
|
+
try {
|
|
50
|
+
let params = req.body;
|
|
51
|
+
this.addHttpParams(req, params);
|
|
52
|
+
|
|
53
|
+
let globalParams = [];
|
|
54
|
+
for (const p of params.parameter) {
|
|
55
|
+
if (this.globalNames.has(p.name)) {
|
|
56
|
+
globalParams.push(p);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let output = [];
|
|
61
|
+
|
|
62
|
+
for (const p of params.parameter) {
|
|
63
|
+
if (p.name == 'validation') {
|
|
64
|
+
let op = new Parameters();
|
|
65
|
+
op.jsonObj.parameter = [];
|
|
66
|
+
for (const gp of globalParams) {
|
|
67
|
+
let exists = p.resource.parameter.find(pp => gp.name == pp.name);
|
|
68
|
+
if (gp.name == 'tx-resource' || !exists) {
|
|
69
|
+
op.jsonObj.parameter.push(gp);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
op.jsonObj.parameter.push(...p.resource.parameter);
|
|
73
|
+
|
|
74
|
+
let worker = new ValidateWorker(this.opContext.copy(), this.log, this.provider, this.languages, this.i18n);
|
|
75
|
+
try {
|
|
76
|
+
let p;
|
|
77
|
+
if (this.hasValueSet(op.jsonObj.parameter)) {
|
|
78
|
+
p = await worker.handleValueSetInner(op.jsonObj);
|
|
79
|
+
} else {
|
|
80
|
+
p = await worker.handleCodeSystemInner(op.jsonObj);
|
|
81
|
+
}
|
|
82
|
+
output.push({name: "validation", resource : p});
|
|
83
|
+
} catch (error) {
|
|
84
|
+
this.log.error(error);
|
|
85
|
+
if (error instanceof Issue) {
|
|
86
|
+
let op = new OperationOutcome();
|
|
87
|
+
op.addIssue(error);
|
|
88
|
+
output.push({name: "validation", resource : op.jsonObj});
|
|
89
|
+
} else {
|
|
90
|
+
output.push({name: "validation", resource : this.operationOutcome('error', error.issueCode || 'exception', error.message) } );
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
let result = { resourceType : "Parameters", parameter: output}
|
|
96
|
+
req.logInfo = `${output.length} validations`;
|
|
97
|
+
return res.json(result);
|
|
98
|
+
} catch (error) {
|
|
99
|
+
this.log.error(error);
|
|
100
|
+
return res.status(error.statusCode || 500).json(this.operationOutcome(
|
|
101
|
+
'error', error.issueCode || 'exception', error.message));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Build an OperationOutcome
|
|
107
|
+
*/
|
|
108
|
+
operationOutcome(severity, code, message) {
|
|
109
|
+
return {
|
|
110
|
+
resourceType: 'OperationOutcome',
|
|
111
|
+
issue: [{
|
|
112
|
+
severity,
|
|
113
|
+
code,
|
|
114
|
+
details: {
|
|
115
|
+
text: message
|
|
116
|
+
},
|
|
117
|
+
diagnostics: message
|
|
118
|
+
}]
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
hasValueSet(parameter) {
|
|
123
|
+
return parameter.find(p => p.name == 'url' || p.name == 'valueSet');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
module.exports = {
|
|
128
|
+
BatchValidateWorker
|
|
129
|
+
};
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Batch Worker - Handles batch terminology operations
|
|
3
|
+
//
|
|
4
|
+
// POST /tx/r5 (with Bundle)
|
|
5
|
+
//
|
|
6
|
+
|
|
7
|
+
const { TerminologyWorker } = require('./worker');
|
|
8
|
+
const { Issue, OperationOutcome } = require('../library/operation-outcome');
|
|
9
|
+
|
|
10
|
+
class BatchWorker extends TerminologyWorker {
|
|
11
|
+
/**
|
|
12
|
+
* @param {OperationContext} opContext - Operation context
|
|
13
|
+
* @param {Logger} log - Logger instance
|
|
14
|
+
* @param {Provider} provider - Provider for terminology resources
|
|
15
|
+
* @param {LanguageDefinitions} languages - Language definitions
|
|
16
|
+
* @param {I18nSupport} i18n - Internationalization support
|
|
17
|
+
* @param {Object} workers - Map of operation workers (validate, lookup, subsumes, translate, expand)
|
|
18
|
+
*/
|
|
19
|
+
constructor(opContext, log, provider, languages, i18n, workers) {
|
|
20
|
+
super(opContext, log, provider, languages, i18n);
|
|
21
|
+
this.workers = workers;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get operation name
|
|
26
|
+
* @returns {string}
|
|
27
|
+
*/
|
|
28
|
+
opName() {
|
|
29
|
+
return 'batch-validate-code';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Handle a batch request
|
|
34
|
+
* POST with Bundle resource
|
|
35
|
+
* @param {express.Request} req - Express request
|
|
36
|
+
* @param {express.Response} res - Express response
|
|
37
|
+
*/
|
|
38
|
+
async handle(req, res) {
|
|
39
|
+
try {
|
|
40
|
+
await this.handleBatch(req, res);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
this.log.error(error);
|
|
43
|
+
if (error instanceof Issue) {
|
|
44
|
+
const oo = new OperationOutcome();
|
|
45
|
+
oo.addIssue(error);
|
|
46
|
+
return res.status(error.statusCode || 500).json(oo.jsonObj);
|
|
47
|
+
} else {
|
|
48
|
+
return res.status(error.statusCode || 500).json(this.operationOutcome(
|
|
49
|
+
'error', error.issueCode || 'exception', error.message));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Handle the batch operation
|
|
56
|
+
* @param {express.Request} req - Express request
|
|
57
|
+
* @param {express.Response} res - Express response
|
|
58
|
+
*/
|
|
59
|
+
async handleBatch(req, res) {
|
|
60
|
+
this.deadCheck('batch');
|
|
61
|
+
|
|
62
|
+
const bundle = req.body;
|
|
63
|
+
|
|
64
|
+
// Validate input is a Bundle
|
|
65
|
+
if (!bundle || bundle.resourceType !== 'Bundle') {
|
|
66
|
+
throw new Issue('error', 'invalid', null, null,
|
|
67
|
+
'Request body must be a Bundle resource', null, 400);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Must be a batch bundle
|
|
71
|
+
if (bundle.type !== 'batch') {
|
|
72
|
+
throw new Issue('error', 'invalid', null, null,
|
|
73
|
+
`Bundle type must be 'batch', got '${bundle.type}'`, null, 400);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Process entries
|
|
77
|
+
const entries = bundle.entry || [];
|
|
78
|
+
const responseEntries = [];
|
|
79
|
+
|
|
80
|
+
for (let i = 0; i < entries.length; i++) {
|
|
81
|
+
this.deadCheck(`batch-entry-${i}`);
|
|
82
|
+
const entry = entries[i];
|
|
83
|
+
const responseEntry = await this.processEntry(entry, i);
|
|
84
|
+
responseEntries.push(responseEntry);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Build response bundle
|
|
88
|
+
const responseBundle = {
|
|
89
|
+
resourceType: 'Bundle',
|
|
90
|
+
type: 'batch-response',
|
|
91
|
+
entry: responseEntries
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
return res.status(200).json(responseBundle);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Process a single batch entry
|
|
99
|
+
* @param {Object} entry - Bundle entry
|
|
100
|
+
* @param {number} index - Entry index for error reporting
|
|
101
|
+
* @returns {Object} Response entry
|
|
102
|
+
*/
|
|
103
|
+
async processEntry(entry, index) {
|
|
104
|
+
try {
|
|
105
|
+
// Validate entry has request
|
|
106
|
+
if (!entry.request) {
|
|
107
|
+
return this.errorEntry(400, 'invalid', `Entry ${index}: missing request element`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const request = entry.request;
|
|
111
|
+
const method = request.method?.toUpperCase();
|
|
112
|
+
const url = request.url;
|
|
113
|
+
|
|
114
|
+
if (!method) {
|
|
115
|
+
return this.errorEntry(400, 'invalid', `Entry ${index}: missing request.method`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!url) {
|
|
119
|
+
return this.errorEntry(400, 'invalid', `Entry ${index}: missing request.url`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Parse the URL to determine the operation
|
|
123
|
+
const parsedOp = this.parseOperationUrl(url);
|
|
124
|
+
|
|
125
|
+
if (!parsedOp) {
|
|
126
|
+
return this.errorEntry(400, 'not-supported',
|
|
127
|
+
`Entry ${index}: unsupported URL pattern '${url}'`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Get the appropriate worker
|
|
131
|
+
const worker = this.getWorkerForOperation(parsedOp.operation);
|
|
132
|
+
|
|
133
|
+
if (!worker) {
|
|
134
|
+
return this.errorEntry(501, 'not-supported',
|
|
135
|
+
`Entry ${index}: operation '${parsedOp.operation}' not supported`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Build a mock request object for the worker
|
|
139
|
+
const mockReq = this.buildMockRequest(method, parsedOp, entry.resource, request);
|
|
140
|
+
|
|
141
|
+
// Build a mock response object to capture the result
|
|
142
|
+
const mockRes = this.buildMockResponse();
|
|
143
|
+
|
|
144
|
+
// Execute the operation
|
|
145
|
+
if (parsedOp.instanceId) {
|
|
146
|
+
await worker.handleInstance(mockReq, mockRes);
|
|
147
|
+
} else {
|
|
148
|
+
await worker.handle(mockReq, mockRes);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Build response entry from captured result
|
|
152
|
+
return {
|
|
153
|
+
resource: mockRes.body,
|
|
154
|
+
response: {
|
|
155
|
+
status: `${mockRes.statusCode}`,
|
|
156
|
+
outcome: mockRes.statusCode >= 400 ? mockRes.body : undefined
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
} catch (error) {
|
|
161
|
+
this.log.error(error);
|
|
162
|
+
const statusCode = error.statusCode || 500;
|
|
163
|
+
const issueCode = error.issueCode || 'exception';
|
|
164
|
+
|
|
165
|
+
return this.errorEntry(statusCode, issueCode, error.message);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Parse an operation URL to extract the operation type and parameters
|
|
171
|
+
* @param {string} url - Request URL (e.g., "CodeSystem/$lookup?system=...&code=...")
|
|
172
|
+
* @returns {Object|null} Parsed operation info or null if not recognized
|
|
173
|
+
*/
|
|
174
|
+
parseOperationUrl(url) {
|
|
175
|
+
// Remove leading slash if present
|
|
176
|
+
const cleanUrl = url.startsWith('/') ? url.substring(1) : url;
|
|
177
|
+
|
|
178
|
+
// Split URL and query string
|
|
179
|
+
const [path, queryString] = cleanUrl.split('?');
|
|
180
|
+
const queryParams = this.parseQueryString(queryString);
|
|
181
|
+
|
|
182
|
+
// Patterns to match:
|
|
183
|
+
// CodeSystem/$lookup
|
|
184
|
+
// CodeSystem/{id}/$lookup
|
|
185
|
+
// CodeSystem/$validate-code
|
|
186
|
+
// CodeSystem/{id}/$validate-code
|
|
187
|
+
// CodeSystem/$subsumes
|
|
188
|
+
// CodeSystem/{id}/$subsumes
|
|
189
|
+
// ValueSet/$expand
|
|
190
|
+
// ValueSet/{id}/$expand
|
|
191
|
+
// ValueSet/$validate-code
|
|
192
|
+
// ValueSet/{id}/$validate-code
|
|
193
|
+
// ConceptMap/$translate
|
|
194
|
+
// ConceptMap/{id}/$translate
|
|
195
|
+
|
|
196
|
+
// Type-level operations
|
|
197
|
+
const typeLevelMatch = path.match(/^(CodeSystem|ValueSet|ConceptMap)\/\$(.+)$/);
|
|
198
|
+
if (typeLevelMatch) {
|
|
199
|
+
return {
|
|
200
|
+
resourceType: typeLevelMatch[1],
|
|
201
|
+
operation: typeLevelMatch[2],
|
|
202
|
+
instanceId: null,
|
|
203
|
+
queryParams
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Instance-level operations
|
|
208
|
+
const instanceLevelMatch = path.match(/^(CodeSystem|ValueSet|ConceptMap)\/([^/$]+)\/\$(.+)$/);
|
|
209
|
+
if (instanceLevelMatch) {
|
|
210
|
+
return {
|
|
211
|
+
resourceType: instanceLevelMatch[1],
|
|
212
|
+
operation: instanceLevelMatch[3],
|
|
213
|
+
instanceId: instanceLevelMatch[2],
|
|
214
|
+
queryParams
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Parse a query string into an object
|
|
223
|
+
* @param {string} queryString - Query string (without leading ?)
|
|
224
|
+
* @returns {Object} Parsed query parameters
|
|
225
|
+
*/
|
|
226
|
+
parseQueryString(queryString) {
|
|
227
|
+
if (!queryString) {
|
|
228
|
+
return {};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const params = {};
|
|
232
|
+
const pairs = queryString.split('&');
|
|
233
|
+
|
|
234
|
+
for (const pair of pairs) {
|
|
235
|
+
const [key, value] = pair.split('=').map(decodeURIComponent);
|
|
236
|
+
if (key) {
|
|
237
|
+
// Handle repeated parameters
|
|
238
|
+
if (params[key] !== undefined) {
|
|
239
|
+
if (Array.isArray(params[key])) {
|
|
240
|
+
params[key].push(value);
|
|
241
|
+
} else {
|
|
242
|
+
params[key] = [params[key], value];
|
|
243
|
+
}
|
|
244
|
+
} else {
|
|
245
|
+
params[key] = value;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return params;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Get the worker for a given operation
|
|
255
|
+
* @param {string} operation - Operation name (e.g., 'lookup', 'validate-code', 'expand')
|
|
256
|
+
* @returns {Object|null} Worker instance or null
|
|
257
|
+
*/
|
|
258
|
+
getWorkerForOperation(operation) {
|
|
259
|
+
const operationMap = {
|
|
260
|
+
'lookup': this.workers.lookup,
|
|
261
|
+
'validate-code': this.workers.validate,
|
|
262
|
+
'subsumes': this.workers.subsumes,
|
|
263
|
+
'expand': this.workers.expand,
|
|
264
|
+
'translate': this.workers.translate
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
return operationMap[operation] || null;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Build a mock request object for a worker
|
|
272
|
+
* @param {string} method - HTTP method
|
|
273
|
+
* @param {Object} parsedOp - Parsed operation info
|
|
274
|
+
* @param {Object} resource - Request resource (for POST)
|
|
275
|
+
* @param {Object} request - Original request element
|
|
276
|
+
* @returns {Object} Mock request object
|
|
277
|
+
*/
|
|
278
|
+
buildMockRequest(method, parsedOp, resource) {
|
|
279
|
+
return {
|
|
280
|
+
method,
|
|
281
|
+
params: {
|
|
282
|
+
id: parsedOp.instanceId
|
|
283
|
+
},
|
|
284
|
+
query: parsedOp.queryParams,
|
|
285
|
+
body: resource || {},
|
|
286
|
+
headers: {
|
|
287
|
+
'content-type': 'application/json',
|
|
288
|
+
'accept': 'application/json'
|
|
289
|
+
},
|
|
290
|
+
get: function(header) {
|
|
291
|
+
return this.headers[header.toLowerCase()];
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Build a mock response object to capture worker output
|
|
298
|
+
* @returns {Object} Mock response object
|
|
299
|
+
*/
|
|
300
|
+
buildMockResponse() {
|
|
301
|
+
const mockRes = {
|
|
302
|
+
statusCode: 200,
|
|
303
|
+
body: null,
|
|
304
|
+
status: function(code) {
|
|
305
|
+
this.statusCode = code;
|
|
306
|
+
return this;
|
|
307
|
+
},
|
|
308
|
+
json: function(body) {
|
|
309
|
+
this.body = body;
|
|
310
|
+
return this;
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
return mockRes;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Build an error response entry
|
|
318
|
+
* @param {number} statusCode - HTTP status code
|
|
319
|
+
* @param {string} issueCode - FHIR issue code
|
|
320
|
+
* @param {string} message - Error message
|
|
321
|
+
* @returns {Object} Error entry
|
|
322
|
+
*/
|
|
323
|
+
errorEntry(statusCode, issueCode, message) {
|
|
324
|
+
const outcome = {
|
|
325
|
+
resourceType: 'OperationOutcome',
|
|
326
|
+
issue: [{
|
|
327
|
+
severity: 'error',
|
|
328
|
+
code: issueCode,
|
|
329
|
+
diagnostics: message
|
|
330
|
+
}]
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
return {
|
|
334
|
+
resource: outcome,
|
|
335
|
+
response: {
|
|
336
|
+
status: `${statusCode}`,
|
|
337
|
+
outcome: outcome
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Build an OperationOutcome
|
|
344
|
+
* @param {string} severity - error, warning, information
|
|
345
|
+
* @param {string} code - Issue code
|
|
346
|
+
* @param {string} message - Diagnostic message
|
|
347
|
+
* @returns {Object} OperationOutcome resource
|
|
348
|
+
*/
|
|
349
|
+
operationOutcome(severity, code, message) {
|
|
350
|
+
return {
|
|
351
|
+
resourceType: 'OperationOutcome',
|
|
352
|
+
issue: [{
|
|
353
|
+
severity,
|
|
354
|
+
code,
|
|
355
|
+
diagnostics: message
|
|
356
|
+
}]
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
module.exports = BatchWorker;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Closure Worker - Handles ConceptMap $closure operation
|
|
3
|
+
//
|
|
4
|
+
// GET /ConceptMap/$closure?{params}
|
|
5
|
+
// POST /ConceptMap/$closure
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
class ClosureWorker {
|
|
9
|
+
/**
|
|
10
|
+
* Handle a $closure request
|
|
11
|
+
* @param {express.Request} req - Express request (with txProvider attached)
|
|
12
|
+
* @param {express.Response} res - Express response
|
|
13
|
+
* @param {Object} log - Logger instance
|
|
14
|
+
*/
|
|
15
|
+
static handle(req, res) {
|
|
16
|
+
const params = req.method === 'POST' ? req.body : req.query;
|
|
17
|
+
|
|
18
|
+
this.log.debug('ConceptMap $closure with params:', params);
|
|
19
|
+
|
|
20
|
+
// TODO: Implement closure logic using provider
|
|
21
|
+
res.status(501).json({
|
|
22
|
+
resourceType: 'OperationOutcome',
|
|
23
|
+
issue: [{
|
|
24
|
+
severity: 'error',
|
|
25
|
+
code: 'not-supported',
|
|
26
|
+
diagnostics: 'ConceptMap $closure operation not yet implemented'
|
|
27
|
+
}]
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = ClosureWorker;
|