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.
Files changed (214) hide show
  1. package/ACCESSIBILITY.md +119 -0
  2. package/CHANGELOG.md +190 -9
  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 +331 -33
  15. package/ips/jurisdictions.js +65 -0
  16. package/ips/nationality.js +120 -0
  17. package/ips/pipeline.js +131 -17
  18. package/ips/presenter.js +1 -1
  19. package/ips/profile-editor.js +283 -4
  20. package/ips/readme.md +170 -20
  21. package/ips/render.js +769 -109
  22. package/ips/scripts.js +46 -0
  23. package/ips/store.js +123 -11
  24. package/ips/structure.js +247 -0
  25. package/ips/terminology.js +1 -1
  26. package/library/body-limit.js +22 -0
  27. package/library/cache-generation.js +15 -0
  28. package/library/cgroup-memory.js +133 -1
  29. package/library/cluster-counts.js +121 -0
  30. package/library/cluster-events.js +153 -0
  31. package/library/cluster-metrics.js +193 -0
  32. package/library/cluster-status.js +468 -0
  33. package/library/config-reader.js +96 -0
  34. package/library/daily-report.js +100 -2
  35. package/library/html-server.js +27 -1
  36. package/library/i18nsupport.js +23 -1
  37. package/library/keep-alive.js +18 -0
  38. package/library/languages.js +17 -3
  39. package/library/logger.js +31 -0
  40. package/library/memory-guard.js +428 -0
  41. package/library/native-memory.js +52 -0
  42. package/library/npm-audit.js +51 -25
  43. package/library/openapi-doc.js +7 -5
  44. package/library/package-manager.js +8 -0
  45. package/library/role.js +197 -0
  46. package/library/server-credentials.js +169 -0
  47. package/library/sqlite-utilities.js +59 -0
  48. package/library/ssrf-guard.js +100 -0
  49. package/library/static-response-stats.js +16 -0
  50. package/library/version-utilities.js +50 -0
  51. package/library/worker-count.js +192 -0
  52. package/nginx.md +30 -11
  53. package/npmprojector/indexer.js +18 -10
  54. package/package.json +11 -8
  55. package/packages/feed-check-page.js +117 -0
  56. package/packages/feed-check.js +279 -0
  57. package/packages/npm-check.js +57 -3
  58. package/packages/npm-signing.js +219 -0
  59. package/packages/openapi.yaml +207 -1
  60. package/packages/package-crawler.js +322 -41
  61. package/packages/packages-template.html +6 -8
  62. package/packages/packages.js +604 -57
  63. package/packages/readme.md +113 -1
  64. package/publisher/publisher-template.html +2 -5
  65. package/publisher/publisher.js +121 -110
  66. package/publisher/readme.md +75 -0
  67. package/registry/api.js +179 -1
  68. package/registry/crawler.js +86 -108
  69. package/registry/documentation/api.md +68 -2
  70. package/registry/documentation/configuration.md +34 -0
  71. package/registry/documentation/internals.md +4 -1
  72. package/registry/documentation/readme.md +3 -2
  73. package/registry/model.js +8 -0
  74. package/registry/openapi.yaml +84 -0
  75. package/registry/registry-template.html +5 -8
  76. package/registry/registry.js +58 -6
  77. package/root-bare-template.html +7 -4
  78. package/root-template.html +5 -8
  79. package/server.js +398 -198
  80. package/shl/shl.js +38 -37
  81. package/static/a11y/a11y-check.js +832 -0
  82. package/static/a11y/axe.min.js +12 -0
  83. package/static/assets/css/bootstrap-fhir.css +15 -15
  84. package/static/assets/images/flags/au.svg +18 -0
  85. package/static/assets/images/flags/unknown.svg +11 -0
  86. package/static/assets/images/flags/us.svg +63 -0
  87. package/static/assets/images/icon_audit.png +0 -0
  88. package/static/assets/images/icon_clinical.png +0 -0
  89. package/static/assets/images/icon_document.png +0 -0
  90. package/static/assets/images/icon_external.png +0 -0
  91. package/static/assets/images/icon_medication.png +0 -0
  92. package/static/assets/images/icon_organization.png +0 -0
  93. package/static/assets/images/icon_other.png +0 -0
  94. package/static/assets/images/icon_person.png +0 -0
  95. package/static/assets/images/icon_section.png +0 -0
  96. package/stats-db.js +45 -0
  97. package/stats.js +197 -10
  98. package/testing/testing-template.html +5 -8
  99. package/token/nginx_passport_setup.md +1 -1
  100. package/token/security_guide.md +1 -1
  101. package/token/token-template.html +2 -5
  102. package/token/token.js +40 -36
  103. package/translations/Messages.properties +19 -0
  104. package/tx/README.md +158 -4
  105. package/tx/closure/closure-store.js +126 -3
  106. package/tx/cm/cm-database.js +69 -57
  107. package/tx/cm/cm-package.js +50 -16
  108. package/tx/cs/cs-api.js +284 -19
  109. package/tx/cs/cs-api.md +41 -4
  110. package/tx/cs/cs-areacode.js +1 -1
  111. package/tx/cs/cs-country.js +17 -5
  112. package/tx/cs/cs-cpt.js +33 -16
  113. package/tx/cs/cs-cs.js +199 -24
  114. package/tx/cs/cs-currency.js +1 -1
  115. package/tx/cs/cs-icd11.js +21 -13
  116. package/tx/cs/cs-icf.js +22 -3
  117. package/tx/cs/cs-lang.js +2 -2
  118. package/tx/cs/cs-loinc.js +193 -67
  119. package/tx/cs/cs-mimetypes.js +12 -2
  120. package/tx/cs/cs-ndc.js +40 -25
  121. package/tx/cs/cs-omop.js +54 -29
  122. package/tx/cs/cs-rxnorm.js +300 -196
  123. package/tx/cs/cs-snomed.js +861 -95
  124. package/tx/cs/cs-ucum.js +14 -4
  125. package/tx/cs/cs-unii.js +18 -14
  126. package/tx/cs/cs-virtual.js +210 -0
  127. package/tx/ecosystem-resolver.js +413 -0
  128. package/tx/html/cluster-dash-metrics.liquid +26 -0
  129. package/tx/html/cluster-home-metrics.liquid +31 -0
  130. package/tx/html/cluster-metrics-script.liquid +251 -0
  131. package/tx/html/codesystem-operations.liquid +3 -3
  132. package/tx/html/ecl-panel.liquid +1 -1
  133. package/tx/html/external-metrics.liquid +17 -0
  134. package/tx/html/operations-form.liquid +17 -17
  135. package/tx/html/search-form.liquid +11 -11
  136. package/tx/html/status-line.liquid +58 -0
  137. package/tx/html/status-panel.liquid +140 -0
  138. package/tx/html/status-tasks.liquid +25 -0
  139. package/tx/html/tx-template.html +2 -5
  140. package/tx/importers/atc-to-fhir.js +17 -16
  141. package/tx/importers/export-icd11-codesystem.js +3 -2
  142. package/tx/importers/import-icd11.js +4 -3
  143. package/tx/importers/import-loinc.module.js +118 -56
  144. package/tx/importers/import-ndc.module.js +56 -44
  145. package/tx/importers/import-rxnorm.module.js +118 -79
  146. package/tx/importers/import-sct.module.js +125 -61
  147. package/tx/importers/import-unii.module.js +37 -36
  148. package/tx/importers/subset-loinc.module.js +49 -48
  149. package/tx/importers/subset-rxnorm.module.js +29 -28
  150. package/tx/importers/terminal.js +18 -0
  151. package/tx/importers/tx-import-base.js +13 -12
  152. package/tx/importers/tx-import-settings.js +5 -4
  153. package/tx/importers/tx-import.js +53 -52
  154. package/tx/library/canonical-resource.js +49 -0
  155. package/tx/library/codesystem.js +94 -5
  156. package/tx/library/designations.js +45 -2
  157. package/tx/library/extensions.js +13 -7
  158. package/tx/library/implicit-valuesets.js +66 -0
  159. package/tx/library/renderer.js +77 -21
  160. package/tx/library/sqlite-utilities.js +2 -0
  161. package/tx/library/tho-precedence.js +40 -0
  162. package/tx/library/ucum-parsers.js +11 -1
  163. package/tx/library/wait-for-cache.js +43 -0
  164. package/tx/library.js +321 -22
  165. package/tx/openapi-operations.js +6 -4
  166. package/tx/operation-context.js +460 -99
  167. package/tx/params.js +25 -0
  168. package/tx/problems.js +3 -5
  169. package/tx/provider.js +314 -67
  170. package/tx/resource-cache-sqlite.js +1050 -0
  171. package/tx/sct/documentation.md +45 -21
  172. package/tx/sct/ecl.js +81 -17
  173. package/tx/sct/editions.js +39 -0
  174. package/tx/sct/expressions.js +10 -1
  175. package/tx/sct/load-config.js +107 -0
  176. package/tx/sct/mmap.js +109 -0
  177. package/tx/sct/structures.js +175 -15
  178. package/tx/sct/text-index.js +5 -1
  179. package/tx/syndication/syndication-feed.js +2 -2
  180. package/tx/tx-html.js +44 -8
  181. package/tx/tx.fhir.org.yml +8 -8
  182. package/tx/tx.js +282 -80
  183. package/tx/usage-tracker.js +39 -11
  184. package/tx/vs/vs-api.js +19 -0
  185. package/tx/vs/vs-database.js +386 -62
  186. package/tx/vs/vs-package.js +44 -17
  187. package/tx/vs/vs-vsac.js +649 -101
  188. package/tx/workers/batch-validate.js +37 -10
  189. package/tx/workers/batch.js +6 -1
  190. package/tx/workers/cache-control.js +15 -4
  191. package/tx/workers/closure.js +156 -13
  192. package/tx/workers/compare.js +8 -5
  193. package/tx/workers/expand.js +1173 -289
  194. package/tx/workers/metadata.js +35 -0
  195. package/tx/workers/search.js +53 -1
  196. package/tx/workers/supplement-filters.js +145 -0
  197. package/tx/workers/translate.js +169 -4
  198. package/tx/workers/validate.js +223 -119
  199. package/tx/workers/worker.js +123 -31
  200. package/tx/xversion/xv-valueset.js +7 -4
  201. package/utilities/submit-test-report.js +3 -1
  202. package/utilities/tx-stress-test.js +482 -0
  203. package/vcl/readme.md +37 -0
  204. package/vcl/vcl-parser.js +15 -0
  205. package/xig/readme.md +52 -0
  206. package/xig/xig-template.html +5 -8
  207. package/xig/xig.js +238 -422
  208. package/changelog-template.md +0 -22
  209. package/registry/test-runner.js +0 -237
  210. package/release.sh +0 -80
  211. package/test-scripts/repro-re2-wasm-leak.js +0 -34
  212. package/tsconfig.eslint.json +0 -9
  213. package/tx/html/dash-metrics.liquid +0 -147
  214. package/tx/html/home-metrics.liquid +0 -225
package/shl/shl.js CHANGED
@@ -14,6 +14,7 @@ const pako = require('pako');
14
14
  const base45 = require('base45');
15
15
  const fs = require('fs');
16
16
  const folders = require('../library/folder-setup');
17
+ const { guarded, failRequest } = require('../library/sqlite-utilities');
17
18
 
18
19
  const Logger = require('../library/logger');
19
20
  const shlLog = Logger.getInstance().child({ module: 'shl' });
@@ -278,7 +279,7 @@ class SHLModule {
278
279
  shlLog.info('Connected to SHL SQLite database at "'+dbPath+'"');
279
280
 
280
281
  // Check if tables already exist before creating them
281
- this.db.get("SELECT name FROM sqlite_master WHERE type='table' AND name='SHL'", (err, row) => {
282
+ this.db.get("SELECT name FROM sqlite_master WHERE type='table' AND name='SHL'", guarded(reject, (err, row) => {
282
283
  if (err) {
283
284
  reject(err);
284
285
  } else if (row) {
@@ -288,7 +289,7 @@ class SHLModule {
288
289
  // Tables don't exist, create them
289
290
  this.createTables().then(resolve).catch(reject);
290
291
  }
291
- });
292
+ }));
292
293
  }
293
294
  });
294
295
  });
@@ -339,32 +340,32 @@ class SHLModule {
339
340
  }
340
341
  };
341
342
 
342
- this.db.run(createSHLTable, (err) => {
343
+ this.db.run(createSHLTable, guarded(reject, (err) => {
343
344
  if (err) {
344
345
  shlLog.error('Error creating SHL table:', err.message);
345
346
  reject(err);
346
347
  } else {
347
348
  checkComplete();
348
349
  }
349
- });
350
+ }));
350
351
 
351
- this.db.run(createSHLFilesTable, (err) => {
352
+ this.db.run(createSHLFilesTable, guarded(reject, (err) => {
352
353
  if (err) {
353
354
  shlLog.error('Error creating SHLFiles table:', err.message);
354
355
  reject(err);
355
356
  } else {
356
357
  checkComplete();
357
358
  }
358
- });
359
+ }));
359
360
 
360
- this.db.run(createSHLViewsTable, (err) => {
361
+ this.db.run(createSHLViewsTable, guarded(reject, (err) => {
361
362
  if (err) {
362
363
  shlLog.error('Error creating SHLViews table:', err.message);
363
364
  reject(err);
364
365
  } else {
365
366
  checkComplete();
366
367
  }
367
- });
368
+ }));
368
369
  });
369
370
  }
370
371
 
@@ -430,13 +431,13 @@ class SHLModule {
430
431
  cleanupExpiredEntries() {
431
432
  const deleteSql = 'DELETE FROM SHL WHERE expires_at < datetime("now")';
432
433
 
433
- this.db.run(deleteSql, function(err) {
434
+ this.db.run(deleteSql, guarded(null, function(err) {
434
435
  if (err) {
435
436
  shlLog.error('SHL cleanup error:', err.message);
436
437
  } else if (this.changes > 0) {
437
438
  shlLog.info(`Cleaned up ${this.changes} expired SHL entries`);
438
439
  }
439
- });
440
+ }));
440
441
  }
441
442
 
442
443
  generateUUID() {
@@ -678,7 +679,7 @@ class SHLModule {
678
679
  });
679
680
 
680
681
  // SHL create endpoint
681
- this.router.post('/create', this.validateJsonBody(['vhl', 'password', 'days']), (req, res) => {
682
+ this.router.post('/create', this.validateJsonBody(['vhl', 'password', 'days']), (req, res, next) => {
682
683
  const start = Date.now();
683
684
  try {
684
685
  const {vhl, password, days} = req.body;
@@ -718,7 +719,7 @@ class SHLModule {
718
719
 
719
720
  const insertSql = 'INSERT INTO SHL (uuid, vhl, expires_at, password) VALUES (?, ?, ?, ?)';
720
721
 
721
- this.db.run(insertSql, [uuid, vhl, expiryDateString, newPassword], function (err) {
722
+ this.db.run(insertSql, [uuid, vhl, expiryDateString, newPassword], guarded(next, function (err) {
722
723
  if (err) {
723
724
  return res.status(500).json({error: 'Failed to create SHL entry: ' + err.message});
724
725
  }
@@ -730,14 +731,14 @@ class SHLModule {
730
731
  pword: newPassword,
731
732
  link: `https://${host}/shl/access/${uuid}`
732
733
  });
733
- });
734
+ }));
734
735
  } finally {
735
736
  this.stats.countRequest('create', Date.now() - start);
736
737
  }
737
738
  });
738
739
 
739
740
  // SHL upload endpoint
740
- this.router.post('/upload', this.validateJsonBody(['uuid', 'pword', 'files']), (req, res) => {
741
+ this.router.post('/upload', this.validateJsonBody(['uuid', 'pword', 'files']), (req, res, next) => {
741
742
  const start = Date.now();
742
743
  try {
743
744
  const {uuid, pword, files} = req.body;
@@ -758,7 +759,7 @@ class SHLModule {
758
759
 
759
760
  const checkSHLSql = 'SELECT vhl, password FROM SHL WHERE uuid = ?';
760
761
 
761
- this.db.get(checkSHLSql, [uuid], (err, row) => {
762
+ this.db.get(checkSHLSql, [uuid], guarded(next, (err, row) => {
762
763
  if (err) {
763
764
  return res.status(500).json({error: 'Database error'});
764
765
  }
@@ -773,7 +774,7 @@ class SHLModule {
773
774
 
774
775
  const deleteExistingFilesSql = 'DELETE FROM SHLFiles WHERE shl_uuid = ?';
775
776
 
776
- this.db.run(deleteExistingFilesSql, [uuid], (err) => {
777
+ this.db.run(deleteExistingFilesSql, [uuid], guarded(next, (err) => {
777
778
  if (err) {
778
779
  return res.status(500).json({error: 'Failed to clear existing files'});
779
780
  }
@@ -783,13 +784,13 @@ class SHLModule {
783
784
  const fileId = this.generateUUID();
784
785
  const insertFileSql = 'INSERT INTO SHLFiles (id, shl_uuid, cnt, type) VALUES (?, ?, ?, ?)';
785
786
 
786
- this.db.run(insertFileSql, [fileId, uuid, f.cnt, f.type], function (err) {
787
+ this.db.run(insertFileSql, [fileId, uuid, f.cnt, f.type], guarded(reject, function (err) {
787
788
  if (err) {
788
789
  reject(err);
789
790
  } else {
790
791
  resolve();
791
792
  }
792
- });
793
+ }));
793
794
  });
794
795
  });
795
796
 
@@ -801,8 +802,8 @@ class SHLModule {
801
802
  shlLog.error('File upload error:', error);
802
803
  res.status(500).json({error: 'Failed to upload files'});
803
804
  });
804
- });
805
- });
805
+ }));
806
+ }));
806
807
  } finally {
807
808
  this.stats.countRequest('upload', Date.now() - start);
808
809
  }
@@ -845,7 +846,7 @@ class SHLModule {
845
846
 
846
847
  const checkSHLSql = 'SELECT uuid, vhl FROM SHL WHERE uuid = ? AND expires_at > datetime("now")';
847
848
 
848
- this.db.get(checkSHLSql, [uuid], (err, shlRow) => {
849
+ this.db.get(checkSHLSql, [uuid], guarded(failRequest(res), (err, shlRow) => {
849
850
  if (err) {
850
851
  return res.status(500).json({error: 'Database error'});
851
852
  }
@@ -856,14 +857,14 @@ class SHLModule {
856
857
 
857
858
  const logAccessSql = 'INSERT INTO SHLViews (shl_uuid, recipient, ip_address) VALUES (?, ?, ?)';
858
859
 
859
- this.db.run(logAccessSql, [uuid, recipient, clientIP], (logErr) => {
860
+ this.db.run(logAccessSql, [uuid, recipient, clientIP], guarded(failRequest(res), (logErr) => {
860
861
  if (logErr) {
861
862
  shlLog.error('Failed to log SHL access:', logErr.message);
862
863
  }
863
864
 
864
865
  const getFilesSql = 'SELECT id, cnt, type FROM SHLFiles WHERE shl_uuid = ?';
865
866
 
866
- this.db.all(getFilesSql, [uuid], (err, fileRows) => {
867
+ this.db.all(getFilesSql, [uuid], guarded(failRequest(res), (err, fileRows) => {
867
868
  if (err) {
868
869
  return res.status(500).json({error: 'Failed to retrieve files'});
869
870
  }
@@ -911,9 +912,9 @@ class SHLModule {
911
912
  } else {
912
913
  res.json(standardResponse);
913
914
  }
914
- });
915
- });
916
- });
915
+ }));
916
+ }));
917
+ }));
917
918
  } finally {
918
919
  this.stats.countRequest('access', Date.now() - start);
919
920
  }
@@ -924,7 +925,7 @@ class SHLModule {
924
925
  this.router.post('/access/:uuid', this.validateJsonBody(['recipient'], ['embeddedLengthMax']), handleSHLAccess);
925
926
 
926
927
  // SHL file endpoint - serves individual files
927
- this.router.get('/file/:fileId', (req, res) => {
928
+ this.router.get('/file/:fileId', (req, res, next) => {
928
929
  const start = Date.now();
929
930
  try {
930
931
  const {fileId} = req.params;
@@ -940,7 +941,7 @@ class SHLModule {
940
941
 
941
942
  const getFileSql = 'SELECT id, shl_uuid, cnt, type FROM SHLFiles WHERE id = ?';
942
943
 
943
- this.db.get(getFileSql, [fileId], (err, fileRow) => {
944
+ this.db.get(getFileSql, [fileId], guarded(next, (err, fileRow) => {
944
945
  if (err) {
945
946
  return res.status(500).json({error: 'Database error'});
946
947
  }
@@ -952,17 +953,17 @@ class SHLModule {
952
953
  const logMasterAccessSql = 'INSERT INTO SHLViews (shl_uuid, recipient, ip_address) VALUES (?, ?, ?)';
953
954
  const logFileAccessSql = 'INSERT INTO SHLViews (shl_uuid, recipient, ip_address) VALUES (?, ?, ?)';
954
955
 
955
- this.db.run(logMasterAccessSql, [fileRow.shl_uuid, null, clientIP], function (logErr) {
956
+ this.db.run(logMasterAccessSql, [fileRow.shl_uuid, null, clientIP], guarded(next, function (logErr) {
956
957
  if (logErr) {
957
958
  shlLog.error('Failed to log master SHL file access:', logErr.message);
958
959
  }
959
- });
960
+ }));
960
961
 
961
- this.db.run(logFileAccessSql, [fileRow.id, null, clientIP], function (logErr) {
962
+ this.db.run(logFileAccessSql, [fileRow.id, null, clientIP], guarded(next, function (logErr) {
962
963
  if (logErr) {
963
964
  shlLog.error('Failed to log file-specific access:', logErr.message);
964
965
  }
965
- });
966
+ }));
966
967
 
967
968
  try {
968
969
  const fileBuffer = Buffer.from(fileRow.cnt, 'base64');
@@ -971,7 +972,7 @@ class SHLModule {
971
972
  } catch (decodeErr) {
972
973
  res.status(500).json({error: 'Failed to decode file content'});
973
974
  }
974
- });
975
+ }));
975
976
  } finally {
976
977
  this.stats.countRequest('file', Date.now() - start);
977
978
  }
@@ -1093,7 +1094,7 @@ class SHLModule {
1093
1094
  return 'The database is not open.';
1094
1095
  }
1095
1096
  const get = (sql, params) => new Promise((resolve, reject) =>
1096
- this.db.get(sql, params, (err, row) => err ? reject(err) : resolve(row)));
1097
+ this.db.get(sql, params, guarded(reject, (err, row) => err ? reject(err) : resolve(row))));
1097
1098
  // created_at is CURRENT_TIMESTAMP: UTC, 'YYYY-MM-DD HH:MM:SS'
1098
1099
  const utc = ms => new Date(ms).toISOString().replace('T', ' ').substring(0, 19);
1099
1100
  try {
@@ -1131,15 +1132,15 @@ class SHLModule {
1131
1132
  }
1132
1133
 
1133
1134
  if (this.db) {
1134
- return new Promise((resolve) => {
1135
- this.db.close((err) => {
1135
+ return new Promise((resolve, reject) => {
1136
+ this.db.close(guarded(reject, (err) => {
1136
1137
  if (err) {
1137
1138
  shlLog.error('Error closing SHL database:', err.message);
1138
1139
  } else {
1139
1140
  shlLog.info('SHL database connection closed');
1140
1141
  }
1141
1142
  resolve();
1142
- });
1143
+ }));
1143
1144
  });
1144
1145
  }
1145
1146
  }