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
@@ -295,8 +295,8 @@ textarea {
295
295
  line-height: inherit;
296
296
  }
297
297
  a {
298
- color: #428bca;
299
- text-decoration: none;
298
+ color: #1f4465;
299
+ text-decoration: underline;
300
300
  }
301
301
  a:hover,
302
302
  a:focus {
@@ -349,19 +349,19 @@ cite {
349
349
  font-style: normal;
350
350
  }
351
351
  .text-muted {
352
- color: #999999;
352
+ color: #727272;
353
353
  }
354
354
  .text-primary {
355
- color: #428bca;
355
+ color: #2c6597;
356
356
  }
357
357
  .text-warning {
358
- color: #c09853;
358
+ color: #92681e;
359
359
  }
360
360
  .text-danger {
361
361
  color: #b94a48;
362
362
  }
363
363
  .text-success {
364
- color: #468847;
364
+ color: #356735;
365
365
  }
366
366
  .text-info {
367
367
  color: #3a87ad;
@@ -1463,7 +1463,7 @@ fieldset[disabled] .btn-default.active {
1463
1463
  }
1464
1464
  .btn-primary {
1465
1465
  color: #ffffff;
1466
- background-color: #428bca;
1466
+ background-color: #2c618f;
1467
1467
  border-color: #428bca;
1468
1468
  }
1469
1469
  .btn-primary:hover,
@@ -2853,7 +2853,7 @@ button.close {
2853
2853
  }
2854
2854
  .pagination > .active > a,
2855
2855
  .pagination > .active > span {
2856
- color: #999999;
2856
+ color: #565656;
2857
2857
  cursor: default;
2858
2858
  }
2859
2859
  .pagination > .disabled > span,
@@ -3310,7 +3310,7 @@ button.close {
3310
3310
  .alert {
3311
3311
  padding: 10px 35px 10px 15px;
3312
3312
  margin-bottom: 20px;
3313
- color: #c09853;
3313
+ color: #872929;
3314
3314
  background-color: #fcf8e3;
3315
3315
  border: 1px solid #fbeed5;
3316
3316
  border-radius: 4px;
@@ -3344,9 +3344,9 @@ button.close {
3344
3344
  color: #356635;
3345
3345
  }
3346
3346
  .alert-danger {
3347
- background-color: #f2dede;
3347
+ background-color: #fcebeb;
3348
3348
  border-color: #eed3d7;
3349
- color: #b94a48;
3349
+ color: #6c2322;
3350
3350
  }
3351
3351
  .alert-danger hr {
3352
3352
  border-top-color: #e6c1c7;
@@ -3355,9 +3355,9 @@ button.close {
3355
3355
  color: #953b39;
3356
3356
  }
3357
3357
  .alert-info {
3358
- background-color: #d9edf7;
3358
+ background-color: #fff7e6;
3359
3359
  border-color: #bce8f1;
3360
- color: #3a87ad;
3360
+ color: #2a6785;
3361
3361
  }
3362
3362
  .alert-info hr {
3363
3363
  border-top-color: #a6e1ec;
@@ -3445,7 +3445,7 @@ a.thumbnail:focus {
3445
3445
  text-align: center;
3446
3446
  white-space: nowrap;
3447
3447
  vertical-align: baseline;
3448
- background-color: #999999;
3448
+ background-color: #6f6f6f;
3449
3449
  border-radius: .25em;
3450
3450
  }
3451
3451
  .label[href]:hover,
@@ -3494,7 +3494,7 @@ a.thumbnail:focus {
3494
3494
  vertical-align: baseline;
3495
3495
  white-space: nowrap;
3496
3496
  text-align: center;
3497
- background-color: #999999;
3497
+ background-color: #6d6d6d;
3498
3498
  border-radius: 10px;
3499
3499
  }
3500
3500
  .badge:empty {
@@ -0,0 +1,18 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10080 5040" width="10080" height="5040">
2
+ <title>Australia</title>
3
+ <defs><clipPath id="s"><path d="M0,0 v30 h60 v-30 z"/></clipPath><clipPath id="t"><path d="M30,15 h30 v15 z v15 h-30 z h-30 v-15 z v-15 h30 z"/></clipPath></defs>
4
+ <rect width="10080" height="5040" fill="#012169"/>
5
+ <svg width="5040" height="2520" viewBox="0 0 60 30"><g clip-path="url(#s)">
6
+ <path d="M0,0 L60,30 M60,0 L0,30" stroke="#fff" stroke-width="6"/>
7
+ <path d="M0,0 L60,30 M60,0 L0,30" clip-path="url(#t)" stroke="#C8102E" stroke-width="4"/>
8
+ <path d="M30,0 v30 M0,15 h60" stroke="#fff" stroke-width="10"/>
9
+ <path d="M30,0 v30 M0,15 h60" stroke="#C8102E" stroke-width="6"/>
10
+ </g></svg>
11
+ <g fill="#fff">
12
+ <polygon points="2520.0,2700.0 2728.3,3347.5 3364.4,3106.6 2988.0,3673.2 3572.9,4020.3 2895.3,4079.3 2988.6,4753.0 2520.0,4260.0 2051.4,4753.0 2144.7,4079.3 1467.1,4020.3 2052.0,3673.2 1675.6,3106.6 2311.7,3347.5"/>
13
+ <polygon points="7560.0,3720.0 7652.6,4007.8 7935.3,3900.7 7768.0,4152.5 8028.0,4306.8 7726.8,4333.0 7768.3,4632.5 7560.0,4413.3 7351.7,4632.5 7393.2,4333.0 7092.0,4306.8 7352.0,4152.5 7184.7,3900.7 7467.4,4007.8"/>
14
+ <polygon points="6300.0,1725.0 6392.6,2012.8 6675.3,1905.7 6508.0,2157.5 6768.0,2311.8 6466.8,2338.0 6508.3,2637.5 6300.0,2418.3 6091.7,2637.5 6133.2,2338.0 5832.0,2311.8 6092.0,2157.5 5924.7,1905.7 6207.4,2012.8"/>
15
+ <polygon points="7560.0,360.0 7652.6,647.8 7935.3,540.7 7768.0,792.5 8028.0,946.8 7726.8,973.0 7768.3,1272.5 7560.0,1053.3 7351.7,1272.5 7393.2,973.0 7092.0,946.8 7352.0,792.5 7184.7,540.7 7467.4,647.8"/>
16
+ <polygon points="8680.0,1389.0 8772.6,1676.8 9055.3,1569.7 8888.0,1821.5 9148.0,1975.8 8846.8,2002.0 8888.3,2301.5 8680.0,2082.3 8471.7,2301.5 8513.2,2002.0 8212.0,1975.8 8472.0,1821.5 8304.7,1569.7 8587.4,1676.8"/>
17
+ <polygon points="8064.0,2450.0 8129.8,2639.4 8330.3,2643.5 8170.5,2764.6 8228.6,2956.5 8064.0,2842.0 7899.4,2956.5 7957.5,2764.6 7797.7,2643.5 7998.2,2639.4"/>
18
+ </g></svg>
@@ -0,0 +1,11 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
2
+ <title>Nationality unknown</title>
3
+ <g fill="none" stroke="#000" stroke-width="2.5">
4
+ <circle cx="32" cy="32" r="28"/>
5
+ <ellipse cx="32" cy="32" rx="12" ry="28"/>
6
+ <line x1="32" y1="4" x2="32" y2="60"/>
7
+ <line x1="4" y1="32" x2="60" y2="32"/>
8
+ <path d="M8.5 18 Q32 24 55.5 18"/>
9
+ <path d="M8.5 46 Q32 40 55.5 46"/>
10
+ </g>
11
+ </svg>
@@ -0,0 +1,63 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 247 130" width="247" height="130">
2
+ <title>United States</title>
3
+ <rect width="247" height="130" fill="#fff"/>
4
+ <rect y="0.00" width="247" height="10.00" fill="#B22234"/>
5
+ <rect y="20.00" width="247" height="10.00" fill="#B22234"/>
6
+ <rect y="40.00" width="247" height="10.00" fill="#B22234"/>
7
+ <rect y="60.00" width="247" height="10.00" fill="#B22234"/>
8
+ <rect y="80.00" width="247" height="10.00" fill="#B22234"/>
9
+ <rect y="100.00" width="247" height="10.00" fill="#B22234"/>
10
+ <rect y="120.00" width="247" height="10.00" fill="#B22234"/>
11
+ <rect width="98.80" height="70.00" fill="#3C3B6E"/>
12
+ <g fill="#fff">
13
+ <polygon points="8.2,2.8 9.2,5.7 12.2,5.7 9.8,7.5 10.7,10.4 8.2,8.6 5.8,10.4 6.7,7.5 4.2,5.7 7.3,5.7"/>
14
+ <polygon points="24.7,2.8 25.6,5.7 28.7,5.7 26.2,7.5 27.2,10.4 24.7,8.6 22.2,10.4 23.2,7.5 20.7,5.7 23.8,5.7"/>
15
+ <polygon points="41.2,2.8 42.1,5.7 45.2,5.7 42.7,7.5 43.6,10.4 41.2,8.6 38.7,10.4 39.6,7.5 37.2,5.7 40.2,5.7"/>
16
+ <polygon points="57.6,2.8 58.6,5.7 61.6,5.7 59.2,7.5 60.1,10.4 57.6,8.6 55.2,10.4 56.1,7.5 53.6,5.7 56.7,5.7"/>
17
+ <polygon points="74.1,2.8 75.0,5.7 78.1,5.7 75.6,7.5 76.6,10.4 74.1,8.6 71.6,10.4 72.6,7.5 70.1,5.7 73.2,5.7"/>
18
+ <polygon points="90.6,2.8 91.5,5.7 94.6,5.7 92.1,7.5 93.0,10.4 90.6,8.6 88.1,10.4 89.0,7.5 86.6,5.7 89.6,5.7"/>
19
+ <polygon points="16.5,9.8 17.4,12.7 20.5,12.7 18.0,14.5 18.9,17.4 16.5,15.6 14.0,17.4 14.9,14.5 12.5,12.7 15.5,12.7"/>
20
+ <polygon points="32.9,9.8 33.9,12.7 36.9,12.7 34.5,14.5 35.4,17.4 32.9,15.6 30.5,17.4 31.4,14.5 28.9,12.7 32.0,12.7"/>
21
+ <polygon points="49.4,9.8 50.3,12.7 53.4,12.7 50.9,14.5 51.9,17.4 49.4,15.6 46.9,17.4 47.9,14.5 45.4,12.7 48.5,12.7"/>
22
+ <polygon points="65.9,9.8 66.8,12.7 69.9,12.7 67.4,14.5 68.3,17.4 65.9,15.6 63.4,17.4 64.3,14.5 61.9,12.7 64.9,12.7"/>
23
+ <polygon points="82.3,9.8 83.3,12.7 86.3,12.7 83.9,14.5 84.8,17.4 82.3,15.6 79.9,17.4 80.8,14.5 78.3,12.7 81.4,12.7"/>
24
+ <polygon points="8.2,16.8 9.2,19.7 12.2,19.7 9.8,21.5 10.7,24.4 8.2,22.6 5.8,24.4 6.7,21.5 4.2,19.7 7.3,19.7"/>
25
+ <polygon points="24.7,16.8 25.6,19.7 28.7,19.7 26.2,21.5 27.2,24.4 24.7,22.6 22.2,24.4 23.2,21.5 20.7,19.7 23.8,19.7"/>
26
+ <polygon points="41.2,16.8 42.1,19.7 45.2,19.7 42.7,21.5 43.6,24.4 41.2,22.6 38.7,24.4 39.6,21.5 37.2,19.7 40.2,19.7"/>
27
+ <polygon points="57.6,16.8 58.6,19.7 61.6,19.7 59.2,21.5 60.1,24.4 57.6,22.6 55.2,24.4 56.1,21.5 53.6,19.7 56.7,19.7"/>
28
+ <polygon points="74.1,16.8 75.0,19.7 78.1,19.7 75.6,21.5 76.6,24.4 74.1,22.6 71.6,24.4 72.6,21.5 70.1,19.7 73.2,19.7"/>
29
+ <polygon points="90.6,16.8 91.5,19.7 94.6,19.7 92.1,21.5 93.0,24.4 90.6,22.6 88.1,24.4 89.0,21.5 86.6,19.7 89.6,19.7"/>
30
+ <polygon points="16.5,23.8 17.4,26.7 20.5,26.7 18.0,28.5 18.9,31.4 16.5,29.6 14.0,31.4 14.9,28.5 12.5,26.7 15.5,26.7"/>
31
+ <polygon points="32.9,23.8 33.9,26.7 36.9,26.7 34.5,28.5 35.4,31.4 32.9,29.6 30.5,31.4 31.4,28.5 28.9,26.7 32.0,26.7"/>
32
+ <polygon points="49.4,23.8 50.3,26.7 53.4,26.7 50.9,28.5 51.9,31.4 49.4,29.6 46.9,31.4 47.9,28.5 45.4,26.7 48.5,26.7"/>
33
+ <polygon points="65.9,23.8 66.8,26.7 69.9,26.7 67.4,28.5 68.3,31.4 65.9,29.6 63.4,31.4 64.3,28.5 61.9,26.7 64.9,26.7"/>
34
+ <polygon points="82.3,23.8 83.3,26.7 86.3,26.7 83.9,28.5 84.8,31.4 82.3,29.6 79.9,31.4 80.8,28.5 78.3,26.7 81.4,26.7"/>
35
+ <polygon points="8.2,30.8 9.2,33.7 12.2,33.7 9.8,35.5 10.7,38.4 8.2,36.6 5.8,38.4 6.7,35.5 4.2,33.7 7.3,33.7"/>
36
+ <polygon points="24.7,30.8 25.6,33.7 28.7,33.7 26.2,35.5 27.2,38.4 24.7,36.6 22.2,38.4 23.2,35.5 20.7,33.7 23.8,33.7"/>
37
+ <polygon points="41.2,30.8 42.1,33.7 45.2,33.7 42.7,35.5 43.6,38.4 41.2,36.6 38.7,38.4 39.6,35.5 37.2,33.7 40.2,33.7"/>
38
+ <polygon points="57.6,30.8 58.6,33.7 61.6,33.7 59.2,35.5 60.1,38.4 57.6,36.6 55.2,38.4 56.1,35.5 53.6,33.7 56.7,33.7"/>
39
+ <polygon points="74.1,30.8 75.0,33.7 78.1,33.7 75.6,35.5 76.6,38.4 74.1,36.6 71.6,38.4 72.6,35.5 70.1,33.7 73.2,33.7"/>
40
+ <polygon points="90.6,30.8 91.5,33.7 94.6,33.7 92.1,35.5 93.0,38.4 90.6,36.6 88.1,38.4 89.0,35.5 86.6,33.7 89.6,33.7"/>
41
+ <polygon points="16.5,37.8 17.4,40.7 20.5,40.7 18.0,42.5 18.9,45.4 16.5,43.6 14.0,45.4 14.9,42.5 12.5,40.7 15.5,40.7"/>
42
+ <polygon points="32.9,37.8 33.9,40.7 36.9,40.7 34.5,42.5 35.4,45.4 32.9,43.6 30.5,45.4 31.4,42.5 28.9,40.7 32.0,40.7"/>
43
+ <polygon points="49.4,37.8 50.3,40.7 53.4,40.7 50.9,42.5 51.9,45.4 49.4,43.6 46.9,45.4 47.9,42.5 45.4,40.7 48.5,40.7"/>
44
+ <polygon points="65.9,37.8 66.8,40.7 69.9,40.7 67.4,42.5 68.3,45.4 65.9,43.6 63.4,45.4 64.3,42.5 61.9,40.7 64.9,40.7"/>
45
+ <polygon points="82.3,37.8 83.3,40.7 86.3,40.7 83.9,42.5 84.8,45.4 82.3,43.6 79.9,45.4 80.8,42.5 78.3,40.7 81.4,40.7"/>
46
+ <polygon points="8.2,44.8 9.2,47.7 12.2,47.7 9.8,49.5 10.7,52.4 8.2,50.6 5.8,52.4 6.7,49.5 4.2,47.7 7.3,47.7"/>
47
+ <polygon points="24.7,44.8 25.6,47.7 28.7,47.7 26.2,49.5 27.2,52.4 24.7,50.6 22.2,52.4 23.2,49.5 20.7,47.7 23.8,47.7"/>
48
+ <polygon points="41.2,44.8 42.1,47.7 45.2,47.7 42.7,49.5 43.6,52.4 41.2,50.6 38.7,52.4 39.6,49.5 37.2,47.7 40.2,47.7"/>
49
+ <polygon points="57.6,44.8 58.6,47.7 61.6,47.7 59.2,49.5 60.1,52.4 57.6,50.6 55.2,52.4 56.1,49.5 53.6,47.7 56.7,47.7"/>
50
+ <polygon points="74.1,44.8 75.0,47.7 78.1,47.7 75.6,49.5 76.6,52.4 74.1,50.6 71.6,52.4 72.6,49.5 70.1,47.7 73.2,47.7"/>
51
+ <polygon points="90.6,44.8 91.5,47.7 94.6,47.7 92.1,49.5 93.0,52.4 90.6,50.6 88.1,52.4 89.0,49.5 86.6,47.7 89.6,47.7"/>
52
+ <polygon points="16.5,51.8 17.4,54.7 20.5,54.7 18.0,56.5 18.9,59.4 16.5,57.6 14.0,59.4 14.9,56.5 12.5,54.7 15.5,54.7"/>
53
+ <polygon points="32.9,51.8 33.9,54.7 36.9,54.7 34.5,56.5 35.4,59.4 32.9,57.6 30.5,59.4 31.4,56.5 28.9,54.7 32.0,54.7"/>
54
+ <polygon points="49.4,51.8 50.3,54.7 53.4,54.7 50.9,56.5 51.9,59.4 49.4,57.6 46.9,59.4 47.9,56.5 45.4,54.7 48.5,54.7"/>
55
+ <polygon points="65.9,51.8 66.8,54.7 69.9,54.7 67.4,56.5 68.3,59.4 65.9,57.6 63.4,59.4 64.3,56.5 61.9,54.7 64.9,54.7"/>
56
+ <polygon points="82.3,51.8 83.3,54.7 86.3,54.7 83.9,56.5 84.8,59.4 82.3,57.6 79.9,59.4 80.8,56.5 78.3,54.7 81.4,54.7"/>
57
+ <polygon points="8.2,58.8 9.2,61.7 12.2,61.7 9.8,63.5 10.7,66.4 8.2,64.6 5.8,66.4 6.7,63.5 4.2,61.7 7.3,61.7"/>
58
+ <polygon points="24.7,58.8 25.6,61.7 28.7,61.7 26.2,63.5 27.2,66.4 24.7,64.6 22.2,66.4 23.2,63.5 20.7,61.7 23.8,61.7"/>
59
+ <polygon points="41.2,58.8 42.1,61.7 45.2,61.7 42.7,63.5 43.6,66.4 41.2,64.6 38.7,66.4 39.6,63.5 37.2,61.7 40.2,61.7"/>
60
+ <polygon points="57.6,58.8 58.6,61.7 61.6,61.7 59.2,63.5 60.1,66.4 57.6,64.6 55.2,66.4 56.1,63.5 53.6,61.7 56.7,61.7"/>
61
+ <polygon points="74.1,58.8 75.0,61.7 78.1,61.7 75.6,63.5 76.6,66.4 74.1,64.6 71.6,66.4 72.6,63.5 70.1,61.7 73.2,61.7"/>
62
+ <polygon points="90.6,58.8 91.5,61.7 94.6,61.7 92.1,63.5 93.0,66.4 90.6,64.6 88.1,66.4 89.0,63.5 86.6,61.7 89.6,61.7"/>
63
+ </g></svg>
package/stats-db.js CHANGED
@@ -93,6 +93,29 @@ class StatsDatabase {
93
93
  }
94
94
  this.touchRunStatement = this.db.prepare(`UPDATE server_runs SET last_seen = ? WHERE id = ?`);
95
95
 
96
+ // Under the launcher (cluster.js): what it saw - processes exiting, being recycled and
97
+ // coming back, and what it answered itself. See library/cluster-events.js
98
+ this.db.exec(`
99
+ CREATE TABLE IF NOT EXISTS cluster_events (
100
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
101
+ time INTEGER NOT NULL,
102
+ kind TEXT NOT NULL,
103
+ process TEXT,
104
+ detail TEXT,
105
+ count INTEGER NOT NULL,
106
+ ms INTEGER NOT NULL
107
+ )
108
+ `);
109
+ this.db.exec(`CREATE INDEX IF NOT EXISTS idx_cluster_events_time ON cluster_events(time)`);
110
+ this.insertClusterEvent = this.db.prepare(`
111
+ INSERT INTO cluster_events (time, kind, process, detail, count, ms) VALUES (?, ?, ?, ?, ?, ?)
112
+ `);
113
+ this.insertClusterEvents = this.db.transaction((rows) => {
114
+ for (const row of rows) {
115
+ this.insertClusterEvent.run(row.time, row.kind, row.process, row.detail, row.count, row.ms);
116
+ }
117
+ });
118
+
96
119
  this.insertStatement = this.db.prepare(`
97
120
  INSERT INTO request_counts
98
121
  (time, interval_ms, module, endpoint, operation, count_delta, count_total, time_delta, time_total)
@@ -199,6 +222,28 @@ class StatsDatabase {
199
222
  `).all(module, since, now);
200
223
  }
201
224
 
225
+ /**
226
+ * Record what the launcher saw (rows as library/cluster-events.js validRow gives them).
227
+ */
228
+ writeClusterEvents(rows) {
229
+ if (this.db && rows && rows.length > 0) {
230
+ this.insertClusterEvents(rows);
231
+ }
232
+ }
233
+
234
+ /**
235
+ * What the launcher saw in (since, now], oldest first: [{time, kind, process, detail, count, ms}].
236
+ */
237
+ clusterEventsSince(since, now) {
238
+ if (!this.db) {
239
+ return [];
240
+ }
241
+ return this.db.prepare(`
242
+ SELECT time, kind, process, detail, count, ms FROM cluster_events
243
+ WHERE time > ? AND time <= ? ORDER BY time, id
244
+ `).all(since, now);
245
+ }
246
+
202
247
  /**
203
248
  * Write one interval's worth of rows. All or nothing - if the transaction
204
249
  * fails the caller keeps its deltas and tries again next interval.
package/stats.js CHANGED
@@ -2,6 +2,8 @@ const { monitorEventLoopDelay } = require('perf_hooks');
2
2
  const {Utilities} = require("./library/utilities");
3
3
  const escape = require('escape-html');
4
4
  const StatsDatabase = require('./stats-db');
5
+ const { validRow: validClusterEvent } = require('./library/cluster-events');
6
+ const { readProcessMemory } = require('./library/cgroup-memory');
5
7
 
6
8
  // Requests that aren't counted by a module (the root page, the dashboard) are
7
9
  // attributed here, and requests that have no endpoint dimension (everything
@@ -30,6 +32,10 @@ class ModuleStats {
30
32
  this.parent.countRequest(name, tat, this.module, endpoint);
31
33
  }
32
34
 
35
+ countExternalRequest(name, tat, endpoint = NO_ENDPOINT) {
36
+ this.parent.countExternalRequest(name, tat, this.module, endpoint);
37
+ }
38
+
33
39
  addTask(name, frequency) { this.parent.addTask(name, frequency); }
34
40
  task(name, state) { this.parent.task(name, state); }
35
41
  taskDone(name, state) { this.parent.taskDone(name, state); }
@@ -58,11 +64,22 @@ class ServerStats {
58
64
  started = false;
59
65
  requestCount = 0;
60
66
  staticRequestCount = 0;
67
+ // on the master under the launcher: the workers' requests added by mergeCounts
68
+ mergedRequestCount = 0;
61
69
  requestTime = 0;
62
- // Collect metrics every 10 minutes
70
+ // requests that had to ask another server (see countExternalRequest): in requestCount, but
71
+ // not in requestTime, and sampled separately
72
+ externalCount = 0;
73
+ externalTime = 0;
74
+ externalCountSnapshot = 0;
75
+ // Collect metrics every 10 minutes (stats.sampleSeconds, else stats.intervalMinutes)
63
76
  intervalMs = 10 * 60 * 1000;
77
+ // and write the counters to the database every 10 minutes (stats.intervalMinutes)
78
+ flushMs = 10 * 60 * 1000;
64
79
  history = [];
65
80
  requestCountSnapshot = 0;
81
+ refusedCount = 0;
82
+ refusedCountSnapshot = 0;
66
83
  startMem = 0;
67
84
  startTime = Date.now();
68
85
  timer;
@@ -84,6 +101,12 @@ class ServerStats {
84
101
  this.log = log || console;
85
102
  if (this.config.intervalMinutes) {
86
103
  this.intervalMs = this.config.intervalMinutes * 60 * 1000;
104
+ this.flushMs = this.intervalMs;
105
+ }
106
+ // the graphs' sample interval, when it isn't the counters' (with a role, a minute at most:
107
+ // library/role.js)
108
+ if (this.config.sampleSeconds > 0) {
109
+ this.intervalMs = this.config.sampleSeconds * 1000;
87
110
  }
88
111
  this.openDatabase();
89
112
  this.timer = setInterval(() => {
@@ -129,11 +152,17 @@ class ServerStats {
129
152
  const currentMem = process.memoryUsage().heapUsed;
130
153
  const combinedCount = this.requestCount + this.staticRequestCount;
131
154
  const requestsDelta = combinedCount - this.requestCountSnapshot;
132
- const requestsTat = requestsDelta > 0 ? this.requestTime / requestsDelta : 0;
133
- const minutesSinceStart = this.history.length > 1
134
- ? this.intervalMs / 60000
135
- : (now - this.startTime) / 60000;
136
- const requestsPerMin = minutesSinceStart > 0 ? requestsDelta / minutesSinceStart : 0;
155
+ // the external requests are timed separately: they are orders of magnitude slower
156
+ const externalDelta = this.externalCount - this.externalCountSnapshot;
157
+ const ownDelta = requestsDelta - externalDelta;
158
+ const requestsTat = ownDelta > 0 ? this.requestTime / ownDelta : 0;
159
+ // Under load, the timer can run late. Use the actual time since the last sample, so
160
+ // an event-loop stall does not inflate the displayed request rate.
161
+ const intervalMinutes = (now - (this.lastTime ?? this.startTime)) / 60000;
162
+ const requestsPerMin = intervalMinutes > 0 ? requestsDelta / intervalMinutes : 0;
163
+ // where this sample's interval began, so the cluster graphs can place a late sample
164
+ const intervalStart = this.lastTime ?? this.startTime;
165
+ const refused = this.refusedCount - this.refusedCountSnapshot;
137
166
 
138
167
  const loopDelay = this.eventLoopMonitor.mean / 1e6;
139
168
  // Worst single stall in the window - the mean dilutes a multi-second
@@ -146,11 +175,30 @@ class ServerStats {
146
175
  // shows its current size.)
147
176
  const clientConcepts = this._sumCachingModules('clientConceptCount');
148
177
 
149
- this.history.push({time: now, mem: currentMem - this.startMem, rpm: requestsPerMin, tat: requestsTat, block: loopDelay, blockMax: loopMax, clientConcepts: clientConcepts});
178
+ const point = {time: now, mem: currentMem - this.startMem, rpm: requestsPerMin, tat: requestsTat, block: loopDelay, blockMax: loopMax, clientConcepts: clientConcepts};
179
+ // where the interval began, and this process's private memory (RssAnon: RSS less mapped
180
+ // files; null where /proc can't be read), for the graphs (library/cluster-metrics.js)
181
+ point.intervalStart = intervalStart;
182
+ // the external requests in the interval, and their mean turnaround (null: none)
183
+ point.ext = externalDelta;
184
+ point.extTat = externalDelta > 0 ? this.externalTime / externalDelta : null;
185
+ const own = readProcessMemory('self');
186
+ point.anon = own ? own.anon : null;
187
+ if (this.onMetric) {
188
+ // under the launcher: the memory guard's refusals, which only a role counts
189
+ point.refused = refused;
190
+ }
191
+ this.history.push(point);
192
+ if (this.onMetric) {
193
+ this.onMetric(point);
194
+ }
150
195
 
151
196
  this.eventLoopMonitor.reset();
152
197
  this.requestCountSnapshot = combinedCount;
198
+ this.refusedCountSnapshot = this.refusedCount;
153
199
  this.requestTime = 0;
200
+ this.externalCountSnapshot = this.externalCount;
201
+ this.externalTime = 0;
154
202
  this.lastTime = now;
155
203
 
156
204
  // Prune old data (keep 24 hours). Only the in-memory history is pruned -
@@ -158,7 +206,11 @@ class ServerStats {
158
206
  const cutoff = now - (24 * 60 * 60 * 1000); // 24 hours ago
159
207
  this.history = this.history.filter(m => m.time > cutoff);
160
208
 
161
- this.flushCounters(now, this.intervalMs);
209
+ // the counters every flushMs: at each sample when the two intervals are the same, as
210
+ // before, else at the first sample after (half a sample's grace for a timer that fires early)
211
+ if (this.lastFlush === undefined || now - this.lastFlush >= this.flushMs - this.intervalMs / 2) {
212
+ this.flushCounters(now, this.flushMs);
213
+ }
162
214
  }
163
215
  }
164
216
 
@@ -291,6 +343,119 @@ class ServerStats {
291
343
  counter.timeTotal = counter.timeTotal + tat;
292
344
  }
293
345
 
346
+ /**
347
+ * Count one request whose answer had to ask another server (the tx module's ecosystem
348
+ * resolution: tx/ecosystem-resolver.js). These take seconds, not milliseconds, so they are
349
+ * kept out of the turnaround time graph and sampled separately (point.ext, point.extTat),
350
+ * and counted under their own operation, '<name> (external)'
351
+ */
352
+ countExternalRequest(name, tat, module = SERVER_MODULE, endpoint = NO_ENDPOINT) {
353
+ this.requestCount++;
354
+ this.externalCount++;
355
+ this.externalTime = this.externalTime + tat;
356
+
357
+ const counter = this.counterFor(module || SERVER_MODULE, endpoint || NO_ENDPOINT, (name || '(unnamed)') + ' (external)');
358
+ counter.countDelta++;
359
+ counter.countTotal++;
360
+ counter.timeDelta = counter.timeDelta + tat;
361
+ counter.timeTotal = counter.timeTotal + tat;
362
+ }
363
+
364
+ // The memory guard runs before routing and body parsing. Keep its refusals
365
+ // separate from completed module work and its turnaround-time denominator.
366
+ countRefusedRequest() {
367
+ this.refusedCount++;
368
+ }
369
+
370
+ /**
371
+ * For a tx-worker under the launcher (library/cluster-counts.js): the requests counted
372
+ * since the last call, [{module, endpoint, operation, count, time}], and start again
373
+ * from zero. A worker has no statistics database; the master merges these into its
374
+ * own counters (mergeCounts), so the master's database and daily report count every
375
+ * process, each request once.
376
+ */
377
+ takeCounts() {
378
+ const rows = [];
379
+ for (const counter of this.counters.values()) {
380
+ if (counter.countDelta > 0) {
381
+ rows.push({module: counter.module, endpoint: counter.endpoint, operation: counter.operation,
382
+ count: counter.countDelta, time: counter.timeDelta});
383
+ counter.countDelta = 0;
384
+ counter.timeDelta = 0;
385
+ }
386
+ }
387
+ return rows;
388
+ }
389
+
390
+ /** Put back what takeCounts gave, when it couldn't be sent: it goes with the next lot. */
391
+ restoreCounts(rows) {
392
+ for (const row of rows) {
393
+ const counter = this.counterFor(row.module, row.endpoint, row.operation);
394
+ counter.countDelta = counter.countDelta + row.count;
395
+ counter.timeDelta = counter.timeDelta + row.time;
396
+ }
397
+ }
398
+
399
+ /**
400
+ * On the master: add another process's requests (from its takeCounts) to the counters,
401
+ * as if this process had counted them. They are written out with the next interval and
402
+ * are in countsSince() at once. Rows that aren't well formed are ignored.
403
+ * @returns {number} the requests added
404
+ */
405
+ mergeCounts(rows) {
406
+ let added = 0;
407
+ for (const row of Array.isArray(rows) ? rows : []) {
408
+ if (!row || typeof row.module !== 'string' || typeof row.endpoint !== 'string' ||
409
+ typeof row.operation !== 'string' || !Number.isInteger(row.count) || row.count <= 0 ||
410
+ !Number.isFinite(row.time) || row.time < 0) {
411
+ continue;
412
+ }
413
+ const counter = this.counterFor(row.module, row.endpoint, row.operation);
414
+ counter.countDelta = counter.countDelta + row.count;
415
+ counter.countTotal = counter.countTotal + row.count;
416
+ counter.timeDelta = counter.timeDelta + row.time;
417
+ counter.timeTotal = counter.timeTotal + row.time;
418
+ added = added + row.count;
419
+ }
420
+ this.mergedRequestCount = this.mergedRequestCount + added;
421
+ return added;
422
+ }
423
+
424
+ /**
425
+ * On the master under the launcher: what the launcher saw (library/cluster-events.js),
426
+ * written straight to the database - there are few, and the daily report reads them from
427
+ * there. Rows that aren't well formed are ignored; without a database, all of them are.
428
+ * @returns {number} the rows written
429
+ */
430
+ recordClusterEvents(rows) {
431
+ if (!this.db) {
432
+ return 0;
433
+ }
434
+ const valid = (Array.isArray(rows) ? rows : []).map(validClusterEvent).filter(Boolean);
435
+ try {
436
+ this.db.writeClusterEvents(valid);
437
+ return valid.length;
438
+ } catch (e) {
439
+ this.log.error(`Unable to record the launcher's events: ${e.message}`);
440
+ return 0;
441
+ }
442
+ }
443
+
444
+ /**
445
+ * What the launcher saw in (since, now], oldest first, or null without a database.
446
+ */
447
+ clusterEventsSince(since, now = Date.now()) {
448
+ if (!this.db) {
449
+ return null;
450
+ }
451
+ try {
452
+ return this.db.clusterEventsSince(since, now);
453
+ } catch (e) {
454
+ this.log.error(`Unable to read the launcher's events: ${e.message}`);
455
+ return null;
456
+ }
457
+ }
458
+
294
459
  /**
295
460
  * Write out the counters that saw traffic this interval, and start the next
296
461
  * one. The deltas are only cleared if the write succeeded, so a transient
@@ -404,6 +569,28 @@ class ServerStats {
404
569
  }
405
570
  }
406
571
 
572
+ /**
573
+ * The background tasks for the status page: those at work, in full
574
+ * ([{name, state, frequency, date, ago}]); the names of those whose last run failed (the status
575
+ * line lists them, with their state); and the names of the rest (resting, or not run yet)
576
+ */
577
+ taskSummary(now = Date.now()) {
578
+ const working = [];
579
+ const failed = [];
580
+ const idle = [];
581
+ for (const [name, info] of this.taskMap) {
582
+ if (info.status === 'working') {
583
+ working.push({name: name, state: info.state, frequency: info.frequency, date: info.date,
584
+ ago: info.date ? Utilities.formatDuration(info.date, now) : ''});
585
+ } else if (info.status === 'error') {
586
+ failed.push(name);
587
+ } else {
588
+ idle.push(name);
589
+ }
590
+ }
591
+ return { working, failed, idle };
592
+ }
593
+
407
594
  /**
408
595
  * The background tasks whose last run failed: [{name, state, frequency, date}]
409
596
  */
@@ -449,7 +636,7 @@ class ServerStats {
449
636
  this.touchRun();
450
637
  // Synchronous, so the last partial interval is on disk before server.js
451
638
  // calls process.exit().
452
- this.flushCounters(Date.now(), Date.now() - (this.lastTime || this.startTime));
639
+ this.flushCounters(Date.now(), Date.now() - (this.lastFlush || this.lastTime || this.startTime));
453
640
  if (this.db) {
454
641
  try {
455
642
  this.db.close();
@@ -503,4 +690,4 @@ class ServerStats {
503
690
  }
504
691
  module.exports = ServerStats;
505
692
  module.exports.ModuleStats = ModuleStats;
506
- module.exports.SERVER_MODULE = SERVER_MODULE;
693
+ module.exports.SERVER_MODULE = SERVER_MODULE;
@@ -55,8 +55,6 @@
55
55
  <div class="container"> <!-- container -->
56
56
  <div style="background-color: #ad1f2f; padding: 6px; color: white;"> <!-- container -->
57
57
 
58
- <a href="http://www.hl7.org/fhir" style="color: gold" title="Fast Healthcare Interoperability Resources - Home Page"><img border="0" src="/icon-fhir-16.png" style="vertical-align: text-bottom"/> <b>FHIR</b></a> &copy; HL7.org &nbsp;|&nbsp;
59
- <a href="https://github.com/HealthIntersections/FHIRsmith/blob/main/README.md" style="color: gold"><img border="0" src="/FHIRsmith16.png" style="vertical-align: text-bottom"/> FHIRsmith</a> [%ver%] &nbsp;|&nbsp;
60
58
  <a href="/" style="color: gold"> Server Home</a> &nbsp;|&nbsp;
61
59
  <a href="/testing" style="color: gold">Test Reports</a> &nbsp;|&nbsp;
62
60
  <a href="/testing/summary" style="color: gold">Summary</a> &nbsp;|&nbsp;
@@ -73,7 +71,7 @@
73
71
  <div class="inner-wrapper">
74
72
  <div class="col-12">
75
73
 
76
- <h2><img border="0" src="/FHIRsmith32.png" style="vertical-align: text-bottom"/> [%title%] </h2>
74
+ <h2><a href="https://github.com/HealthIntersections/FHIRsmith/blob/main/README.md" title="FHIRsmith [%ver%]"><img border="0" src="/FHIRsmith32.png" alt="FHIRsmith Logo" style="vertical-align: text-bottom"/></a> [%title%] </h2>
77
75
 
78
76
  [%content%]
79
77
 
@@ -91,8 +89,7 @@
91
89
  <div class="container"> <!-- container -->
92
90
  <div class="inner-wrapper">
93
91
  <p>
94
- <a href="http://www.hl7.org/fhir" style="color: gold" title="Fast Healthcare Interoperability Resources - Home Page"><img border="0" src="/icon-fhir-16.png" style="vertical-align: text-bottom"/> <b>FHIR</b></a> &copy; HL7.org 2011+. &nbsp;|&nbsp;
95
- <a href="https://github.com/HealthIntersections/FHIRsmith/blob/main/README.md" style="color: gold"><img border="0" src="/FHIRsmith16.png" style="vertical-align: text-bottom"/> FHIRsmith</a> [%ver%] &copy; HealthIntersections.com.au 2023+ &nbsp;|&nbsp;
92
+ <a href="https://github.com/HealthIntersections/FHIRsmith/blob/main/README.md" style="color: gold"><img border="0" src="/FHIRsmith16.png" alt="FHIRsmith Logo" style="vertical-align: text-bottom"/> FHIRsmith</a> [%ver%] &copy; HealthIntersections.com.au 2023+ &nbsp;|&nbsp;
96
93
  &nbsp; ([%ms%] ms)
97
94
  [%sponsorMessage%]
98
95
  </p>
@@ -115,10 +112,10 @@
115
112
  ================================================== -->
116
113
  <!-- Placed at the end of the document so the pages load faster -->
117
114
  <!-- <script src="/assets/js/jquery.js"/> -->
118
- <script src="/assets/js/bootstrap.min.js"/>
119
- <script src="/assets/js/respond.min.js"/>
115
+ <script src="/assets/js/bootstrap.min.js"></script>
116
+ <script src="/assets/js/respond.min.js"></script>
120
117
 
121
- <script src="/assets/js/fhir.js"/>
118
+ <script src="/assets/js/fhir.js"></script>
122
119
 
123
120
  <!-- Analytics Below
124
121
  ================================================== -->
@@ -48,7 +48,7 @@ initializeSession() {
48
48
  rolling: true,
49
49
  cookie: {
50
50
  // This is key - secure cookies when behind HTTPS proxy
51
- secure: this.config.server?.httpsProxy || process.env.NODE_ENV === 'production',
51
+ secure: this.config.cookieSecure ?? 'auto', // Secure when the request came over https (req.secure)
52
52
  httpOnly: true,
53
53
  maxAge: 24 * 60 * 60 * 1000,
54
54
  sameSite: 'lax'
@@ -17,7 +17,7 @@ This guide covers the security considerations and proper implementation of Passp
17
17
  saveUninitialized: false, // Don't create sessions until something stored
18
18
  rolling: true, // Reset expiration on each request
19
19
  cookie: {
20
- secure: NODE_ENV === 'production', // HTTPS only in production
20
+ secure: this.config.cookieSecure ?? 'auto', // Secure when the request came over https (req.secure)
21
21
  httpOnly: true, // Prevent XSS access to cookies
22
22
  maxAge: 24 * 60 * 60 * 1000, // 24 hour expiration
23
23
  sameSite: 'lax' // CSRF protection