fhirsmith 0.18.2 → 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.
Files changed (203) hide show
  1. package/ACCESSIBILITY.md +119 -0
  2. package/CHANGELOG.md +149 -0
  3. package/CONFIGURATION.md +305 -0
  4. package/LICENSE +29 -0
  5. package/README.md +29 -122
  6. package/cluster.js +1220 -0
  7. package/cluster.md +707 -0
  8. package/config-template.json +77 -8
  9. package/extension-tracker/extension-tracker-template.html +5 -8
  10. package/extension-tracker/extension-tracker.js +0 -2
  11. package/folder/folder.js +7 -6
  12. package/ips/analysis.js +560 -0
  13. package/ips/ips-template.html +5 -8
  14. package/ips/ips.js +132 -59
  15. package/ips/nationality.js +120 -0
  16. package/ips/pipeline.js +74 -21
  17. package/ips/presenter.js +1 -1
  18. package/ips/readme.md +132 -25
  19. package/ips/render.js +466 -77
  20. package/ips/store.js +79 -22
  21. package/ips/structure.js +247 -0
  22. package/ips/terminology.js +1 -1
  23. package/library/body-limit.js +22 -0
  24. package/library/cache-generation.js +15 -0
  25. package/library/cgroup-memory.js +133 -1
  26. package/library/cluster-counts.js +121 -0
  27. package/library/cluster-events.js +153 -0
  28. package/library/cluster-metrics.js +193 -0
  29. package/library/cluster-status.js +468 -0
  30. package/library/config-reader.js +96 -0
  31. package/library/daily-report.js +100 -2
  32. package/library/html-server.js +27 -1
  33. package/library/i18nsupport.js +23 -1
  34. package/library/keep-alive.js +18 -0
  35. package/library/languages.js +17 -3
  36. package/library/logger.js +31 -0
  37. package/library/memory-guard.js +428 -0
  38. package/library/native-memory.js +52 -0
  39. package/library/npm-audit.js +51 -25
  40. package/library/openapi-doc.js +7 -5
  41. package/library/package-manager.js +8 -0
  42. package/library/role.js +197 -0
  43. package/library/server-credentials.js +169 -0
  44. package/library/sqlite-utilities.js +59 -0
  45. package/library/ssrf-guard.js +100 -0
  46. package/library/static-response-stats.js +16 -0
  47. package/library/version-utilities.js +50 -0
  48. package/library/worker-count.js +192 -0
  49. package/nginx.md +30 -11
  50. package/npmprojector/indexer.js +18 -10
  51. package/package.json +11 -8
  52. package/packages/feed-check-page.js +117 -0
  53. package/packages/feed-check.js +279 -0
  54. package/packages/npm-check.js +57 -3
  55. package/packages/npm-signing.js +219 -0
  56. package/packages/openapi.yaml +207 -1
  57. package/packages/package-crawler.js +322 -41
  58. package/packages/packages-template.html +6 -8
  59. package/packages/packages.js +604 -57
  60. package/packages/readme.md +113 -1
  61. package/publisher/publisher-template.html +2 -5
  62. package/publisher/publisher.js +121 -110
  63. package/publisher/readme.md +75 -0
  64. package/registry/crawler.js +84 -108
  65. package/registry/documentation/configuration.md +34 -0
  66. package/registry/registry-template.html +5 -8
  67. package/registry/registry.js +5 -4
  68. package/root-bare-template.html +7 -4
  69. package/root-template.html +5 -8
  70. package/server.js +398 -198
  71. package/shl/shl.js +38 -37
  72. package/static/a11y/a11y-check.js +832 -0
  73. package/static/a11y/axe.min.js +12 -0
  74. package/static/assets/css/bootstrap-fhir.css +15 -15
  75. package/static/assets/images/flags/au.svg +18 -0
  76. package/static/assets/images/flags/unknown.svg +11 -0
  77. package/static/assets/images/flags/us.svg +63 -0
  78. package/static/assets/images/icon_audit.png +0 -0
  79. package/static/assets/images/icon_clinical.png +0 -0
  80. package/static/assets/images/icon_document.png +0 -0
  81. package/static/assets/images/icon_external.png +0 -0
  82. package/static/assets/images/icon_medication.png +0 -0
  83. package/static/assets/images/icon_organization.png +0 -0
  84. package/static/assets/images/icon_other.png +0 -0
  85. package/static/assets/images/icon_person.png +0 -0
  86. package/static/assets/images/icon_section.png +0 -0
  87. package/stats-db.js +45 -0
  88. package/stats.js +197 -10
  89. package/testing/testing-template.html +5 -8
  90. package/token/nginx_passport_setup.md +1 -1
  91. package/token/security_guide.md +1 -1
  92. package/token/token-template.html +2 -5
  93. package/token/token.js +40 -36
  94. package/translations/Messages.properties +19 -0
  95. package/tx/README.md +154 -4
  96. package/tx/closure/closure-store.js +126 -3
  97. package/tx/cm/cm-database.js +69 -57
  98. package/tx/cm/cm-package.js +29 -1
  99. package/tx/cs/cs-api.js +284 -19
  100. package/tx/cs/cs-api.md +41 -4
  101. package/tx/cs/cs-areacode.js +1 -1
  102. package/tx/cs/cs-country.js +17 -5
  103. package/tx/cs/cs-cpt.js +33 -16
  104. package/tx/cs/cs-cs.js +148 -14
  105. package/tx/cs/cs-currency.js +1 -1
  106. package/tx/cs/cs-icd11.js +21 -13
  107. package/tx/cs/cs-icf.js +22 -3
  108. package/tx/cs/cs-lang.js +2 -2
  109. package/tx/cs/cs-loinc.js +185 -66
  110. package/tx/cs/cs-mimetypes.js +12 -2
  111. package/tx/cs/cs-ndc.js +40 -25
  112. package/tx/cs/cs-omop.js +54 -29
  113. package/tx/cs/cs-rxnorm.js +300 -196
  114. package/tx/cs/cs-snomed.js +608 -64
  115. package/tx/cs/cs-ucum.js +14 -4
  116. package/tx/cs/cs-unii.js +18 -14
  117. package/tx/cs/cs-virtual.js +210 -0
  118. package/tx/ecosystem-resolver.js +413 -0
  119. package/tx/html/cluster-dash-metrics.liquid +26 -0
  120. package/tx/html/cluster-home-metrics.liquid +31 -0
  121. package/tx/html/cluster-metrics-script.liquid +251 -0
  122. package/tx/html/codesystem-operations.liquid +3 -3
  123. package/tx/html/ecl-panel.liquid +1 -1
  124. package/tx/html/external-metrics.liquid +17 -0
  125. package/tx/html/operations-form.liquid +17 -17
  126. package/tx/html/search-form.liquid +11 -11
  127. package/tx/html/status-line.liquid +58 -0
  128. package/tx/html/status-panel.liquid +140 -0
  129. package/tx/html/status-tasks.liquid +25 -0
  130. package/tx/html/tx-template.html +2 -5
  131. package/tx/importers/atc-to-fhir.js +17 -16
  132. package/tx/importers/export-icd11-codesystem.js +3 -2
  133. package/tx/importers/import-icd11.js +4 -3
  134. package/tx/importers/import-loinc.module.js +118 -56
  135. package/tx/importers/import-ndc.module.js +56 -44
  136. package/tx/importers/import-rxnorm.module.js +118 -79
  137. package/tx/importers/import-sct.module.js +57 -58
  138. package/tx/importers/import-unii.module.js +37 -36
  139. package/tx/importers/subset-loinc.module.js +49 -48
  140. package/tx/importers/subset-rxnorm.module.js +29 -28
  141. package/tx/importers/terminal.js +18 -0
  142. package/tx/importers/tx-import-base.js +13 -12
  143. package/tx/importers/tx-import-settings.js +5 -4
  144. package/tx/importers/tx-import.js +53 -52
  145. package/tx/library/canonical-resource.js +49 -0
  146. package/tx/library/codesystem.js +94 -5
  147. package/tx/library/designations.js +45 -2
  148. package/tx/library/extensions.js +13 -7
  149. package/tx/library/implicit-valuesets.js +66 -0
  150. package/tx/library/renderer.js +77 -21
  151. package/tx/library/sqlite-utilities.js +2 -0
  152. package/tx/library/ucum-parsers.js +11 -1
  153. package/tx/library/wait-for-cache.js +43 -0
  154. package/tx/library.js +321 -22
  155. package/tx/openapi-operations.js +6 -4
  156. package/tx/operation-context.js +460 -99
  157. package/tx/params.js +25 -0
  158. package/tx/problems.js +3 -5
  159. package/tx/provider.js +278 -42
  160. package/tx/resource-cache-sqlite.js +1050 -0
  161. package/tx/sct/ecl.js +47 -6
  162. package/tx/sct/editions.js +39 -0
  163. package/tx/sct/expressions.js +10 -1
  164. package/tx/sct/load-config.js +107 -0
  165. package/tx/sct/mmap.js +109 -0
  166. package/tx/sct/structures.js +166 -13
  167. package/tx/sct/text-index.js +5 -1
  168. package/tx/syndication/syndication-feed.js +2 -2
  169. package/tx/tx-html.js +44 -8
  170. package/tx/tx.fhir.org.yml +8 -8
  171. package/tx/tx.js +282 -80
  172. package/tx/usage-tracker.js +39 -11
  173. package/tx/vs/vs-api.js +19 -0
  174. package/tx/vs/vs-database.js +386 -62
  175. package/tx/vs/vs-package.js +27 -4
  176. package/tx/vs/vs-vsac.js +648 -100
  177. package/tx/workers/batch-validate.js +37 -10
  178. package/tx/workers/batch.js +6 -1
  179. package/tx/workers/cache-control.js +15 -4
  180. package/tx/workers/closure.js +156 -13
  181. package/tx/workers/compare.js +8 -5
  182. package/tx/workers/expand.js +1173 -289
  183. package/tx/workers/metadata.js +35 -0
  184. package/tx/workers/search.js +53 -1
  185. package/tx/workers/supplement-filters.js +145 -0
  186. package/tx/workers/translate.js +169 -4
  187. package/tx/workers/validate.js +223 -119
  188. package/tx/workers/worker.js +123 -31
  189. package/tx/xversion/xv-valueset.js +7 -4
  190. package/utilities/submit-test-report.js +3 -1
  191. package/utilities/tx-stress-test.js +482 -0
  192. package/vcl/readme.md +37 -0
  193. package/vcl/vcl-parser.js +15 -0
  194. package/xig/readme.md +52 -0
  195. package/xig/xig-template.html +5 -8
  196. package/xig/xig.js +238 -422
  197. package/changelog-template.md +0 -22
  198. package/registry/test-runner.js +0 -237
  199. package/release.sh +0 -80
  200. package/test-scripts/repro-re2-wasm-leak.js +0 -34
  201. package/tsconfig.eslint.json +0 -9
  202. package/tx/html/dash-metrics.liquid +0 -147
  203. package/tx/html/home-metrics.liquid +0 -225
@@ -741,12 +741,16 @@ class MimeTypeServicesFactory extends CodeSystemFactoryProvider {
741
741
  * @param {I18nSupport} i18n
742
742
  * @param {string} cacheFolder - where the downloaded copy of the IANA registry is kept
743
743
  * @param {Logger} log
744
+ * @param {Object} [options]
745
+ * @param {boolean} [options.readOnly] - only read the cached copy: never download or write
746
+ * it (a tx-worker, cluster.md: the master refreshes it before the workers start)
744
747
  */
745
- constructor(i18n, cacheFolder = null, log = null) {
748
+ constructor(i18n, cacheFolder = null, log = null, options = {}) {
746
749
  super(i18n);
747
750
  this.uses = 0;
748
751
  this.cacheFolder = cacheFolder;
749
752
  this.log = log;
753
+ this.readOnly = options.readOnly === true;
750
754
  this.registeredTypes = null;
751
755
  }
752
756
 
@@ -761,7 +765,13 @@ class MimeTypeServicesFactory extends CodeSystemFactoryProvider {
761
765
  return; // no cache configured: the registered filter is unavailable
762
766
  }
763
767
  const target = path.join(this.cacheFolder, IANA_MEDIA_TYPES_FILE);
764
- await this.#refresh(target);
768
+ if (this.readOnly) {
769
+ if (!fs.existsSync(target)) {
770
+ this.log?.warn(`Mime types: no cached copy of the IANA registry at ${target} (a tx-worker reads the one the master downloads, and never downloads it)`);
771
+ }
772
+ } else {
773
+ await this.#refresh(target);
774
+ }
765
775
 
766
776
  if (fs.existsSync(target)) {
767
777
  try {
package/tx/cs/cs-ndc.js CHANGED
@@ -3,6 +3,7 @@ const assert = require('assert');
3
3
  const { CodeSystem } = require('../library/codesystem');
4
4
  const { CodeSystemProvider, CodeSystemFactoryProvider} = require('./cs-api');
5
5
  const {validateArrayParameter} = require("../../library/utilities");
6
+ const { guarded } = require('../library/sqlite-utilities');
6
7
 
7
8
  class NdcConcept {
8
9
  constructor(code, display, isPackage = false, key = null) {
@@ -31,10 +32,7 @@ class NdcServices extends CodeSystemProvider {
31
32
 
32
33
  // Clean up database connection when provider is destroyed
33
34
  close() {
34
- if (this.db) {
35
- this.db.close();
36
- this.db = null;
37
- }
35
+ this.closeDb();
38
36
  }
39
37
 
40
38
  // Metadata methods
@@ -134,6 +132,14 @@ class NdcServices extends CodeSystemProvider {
134
132
  }
135
133
  }
136
134
 
135
+ /**
136
+ * The concept's properties for $expand: built by extendLookup, so that an expansion reports
137
+ * the same properties $lookup does (see CodeSystemProvider._propertiesFromLookup)
138
+ */
139
+ async properties(context, names) {
140
+ return this._propertiesFromLookup(context, names);
141
+ }
142
+
137
143
  async extendLookup(ctxt, props, params) {
138
144
  validateArrayParameter(props, 'props', String);
139
145
  validateArrayParameter(params, 'params', Object);
@@ -248,7 +254,7 @@ class NdcServices extends CodeSystemProvider {
248
254
  params = [concept.key];
249
255
  }
250
256
 
251
- this.db.get(sql, params, (err, row) => {
257
+ this.db.get(sql, params, guarded(reject, (err, row) => {
252
258
  if (err) {
253
259
  reject(err);
254
260
  } else if (!row) {
@@ -277,7 +283,7 @@ class NdcServices extends CodeSystemProvider {
277
283
 
278
284
  resolve(result);
279
285
  }
280
- });
286
+ }));
281
287
  });
282
288
  }
283
289
 
@@ -358,7 +364,7 @@ class NdcServices extends CodeSystemProvider {
358
364
  LIMIT 1
359
365
  `;
360
366
 
361
- this.db.get(sql, [code, code], (err, row) => {
367
+ this.db.get(sql, [code, code], guarded(reject, (err, row) => {
362
368
  if (err) {
363
369
  reject(err);
364
370
  } else if (row) {
@@ -370,7 +376,7 @@ class NdcServices extends CodeSystemProvider {
370
376
  } else {
371
377
  resolve(null);
372
378
  }
373
- });
379
+ }));
374
380
  });
375
381
  }
376
382
 
@@ -383,7 +389,7 @@ class NdcServices extends CodeSystemProvider {
383
389
  LIMIT 1
384
390
  `;
385
391
 
386
- this.db.get(sql, [code], (err, row) => {
392
+ this.db.get(sql, [code], guarded(reject, (err, row) => {
387
393
  if (err) {
388
394
  reject(err);
389
395
  } else if (row) {
@@ -393,7 +399,7 @@ class NdcServices extends CodeSystemProvider {
393
399
  } else {
394
400
  resolve(null);
395
401
  }
396
- });
402
+ }));
397
403
  });
398
404
  }
399
405
 
@@ -443,10 +449,10 @@ class NdcServices extends CodeSystemProvider {
443
449
  return;
444
450
  }
445
451
 
446
- this.db.get(sql, (err, row) => {
452
+ this.db.get(sql, guarded(reject, (err, row) => {
447
453
  if (err) reject(err);
448
454
  else resolve(row.count);
449
- });
455
+ }));
450
456
  });
451
457
  }
452
458
 
@@ -495,7 +501,7 @@ class NdcServices extends CodeSystemProvider {
495
501
  return;
496
502
  }
497
503
 
498
- this.db.get(sql, [set._iterator.offset], (err, row) => {
504
+ this.db.get(sql, [set._iterator.offset], guarded(reject, (err, row) => {
499
505
  if (err) {
500
506
  reject(err);
501
507
  } else if (row) {
@@ -514,7 +520,7 @@ class NdcServices extends CodeSystemProvider {
514
520
  set._iterator.hasMore = false;
515
521
  resolve(null);
516
522
  }
517
- });
523
+ }));
518
524
  });
519
525
  }
520
526
 
@@ -571,9 +577,14 @@ class NdcServices extends CodeSystemProvider {
571
577
  }
572
578
 
573
579
  class NdcServicesFactory extends CodeSystemFactoryProvider {
574
- constructor(i18n, dbPath) {
580
+ /**
581
+ * @param {Object} [options] - {readOnly: true} in a tx-worker (cluster.md): the file is
582
+ * the master's; this process opens it read-only
583
+ */
584
+ constructor(i18n, dbPath, options = {}) {
575
585
  super(i18n);
576
586
  this.dbPath = dbPath;
587
+ this.readOnly = !!options.readOnly;
577
588
  this.uses = 0;
578
589
  this._loaded = false;
579
590
  this._lookupTables = null;
@@ -601,17 +612,21 @@ class NdcServicesFactory extends CodeSystemFactoryProvider {
601
612
  }
602
613
  }
603
614
 
615
+ #open() {
616
+ return this.readOnly ? new sqlite3.Database(this.dbPath, sqlite3.OPEN_READONLY) : new sqlite3.Database(this.dbPath);
617
+ }
618
+
604
619
  async load() {
605
620
  // Use temporary database connection for loading
606
- const tempDb = new sqlite3.Database(this.dbPath);
621
+ const tempDb = this.#open();
607
622
 
608
623
  try {
609
624
  // Load version
610
625
  this._version = await new Promise((resolve, reject) => {
611
- tempDb.get('SELECT Version FROM NDCVersion ORDER BY Version DESC LIMIT 1', (err, row) => {
626
+ tempDb.get('SELECT Version FROM NDCVersion ORDER BY Version DESC LIMIT 1', guarded(reject, (err, row) => {
612
627
  if (err) reject(err);
613
628
  else resolve(row ? row.Version : 'unknown');
614
- });
629
+ }));
615
630
  });
616
631
 
617
632
  // Initialize lookup tables
@@ -632,30 +647,30 @@ class NdcServicesFactory extends CodeSystemFactoryProvider {
632
647
 
633
648
  for (const table of tables) {
634
649
  await new Promise((resolve, reject) => {
635
- tempDb.all(table.sql, (err, rows) => {
650
+ tempDb.all(table.sql, guarded(reject, (err, rows) => {
636
651
  if (err) reject(err);
637
652
  else {
638
653
  const map = this._lookupTables[table.name];
639
654
  rows.forEach(row => map.set(row.NDCKey, row.Name));
640
655
  resolve();
641
656
  }
642
- });
657
+ }));
643
658
  });
644
659
  }
645
660
 
646
661
  // Load counts
647
662
  this._packageCount = await new Promise((resolve, reject) => {
648
- tempDb.get('SELECT COUNT(NDCKey) as count FROM NDCPackages', (err, row) => {
663
+ tempDb.get('SELECT COUNT(NDCKey) as count FROM NDCPackages', guarded(reject, (err, row) => {
649
664
  if (err) reject(err);
650
665
  else resolve(row.count);
651
- });
666
+ }));
652
667
  });
653
668
 
654
669
  this._productCount = await new Promise((resolve, reject) => {
655
- tempDb.get('SELECT COUNT(NDCKey) as count FROM NDCProducts', (err, row) => {
670
+ tempDb.get('SELECT COUNT(NDCKey) as count FROM NDCProducts', guarded(reject, (err, row) => {
656
671
  if (err) reject(err);
657
672
  else resolve(row.count);
658
- });
673
+ }));
659
674
  });
660
675
 
661
676
  } finally {
@@ -673,7 +688,7 @@ class NdcServicesFactory extends CodeSystemFactoryProvider {
673
688
  this.recordUse();
674
689
 
675
690
  // Create fresh database connection for this provider instance
676
- const db = new sqlite3.Database(this.dbPath);
691
+ const db = this.#open();
677
692
 
678
693
  return new NdcServices(
679
694
  opContext, supplements,
package/tx/cs/cs-omop.js CHANGED
@@ -5,6 +5,7 @@ const { FilterExecutionContext, CodeSystemFactoryProvider } = require('./cs-api'
5
5
  const {validateOptionalParameter, validateArrayParameter} = require("../../library/utilities");
6
6
  const {ConceptMap} = require("../library/conceptmap");
7
7
  const {BaseCSServices} = require("./cs-base");
8
+ const { guarded } = require('../library/sqlite-utilities');
8
9
 
9
10
  class OMOPConcept {
10
11
  constructor(code, display, domain, conceptClass, standard, vocabulary) {
@@ -135,10 +136,7 @@ class OMOPServices extends BaseCSServices {
135
136
  }
136
137
 
137
138
  close() {
138
- if (this.db) {
139
- this.db.close();
140
- this.db = null;
141
- }
139
+ this.closeDb();
142
140
  }
143
141
 
144
142
  // Metadata methods
@@ -166,10 +164,10 @@ class OMOPServices extends BaseCSServices {
166
164
 
167
165
  async totalCount() {
168
166
  return new Promise((resolve, reject) => {
169
- this.db.get('SELECT COUNT(*) as count FROM Concepts', (err, row) => {
167
+ this.db.get('SELECT COUNT(*) as count FROM Concepts', guarded(reject, (err, row) => {
170
168
  if (err) reject(err);
171
169
  else resolve(row.count);
172
- });
170
+ }));
173
171
  });
174
172
  }
175
173
 
@@ -245,7 +243,7 @@ class OMOPServices extends BaseCSServices {
245
243
  AND ConceptSynonyms.concept_id = ?
246
244
  `;
247
245
 
248
- this.db.all(sql, [code], (err, rows) => {
246
+ this.db.all(sql, [code], guarded(reject, (err, rows) => {
249
247
  if (err) {
250
248
  reject(err);
251
249
  } else {
@@ -255,10 +253,26 @@ class OMOPServices extends BaseCSServices {
255
253
  }));
256
254
  resolve(synonyms);
257
255
  }
258
- });
256
+ }));
259
257
  });
260
258
  }
261
259
 
260
+ /**
261
+ * The concept's properties for $expand: built by extendLookup, so that an expansion reports
262
+ * the same properties $lookup does (see CodeSystemProvider._propertiesFromLookup)
263
+ */
264
+ async properties(context, names) {
265
+ // a concept from a filter is partial (domain_id is the domain's concept id, no concept class
266
+ // or vocabulary): the properties are those of the concept itself
267
+ if (names && names.length > 0 && context instanceof OMOPConcept && context.conceptClass === '') {
268
+ const located = await this.locate(String(context.code));
269
+ if (located.context) {
270
+ context = located.context;
271
+ }
272
+ }
273
+ return this._propertiesFromLookup(context, names);
274
+ }
275
+
262
276
  async extendLookup(ctxt, props, params) {
263
277
  validateArrayParameter(props, 'props', String);
264
278
  validateArrayParameter(params, 'params', Object);
@@ -307,7 +321,7 @@ class OMOPServices extends BaseCSServices {
307
321
  return new Promise((resolve, reject) => {
308
322
  const sql = 'SELECT * FROM Concepts WHERE concept_id = ?';
309
323
 
310
- this.db.get(sql, [ctxt.code], (err, row) => {
324
+ this.db.get(sql, [ctxt.code], guarded(reject, (err, row) => {
311
325
  if (err) {
312
326
  reject(err);
313
327
  } else if (row) {
@@ -337,7 +351,7 @@ class OMOPServices extends BaseCSServices {
337
351
  } else {
338
352
  resolve();
339
353
  }
340
- });
354
+ }));
341
355
  });
342
356
  }
343
357
 
@@ -354,7 +368,7 @@ class OMOPServices extends BaseCSServices {
354
368
  AND ConceptRelationships.concept_id_1 = ?
355
369
  `;
356
370
 
357
- this.db.all(sql, [ctxt.code], (err, rows) => {
371
+ this.db.all(sql, [ctxt.code], guarded(reject, (err, rows) => {
358
372
  if (err) {
359
373
  reject(err);
360
374
  } else {
@@ -367,7 +381,7 @@ class OMOPServices extends BaseCSServices {
367
381
  }
368
382
  resolve();
369
383
  }
370
- });
384
+ }));
371
385
  });
372
386
 
373
387
  // Reverse relationships
@@ -380,7 +394,7 @@ class OMOPServices extends BaseCSServices {
380
394
  AND ConceptRelationships.concept_id_2 = ?
381
395
  `;
382
396
 
383
- this.db.all(sql, [ctxt.code], (err, rows) => {
397
+ this.db.all(sql, [ctxt.code], guarded(reject, (err, rows) => {
384
398
  if (err) {
385
399
  reject(err);
386
400
  } else {
@@ -394,7 +408,7 @@ class OMOPServices extends BaseCSServices {
394
408
  }
395
409
  resolve();
396
410
  }
397
- });
411
+ }));
398
412
  });
399
413
  }
400
414
 
@@ -512,7 +526,7 @@ class OMOPServices extends BaseCSServices {
512
526
  AND concept_id = ?
513
527
  `;
514
528
 
515
- this.db.get(sql, [code], (err, row) => {
529
+ this.db.get(sql, [code], guarded(reject, (err, row) => {
516
530
  if (err) {
517
531
  reject(err);
518
532
  } else if (row && row.concept_id.toString() === code) {
@@ -528,7 +542,7 @@ class OMOPServices extends BaseCSServices {
528
542
  } else {
529
543
  resolve({ context: null, message: undefined });
530
544
  }
531
- });
545
+ }));
532
546
  });
533
547
  }
534
548
 
@@ -605,7 +619,7 @@ class OMOPServices extends BaseCSServices {
605
619
  set.cursor++;
606
620
 
607
621
  return new OMOPConcept(
608
- row.concept_id,
622
+ String(row.concept_id), // a code is a string (the expansion's code was a JSON number)
609
623
  row.concept_name,
610
624
  row.domain_id,
611
625
  '', // concept_class not in basic filter query
@@ -657,7 +671,9 @@ class OMOPServices extends BaseCSServices {
657
671
  }
658
672
 
659
673
 
660
- // Subsumption testing - not implemented
674
+ // Subsumption: this provider has no hierarchy (the OMOP concept_ancestor table isn't
675
+ // imported), so two concepts that both exist are not-subsumed (the caller deals with equal
676
+ // codes: equivalent)
661
677
  async subsumesTest(codeA, codeB) {
662
678
  await this.#ensureContext(codeA);
663
679
  await this.#ensureContext(codeB);
@@ -683,7 +699,7 @@ class OMOPServices extends BaseCSServices {
683
699
  WHERE concept_id = ? AND vocabulary_id = ?
684
700
  `;
685
701
 
686
- this.db.all(sql, [coding.code, vocabId], (err, rows) => {
702
+ this.db.all(sql, [coding.code, vocabId], guarded(reject, (err, rows) => {
687
703
  if (err) {
688
704
  reject(err);
689
705
  } else {
@@ -696,7 +712,7 @@ class OMOPServices extends BaseCSServices {
696
712
  }));
697
713
  resolve(translations);
698
714
  }
699
- });
715
+ }));
700
716
  });
701
717
  }
702
718
 
@@ -712,7 +728,7 @@ class OMOPServices extends BaseCSServices {
712
728
  AND Domains.domain_concept_id = Concepts.concept_id
713
729
  `;
714
730
 
715
- this.db.get(sql, [domain], (err, row) => {
731
+ this.db.get(sql, [domain], guarded(reject, (err, row) => {
716
732
  if (err) {
717
733
  reject(err);
718
734
  } else if (row && row.domain_id === domain) {
@@ -740,7 +756,7 @@ class OMOPServices extends BaseCSServices {
740
756
  } else {
741
757
  reject(new Error(`Unknown Value Domain ${id}`));
742
758
  }
743
- });
759
+ }));
744
760
  });
745
761
  }
746
762
 
@@ -749,7 +765,7 @@ class OMOPServices extends BaseCSServices {
749
765
  return new Promise((resolve, reject) => {
750
766
  const sql = 'SELECT DISTINCT vocabulary_id FROM Concepts';
751
767
 
752
- this.db.all(sql, (err, rows) => {
768
+ this.db.all(sql, guarded(reject, (err, rows) => {
753
769
  if (err) {
754
770
  reject(err);
755
771
  } else {
@@ -774,7 +790,7 @@ class OMOPServices extends BaseCSServices {
774
790
  }
775
791
  resolve();
776
792
  }
777
- });
793
+ }));
778
794
  });
779
795
  }
780
796
 
@@ -784,9 +800,14 @@ class OMOPServices extends BaseCSServices {
784
800
  }
785
801
 
786
802
  class OMOPServicesFactory extends CodeSystemFactoryProvider {
787
- constructor(i18n, dbPath) {
803
+ /**
804
+ * @param {Object} [options] - {readOnly: true} in a tx-worker (cluster.md): the file is
805
+ * the master's; this process opens it read-only
806
+ */
807
+ constructor(i18n, dbPath, options = {}) {
788
808
  super(i18n);
789
809
  this.dbPath = dbPath;
810
+ this.readOnly = !!options.readOnly;
790
811
  this.uses = 0;
791
812
  this._loaded = false;
792
813
  this._sharedData = null;
@@ -855,8 +876,12 @@ class OMOPServicesFactory extends CodeSystemFactoryProvider {
855
876
  }
856
877
  }
857
878
 
879
+ #open() {
880
+ return this.readOnly ? new sqlite3.Database(this.dbPath, sqlite3.OPEN_READONLY) : new sqlite3.Database(this.dbPath);
881
+ }
882
+
858
883
  async load() {
859
- const db = new sqlite3.Database(this.dbPath);
884
+ const db = this.#open();
860
885
 
861
886
  try {
862
887
  this._sharedData = {
@@ -880,7 +905,7 @@ class OMOPServicesFactory extends CodeSystemFactoryProvider {
880
905
  WHERE vocabulary_id = 'OMOP Extension'
881
906
  `;
882
907
 
883
- db.get(sql, (err, row) => {
908
+ db.get(sql, guarded(reject, (err, row) => {
884
909
  if (err) {
885
910
  reject(err);
886
911
  } else if (row) {
@@ -894,7 +919,7 @@ class OMOPServicesFactory extends CodeSystemFactoryProvider {
894
919
  this._sharedData._version = 'unknown';
895
920
  resolve();
896
921
  }
897
- });
922
+ }));
898
923
  });
899
924
  }
900
925
 
@@ -907,7 +932,7 @@ class OMOPServicesFactory extends CodeSystemFactoryProvider {
907
932
  this.recordUse();
908
933
 
909
934
  // Create fresh database connection for this provider instance
910
- const db = new sqlite3.Database(this.dbPath);
935
+ const db = this.#open();
911
936
 
912
937
  return new OMOPServices(opContext, supplements, db, this._sharedData);
913
938
  }