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,467 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Metadata Handler - Handles /metadata endpoint and $versions operation
|
|
3
|
+
//
|
|
4
|
+
// GET /metadata - Returns CapabilityStatement
|
|
5
|
+
// GET /metadata?mode=terminology - Returns TerminologyCapabilities
|
|
6
|
+
// GET /$versions - Returns supported FHIR versions
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
const {CapabilityStatement} = require("../library/capabilitystatement");
|
|
10
|
+
const {TerminologyCapabilities} = require("../library/terminologycapabilities");
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Metadata handler for FHIR terminology server
|
|
14
|
+
* Used by TXModule to handle /metadata requests
|
|
15
|
+
*/
|
|
16
|
+
class MetadataHandler {
|
|
17
|
+
host;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {Object} config - Server configuration
|
|
21
|
+
*/
|
|
22
|
+
constructor(config = {}) {
|
|
23
|
+
this.config = config;
|
|
24
|
+
this.host = config.host;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Handle GET /metadata request
|
|
29
|
+
* @param {express.Request} req - Express request (with txEndpoint and txProvider attached)
|
|
30
|
+
* @param {express.Response} res - Express response
|
|
31
|
+
*/
|
|
32
|
+
async handle(req, res) {
|
|
33
|
+
const mode = req.query.mode;
|
|
34
|
+
const endpoint = req.txEndpoint;
|
|
35
|
+
const provider = req.txProvider;
|
|
36
|
+
|
|
37
|
+
if (mode === 'terminology') {
|
|
38
|
+
this.logInfo = 'termcaps';
|
|
39
|
+
const tc = new TerminologyCapabilities(await this.buildTerminologyCapabilities(endpoint, provider));
|
|
40
|
+
return res.json(tc.toJSON(endpoint.fhirVersion));
|
|
41
|
+
}
|
|
42
|
+
this.logInfo = 'metadata';
|
|
43
|
+
|
|
44
|
+
// Default: return CapabilityStatement
|
|
45
|
+
const cs = new CapabilityStatement(this.buildCapabilityStatement(endpoint, provider));
|
|
46
|
+
return res.json(cs.toJSON(endpoint.fhirVersion));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Handle GET /$versions request
|
|
51
|
+
* @param {express.Request} req - Express request (with txEndpoint attached)
|
|
52
|
+
* @param {express.Response} res - Express response
|
|
53
|
+
*/
|
|
54
|
+
handleVersions(req, res) {
|
|
55
|
+
const endpoint = req.txEndpoint;
|
|
56
|
+
const fhirVersion = this.getShortFhirVersion(endpoint.fhirVersion);
|
|
57
|
+
|
|
58
|
+
// Check Accept header to determine response format
|
|
59
|
+
const accept = req.get('Accept') || '';
|
|
60
|
+
const isFhirJson = accept.includes('application/fhir+json') ||
|
|
61
|
+
accept.includes('application/fhir+xml');
|
|
62
|
+
|
|
63
|
+
if (isFhirJson) {
|
|
64
|
+
// Return FHIR Parameters resource
|
|
65
|
+
return res.json({
|
|
66
|
+
resourceType: 'Parameters',
|
|
67
|
+
parameter: [
|
|
68
|
+
{
|
|
69
|
+
name: 'version',
|
|
70
|
+
valueCode: fhirVersion
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: 'default',
|
|
74
|
+
valueCode: fhirVersion
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
});
|
|
78
|
+
} else {
|
|
79
|
+
// Return simple JSON
|
|
80
|
+
return res.json({
|
|
81
|
+
versions: [fhirVersion],
|
|
82
|
+
default: fhirVersion
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get short FHIR version (e.g., "4.0" from "4.0.1" or "4.0")
|
|
89
|
+
* @param {string} version - FHIR version string
|
|
90
|
+
* @returns {string} Short version (X.0 format)
|
|
91
|
+
*/
|
|
92
|
+
getShortFhirVersion(version) {
|
|
93
|
+
if (!version) return '4.0';
|
|
94
|
+
|
|
95
|
+
// If already short format (e.g., "4.0"), return as-is
|
|
96
|
+
const parts = String(version).split('.');
|
|
97
|
+
if (parts.length >= 2) {
|
|
98
|
+
return `${parts[0]}.${parts[1]}`;
|
|
99
|
+
}
|
|
100
|
+
return version;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Build CapabilityStatement for an endpoint
|
|
105
|
+
* @param {Object} endpoint - Endpoint info {path, fhirVersion, context}
|
|
106
|
+
* @param {Object} provider - Provider for code systems and resources
|
|
107
|
+
* @returns {Object} CapabilityStatement resource
|
|
108
|
+
*/
|
|
109
|
+
buildCapabilityStatement(endpoint) {
|
|
110
|
+
const now = new Date().toISOString();
|
|
111
|
+
const fhirVersion = this.mapFhirVersion(endpoint.fhirVersion);
|
|
112
|
+
const baseUrl = this.config.baseUrl || `https://${this.host}${endpoint.path}`;
|
|
113
|
+
const serverVersion = this.config.serverVersion || '1.0.0';
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
resourceType: 'CapabilityStatement',
|
|
117
|
+
id: this.config.id || 'FhirServer',
|
|
118
|
+
'extension' : [
|
|
119
|
+
{
|
|
120
|
+
'extension' : [
|
|
121
|
+
{
|
|
122
|
+
'url' : 'definition',
|
|
123
|
+
'valueCanonical' : 'http://hl7.org/fhir/uv/tx-tests/FeatureDefinition/test-version'
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
'url' : 'value',
|
|
127
|
+
'valueCode' : '1.8.0'
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
'extension' : [
|
|
131
|
+
{
|
|
132
|
+
'url' : 'name',
|
|
133
|
+
'valueCode' : 'mode'
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
'url' : 'value',
|
|
137
|
+
'valueCode' : 'tx.fhir.org'
|
|
138
|
+
}
|
|
139
|
+
],
|
|
140
|
+
'url' : 'qualifier'
|
|
141
|
+
}
|
|
142
|
+
],
|
|
143
|
+
'url' : 'http://hl7.org/fhir/uv/application-feature/StructureDefinition/feature'
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
'extension' : [
|
|
147
|
+
{
|
|
148
|
+
'url' : 'definition',
|
|
149
|
+
'valueCanonical' : 'http://hl7.org/fhir/uv/tx-ecosystem/FeatureDefinition/CodeSystemAsParameter'
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
'url' : 'value',
|
|
153
|
+
'valueBoolean' : true
|
|
154
|
+
}
|
|
155
|
+
],
|
|
156
|
+
'url' : 'http://hl7.org/fhir/uv/application-feature/StructureDefinition/feature'
|
|
157
|
+
}
|
|
158
|
+
],
|
|
159
|
+
url: `${baseUrl}/CapabilityStatement/tx`,
|
|
160
|
+
version: `${fhirVersion}-${serverVersion}`,
|
|
161
|
+
name: this.config.name || 'FHIRTerminologyServer',
|
|
162
|
+
title: this.config.title || 'FHIR Terminology Server Conformance Statement',
|
|
163
|
+
status: 'active',
|
|
164
|
+
date: now,
|
|
165
|
+
contact: this.config.contact || [
|
|
166
|
+
{
|
|
167
|
+
telecom: [
|
|
168
|
+
{
|
|
169
|
+
system: 'other',
|
|
170
|
+
value: this.config.contactUrl || 'http://example.org/'
|
|
171
|
+
}
|
|
172
|
+
]
|
|
173
|
+
}
|
|
174
|
+
],
|
|
175
|
+
description: this.config.description || 'FHIR Terminology Server',
|
|
176
|
+
kind: 'instance',
|
|
177
|
+
instantiates: [
|
|
178
|
+
'http://hl7.org/fhir/CapabilityStatement/terminology-server'
|
|
179
|
+
],
|
|
180
|
+
software: {
|
|
181
|
+
name: this.config.softwareName || 'FHIR Terminology Server',
|
|
182
|
+
version: serverVersion,
|
|
183
|
+
releaseDate: this.config.releaseDate || now
|
|
184
|
+
},
|
|
185
|
+
implementation: {
|
|
186
|
+
description: `FHIR Server running at ${baseUrl}`,
|
|
187
|
+
url: baseUrl
|
|
188
|
+
},
|
|
189
|
+
fhirVersion: fhirVersion,
|
|
190
|
+
format: ['application/fhir+xml', 'application/fhir+json'],
|
|
191
|
+
rest: [
|
|
192
|
+
{
|
|
193
|
+
mode: 'server',
|
|
194
|
+
security: {
|
|
195
|
+
cors: true
|
|
196
|
+
},
|
|
197
|
+
resource: [
|
|
198
|
+
{
|
|
199
|
+
type: 'CodeSystem',
|
|
200
|
+
interaction: [
|
|
201
|
+
{ code: 'read', documentation: 'Read a code system' },
|
|
202
|
+
{ code: 'search-type', documentation: 'Search the code systems' }
|
|
203
|
+
],
|
|
204
|
+
searchParam: [
|
|
205
|
+
{ name: 'url', type: 'uri' },
|
|
206
|
+
{ name: 'version', type: 'token' },
|
|
207
|
+
{ name: 'name', type: 'string' },
|
|
208
|
+
{ name: 'title', type: 'string' },
|
|
209
|
+
{ name: 'status', type: 'token' },
|
|
210
|
+
{ name: '_id', type: 'token' }
|
|
211
|
+
],
|
|
212
|
+
operation: [
|
|
213
|
+
{ name: 'lookup', definition: 'http://hl7.org/fhir/OperationDefinition/CodeSystem-lookup' },
|
|
214
|
+
{ name: 'validate-code', definition: 'http://hl7.org/fhir/OperationDefinition/CodeSystem-validate-code' },
|
|
215
|
+
{ name: 'subsumes', definition: 'http://hl7.org/fhir/OperationDefinition/CodeSystem-subsumes' }
|
|
216
|
+
]
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
type: 'ValueSet',
|
|
220
|
+
interaction: [
|
|
221
|
+
{ code: 'read', documentation: 'Read a ValueSet' },
|
|
222
|
+
{ code: 'search-type', documentation: 'Search the value sets' }
|
|
223
|
+
],
|
|
224
|
+
searchParam: [
|
|
225
|
+
{ name: 'url', type: 'uri' },
|
|
226
|
+
{ name: 'version', type: 'token' },
|
|
227
|
+
{ name: 'name', type: 'string' },
|
|
228
|
+
{ name: 'title', type: 'string' },
|
|
229
|
+
{ name: 'status', type: 'token' },
|
|
230
|
+
{ name: '_id', type: 'token' }
|
|
231
|
+
],
|
|
232
|
+
operation: [
|
|
233
|
+
{ name: 'expand', definition: 'http://hl7.org/fhir/OperationDefinition/ValueSet-expand' },
|
|
234
|
+
{ name: 'validate-code', definition: 'http://hl7.org/fhir/OperationDefinition/ValueSet-validate-code' }
|
|
235
|
+
]
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
type: 'ConceptMap',
|
|
239
|
+
interaction: [
|
|
240
|
+
{ code: 'read', documentation: 'Read a ConceptMap' },
|
|
241
|
+
{ code: 'search-type', documentation: 'Search the concept maps' }
|
|
242
|
+
],
|
|
243
|
+
searchParam: [
|
|
244
|
+
{ name: 'url', type: 'uri' },
|
|
245
|
+
{ name: 'version', type: 'token' },
|
|
246
|
+
{ name: 'name', type: 'string' },
|
|
247
|
+
{ name: 'title', type: 'string' },
|
|
248
|
+
{ name: 'status', type: 'token' },
|
|
249
|
+
{ name: '_id', type: 'token' }
|
|
250
|
+
],
|
|
251
|
+
operation: [
|
|
252
|
+
{ name: 'translate', definition: 'http://hl7.org/fhir/OperationDefinition/ConceptMap-translate' },
|
|
253
|
+
{ name: 'closure', definition: 'http://hl7.org/fhir/OperationDefinition/ConceptMap-closure' }
|
|
254
|
+
]
|
|
255
|
+
}
|
|
256
|
+
],
|
|
257
|
+
interaction: [
|
|
258
|
+
{ code: 'transaction' }
|
|
259
|
+
],
|
|
260
|
+
operation: [
|
|
261
|
+
{ name: 'expand', definition: 'http://hl7.org/fhir/OperationDefinition/ValueSet-expand' },
|
|
262
|
+
{ name: 'lookup', definition: 'http://hl7.org/fhir/OperationDefinition/CodeSystem-lookup' },
|
|
263
|
+
{ name: 'subsumes', definition: 'http://hl7.org/fhir/OperationDefinition/CodeSystem-subsumes' },
|
|
264
|
+
{ name: 'validate-code', definition: 'http://hl7.org/fhir/OperationDefinition/Resource-validate-code' },
|
|
265
|
+
{ name: 'translate', definition: 'http://hl7.org/fhir/OperationDefinition/ConceptMap-translate' },
|
|
266
|
+
{ name: 'closure', definition: 'http://hl7.org/fhir/OperationDefinition/ConceptMap-closure' },
|
|
267
|
+
{ name: 'versions', definition: 'http://hl7.org/fhir/OperationDefinition/fhir-versions' }
|
|
268
|
+
]
|
|
269
|
+
}
|
|
270
|
+
]
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Build TerminologyCapabilities resource
|
|
276
|
+
* @param {Object} endpoint - Endpoint info
|
|
277
|
+
* @param {Object} provider - Provider for code systems and resources
|
|
278
|
+
* @returns {Object} TerminologyCapabilities resource
|
|
279
|
+
*/
|
|
280
|
+
async buildTerminologyCapabilities(endpoint, provider) {
|
|
281
|
+
const now = new Date().toISOString();
|
|
282
|
+
const baseUrl = this.config.baseUrl || `https://${this.host}${endpoint.path}`;
|
|
283
|
+
const serverVersion = this.config.serverVersion || '1.0.0';
|
|
284
|
+
|
|
285
|
+
const tc = {
|
|
286
|
+
resourceType: 'TerminologyCapabilities',
|
|
287
|
+
id: this.config.id || 'FhirServer',
|
|
288
|
+
url: `${baseUrl}/TerminologyCapabilities/tx`,
|
|
289
|
+
version: serverVersion,
|
|
290
|
+
name: this.config.name || 'FHIRTerminologyServerCapabilities',
|
|
291
|
+
title: this.config.title || 'FHIR Terminology Server Capability Statement',
|
|
292
|
+
status: 'active',
|
|
293
|
+
date: now,
|
|
294
|
+
contact: this.config.contact || [
|
|
295
|
+
{
|
|
296
|
+
telecom: [
|
|
297
|
+
{
|
|
298
|
+
system: 'other',
|
|
299
|
+
value: this.config.contactUrl || 'http://example.org/'
|
|
300
|
+
}
|
|
301
|
+
]
|
|
302
|
+
}
|
|
303
|
+
],
|
|
304
|
+
description: this.config.description || 'Terminology Capability Statement for FHIR Terminology Server',
|
|
305
|
+
kind: 'instance',
|
|
306
|
+
codeSystem: await this.buildCodeSystemEntries(provider),
|
|
307
|
+
expansion: this.buildExpansionCapabilities(),
|
|
308
|
+
validateCode: this.buildValidateCodeCapabilities(),
|
|
309
|
+
translation: this.buildTranslationCapabilities()
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
return tc;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Build codeSystem entries from provider
|
|
317
|
+
* @param {Object} provider - Provider with codeSystems and codeSystemFactories
|
|
318
|
+
* @returns {Object[]} Array of codeSystem entries
|
|
319
|
+
*/
|
|
320
|
+
async buildCodeSystemEntries(provider) {
|
|
321
|
+
const seenSystems = new Map(); // url -> entry for deduplication
|
|
322
|
+
|
|
323
|
+
// Process provider.codeSystems (direct CodeSystem resources)
|
|
324
|
+
if (provider && provider.codeSystems) {
|
|
325
|
+
for (const cs of provider.codeSystems.values()) {
|
|
326
|
+
const url = cs.url || (cs.jsonObj && cs.jsonObj.url);
|
|
327
|
+
const version = cs.version || (cs.jsonObj && cs.jsonObj.version);
|
|
328
|
+
|
|
329
|
+
if (url) {
|
|
330
|
+
this.addCodeSystemEntry(seenSystems, url, version);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Process provider.codeSystemFactories (factory providers)
|
|
336
|
+
if (provider && provider.codeSystemFactories) {
|
|
337
|
+
for (const factory of provider.codeSystemFactories.values()) {
|
|
338
|
+
const url = factory.system();
|
|
339
|
+
const version = factory.version();
|
|
340
|
+
|
|
341
|
+
if (url) {
|
|
342
|
+
this.addCodeSystemEntry(seenSystems, url, version);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Convert map to array and sort by URI
|
|
348
|
+
const entries = Array.from(seenSystems.values());
|
|
349
|
+
entries.sort((a, b) => (a.uri || '').localeCompare(b.uri || ''));
|
|
350
|
+
|
|
351
|
+
return entries.length > 0 ? entries : undefined;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Add or update a code system entry
|
|
356
|
+
* @param {Map} seenSystems - Map of URL to entry
|
|
357
|
+
* @param {string} url - Code system URL
|
|
358
|
+
* @param {string} version - Code system version (may be null)
|
|
359
|
+
*/
|
|
360
|
+
addCodeSystemEntry(seenSystems, url, version) {
|
|
361
|
+
if (!seenSystems.has(url)) {
|
|
362
|
+
// Create new entry
|
|
363
|
+
const entry = { uri: url };
|
|
364
|
+
if (version) {
|
|
365
|
+
entry.version = [{ code: version }];
|
|
366
|
+
}
|
|
367
|
+
seenSystems.set(url, entry);
|
|
368
|
+
} else if (version) {
|
|
369
|
+
// Add version to existing entry
|
|
370
|
+
const entry = seenSystems.get(url);
|
|
371
|
+
if (!entry.version) {
|
|
372
|
+
entry.version = [];
|
|
373
|
+
}
|
|
374
|
+
// Check if version already exists
|
|
375
|
+
if (!entry.version.some(v => v.code === version)) {
|
|
376
|
+
entry.version.push({ code: version });
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Build expansion capabilities
|
|
383
|
+
* @returns {Object} Expansion capabilities object
|
|
384
|
+
*/
|
|
385
|
+
buildExpansionCapabilities() {
|
|
386
|
+
return {
|
|
387
|
+
parameter: [
|
|
388
|
+
{
|
|
389
|
+
name: 'cache-id',
|
|
390
|
+
documentation: 'This server supports caching terminology resources between calls. Clients only need to send value sets and codesystems once; thereafter they are automatically in scope for calls with the same cache-id. The cache is retained for 30 min from last call'
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
name: 'tx-resource',
|
|
394
|
+
documentation: 'Additional valuesets needed for evaluation e.g. value sets referred to from the import statement of the value set being expanded'
|
|
395
|
+
},
|
|
396
|
+
{ name: '_incomplete' },
|
|
397
|
+
{ name: 'abstract' },
|
|
398
|
+
{ name: 'activeOnly' },
|
|
399
|
+
{ name: 'check-system-version' },
|
|
400
|
+
{ name: 'count' },
|
|
401
|
+
{ name: 'default-to-latest-version' },
|
|
402
|
+
{ name: 'displayLanguage' },
|
|
403
|
+
{ name: 'excludeNested' },
|
|
404
|
+
{ name: 'excludeNotForUI' },
|
|
405
|
+
{ name: 'excludePostCoordinated' },
|
|
406
|
+
{ name: 'force-system-version' },
|
|
407
|
+
{ name: 'inactive' },
|
|
408
|
+
{ name: 'includeAlternateCodes' },
|
|
409
|
+
{ name: 'includeDefinition' },
|
|
410
|
+
{ name: 'includeDesignations' },
|
|
411
|
+
{ name: 'incomplete-ok' },
|
|
412
|
+
{ name: 'limitedExpansion' },
|
|
413
|
+
{
|
|
414
|
+
name: 'mode',
|
|
415
|
+
documentation: '=lenient-display-validation'
|
|
416
|
+
},
|
|
417
|
+
{ name: 'no-cache' },
|
|
418
|
+
{ name: 'offset' },
|
|
419
|
+
{ name: 'profile' },
|
|
420
|
+
{ name: 'property' },
|
|
421
|
+
{ name: 'system-version' },
|
|
422
|
+
{
|
|
423
|
+
name: 'valueSetMode',
|
|
424
|
+
documentation: '= CHECK_MEMBERSHIP_ONLY | NO_MEMBERSHIP_CHECK'
|
|
425
|
+
}
|
|
426
|
+
]
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Build validateCode capabilities
|
|
432
|
+
* @returns {Object} ValidateCode capabilities object
|
|
433
|
+
*/
|
|
434
|
+
buildValidateCodeCapabilities() {
|
|
435
|
+
return {
|
|
436
|
+
"translations" : true
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Build translation capabilities
|
|
442
|
+
* @returns {Object} Translation capabilities object
|
|
443
|
+
*/
|
|
444
|
+
buildTranslationCapabilities() {
|
|
445
|
+
return {
|
|
446
|
+
needsMap : false
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Map short FHIR version to full version string
|
|
452
|
+
* @param {string} version - Short version (e.g., '4.0')
|
|
453
|
+
* @returns {string} Full version (e.g., '4.0.1')
|
|
454
|
+
*/
|
|
455
|
+
mapFhirVersion(version) {
|
|
456
|
+
const versionMap = {
|
|
457
|
+
'3.0': '3.0.2',
|
|
458
|
+
'4.0': '4.0.1',
|
|
459
|
+
'4.3': '4.3.0',
|
|
460
|
+
'5.0': '5.0.0',
|
|
461
|
+
'6.0': '6.0.0'
|
|
462
|
+
};
|
|
463
|
+
return versionMap[version] || version || '4.0.1';
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
module.exports = { MetadataHandler };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
|
|
2
|
+
const {TerminologyWorker} = require("./worker");
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* this handles assembling the information for the operations form
|
|
6
|
+
*/
|
|
7
|
+
class OperationsWorker extends TerminologyWorker {
|
|
8
|
+
/**
|
|
9
|
+
* @param {OperationContext} opContext - Operation context
|
|
10
|
+
* @param {Logger} log - Logger instance
|
|
11
|
+
* @param {Provider} provider - Provider for code systems and resources
|
|
12
|
+
* @param {LanguageDefinitions} languages - Language definitions
|
|
13
|
+
* @param {I18nSupport} i18n - Internationalization support
|
|
14
|
+
*/
|
|
15
|
+
constructor(opContext, log, provider, languages, i18n) {
|
|
16
|
+
super(opContext, log, provider, languages, i18n);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get operation name
|
|
21
|
+
* @returns {string}
|
|
22
|
+
*/
|
|
23
|
+
opName() {
|
|
24
|
+
return 'search';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async handle(req, res) {
|
|
28
|
+
const formData = { resourceType : "Operations" };
|
|
29
|
+
formData.valueSets = await this.provider.listAllValueSets();
|
|
30
|
+
return res.json(formData);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = { OperationsWorker };
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Read Worker - Handles resource read operations
|
|
3
|
+
//
|
|
4
|
+
// GET /{type}/{id}
|
|
5
|
+
//
|
|
6
|
+
|
|
7
|
+
const { TerminologyWorker } = require('./worker');
|
|
8
|
+
|
|
9
|
+
class ReadWorker extends TerminologyWorker {
|
|
10
|
+
/**
|
|
11
|
+
* @param {OperationContext} opContext - Operation context
|
|
12
|
+
* @param {Logger} log - Logger instance
|
|
13
|
+
* @param {Provider} provider - Provider for code systems and resources
|
|
14
|
+
* @param {LanguageDefinitions} languages - Language definitions
|
|
15
|
+
* @param {I18nSupport} i18n - Internationalization support
|
|
16
|
+
*/
|
|
17
|
+
constructor(opContext, log, provider, languages, i18n) {
|
|
18
|
+
super(opContext, log, provider, languages, i18n);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get operation name
|
|
23
|
+
* @returns {string}
|
|
24
|
+
*/
|
|
25
|
+
opName() {
|
|
26
|
+
return 'read';
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Handle a read request
|
|
30
|
+
* @param {express.Request} req - Express request (with txProvider attached)
|
|
31
|
+
* @param {express.Response} res - Express response
|
|
32
|
+
* @param {string} resourceType - The resource type (CodeSystem, ValueSet, ConceptMap)
|
|
33
|
+
* @param {Object} log - Logger instance
|
|
34
|
+
*/
|
|
35
|
+
async handle(req, res, resourceType) {
|
|
36
|
+
const { id } = req.params;
|
|
37
|
+
|
|
38
|
+
this.log.debug(`Read ${resourceType}/${id}`);
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
switch (resourceType) {
|
|
42
|
+
case 'CodeSystem':
|
|
43
|
+
return await this.handleCodeSystem(req, res, id);
|
|
44
|
+
|
|
45
|
+
case 'ValueSet':
|
|
46
|
+
return await this.handleValueSet(req, res, id);
|
|
47
|
+
|
|
48
|
+
case 'ConceptMap':
|
|
49
|
+
return res.status(501).json({
|
|
50
|
+
resourceType: 'OperationOutcome',
|
|
51
|
+
issue: [{
|
|
52
|
+
severity: 'error',
|
|
53
|
+
code: 'not-supported',
|
|
54
|
+
diagnostics: 'ConceptMap read not yet implemented'
|
|
55
|
+
}]
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
default:
|
|
59
|
+
return res.status(404).json({
|
|
60
|
+
resourceType: 'OperationOutcome',
|
|
61
|
+
issue: [{
|
|
62
|
+
severity: 'error',
|
|
63
|
+
code: 'not-found',
|
|
64
|
+
diagnostics: `Unknown resource type: ${resourceType}`
|
|
65
|
+
}]
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
} catch (error) {
|
|
69
|
+
req.logInfo = this.usedSources.join("|")+" - error"+(error.msgId ? " "+error.msgId : "");
|
|
70
|
+
this.log.error(error);
|
|
71
|
+
return res.status(500).json({
|
|
72
|
+
resourceType: 'OperationOutcome',
|
|
73
|
+
issue: [{
|
|
74
|
+
severity: 'error',
|
|
75
|
+
code: 'exception',
|
|
76
|
+
diagnostics: error.message
|
|
77
|
+
}]
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Handle CodeSystem read
|
|
84
|
+
*/
|
|
85
|
+
async handleCodeSystem(req, res, id) {
|
|
86
|
+
let cs = this.provider.getCodeSystemById(this.opContext, id);
|
|
87
|
+
if (cs != null) {
|
|
88
|
+
return res.json(cs.jsonObj);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (id.startsWith("x-")) {
|
|
92
|
+
cs = this.provider.getCodeSystemFactoryById(this.opContext, id.substring(2));
|
|
93
|
+
if (cs != null) {
|
|
94
|
+
let json = {
|
|
95
|
+
resourceType: "CodeSystem",
|
|
96
|
+
id: "x-" + cs.id(),
|
|
97
|
+
url: cs.system(),
|
|
98
|
+
name: cs.name(),
|
|
99
|
+
status: "active",
|
|
100
|
+
description: "This is a place holder for the code system which is fully supported through internal means (not by this code system)",
|
|
101
|
+
content: "not-present"
|
|
102
|
+
}
|
|
103
|
+
if (cs.version()) {
|
|
104
|
+
json.version = cs.version();
|
|
105
|
+
}
|
|
106
|
+
if (cs.iteratable()) {
|
|
107
|
+
json.content = "conplete",
|
|
108
|
+
json.concept = [];
|
|
109
|
+
let csp = cs.build(this.opContext, []);
|
|
110
|
+
let iter = await csp.iteratorAll();
|
|
111
|
+
let c = await csp.nextContext(iter);
|
|
112
|
+
while (c) {
|
|
113
|
+
let cc = {
|
|
114
|
+
code: await csp.code(c),
|
|
115
|
+
display: await csp.display(c)
|
|
116
|
+
}
|
|
117
|
+
let def = await csp.definition(c);
|
|
118
|
+
if (def) {
|
|
119
|
+
cc.definition = def;
|
|
120
|
+
}
|
|
121
|
+
json.concept.push(cc);
|
|
122
|
+
c = await csp.nextContext(iter);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
}
|
|
126
|
+
return res.json(json);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return res.status(404).json({
|
|
131
|
+
resourceType: 'OperationOutcome',
|
|
132
|
+
issue: [{
|
|
133
|
+
severity: 'error',
|
|
134
|
+
code: 'not-found',
|
|
135
|
+
diagnostics: `CodeSystem/${id} not found`
|
|
136
|
+
}]
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Handle ValueSet read
|
|
142
|
+
*/
|
|
143
|
+
async handleValueSet(req, res, id) {
|
|
144
|
+
// Iterate through valueSetProviders in order
|
|
145
|
+
for (const vsp of this.provider.valueSetProviders) {
|
|
146
|
+
this.deadCheck('handleValueSet-loop');
|
|
147
|
+
const vs = await vsp.fetchValueSetById(id);
|
|
148
|
+
if (vs) {
|
|
149
|
+
return res.json(vs.jsonObj);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return res.status(404).json({
|
|
154
|
+
resourceType: 'OperationOutcome',
|
|
155
|
+
issue: [{
|
|
156
|
+
severity: 'error',
|
|
157
|
+
code: 'not-found',
|
|
158
|
+
diagnostics: `ValueSet/${id} not found`
|
|
159
|
+
}]
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
module.exports = ReadWorker;
|