fhirsmith 0.18.1 → 1.0.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/ACCESSIBILITY.md +119 -0
- package/CHANGELOG.md +190 -9
- package/CONFIGURATION.md +305 -0
- package/LICENSE +29 -0
- package/README.md +29 -122
- package/cluster.js +1220 -0
- package/cluster.md +707 -0
- package/config-template.json +77 -8
- package/extension-tracker/extension-tracker-template.html +5 -8
- package/extension-tracker/extension-tracker.js +0 -2
- package/folder/folder.js +7 -6
- package/ips/analysis.js +560 -0
- package/ips/ips-template.html +5 -8
- package/ips/ips.js +331 -33
- package/ips/jurisdictions.js +65 -0
- package/ips/nationality.js +120 -0
- package/ips/pipeline.js +131 -17
- package/ips/presenter.js +1 -1
- package/ips/profile-editor.js +283 -4
- package/ips/readme.md +170 -20
- package/ips/render.js +769 -109
- package/ips/scripts.js +46 -0
- package/ips/store.js +123 -11
- package/ips/structure.js +247 -0
- package/ips/terminology.js +1 -1
- package/library/body-limit.js +22 -0
- package/library/cache-generation.js +15 -0
- package/library/cgroup-memory.js +133 -1
- package/library/cluster-counts.js +121 -0
- package/library/cluster-events.js +153 -0
- package/library/cluster-metrics.js +193 -0
- package/library/cluster-status.js +468 -0
- package/library/config-reader.js +96 -0
- package/library/daily-report.js +100 -2
- package/library/html-server.js +27 -1
- package/library/i18nsupport.js +23 -1
- package/library/keep-alive.js +18 -0
- package/library/languages.js +17 -3
- package/library/logger.js +31 -0
- package/library/memory-guard.js +428 -0
- package/library/native-memory.js +52 -0
- package/library/npm-audit.js +51 -25
- package/library/openapi-doc.js +7 -5
- package/library/package-manager.js +8 -0
- package/library/role.js +197 -0
- package/library/server-credentials.js +169 -0
- package/library/sqlite-utilities.js +59 -0
- package/library/ssrf-guard.js +100 -0
- package/library/static-response-stats.js +16 -0
- package/library/version-utilities.js +50 -0
- package/library/worker-count.js +192 -0
- package/nginx.md +30 -11
- package/npmprojector/indexer.js +18 -10
- package/package.json +11 -8
- package/packages/feed-check-page.js +117 -0
- package/packages/feed-check.js +279 -0
- package/packages/npm-check.js +57 -3
- package/packages/npm-signing.js +219 -0
- package/packages/openapi.yaml +207 -1
- package/packages/package-crawler.js +322 -41
- package/packages/packages-template.html +6 -8
- package/packages/packages.js +604 -57
- package/packages/readme.md +113 -1
- package/publisher/publisher-template.html +2 -5
- package/publisher/publisher.js +121 -110
- package/publisher/readme.md +75 -0
- package/registry/api.js +179 -1
- package/registry/crawler.js +86 -108
- package/registry/documentation/api.md +68 -2
- package/registry/documentation/configuration.md +34 -0
- package/registry/documentation/internals.md +4 -1
- package/registry/documentation/readme.md +3 -2
- package/registry/model.js +8 -0
- package/registry/openapi.yaml +84 -0
- package/registry/registry-template.html +5 -8
- package/registry/registry.js +58 -6
- package/root-bare-template.html +7 -4
- package/root-template.html +5 -8
- package/server.js +398 -198
- package/shl/shl.js +38 -37
- package/static/a11y/a11y-check.js +832 -0
- package/static/a11y/axe.min.js +12 -0
- package/static/assets/css/bootstrap-fhir.css +15 -15
- package/static/assets/images/flags/au.svg +18 -0
- package/static/assets/images/flags/unknown.svg +11 -0
- package/static/assets/images/flags/us.svg +63 -0
- package/static/assets/images/icon_audit.png +0 -0
- package/static/assets/images/icon_clinical.png +0 -0
- package/static/assets/images/icon_document.png +0 -0
- package/static/assets/images/icon_external.png +0 -0
- package/static/assets/images/icon_medication.png +0 -0
- package/static/assets/images/icon_organization.png +0 -0
- package/static/assets/images/icon_other.png +0 -0
- package/static/assets/images/icon_person.png +0 -0
- package/static/assets/images/icon_section.png +0 -0
- package/stats-db.js +45 -0
- package/stats.js +197 -10
- package/testing/testing-template.html +5 -8
- package/token/nginx_passport_setup.md +1 -1
- package/token/security_guide.md +1 -1
- package/token/token-template.html +2 -5
- package/token/token.js +40 -36
- package/translations/Messages.properties +19 -0
- package/tx/README.md +158 -4
- package/tx/closure/closure-store.js +126 -3
- package/tx/cm/cm-database.js +69 -57
- package/tx/cm/cm-package.js +50 -16
- package/tx/cs/cs-api.js +284 -19
- package/tx/cs/cs-api.md +41 -4
- package/tx/cs/cs-areacode.js +1 -1
- package/tx/cs/cs-country.js +17 -5
- package/tx/cs/cs-cpt.js +33 -16
- package/tx/cs/cs-cs.js +199 -24
- package/tx/cs/cs-currency.js +1 -1
- package/tx/cs/cs-icd11.js +21 -13
- package/tx/cs/cs-icf.js +22 -3
- package/tx/cs/cs-lang.js +2 -2
- package/tx/cs/cs-loinc.js +193 -67
- package/tx/cs/cs-mimetypes.js +12 -2
- package/tx/cs/cs-ndc.js +40 -25
- package/tx/cs/cs-omop.js +54 -29
- package/tx/cs/cs-rxnorm.js +300 -196
- package/tx/cs/cs-snomed.js +861 -95
- package/tx/cs/cs-ucum.js +14 -4
- package/tx/cs/cs-unii.js +18 -14
- package/tx/cs/cs-virtual.js +210 -0
- package/tx/ecosystem-resolver.js +413 -0
- package/tx/html/cluster-dash-metrics.liquid +26 -0
- package/tx/html/cluster-home-metrics.liquid +31 -0
- package/tx/html/cluster-metrics-script.liquid +251 -0
- package/tx/html/codesystem-operations.liquid +3 -3
- package/tx/html/ecl-panel.liquid +1 -1
- package/tx/html/external-metrics.liquid +17 -0
- package/tx/html/operations-form.liquid +17 -17
- package/tx/html/search-form.liquid +11 -11
- package/tx/html/status-line.liquid +58 -0
- package/tx/html/status-panel.liquid +140 -0
- package/tx/html/status-tasks.liquid +25 -0
- package/tx/html/tx-template.html +2 -5
- package/tx/importers/atc-to-fhir.js +17 -16
- package/tx/importers/export-icd11-codesystem.js +3 -2
- package/tx/importers/import-icd11.js +4 -3
- package/tx/importers/import-loinc.module.js +118 -56
- package/tx/importers/import-ndc.module.js +56 -44
- package/tx/importers/import-rxnorm.module.js +118 -79
- package/tx/importers/import-sct.module.js +125 -61
- package/tx/importers/import-unii.module.js +37 -36
- package/tx/importers/subset-loinc.module.js +49 -48
- package/tx/importers/subset-rxnorm.module.js +29 -28
- package/tx/importers/terminal.js +18 -0
- package/tx/importers/tx-import-base.js +13 -12
- package/tx/importers/tx-import-settings.js +5 -4
- package/tx/importers/tx-import.js +53 -52
- package/tx/library/canonical-resource.js +49 -0
- package/tx/library/codesystem.js +94 -5
- package/tx/library/designations.js +45 -2
- package/tx/library/extensions.js +13 -7
- package/tx/library/implicit-valuesets.js +66 -0
- package/tx/library/renderer.js +77 -21
- package/tx/library/sqlite-utilities.js +2 -0
- package/tx/library/tho-precedence.js +40 -0
- package/tx/library/ucum-parsers.js +11 -1
- package/tx/library/wait-for-cache.js +43 -0
- package/tx/library.js +321 -22
- package/tx/openapi-operations.js +6 -4
- package/tx/operation-context.js +460 -99
- package/tx/params.js +25 -0
- package/tx/problems.js +3 -5
- package/tx/provider.js +314 -67
- package/tx/resource-cache-sqlite.js +1050 -0
- package/tx/sct/documentation.md +45 -21
- package/tx/sct/ecl.js +81 -17
- package/tx/sct/editions.js +39 -0
- package/tx/sct/expressions.js +10 -1
- package/tx/sct/load-config.js +107 -0
- package/tx/sct/mmap.js +109 -0
- package/tx/sct/structures.js +175 -15
- package/tx/sct/text-index.js +5 -1
- package/tx/syndication/syndication-feed.js +2 -2
- package/tx/tx-html.js +44 -8
- package/tx/tx.fhir.org.yml +8 -8
- package/tx/tx.js +282 -80
- package/tx/usage-tracker.js +39 -11
- package/tx/vs/vs-api.js +19 -0
- package/tx/vs/vs-database.js +386 -62
- package/tx/vs/vs-package.js +44 -17
- package/tx/vs/vs-vsac.js +649 -101
- package/tx/workers/batch-validate.js +37 -10
- package/tx/workers/batch.js +6 -1
- package/tx/workers/cache-control.js +15 -4
- package/tx/workers/closure.js +156 -13
- package/tx/workers/compare.js +8 -5
- package/tx/workers/expand.js +1173 -289
- package/tx/workers/metadata.js +35 -0
- package/tx/workers/search.js +53 -1
- package/tx/workers/supplement-filters.js +145 -0
- package/tx/workers/translate.js +169 -4
- package/tx/workers/validate.js +223 -119
- package/tx/workers/worker.js +123 -31
- package/tx/xversion/xv-valueset.js +7 -4
- package/utilities/submit-test-report.js +3 -1
- package/utilities/tx-stress-test.js +482 -0
- package/vcl/readme.md +37 -0
- package/vcl/vcl-parser.js +15 -0
- package/xig/readme.md +52 -0
- package/xig/xig-template.html +5 -8
- package/xig/xig.js +238 -422
- package/changelog-template.md +0 -22
- package/registry/test-runner.js +0 -237
- package/release.sh +0 -80
- package/test-scripts/repro-re2-wasm-leak.js +0 -34
- package/tsconfig.eslint.json +0 -9
- package/tx/html/dash-metrics.liquid +0 -147
- package/tx/html/home-metrics.liquid +0 -225
package/tx/cm/cm-package.js
CHANGED
|
@@ -8,6 +8,8 @@ const { VersionUtilities } = require('../../library/version-utilities');
|
|
|
8
8
|
const {validateParameter} = require("../../library/utilities");
|
|
9
9
|
const {ConceptMap} = require("../library/conceptmap");
|
|
10
10
|
const { applyIdPrefix, stripIdPrefix } = require('../library/resource-ids');
|
|
11
|
+
const { keepForPinnedReference } = require('../library/tho-precedence');
|
|
12
|
+
const { waitForCache } = require('../library/wait-for-cache');
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
15
|
* Package-based ConceptMap provider using shared database layer
|
|
@@ -17,14 +19,19 @@ class PackageConceptMapProvider extends AbstractConceptMapProvider {
|
|
|
17
19
|
|
|
18
20
|
/**
|
|
19
21
|
* @param {PackageContentLoader} packageLoader - Path to the extracted package folder
|
|
22
|
+
* @param {Object} [options]
|
|
23
|
+
* @param {boolean} [options.readOnly] - a tx-worker (cluster.md): the cache is the master's; it
|
|
24
|
+
* is only read (ConceptMapDatabase reads with OPEN_READONLY), waited for until the master has
|
|
25
|
+
* finished it, and never built or changed
|
|
20
26
|
*/
|
|
21
|
-
constructor(packageLoader) {
|
|
27
|
+
constructor(packageLoader, options = {}) {
|
|
22
28
|
super();
|
|
23
29
|
validateParameter(packageLoader, "packageLoader", PackageContentLoader);
|
|
24
30
|
this.packageLoader = packageLoader;
|
|
25
31
|
// -2: caches from before concept maps were checked for active content in their
|
|
26
32
|
// narrative are rebuilt (see initialize)
|
|
27
33
|
this.dbPath = path.join(packageLoader.packageFolder, '.conceptmaps-2.db');
|
|
34
|
+
this.readOnly = !!options.readOnly;
|
|
28
35
|
this.database = new ConceptMapDatabase(this.dbPath);
|
|
29
36
|
this.conceptMapMap = new Map();
|
|
30
37
|
this.initialized = false;
|
|
@@ -40,6 +47,14 @@ class PackageConceptMapProvider extends AbstractConceptMapProvider {
|
|
|
40
47
|
return;
|
|
41
48
|
}
|
|
42
49
|
await this.packageLoader.initialize();
|
|
50
|
+
if (this.readOnly) {
|
|
51
|
+
await waitForCache(async () => (await this.database.exists()) && (await this.database.isComplete()),
|
|
52
|
+
`Package ${this.packageLoader.pid ? this.packageLoader.pid() : ''}: concept map cache ${this.dbPath}`);
|
|
53
|
+
this.conceptMapMap = await this.database.loadAllConceptMaps();
|
|
54
|
+
this.#applyIdPrefix();
|
|
55
|
+
this.initialized = true;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
43
58
|
await fs.rm(path.join(this.packageLoader.packageFolder, '.conceptmaps.db'), { force: true }); // the old cache
|
|
44
59
|
|
|
45
60
|
// A cache is only used once its population finished. One that was cut short
|
|
@@ -169,7 +184,7 @@ class PackageConceptMapProvider extends AbstractConceptMapProvider {
|
|
|
169
184
|
|
|
170
185
|
// Only process each ConceptMap once (use URL to deduplicate)
|
|
171
186
|
const vsUrl = json.url;
|
|
172
|
-
if (seen.has(vsUrl)) {
|
|
187
|
+
if (seen.has(vsUrl) || this.#pinnedOnly.has(vs)) {
|
|
173
188
|
continue;
|
|
174
189
|
}
|
|
175
190
|
|
|
@@ -193,6 +208,20 @@ class PackageConceptMapProvider extends AbstractConceptMapProvider {
|
|
|
193
208
|
isMatch = false;
|
|
194
209
|
break;
|
|
195
210
|
}
|
|
211
|
+
} else if (param === 'url') {
|
|
212
|
+
// a left match, on the url as asked for (not lower cased)
|
|
213
|
+
const asked = searchParams.find(sp => sp.name === 'url').value;
|
|
214
|
+
if (!(json.url || '').startsWith(asked)) {
|
|
215
|
+
isMatch = false;
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
} else if (param === 'version') {
|
|
219
|
+
// version-aware matching, on the version as asked for (not lower cased)
|
|
220
|
+
const asked = searchParams.find(sp => sp.name === 'version').value;
|
|
221
|
+
if (!VersionUtilities.versionSearchMatches(asked, json.version, VersionUtilities.versionAlgorithmOf(json))) {
|
|
222
|
+
isMatch = false;
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
196
225
|
} else if (param === 'jurisdiction') {
|
|
197
226
|
// Special handling for jurisdiction - array of CodeableConcept
|
|
198
227
|
if (!this._matchJurisdiction(json.jurisdiction, searchValue)) {
|
|
@@ -330,35 +359,39 @@ class PackageConceptMapProvider extends AbstractConceptMapProvider {
|
|
|
330
359
|
return this.packageLoader.id();
|
|
331
360
|
}
|
|
332
361
|
|
|
333
|
-
// the concept maps
|
|
362
|
+
// the concept maps that keepPinnedOnly restricted, by their own (unprefixed) id, and themselves
|
|
334
363
|
#excludedIds = new Set();
|
|
364
|
+
#pinnedOnly = new Set();
|
|
335
365
|
|
|
336
366
|
/**
|
|
337
|
-
*
|
|
338
|
-
*
|
|
339
|
-
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
367
|
+
* The concept maps with these urls are hl7.terminology's to provide (see
|
|
368
|
+
* Provider.applyTHOPrecedence): from this (core) package they are only available to a
|
|
369
|
+
* reference that names their version, where THO doesn't provide that version itself. They
|
|
370
|
+
* aren't the default for their url, can't be read by id, aren't found by search, and aren't
|
|
371
|
+
* used for translation unless asked for. Must be called once the provider is initialized -
|
|
372
|
+
* the database is shared, so it isn't changed.
|
|
342
373
|
*
|
|
343
|
-
* @param {Set<string
|
|
374
|
+
* @param {Map<string, Set<string>>} thoVersions - url -> the versions hl7.terminology provides
|
|
344
375
|
*/
|
|
345
|
-
|
|
346
|
-
const
|
|
376
|
+
keepPinnedOnly(thoVersions) {
|
|
377
|
+
const affected = new Set();
|
|
347
378
|
for (const cm of this.conceptMapMap.values()) {
|
|
348
|
-
if (cm &&
|
|
349
|
-
|
|
379
|
+
if (cm && thoVersions.has(cm.url)) {
|
|
380
|
+
affected.add(cm);
|
|
350
381
|
}
|
|
351
382
|
}
|
|
352
383
|
for (const [key, cm] of [...this.conceptMapMap]) {
|
|
353
|
-
if (
|
|
384
|
+
if (affected.has(cm) && !keepForPinnedReference(key, cm, thoVersions.get(cm.url))) {
|
|
354
385
|
this.conceptMapMap.delete(key);
|
|
355
386
|
}
|
|
356
387
|
}
|
|
357
|
-
for (const cm of
|
|
388
|
+
for (const cm of affected) {
|
|
389
|
+
this.#pinnedOnly.add(cm);
|
|
358
390
|
this.#excludedIds.add(stripIdPrefix(cm.id, this.spaceId) ?? cm.id);
|
|
359
391
|
}
|
|
360
392
|
}
|
|
361
393
|
|
|
394
|
+
|
|
362
395
|
prefixIds() {
|
|
363
396
|
this.#applyIdPrefix();
|
|
364
397
|
}
|
|
@@ -371,7 +404,8 @@ class PackageConceptMapProvider extends AbstractConceptMapProvider {
|
|
|
371
404
|
|
|
372
405
|
async findConceptMapForTranslation(opContext, conceptMaps, sourceSystem, sourceScope, targetScope, targetSystem) {
|
|
373
406
|
for (let cm of this.conceptMapMap.values()) {
|
|
374
|
-
|
|
407
|
+
// a concept map that THO also provides is only used when it's asked for by version
|
|
408
|
+
if (!this.#pinnedOnly.has(cm) && cm.providesTranslation(sourceSystem, sourceScope, targetScope, targetSystem)) {
|
|
375
409
|
conceptMaps.push(cm);
|
|
376
410
|
}
|
|
377
411
|
}
|
package/tx/cs/cs-api.js
CHANGED
|
@@ -224,6 +224,17 @@ class CodeSystemProvider {
|
|
|
224
224
|
return false;
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
+
/**
|
|
228
|
+
* Why an expansion of all of the code system is not closed even though every code it defines
|
|
229
|
+
* is in it - e.g. it reserves code spaces for users to define their own codes
|
|
230
|
+
* (codesystem-reservedCodeSpace), and those codes can't be listed - or null when it is closed
|
|
231
|
+
* (as far as this is concerned: see isNotClosed() for grammars)
|
|
232
|
+
* @returns {string|null}
|
|
233
|
+
*/
|
|
234
|
+
openCodeSpaceReason() {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
|
|
227
238
|
/**
|
|
228
239
|
* returns false if the code system cannot be iterated / enumerated in any
|
|
229
240
|
* meaningful sense - as distinct from isNotClosed(), which marks a code system
|
|
@@ -268,6 +279,96 @@ class CodeSystemProvider {
|
|
|
268
279
|
}
|
|
269
280
|
}
|
|
270
281
|
|
|
282
|
+
/**
|
|
283
|
+
* The language a text filter ($expand filter) searches. A text filter matches a code's
|
|
284
|
+
* displays and designations in one language only (the same rule as for the SNOMED CT display
|
|
285
|
+
* filter): the first language the request names (displayLanguage, else Accept-Language) that
|
|
286
|
+
* the code system - or a supplement in use - has displays in, else the code system's default
|
|
287
|
+
* language. Matching is at the language subtag level: de-DE searches all German.
|
|
288
|
+
*
|
|
289
|
+
* @param {Languages|null} languages - what the request asked for
|
|
290
|
+
* @returns {Language}
|
|
291
|
+
*/
|
|
292
|
+
searchLanguage(languages) {
|
|
293
|
+
for (const lang of (languages && languages.languages) || []) {
|
|
294
|
+
if (!lang || !lang.language || lang.code === '*') {
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
const one = new Languages();
|
|
298
|
+
one.add(lang);
|
|
299
|
+
if (this.hasAnyDisplays(one) || this._hasSupplementText(lang)) {
|
|
300
|
+
return lang;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return new Language(this.defLang());
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Whether a supplement in use has any display or designation (of any use) in the language
|
|
308
|
+
*
|
|
309
|
+
* @param {Language} lang
|
|
310
|
+
* @returns {boolean}
|
|
311
|
+
*/
|
|
312
|
+
_hasSupplementText(lang) {
|
|
313
|
+
for (const supplement of this.supplements || []) {
|
|
314
|
+
const suppLang = supplement.jsonObj.language || this.defLang();
|
|
315
|
+
for (const concept of supplement.getAllConcepts()) {
|
|
316
|
+
if (concept.display && CodeSystemProvider.inSearchLanguage(suppLang, lang)) {
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
if ((concept.designation || []).some(d => d.value && CodeSystemProvider.inSearchLanguage(d.language, lang, suppLang))) {
|
|
320
|
+
return true;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Whether text in language `code` (a tag; empty means `fallback`) is in the search language
|
|
329
|
+
*
|
|
330
|
+
* @param {string|Language|null} code
|
|
331
|
+
* @param {Language} searchLang - from searchLanguage()
|
|
332
|
+
* @param {string} [fallback] - the language of text that doesn't say
|
|
333
|
+
* @returns {boolean}
|
|
334
|
+
*/
|
|
335
|
+
static inSearchLanguage(code, searchLang, fallback = 'en') {
|
|
336
|
+
let lang = code instanceof Language ? code : (code ? new Language(code) : null);
|
|
337
|
+
if (!lang || !lang.language) {
|
|
338
|
+
lang = new Language(fallback || 'en');
|
|
339
|
+
}
|
|
340
|
+
return lang.language === searchLang.language;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* The codes whose displays or designations in a supplement in use, in the search language,
|
|
345
|
+
* pass a text filter - a supplement's designations are searched like the code system's own,
|
|
346
|
+
* so every word of the filter must match (as LOINC's and SNOMED CT's own text search need)
|
|
347
|
+
*
|
|
348
|
+
* @param {SearchFilterText} filter
|
|
349
|
+
* @param {Language} searchLang - from searchLanguage()
|
|
350
|
+
* @returns {Set<string>}
|
|
351
|
+
*/
|
|
352
|
+
_supplementSearchCodes(filter, searchLang) {
|
|
353
|
+
const codes = new Set();
|
|
354
|
+
for (const supplement of this.supplements || []) {
|
|
355
|
+
const suppLang = supplement.jsonObj.language || this.defLang();
|
|
356
|
+
for (const concept of supplement.getAllConcepts()) {
|
|
357
|
+
if (concept.display && CodeSystemProvider.inSearchLanguage(suppLang, searchLang) && filter.passesAll(concept.display)) {
|
|
358
|
+
codes.add(concept.code);
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
for (const d of concept.designation || []) {
|
|
362
|
+
if (d.value && CodeSystemProvider.inSearchLanguage(d.language, searchLang, suppLang) && filter.passesAll(d.value)) {
|
|
363
|
+
codes.add(concept.code);
|
|
364
|
+
break;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return codes;
|
|
370
|
+
}
|
|
371
|
+
|
|
271
372
|
_hasAnySupplementDisplays(languages) {
|
|
272
373
|
// Check if any supplements have displays in the requested languages
|
|
273
374
|
if (this.supplements) {
|
|
@@ -326,11 +427,12 @@ class CodeSystemProvider {
|
|
|
326
427
|
}
|
|
327
428
|
|
|
328
429
|
/**
|
|
329
|
-
* @param {boolean}
|
|
430
|
+
* @param {boolean} globals - whether to include global supplements (which $expand only reports
|
|
431
|
+
* when they contribute something)
|
|
330
432
|
* @returns {string[]} all supplements in scope
|
|
331
433
|
*/
|
|
332
|
-
listSupplements(
|
|
333
|
-
return this.supplements ? this.supplements.filter(s =>
|
|
434
|
+
listSupplements(globals) {
|
|
435
|
+
return this.supplements ? this.supplements.filter(s => globals || !s.isGlobalSupplement()).map(s => s.vurl) : [];
|
|
334
436
|
}
|
|
335
437
|
|
|
336
438
|
/**
|
|
@@ -377,6 +479,22 @@ class CodeSystemProvider {
|
|
|
377
479
|
* @param {String} code
|
|
378
480
|
* @returns {string} the best display given the languages in the operation context
|
|
379
481
|
*/
|
|
482
|
+
/**
|
|
483
|
+
* @param {string} code - a code users define (one in the code system's reserved code space)
|
|
484
|
+
* @returns {string|null} the display an extension-codes supplement gives it, if one does
|
|
485
|
+
*/
|
|
486
|
+
_extensionCodeDisplay(code) {
|
|
487
|
+
for (const supplement of this.supplements || []) {
|
|
488
|
+
if (supplement.isExtensionCodes()) {
|
|
489
|
+
const concept = supplement.getConceptByCode(code);
|
|
490
|
+
if (concept && concept.display) {
|
|
491
|
+
return concept.display;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return null;
|
|
496
|
+
}
|
|
497
|
+
|
|
380
498
|
_displayFromSupplements(code) {
|
|
381
499
|
assert(typeof code === 'string', 'code must be string');
|
|
382
500
|
if (this.supplements) {
|
|
@@ -499,13 +617,24 @@ class CodeSystemProvider {
|
|
|
499
617
|
*/
|
|
500
618
|
async designations(code, displays, significantOnly = false) { return null; }
|
|
501
619
|
|
|
502
|
-
|
|
620
|
+
/**
|
|
621
|
+
* @param {string} code - the code
|
|
622
|
+
* @param {ConceptDesignations} displays - the designations being built for the code
|
|
623
|
+
* @param {boolean} extensionCode - true if the code is one users define (it is in the code system's
|
|
624
|
+
* reserved code space), so that an extension-codes supplement's display is the code's display. The
|
|
625
|
+
* designations record where it came from (displays.extensionCodeDisplay): 'global' if a global
|
|
626
|
+
* supplement gave it - as if the code system defined it - otherwise 'referenced'
|
|
627
|
+
*/
|
|
628
|
+
_listSupplementDesignations(code, displays, extensionCode = false) {
|
|
503
629
|
assert(typeof code === 'string', 'code must be string');
|
|
504
630
|
|
|
505
631
|
if (this.supplements) {
|
|
506
632
|
for (const supplement of this.supplements) {
|
|
507
633
|
const concept= supplement.getConceptByCode(code);
|
|
508
634
|
if (concept) {
|
|
635
|
+
if (extensionCode && concept.display && supplement.isExtensionCodes()) {
|
|
636
|
+
displays.extensionCodeDisplay = supplement.isGlobalSupplement() || displays.extensionCodeDisplay === 'global' ? 'global' : 'referenced';
|
|
637
|
+
}
|
|
509
638
|
if (concept.display) {
|
|
510
639
|
// sometimes the display is just repeated from the base code system
|
|
511
640
|
if (!displays.hasAnyDisplay(concept.display)) {
|
|
@@ -531,11 +660,50 @@ class CodeSystemProvider {
|
|
|
531
660
|
async extensions(code) { return null; }
|
|
532
661
|
|
|
533
662
|
/**
|
|
534
|
-
|
|
663
|
+
* The properties of a concept, in CodeSystem.concept.property form ({code, value[x]}).
|
|
664
|
+
* $expand reports them on expansion.contains, so a provider that has properties must
|
|
665
|
+
* override this, not just extendLookup - otherwise $lookup returns them and $expand never does
|
|
666
|
+
*
|
|
535
667
|
* @param {string | CodeSystemProviderContext} code
|
|
668
|
+
* @param {string[]} [names] the properties the caller wants ('*' for all of them). A provider
|
|
669
|
+
* that has to compute its properties should compute only these (none when the list is
|
|
670
|
+
* empty: an expansion that asks for no properties must not pay for them); one that holds
|
|
671
|
+
* them in memory may return the lot, and the caller picks what it asked for
|
|
536
672
|
* @returns {CodeSystem.concept.property[]} list of properties (may be empty)
|
|
537
673
|
*/
|
|
538
|
-
|
|
674
|
+
// eslint-disable-next-line no-unused-vars
|
|
675
|
+
async properties(code, names) { return []; }
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* properties() for a provider whose properties are only built by extendLookup: run it for
|
|
679
|
+
* just the named properties, and turn the $lookup property parameters it adds into
|
|
680
|
+
* CodeSystem.concept.property form. Lookup-only parts (subproperty, description, language,
|
|
681
|
+
* code-display...) are dropped; designations are not properties
|
|
682
|
+
*
|
|
683
|
+
* @param {string | CodeSystemProviderContext} context
|
|
684
|
+
* @param {string[]} names - the properties wanted ('*' for all); none means none
|
|
685
|
+
* @returns {Promise<Object[]>}
|
|
686
|
+
*/
|
|
687
|
+
async _propertiesFromLookup(context, names) {
|
|
688
|
+
if (!names || names.length === 0) {
|
|
689
|
+
return [];
|
|
690
|
+
}
|
|
691
|
+
const params = [];
|
|
692
|
+
await this.extendLookup(context, names, params);
|
|
693
|
+
const result = [];
|
|
694
|
+
for (const p of params) {
|
|
695
|
+
if (p.name !== 'property' || !Array.isArray(p.part)) {
|
|
696
|
+
continue;
|
|
697
|
+
}
|
|
698
|
+
const code = p.part.find(x => x.name === 'code')?.valueCode;
|
|
699
|
+
const value = p.part.find(x => x.name === 'value');
|
|
700
|
+
const vn = value ? Object.keys(value).find(k => k.startsWith('value')) : null;
|
|
701
|
+
if (code && vn && value[vn] !== undefined && value[vn] !== null) {
|
|
702
|
+
result.push({ code, [vn]: value[vn] });
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
return result;
|
|
706
|
+
}
|
|
539
707
|
|
|
540
708
|
/**
|
|
541
709
|
|
|
@@ -559,9 +727,12 @@ class CodeSystemProvider {
|
|
|
559
727
|
/**
|
|
560
728
|
|
|
561
729
|
* @param {string } code
|
|
730
|
+
* @param {*} [prefetched] - a handle that prefetchCodes() returned for a list holding this code, or
|
|
731
|
+
* null. A provider uses it only if it is one of its own; otherwise it looks the code up as usual
|
|
562
732
|
* @returns {{context : CodeSystemProviderContext, message : String} the result of looking for the code
|
|
563
733
|
*/
|
|
564
|
-
|
|
734
|
+
// eslint-disable-next-line no-unused-vars
|
|
735
|
+
async locate(code, prefetched = null) { throw new Error("Must override"); }
|
|
565
736
|
|
|
566
737
|
/**
|
|
567
738
|
|
|
@@ -605,6 +776,27 @@ class CodeSystemProvider {
|
|
|
605
776
|
*/
|
|
606
777
|
async subsumesTest(codeA, codeB) { return 'not-subsumed'; }
|
|
607
778
|
|
|
779
|
+
/**
|
|
780
|
+
* The codes a code sits under: every ancestor in the hierarchy subsumesTest() answers from,
|
|
781
|
+
* not just the parents, and not the code itself. Optional: $closure uses it, when it is given,
|
|
782
|
+
* to relate many concepts to one another from one call per concept instead of one
|
|
783
|
+
* subsumesTest() per pair. The default returns null (not supported), and $closure then calls
|
|
784
|
+
* subsumesTest() for each pair, as it always has.
|
|
785
|
+
*
|
|
786
|
+
* A provider that implements it must agree with its own subsumesTest(): for two different
|
|
787
|
+
* codes a and b that this method answers for, subsumesTest(a, b) is 'subsumes' exactly when a
|
|
788
|
+
* is in ancestorsOf(b), 'subsumed-by' exactly when b is in ancestorsOf(a) (and a is not in
|
|
789
|
+
* ancestorsOf(b)), and never 'equivalent'. The codes are given in the form code() returns them.
|
|
790
|
+
* For any code where that can't be promised - an unknown code, a code not in the form code()
|
|
791
|
+
* gives (so another code may be the same concept), a postcoordinated expression - return null,
|
|
792
|
+
* and the pairs that involve it are tested with subsumesTest().
|
|
793
|
+
*
|
|
794
|
+
* @param {string | CodeSystemProviderContext} code
|
|
795
|
+
* @returns {Promise<Set<string>|null>} the ancestors' codes, or null when not supported for this code
|
|
796
|
+
*/
|
|
797
|
+
// eslint-disable-next-line no-unused-vars
|
|
798
|
+
async ancestorsOf(code) { return null; }
|
|
799
|
+
|
|
608
800
|
/**
|
|
609
801
|
|
|
610
802
|
* @param {CodeSystemProviderContext} ctxt the context to add properties for
|
|
@@ -636,21 +828,67 @@ class CodeSystemProvider {
|
|
|
636
828
|
}
|
|
637
829
|
|
|
638
830
|
/**
|
|
639
|
-
*
|
|
831
|
+
* Select the codes of a whole compose in one call (only called when handlesSelecting() is true).
|
|
832
|
+
*
|
|
833
|
+
* expand.js hands over a compose that selects from this code system alone (one system|version),
|
|
834
|
+
* imports no value sets, has no text filter, and lists no concepts (an enumerated concept
|
|
835
|
+
* carries displays and extensions from the value set, so those go through the usual path,
|
|
836
|
+
* where prefetchCodes() applies). Anything else this provider doesn't support - an
|
|
837
|
+
* unknown filter, say - it declines by returning null, and the compose goes through the
|
|
838
|
+
* usual include/exclude path (getPrepContext/filter/executeFilters, locate) instead. A compose
|
|
839
|
+
* with a filter on a property that a supplement in use defines is never handed over: the usual
|
|
840
|
+
* path evaluates that filter from the supplement (tx/workers/supplement-filters.js).
|
|
841
|
+
*
|
|
842
|
+
* The provider applies offset and count: the sets it returns hold just the requested page of
|
|
843
|
+
* the selection, in expansion order. The worker adds every code in them to the expansion
|
|
844
|
+
* (with displays, properties etc. from this provider, as for a filter set), and takes the
|
|
845
|
+
* total from the result.
|
|
846
|
+
*
|
|
847
|
+
* @param {TxParameters} params: information from the request that the user made, to help optimise loading (activeOnly, properties, designations...)
|
|
848
|
+
* @param {Object[]} includes - the compose's includes. Each names this system (and version), with or without filters
|
|
849
|
+
* @param {Object[]} excludes - the compose's excludes, in the same form
|
|
850
|
+
* @param {boolean} excludeInactive: whether the value set leaves out inactive codes (compose.inactive = false)
|
|
851
|
+
* @param {int} offset the index of the first code to return, -1 when the request gave no offset (start at 0)
|
|
852
|
+
* @param {int} count the most codes to return, -1 when the request gave no count (all of them); 0 asks for the total only
|
|
853
|
+
* @param {Object} [context] what the selection is for. `context.valueSet` is `{ url, version }` of the
|
|
854
|
+
* value set whose compose this is (either may be undefined for a value set given inline without
|
|
855
|
+
* them). A provider may ignore it.
|
|
640
856
|
*
|
|
641
|
-
*
|
|
642
|
-
*
|
|
643
|
-
*
|
|
644
|
-
*
|
|
645
|
-
*
|
|
646
|
-
*
|
|
647
|
-
*
|
|
648
|
-
|
|
649
|
-
|
|
857
|
+
* A provider that won't produce a selection because it would cost too much may throw the standard
|
|
858
|
+
* too-costly Issue, in the form expand.js uses when it refuses a value set (Issue from
|
|
859
|
+
* tx/library/operation-outcome.js; vurl is url|version, or url when there is no version):
|
|
860
|
+
*
|
|
861
|
+
* throw new Issue('error', 'too-costly', null, 'VALUESET_TOO_COSTLY',
|
|
862
|
+
* this.opContext.i18n.translate('VALUESET_TOO_COSTLY', params.httpLanguages, [vurl, '>' + limit]),
|
|
863
|
+
* null, 422).withDiagnostics(this.opContext.diagnostics());
|
|
864
|
+
*
|
|
865
|
+
* @returns {Promise<{sets: FilterConceptSet[], total: number, notClosed?: boolean}|null>} null to decline.
|
|
866
|
+
* Otherwise `sets` (in general one; iterated with filterMore/filterConcept) hold the page,
|
|
867
|
+
* `total` is the number of codes in the whole selection before paging, or -1 when the
|
|
868
|
+
* provider hasn't counted it (the expansion then has no total), and `notClosed` is true
|
|
869
|
+
* when the selection is open-ended (a grammar-based code system), so the expansion is
|
|
870
|
+
* marked unclosed
|
|
871
|
+
*/
|
|
872
|
+
processSelection(params, includes, excludes, excludeInactive, offset, count, context = {}) {
|
|
650
873
|
// well, you only need to override if handlesSelecting=true, but that's the only time this will be called
|
|
651
874
|
throw new Error("Must override");
|
|
652
875
|
}
|
|
653
876
|
|
|
877
|
+
/**
|
|
878
|
+
* Before the worker locates each code of a list one at a time: the concepts a value set include
|
|
879
|
+
* or exclude lists ($expand), or that validate-code compares a code with. A provider that
|
|
880
|
+
* answers locate() from a database may read the codes together here and return a handle to
|
|
881
|
+
* what it read: an object of its own, opaque to the worker. The worker passes the handle back
|
|
882
|
+
* as locate(code, prefetched) for each code of that list, and drops it when the loop ends. The
|
|
883
|
+
* provider keeps no state of its own for this: two lists in progress have two handles.
|
|
884
|
+
*
|
|
885
|
+
* @param {String[]} codes - the codes, in the order the worker will locate them (it may stop early)
|
|
886
|
+
* @param {boolean} withDesignations - whether the worker will also list each code's displays and designations
|
|
887
|
+
* @returns {Promise<*>} the handle, or null: this provider does no prefetching (the default)
|
|
888
|
+
*/
|
|
889
|
+
// eslint-disable-next-line no-unused-vars
|
|
890
|
+
async prefetchCodes(codes, withDesignations) { return null; }
|
|
891
|
+
|
|
654
892
|
/**
|
|
655
893
|
* returns true if a filter is supported
|
|
656
894
|
*
|
|
@@ -666,10 +904,17 @@ class CodeSystemProvider {
|
|
|
666
904
|
* it's only use is to be passed back to the CodeSystem provider so it can make use of it to organise the filter process
|
|
667
905
|
*
|
|
668
906
|
* @param {boolean} iterate true if the conceptSets that result from this will be iterated, and false if they'll be used to locate a single code
|
|
907
|
+
* @param {Languages|null} languages the languages the request named (TxParameters.requestedLanguages()), or null if it named none;
|
|
908
|
+
* for filters whose meaning depends on language, such as a regex on the display
|
|
669
909
|
* @returns {FilterExecutionContext} filter
|
|
670
910
|
*
|
|
671
911
|
**/
|
|
672
|
-
|
|
912
|
+
// eslint-disable-next-line no-unused-vars
|
|
913
|
+
async getPrepContext(iterate, languages = null) {
|
|
914
|
+
const result = new FilterExecutionContext(iterate);
|
|
915
|
+
result.languages = languages; // what the request asked for (a text filter searches one language)
|
|
916
|
+
return result;
|
|
917
|
+
}
|
|
673
918
|
|
|
674
919
|
|
|
675
920
|
/**
|
|
@@ -872,6 +1117,18 @@ class CodeSystemProvider {
|
|
|
872
1117
|
return null;
|
|
873
1118
|
}
|
|
874
1119
|
|
|
1120
|
+
/**
|
|
1121
|
+
* For a provider with a sqlite connection in this.db: close it, once. The handle is kept:
|
|
1122
|
+
* sqlite still runs the queries already queued (and any their callbacks issue), and fails
|
|
1123
|
+
* later ones with an error. Setting it to null made such a callback throw, outside any promise.
|
|
1124
|
+
*/
|
|
1125
|
+
closeDb() {
|
|
1126
|
+
if (this.db && !this.dbClosed) {
|
|
1127
|
+
this.dbClosed = true;
|
|
1128
|
+
this.db.close();
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
|
|
875
1132
|
/**
|
|
876
1133
|
* a record of observed usages of codes from this code system
|
|
877
1134
|
* - a map of code and object which has count, an integer count
|
|
@@ -1059,7 +1316,15 @@ class CodeSystemFactoryProvider {
|
|
|
1059
1316
|
throw new Error("Must override");
|
|
1060
1317
|
}
|
|
1061
1318
|
|
|
1062
|
-
|
|
1319
|
+
/**
|
|
1320
|
+
* A link for a code, usually to the code system's own web site, for pages that list codes
|
|
1321
|
+
* (a ValueSet's includes). Override it to give codes from this code system a link; the
|
|
1322
|
+
* default gives none, and then resolveCode() doesn't look the code up at all.
|
|
1323
|
+
*
|
|
1324
|
+
* @param {*} context - what locate() found for the code (the provider's own context)
|
|
1325
|
+
* @returns {string|undefined} the URL, or undefined for no link
|
|
1326
|
+
*/
|
|
1327
|
+
codeLink(context) {
|
|
1063
1328
|
return undefined;
|
|
1064
1329
|
}
|
|
1065
1330
|
|
package/tx/cs/cs-api.md
CHANGED
|
@@ -63,6 +63,7 @@ These methods describe the code system as a whole. All are synchronous.
|
|
|
63
63
|
| `sourcePackage()` | `null` | The NPM package that contributed this code system, if known. |
|
|
64
64
|
| `propertyDefinitions()` | `null` | The set of defined properties; override to expose them. |
|
|
65
65
|
| `isNotClosed()` | `false` | Return `true` for grammar-based systems (e.g. UCUM) that cannot be fully enumerated. |
|
|
66
|
+
| `openCodeSpaceReason()` | `null` | Why an expansion of all of the code system isn't closed although it lists every code the system defines - e.g. the system reserves code spaces for user-defined codes (`codesystem-reservedCodeSpace`). The expansion is marked unclosed with this as the reason. |
|
|
66
67
|
| `isCaseSensitive()` | `true` | Whether code comparisons are case-sensitive. |
|
|
67
68
|
| `hasParents()` | `false` | Whether the code system has a concept hierarchy. |
|
|
68
69
|
| `specialEnumeration()` | `null` | Override to nominate a substitute enumeration (used by UCUM). |
|
|
@@ -109,6 +110,7 @@ All are `async`.
|
|
|
109
110
|
| `sameConcept(a, b)` | `false` | Returns `true` if `a` and `b` refer to the same concept (e.g. different expression forms). |
|
|
110
111
|
| `isDisplay(designation)` | `false` | Called for designations not already marked with a standard display use code; return `true` to treat the designation as a display. |
|
|
111
112
|
| `subsumesTest(codeA, codeB)` | `'not-subsumed'` | Returns one of `equivalent`, `subsumes`, `subsumed-by`, or `not-subsumed`. |
|
|
113
|
+
| `ancestorsOf(code)` | `null` | Optional. A `Set` of the codes this code sits under (all ancestors, not itself), in the form `code()` returns them. `$closure` uses it to relate concepts with one call per concept instead of one `subsumesTest()` per pair. It must agree with `subsumesTest()`: for different codes a and b it answers for, a is in `ancestorsOf(b)` exactly when `subsumesTest(a, b)` is `subsumes`, and the two are never `equivalent`. Return `null` for any code where that can't be promised (unknown, not in `code()` form, an expression); pairs involving it are tested with `subsumesTest()`. |
|
|
112
114
|
| `extendLookup(ctxt, props, params)` | — | Called during `$lookup`; add any additional properties to `params`. |
|
|
113
115
|
|
|
114
116
|
---
|
|
@@ -190,9 +192,43 @@ For large code systems where a value set selects codes from only that system, th
|
|
|
190
192
|
hand the entire include/exclude list to the provider in one call rather than using the filter
|
|
191
193
|
workflow above.
|
|
192
194
|
|
|
193
|
-
Override `handlesSelecting()` to return `true` and then implement
|
|
194
|
-
|
|
195
|
-
|
|
195
|
+
Override `handlesSelecting()` to return `true` and then implement
|
|
196
|
+
`processSelection(params, includes, excludes, excludeInactive, offset, count, context)`.
|
|
197
|
+
|
|
198
|
+
- **When it is called.** `$expand` hands over a compose that selects from this code system
|
|
199
|
+
alone (one system|version), imports no value sets, has no text filter and lists no concepts.
|
|
200
|
+
Value sets that list concepts take the usual path: `locate`, with a `prefetchCodes` handle (see Lookup).
|
|
201
|
+
- **Declining.** Return `null` for anything the provider doesn't support (an unknown filter,
|
|
202
|
+
say). The compose then goes through the filter workflow above, as for any other provider.
|
|
203
|
+
- **Paging.** The provider applies `offset` (-1: none given, start at 0) and `count`
|
|
204
|
+
(-1: none given, return all; 0: the total only). The sets it returns hold just that page,
|
|
205
|
+
in expansion order.
|
|
206
|
+
- **Result.** `{ sets, total, notClosed }`: `sets` is a `FilterConceptSet[]` (in general one),
|
|
207
|
+
iterated with `filterMore`/`filterConcept`; `total` is the size of the whole selection
|
|
208
|
+
before paging, or -1 if the provider hasn't counted it (the expansion then has no total);
|
|
209
|
+
`notClosed` marks an open-ended selection (a grammar-based code system).
|
|
210
|
+
|
|
211
|
+
- **Context.** The optional last argument, `context`, says what the selection is for:
|
|
212
|
+
`context.valueSet` is `{ url, version }` of the value set whose compose this is (either may be
|
|
213
|
+
undefined for a value set given inline without them). A provider may ignore it.
|
|
214
|
+
- **Too costly.** A provider that won't produce a selection because it would cost too much may
|
|
215
|
+
throw the standard too-costly `Issue` (tx/library/operation-outcome.js), in the form `$expand`
|
|
216
|
+
uses when it refuses a value set. Here `vurl` is `url|version`, or `url` when there is no
|
|
217
|
+
version:
|
|
218
|
+
|
|
219
|
+
```js
|
|
220
|
+
throw new Issue('error', 'too-costly', null, 'VALUESET_TOO_COSTLY',
|
|
221
|
+
this.opContext.i18n.translate('VALUESET_TOO_COSTLY', params.httpLanguages, [vurl, '>' + limit]),
|
|
222
|
+
null, 422).withDiagnostics(this.opContext.diagnostics());
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
The response is a 422 with a `too-costly` issue, as for a value set `$expand` refuses itself.
|
|
226
|
+
- **Over the limit.** When an include takes the whole system and the reported `total` is over the
|
|
227
|
+
request's limit, `$expand` refuses as it does on the usual path, unless the requested page
|
|
228
|
+
fits inside the limit.
|
|
229
|
+
|
|
230
|
+
The default `handlesSelecting()` returns `false`, so a provider that doesn't override it is
|
|
231
|
+
never asked.
|
|
196
232
|
|
|
197
233
|
---
|
|
198
234
|
|
|
@@ -202,8 +238,9 @@ Lookup supports the `$lookup` and `$validate-code` operations.
|
|
|
202
238
|
|
|
203
239
|
| Method | Description |
|
|
204
240
|
|---|---|
|
|
205
|
-
| `locate(code)` | Look up a code by string. Returns `{ context, message }`. If found, `context` is a `CodeSystemProviderContext` and `message` is `null`. If not found, `context` is `null` and `message` explains why. **Must override.** |
|
|
241
|
+
| `locate(code, prefetched)` | Look up a code by string. `prefetched` is a handle from `prefetchCodes` for a list holding the code, or `null`; ignore it unless it is one of yours. Returns `{ context, message }`. If found, `context` is a `CodeSystemProviderContext` and `message` is `null`. If not found, `context` is `null` and `message` explains why. **Must override.** |
|
|
206
242
|
| `locateIsA(code)` | Look up a code within the scope of a parent concept. Required only if `hasParents()` returns `true`. |
|
|
243
|
+
| `prefetchCodes(codes, withDesignations)` | Called before the worker locates each code of a list (the concepts a value set include or exclude lists, in `$expand` and `$validate-code`). A database-backed provider may read them together and return a handle to what it read; the worker passes it back as `locate(code, prefetched)` for each code of that list, then drops it. No state on the provider. Default: returns `null` (no prefetching). |
|
|
207
244
|
| `extendLookup(ctxt, props, params)` | Called during `$lookup` to allow the provider to add extra properties to the response `Parameters`. The `props` array lists the property names requested by the client. |
|
|
208
245
|
|
|
209
246
|
The server calls `locate()` first to confirm the code exists, then uses the returned context
|
package/tx/cs/cs-areacode.js
CHANGED