fhirsmith 0.16.1 → 0.18.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 (142) hide show
  1. package/CHANGELOG.md +130 -0
  2. package/README.md +105 -30
  3. package/config-template.json +130 -4
  4. package/extension-tracker/extension-tracker.js +111 -3
  5. package/extension-tracker/readme.md +3 -0
  6. package/ips/ai-client.js +263 -0
  7. package/ips/composer.js +805 -0
  8. package/ips/editor-schema.js +289 -0
  9. package/ips/editor.js +290 -0
  10. package/ips/fhirpath-ips.js +397 -0
  11. package/ips/ips-codeable-concepts.md +1742 -0
  12. package/ips/ips-template.html +125 -0
  13. package/ips/ips-valuesets.js +159 -0
  14. package/ips/ips.js +1736 -0
  15. package/ips/membership.js +179 -0
  16. package/ips/pipeline.js +1012 -0
  17. package/ips/presenter.js +974 -0
  18. package/ips/profile-editor.js +578 -0
  19. package/ips/profiles.js +506 -0
  20. package/ips/readme.md +460 -0
  21. package/ips/render.js +1482 -0
  22. package/ips/search-params.js +83 -0
  23. package/ips/search.js +667 -0
  24. package/ips/share.js +178 -0
  25. package/ips/store.js +650 -0
  26. package/ips/terminology.js +395 -0
  27. package/ips/tx-client.js +202 -0
  28. package/ips/valuesets/CodeSystem-absent-unknown-uv-ips.json +206 -0
  29. package/ips/valuesets/ValueSet-absent-or-unknown-immunizations-uv-ips.json +59 -0
  30. package/ips/valuesets/ValueSet-ips-allergy-code.json +59 -0
  31. package/ips/valuesets/ValueSet-ips-allergy-manifestation.json +356 -0
  32. package/ips/valuesets/ValueSet-ips-allergy-substance.json +37 -0
  33. package/ips/valuesets/ValueSet-ips-body-site.json +26 -0
  34. package/ips/valuesets/ValueSet-ips-clinical-finding-or-none.json +35 -0
  35. package/ips/valuesets/ValueSet-ips-clinical-finding.json +26 -0
  36. package/ips/valuesets/ValueSet-ips-condition-code.json +72 -0
  37. package/ips/valuesets/ValueSet-ips-condition-severity.json +30 -0
  38. package/ips/valuesets/ValueSet-ips-condition-stage.json +26 -0
  39. package/ips/valuesets/ValueSet-ips-exposure-route.json +26 -0
  40. package/ips/valuesets/ValueSet-ips-imaging-procedure.json +25 -0
  41. package/ips/valuesets/ValueSet-ips-immunization-status-reason.json +43 -0
  42. package/ips/valuesets/ValueSet-ips-international-codes.json +25 -0
  43. package/ips/valuesets/ValueSet-ips-medication-ingredient.json +26 -0
  44. package/ips/valuesets/ValueSet-ips-medication-status-reason.json +37 -0
  45. package/ips/valuesets/ValueSet-ips-medication.json +50 -0
  46. package/ips/valuesets/ValueSet-ips-observation-code.json +18 -0
  47. package/ips/valuesets/ValueSet-ips-observation-interpretation.json +18 -0
  48. package/ips/valuesets/ValueSet-ips-procedure-followup.json +51 -0
  49. package/ips/valuesets/ValueSet-ips-procedure-outcome.json +30 -0
  50. package/ips/valuesets/ValueSet-ips-procedure-reason.json +37 -0
  51. package/ips/valuesets/ValueSet-ips-procedure.json +116 -0
  52. package/ips/valuesets/ValueSet-ips-route.json +46 -0
  53. package/ips/valuesets/ValueSet-ips-target-disease.json +171 -0
  54. package/ips/valuesets/ValueSet-ips-vaccine.json +31 -0
  55. package/ips/valuesets/elements.json +44 -0
  56. package/library/daily-report.js +422 -0
  57. package/library/fhir-search-values.js +138 -0
  58. package/library/html-server.js +32 -23
  59. package/library/html.js +9 -0
  60. package/library/mailer.js +77 -0
  61. package/library/narrative-check.js +222 -0
  62. package/library/npm-audit.js +25 -0
  63. package/library/package-manager.js +4 -2
  64. package/library/sqlite-session-store.js +29 -1
  65. package/package.json +8 -2
  66. package/packages/package-crawler.js +189 -82
  67. package/packages/packages.js +36 -63
  68. package/packages/utilities/dedupe-versions.js +22 -8
  69. package/publisher/publisher.js +29 -0
  70. package/registry/auth-store.js +366 -0
  71. package/registry/auth.js +872 -0
  72. package/registry/crawler.js +48 -1
  73. package/registry/documentation/api.md +162 -0
  74. package/registry/documentation/configuration.md +93 -0
  75. package/registry/documentation/documents.md +74 -0
  76. package/registry/documentation/internals.md +151 -0
  77. package/registry/documentation/readme.md +100 -0
  78. package/registry/documentation/security.md +199 -0
  79. package/registry/documents.js +309 -0
  80. package/registry/openapi.yaml +30 -0
  81. package/registry/registry-template.html +1 -1
  82. package/registry/registry.js +247 -54
  83. package/registry/uptime.js +154 -0
  84. package/release.sh +80 -0
  85. package/server.js +105 -44
  86. package/shl/shl.js +64 -9
  87. package/stats-db.js +93 -0
  88. package/stats.js +158 -1
  89. package/testing/readme.md +1 -1
  90. package/testing/search.js +2 -108
  91. package/testing/store.js +5 -0
  92. package/testing/testing.js +20 -0
  93. package/token/token-template.html +5 -4
  94. package/token/token.js +17 -10
  95. package/translations/Messages.properties +9 -0
  96. package/tx/README.md +52 -0
  97. package/tx/cm/cm-database.js +62 -0
  98. package/tx/cm/cm-package.js +20 -3
  99. package/tx/cs/cs-api.js +90 -14
  100. package/tx/cs/cs-cs.js +142 -19
  101. package/tx/cs/cs-icd11.js +366 -112
  102. package/tx/cs/cs-icf.js +418 -0
  103. package/tx/cs/cs-loinc.js +79 -39
  104. package/tx/cs/cs-omop.js +10 -3
  105. package/tx/cs/cs-snomed.js +918 -55
  106. package/tx/cs/cs-ucum.js +6 -0
  107. package/tx/cs/icd11-expressions.js +191 -78
  108. package/tx/importers/export-icd11-codesystem.js +58 -12
  109. package/tx/importers/icd11-schema.md +9 -5
  110. package/tx/library.js +107 -6
  111. package/tx/ocl/README.md +62 -6
  112. package/tx/ocl/cm-ocl.cjs +166 -44
  113. package/tx/ocl/cs-ocl.cjs +163 -13
  114. package/tx/ocl/resolve/public-access.js +143 -0
  115. package/tx/ocl/resolve/reference-resolver.js +458 -0
  116. package/tx/ocl/vs-ocl.cjs +167 -4
  117. package/tx/openapi-operations.js +4 -0
  118. package/tx/params.js +8 -1
  119. package/tx/problems.js +7 -5
  120. package/tx/provider.js +21 -5
  121. package/tx/sct/documentation.md +251 -0
  122. package/tx/sct/ecl.js +758 -46
  123. package/tx/sct/expressions.js +113 -52
  124. package/tx/syndication/syndication-feed.js +218 -0
  125. package/tx/syndication/syndication-store.js +415 -0
  126. package/tx/tx-html.js +37 -8
  127. package/tx/tx.fhir.org.yml +2 -3
  128. package/tx/tx.js +148 -5
  129. package/tx/vs/vs-database.js +26 -0
  130. package/tx/vs/vs-package.js +42 -10
  131. package/tx/vs/vs-vsac.js +25 -6
  132. package/tx/workers/expand.js +34 -7
  133. package/tx/workers/lookup.js +61 -6
  134. package/tx/workers/metadata.js +6 -1
  135. package/tx/workers/subsumes.js +1 -1
  136. package/tx/workers/translate.js +166 -15
  137. package/tx/workers/validate.js +128 -6
  138. package/utilities/make-sct-test-extension.js +264 -0
  139. package/utilities/submit-test-report.js +1 -1
  140. package/xig/xig.js +120 -12
  141. package/gr.sh +0 -3
  142. package/registry/readme.md +0 -124
package/CHANGELOG.md CHANGED
@@ -5,6 +5,136 @@ All notable changes to Health Intersections FHIRsmith will be documented in this
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.18.0] - 2026-10-23
9
+
10
+ A big release: a new IPS module (prototype), a secured and documented registry with uptime reporting, a syndication feed for the terminology server, a daily email report, ICF qualified codes, SNOMED CT edition to edition translation and much more ECL, and the ICD-11 representation agreed at the October 2026 test-case review meetings. Plus the outcome of security reviews of the registry, IPS and token modules. The README now says what state each module is in.
11
+
12
+ Note: We are heading towards a 1.0.0 release.
13
+
14
+ - **Upgrading** - check these before deploying:
15
+ - ICD-11 (MMS and ICF): the code is now the bare entity id, not the short code (see Changed). Clients that send or store short codes still work, but get `CODE_NOT_IN_NORMAL_FORM`; full entity URIs as codes are no longer accepted
16
+ - Token: the JSON API key moves from the URL path to a header (see Security)
17
+ - LOINC: `exists` filters now mean what they say, so value sets that relied on the old behaviour expand differently (see Fixed)
18
+ - VSAC: the default base URL is now https; a configured http `baseUrl` is logged as a warning
19
+ - The SNOMED CT test edition cache is now `sct_test_20250909c` (extension concepts added in module TEST1000003) and needs rebuilding for the tx tests
20
+
21
+ ### Security
22
+
23
+ - Page templates (`library/html-server.js`, every module): placeholders are now substituted in one pass, so a `[%...%]` in a page's content (a submitted IPS narrative, say) stays as it is. Before, `[%context-link%]`, `[%about%]` and the like inside content were replaced with their raw HTML, which inside an attribute let a stranger's narrative become a live element (stored XSS)
24
+ - IPS: the generated narrative escapes dosage timing values (`frequency`, `period` and their maxima) that a document puts where numbers belong, and generated narrative is checked for active content before it is stored or shown
25
+ - IPS: a project's stored AI key is dropped when the provider or base URL changes, so it can't be sent to a host other than the one it was given for. The terminology check no longer sends a coding's display to `$validate-code`: answers are cached by code, so one document with a wrong display could decide the answer for every document after it. Delete confirmations are an escaped attribute, not inline script with a name in it. `setPath`/`getPath` refuse paths that would reach a prototype. Document submissions are read by the module itself (the server's body parsers leave `/ips` alone): the submission token is checked before the body is read, and the body is read no bigger than `maxSize`
26
+ - Registry: markdown documents are rendered without raw HTML (commonmark's safe mode), and a past version of a document keeps the visibility it had then, so a version from when it was private stays private after the document is made public. `/resolve` no longer answers a page request with JSON when the page fails to render. JWTs without `exp` are refused. Changing or removing a user's password ends the user's other sessions. The failed-authentication limit checks the token first, so a valid token is never held back
27
+ - VSAC: the default base URL is now `https://cts.nlm.nih.gov/fhir`. It was http, and VSAC only redirects to https after the request (which carries the API key) has been sent. A configured `baseUrl` that is http is logged as a warning
28
+ - VSAC: a search bundle's `next` link is only followed on the VSAC server (the same host, under `baseUrl`), and always over `baseUrl`'s scheme, so a link can't send the API key elsewhere or over http
29
+ - Token: the JSON API takes the API key in a header - `Authorization: Bearer {key}` or `X-API-Key: {key}` - instead of the URL path (`/token/api/validate`, `/token/api/usage`, `/token/api/stats`), so keys don't end up in access logs. The usage statistics no longer include the IP address of the last request, and the key list's delete button no longer puts the key's name into an inline script
30
+ - Narrative check (`library/narrative-check.js`): the regex that stripped CSS comments from a `style` attribute was quadratic in the attribute's length - a few hundred KB of `/* ` held the event loop for seconds, and a resource with such a narrative could be sent to any module that checks narratives. Comments are now stripped in one pass
31
+ - IPS: narratives are shown as parsed, never as the string submitted, so an unterminated tag at the end of one narrative can't swallow the start of the next on the page and turn its text into attributes of a live element (the active-content check parses each narrative on its own, so it didn't see this). Reference resolution within a document is indexed rather than scanned (a document with tens of thousands of entries took a minute of CPU per page view); index rows per search parameter, sort keys per search, the JSON a search page carries, the terminology-server questions per document and the remembered answers are all bounded; an in-process terminology request has a timeout; error responses and the public document pages no longer carry internal detail (the reason a document's processing failed is shown to its project's managers); the submission form no longer gives every visitor a session; an unknown login takes as long as a known one; verdicts in a submitted document's own extensions are never shown as analysis
32
+
33
+ ### Added
34
+
35
+ - Terminology server: a syndication feed (`modules.tx.syndication.enabled`, off by default) - the content the library loads, published at `{endpoint}/syndication` as a [Terminology Syndication Feed](https://build.fhir.org/ig/FHIR/fhir-syndication-ig/en/) (Atom, NCTS ASF profile) for Ontoserver, other FHIRsmith servers and people. As the library loads, each source is recorded in a store in `terminology-cache/syndication`, from the time syndication is turned on: npm, url and core packages (linked at npm.fhir.org, `FHIR_Package`), the versioned resources in file and folder sources (copied into the store), and the binaries (SNOMED caches, LOINC databases etc, linked at `base.url`, in a FHIRsmith category scheme, with `<rights>`) that are published there - not internal sources, OCL, `#current` packages or the full CPT database. Every link has a SHA-256. The store only grows: no retracts, and a later update to the same content is a new entry (so `contentItemVersion` is not unique, deliberately). The feed is paged as RFC 5005 archived feeds of 30 entries; a browser gets it as a page, and the tx pages have a Syndication nav item and feed autodiscovery link
36
+ - Registry: the software page shows one row per server rather than one per endpoint - the endpoints of a server (its FHIR versions) that run the same software and version are collapsed under their common base URL, with the FHIR versions listed and any unreachable endpoint named. Endpoints running different software keep their own rows
37
+ - Registry: the API can be secured (`modules.registry.security`), in any combination of three ways: a single `apiToken` every client sends; `users` the administrator manages on `/tx-reg/admin/users`, each with their own tokens (256 random bits, stored as a SHA-256 hash, shown once, labelled, optionally expiring, revocable; usage counted per token) and optionally a password to manage their own tokens on `/tx-reg/account`; and `oidc`, JWTs from an OpenID Connect provider verified against its published keys for issuer, audience, expiry and a required scope or role. Only the JSON API is closed - the HTML pages, the OpenAPI description and the login page stay open - and a request without a valid token gets 401 with `WWW-Authenticate: Bearer`. Failed authentications are limited per address (`security.failedAuthPerWindow`, default 60 in 15 minutes, then 429; successful requests aren't counted). Tokens are sent as `Authorization: Bearer {token}` or `Api-Key: {token}`, the headers the HL7 Java tooling already sends for a server in fhir-settings.json. Without `security` the registry is open, as before. New dependency: `jose`
38
+ - Registry: documents (`/tx-reg/docs`, when `security.users` is configured) - markdown or JSON documents the registry stores and serves, each with a name (its address), a type, whether it is public, and when and by whom it was last edited. Public documents are readable by anyone, the others by any logged in user (a browser session, never an API token); administrators create, edit, rename and delete them in a plain text editor. A browser gets the document rendered (markdown as written, raw HTML included - the authors are the administrators; JSON pretty-printed), anything else the raw file with its media type. Every change is logged with the full content, and the history page shows each past version
39
+ - Registry: users can be administrators (a flag the administrator sets): an administrator user manages users and documents as the configured administrator does, and keeps its own account page
40
+ - Registry: uptime. Each scan records whether every endpoint answered (`[data]/registry/registry-uptime.json`, kept for `uptimeRetentionDays`, default 90), and the software page reports each server's uptime over the last `uptimeWindowDays` (default 30) as the share of its endpoint probes that answered. Scans the registry itself missed count against no one
41
+ - IPS module (`/ips`): collections ("projects") of International Patient Summary documents, each with its own FHIR endpoint (`POST`/`GET {project}/Bundle`, `Bundle/{id}`, `metadata`) and web pages - a filterable list, the document's assembled narrative, and a submission form. The administrator (login in the configuration) creates projects and users and assigns managers; managers set a project's submission token and delete documents. Searches support Bundle's parameters and chain to the Composition's and Patient's (`composition.date`, `composition.patient.name`, ...), indexed with fhirpath.js. A document's page has narrative, terminology (every CodeableConcept, with code systems linked to the configured terminology server or, for SNOMED CT, LOINC etc., to their browsers), validation and JSON views. Received documents are queued for a background processing pipeline, of which six steps exist: translation of every CodeableConcept that fails the terminology check, by `ConceptMap/$translate` into the element's value set, the matches added as codings (in the analysed copy) with an extension saying which server, concept map and relationship they came from; generation of the document's narrative with an IPS presenter (the Composition's header and patient, then the sections in the IPS order, each a table of its entries with the columns that suit the resource type and the nested parts as bullets beneath, coded values looked up on the terminology server when they have no display), written into the analysed copy; validation with the FHIR validator (as the shl module runs it) against the IPS Bundle profile, the outcome kept with the document; a terminology check of every CodeableConcept for international exchange: against the module's own value sets for its element (`ips/valuesets/`, on SNOMED CT International, LOINC, EDQM routes and HL7 codes), falling back to any SNOMED CT International, LOINC or HL7 code, by `$validate-code` on the configured terminology server, the verdicts written as extensions into a second, analysed copy of the document and shown in the terminology view; a check against each clinical profile (whether the document conforms, and the constraints it doesn't meet, shown in a Clinical Profiles view); and, when a project turns it on, an AI assessment of the document's consistency, completeness and terminology by an OpenAI-compatible or Anthropic service (the project's own account, its key encrypted at rest, or the server's default; capped per project per day). A document can be shared as a QR code - a SMART Health Link, or a Verifiable Health Link signed as the GDHCN does it - through the shl module (`modules.ips.shl`). An editor (`/ips/{code}/compose`) composes a document in the browser - the patient and header, then each IPS section as entries with code lookups, a narrative, or a reason it is empty - and submits it to the project or hands it back; an existing document can be opened in it. Two FHIRPath functions for IPS documents, `section(system, code [, min, max])` and `entry(system, code [, min, max])`, find sections (nested too) and their resolved entries by code, within cardinality bounds, and `resources(system, code [, type])` finds the document's resources with a code, with `entryIn()` and `resourcesIn()` doing the same by value set, asking the terminology server as they go, with each answer cached (`ips/fhirpath-ips.js`, `ips/membership.js`). Clinical profiles (`/ips/StructureDefinition`), across all projects: what a kind of clinical document is expected to contain, as a profile on Bundle (the document) whose root element's constraints are the expectations - edited in three parts: required sections and entries (a section that must be there, or a section with entries with a code, or a code in a value set) and rules about resources (`Bundle.resources(system, code, type)` and the rest of the rule), each as a table with code lookups, and other rules as FHIRPath; the administrator makes, edits, downloads and uploads them (an upload gets a new id and url, and anything more than such a profile is rejected), and everyone can see them. See `ips/readme.md`
42
+ - Daily report: a markdown report on the server, emailed at midnight (local time) - or as soon as the server is up again if it was down then - to `dailyReport.to`, titled "{hostName} daily report". A paragraph on the server's stability since the last report (uptime, restarts, the longest outage, any upgrade - from the run history in the statistics database, which now records each run's version), a paragraph from each running module that has one (a module implements `dailyReport({since, now})` to take part; by default none do), a paragraph on the system (version, Node.js and OS, how long this run has been up, the log files and the free disk space, and anything the npm audit found - vulnerable packages and the upgrade advice, or a newer release), and, when any background task's last run failed, a list of them. Nothing is sent without the new top-level `email` block (SMTP: `host`, `port`, `user`, `password`, `from`), which is the server's outbound email for anything else that needs it (`library/mailer.js`). New dependency: `nodemailer`. The terminology server, registry, packages, xig, testing, shl, ips and publisher modules each report (see the README for what); the counts come from the statistics database, which now answers per-operation counts over a period (`stats.countsSince`)
43
+ - Home page and dashboard: the uptime box now also reports the server's overall uptime - `Uptime: 38m 16s (99.3% overall)` - the proportion of the time since the first recorded run that the server has been running. Each run is recorded in the statistics database (`server_runs` in `stats.db`) with a heartbeat every minute, so a crash loses at most a minute. Not shown when statistics are turned off
44
+ - `library/fhir-search-values.js`: the date range, prefix and comma-splitting parts of FHIR search, moved out of the testing module for sharing
45
+ - `library/package-manager.js`: a cached package folder with no `package/package.json` (a download that failed after the folder was made) is fetched again instead of being used
46
+ - ICF (`http://hl7.org/fhir/sid/icf`): qualified codes. A category may be followed by `.` and one qualifier digit per position, the positions depending on the component: extent for body functions (`b`); extent, nature and localization for body structures (`s`); performance, capacity, and optionally capacity with assistance and performance without assistance for activities and participation (`d`). Qualifiers on environmental factors (`e`) are not supported yet. Each digit is checked against its scale in the code system (`q1`-`q4`, added to the ICF code systems in fhir.tx.support with a `kind` property). A qualified code gets a generated display (8, not specified, is left out of it; `d` qualifiers are labelled `performance`, `capacity`, ...), its qualifiers as properties in `$lookup` (`extent`, `nature`, `localization`, `performance`, `capacity`, `capacity-with-assistance`, `performance-without-assistance`), and sits under its category for subsumption and `is-a` filters. Value sets can control qualifiers with an `exists` `true`/`false` filter on any of those properties (8 counts as absent) and `qualified` `=` `false`. ICF is now marked as not closed. The behaviour is in `tx/cs/cs-icf.js`, a subclass of the CodeSystem provider that `FhirCodeSystemProvider.create()` picks by url - the way to give any code system loaded from a package behaviour of its own
47
+ - `$translate`: SNOMED CT edition to edition translation with no map - from an edition to one it depends on (per the module dependency reference set), using the implicit map `<edition>/version/<date>?fhir_cm=edition/<target module>` (the url agreed with the SNOMED committee), found from `targetSystem` or nominated by url. A concept in the target edition's modules is its own translation; an inactive concept translates through its historical associations; a fully defined extension concept translates to its normal form (or the target concept with that normal form), when that uses only target edition content; anything else translates to its proximal target-edition ancestors as `source-is-narrower-than-target`, unless those are only top-level hierarchies, which is reported as the reason there is no translation. The source concept in a match carries its version, as given
48
+ - `$translate`: when the target code a map directs to is not in its code system's normal form, the match has a `normalized-concept`. A source code that has a normal form is looked up both as given and in its normal form (a map entry written in the normal form matches any code with that normal form; the map's own entries are not normalised). A target in a code system the server doesn't know gets a `note` saying it was not validated, and a target code that is not valid gives an `error`
49
+ - `$lookup`: a warning when the `displayLanguage` asked for has no display for the code, and the display is in the default language (`LOOKUP_DISPLAY_LANGUAGE_FALLBACK`)
50
+ - ICD-11: the `classificationCode` extension (provisional url `http://id.who.int/icd/schema/extension/classificationCode`) on a Coding carries the short code for an entity id, saving a lookup. When present it is checked in `$validate-code` (CodeSystem and ValueSet): a wrong value, or the extension on a code with no short code, on a short code, or on a system other than MMS, is an error
51
+ - ICD-11 `$lookup`: postcoordination is reported as one `postcoordination` property per axis (subproperties `axis` and `value` Codings, a `/` member's value with its own nested postcoordination), replacing the WHO `postcoordinationScale` shape. With the new parameter `post-coordination-metadata=true` every declared axis is listed with `required`, `allowMultipleValues` and its value set; otherwise only the valued axes. `source` (the Foundation entity) is a Coding, and asking for `source.details` adds what a `$lookup` on it would return, designations included. A single-valued axis with several values gets a message in `$validate-code`
52
+ - SNOMED CT ECL: member filters (`^ refset {{ M ... }}`) - `effectiveTime` (with a time value or set), `active`, `moduleId` (any sub expression) and the reference set's own fields (`targetComponentId = << X`, `mapTarget = "..."`, `mapGroup >= #2`, ...), comma-separated and repeatable. A member filter anywhere other than directly after a memberOf constraint is a syntax error (`INVALID_ECL`). Description filters (`{{ D }}`), concept filters (`{{ C }}`) and history supplements (`{{ + }}`) are now parsed and rejected as `UNSUPPORTED_ECL` rather than failing as a lexer error
53
+ - SNOMED CT ECL: concept filters (`{{ C ... }}`) - `active`, `definitionStatus` (primitive/defined), `definitionStatusId`, `moduleId` (a concept, a bracketed concept list or any sub expression) and `effectiveTime`
54
+ - SNOMED CT ECL: `AND` and `OR` between refinements - between attributes (`: A = x AND B = y`, `: A = x OR B = y`, with brackets for nesting), within an attribute group (`{ A = x OR B = y }`) and between groups (`{ ... } OR { ... }`). Mixing AND and OR without brackets is rejected, as for compound expressions. Previously only `,` (conjunction) was accepted
55
+ - SNOMED CT ECL: description filters (`{{ D ... }}`) - `term` (match and `wild:` search terms, single or a set), `language`, `typeId` and `type` (syn/fsn/def), `dialectId` and `dialect` (en-us, en-gb and the other aliases in the ECL specification) with acceptability sets, `moduleId`, `effectiveTime`, `active` and `id`. The filters in one block apply to the same description; only active descriptions are considered unless `active` is given
56
+ - SNOMED CT ECL: comments (`/* ... */`), top and bottom of set (`!!> (...)`, `!!< (...)`), and attribute names that are expressions rather than a single concept (`: << 762705008 = ...`, `: (A OR B) = ...`, `: ^ refset = ...`, `: * = ...`, and the same after `.` in dotted expressions)
57
+ - SNOMED CT ECL: concrete values in refinements (`= #5`, `!= "x"`, `= match:"x"`, `= true`, ...) and alternate identifiers (`"ICD10#A01.1"`, `SCTID#123`) and reference set field selection on member-of (`^ [targetComponentId] refset`) are now parsed, so they are reported as `UNSUPPORTED_ECL` with a message saying what is missing (concrete value relationships / alternate identifier reference sets are not loaded) rather than as a syntax error
58
+ - SNOMED CT: `tx/sct/documentation.md` - what the server supports for SNOMED CT: editions, ECL, expressions and the MRCM, implicit concept maps and translation
59
+ - `utilities/make-sct-test-extension.js`: builds the extension concepts (module TEST1000003) in the SNOMED CT test edition used by the tx tests - one of each shape (fully defined ungrouped, fully defined role-grouped, equivalent to an International concept, primitive), plus inactive ones with and without historical associations
60
+ - README: each module's status - in production; in production but still under development; under development / prototyping; or on the back-burner
61
+
62
+ ### Changed
63
+
64
+ - SNOMED CT expressions: validation now follows the MRCM the way Ontoserver does (from a comparison over every post-coordinated expression in the published FHIR packages). An attribute must be a concept model attribute (a descendant of 410662002) - linkage concepts such as 20401003 |With| and 280452008 |Interpretation| were allowed before - and an attribute the MRCM attribute domain reference set says nothing about (|Has definitional manifestation|, |Subject of information|, ...) is rejected rather than left unchecked. An attribute the MRCM marks as grouped that is written ungrouped (`40468003:363698007=10200004`) is now accepted and put in a relationship group of its own (the "self-grouped attribute" transformation from Close To User Form to Classifiable Form), as Ontoserver and Snowstorm do; it was rejected. The grouped form is what is rendered and used for the normal form
65
+ - ICD-11 (MMS and ICF): following the October 2026 test-case review meetings, the code is the bare entity id (`257068234`; the full uri is the system url + `/` + the id), and that is the normal form. The short code (`1A00`) is still accepted, with `CODE_NOT_IN_NORMAL_FORM` and the normalized code; the full uri is no longer accepted as a code. The short code is reported as `classificationCode`: a `$lookup` property (the old `code` property; the old `id` property is now `code`), a property on every `$expand` contains entry, and a `$validate-code` output when the request used the id. Parent, child, stem and postcoordination value references are all entity ids, and postcoordinated expressions are written without spaces. The Foundation is unchanged
66
+ - `$lookup`: `name` is the code system's title, translated where possible, falling back to `CodeSystem.name` (a new `lookupName()` on code system providers, so other operations are unaffected). SNOMED CT gives the edition (`SNOMED CT International Edition`), LOINC, UCUM and OMOP their titles; OMOP's `name()` is now `OMOP_Concepts`
67
+ - `$lookup`: a code that is not selectable always returns `abstract` (true) and the `notSelectable` property (true), whatever properties were asked for; a selectable code returns neither unless asked (as agreed for the tx-ecosystem tests)
68
+ - Dependencies: new `jose`, `nodemailer` and `qrcode-generator`; `fhir-validator-wrapper` 1.4.0 to 1.5.0
69
+ - tx.fhir.org configuration: the Swiss and Danish SNOMED CT editions are no longer loaded, and `hl7.fhir.uv.ips` is
70
+
71
+ ### Fixed
72
+
73
+ - IPS: two runs of the processing queue could overlap at start-up while the terminology server was being looked for, and a run could start before it had answered; the queue now claims itself before it waits, and any answer but a CapabilityStatement (a 5xx while the tx module loads, say) counts as "not there yet". Requeueing a document while it is being processed is refused (it lost the requeue and the results), and a pipeline only finishes a document that is still being processed. A clinical profile rule over a value set the terminology server couldn't answer for is "could not be checked" whatever it evaluates to, and a profile deleted while a document is checked is passed over. The migration of profiles from Composition to Bundle rewrites every `Composition.` in a rule, not just a leading one. FHIRPath string escapes (`\uXXXX`, `\f`, ...) are read in the profile editor's shapes. A display lookup the server couldn't answer isn't cached. The editor refuses two fields for one `value[x]`, reads each `value[x]` field back from its own type, and keeps paragraphs on a round trip. A huge `_offset` is a 400, not a 500
74
+ - Registry: sessions are kept in the auth database (logins survive a restart), and pages a visitor only reads start no session. Documents up to the 2 MB limit can be saved (the form parser stopped at 64 KB). An endpoint missing from one crawl (a sub-registry that failed to fetch, a crawl cut short at shutdown) keeps its uptime history; history only ages out, and a scan cut short isn't recorded as a failure. Usage counts survive a failed write. The main pages' navigation shows who is logged in
75
+ - Server: SIGTERM (systemd, docker stop) shuts the server down as SIGINT does, so modules save what they hold
76
+ - ValueSet `$validate-code`: an include with more than one filter only checked the first. The other filters' checks return a message when the code fails them, and a message was taken as a pass
77
+ - LOINC: the `exists` filter ignored its value. On a relationship property (`METHOD_TYP`, `COMPONENT`, `document-kind`, ...) the value was looked up as a code or display, so `exists true` and `exists false` both returned every code with the relationship (because a code displayed as "True" and one as "False" exist), and on a property (`CLASSTYPE`, `ORDER_OBS`, ...) `exists false` meant `exists true`. `true` now returns the codes that have the property, `false` every other code in the code system - Parts, AnswerLists and Answers included, so add `CLASSTYPE exists true` to restrict to LOINC terms (e.g. LOINC terms with no method: `METHOD_TYP exists false` + `CLASSTYPE exists true`). Any other value is not supported
78
+ - Packages: a package download or page request was counted twice in the statistics (once by the validation step, once by the handler), and tarball downloads were not told from requests for a version's JSON document; downloads are now counted as `download`, version documents as `version`, each once
79
+ - SNOMED CT ECL: a hierarchy operator on a bracketed expression was silently dropped - `< (A OR B)` returned A and B themselves rather than their descendants. It now applies to every member of the set, as do operators on `*` and on `^ refset`, which were previously rejected as unsupported
80
+ - SNOMED CT ECL: `^` on a reference set whose members are descriptions (a language reference set) returned an empty expansion; it is now reported as `UNSUPPORTED_ECL`
81
+ - SNOMED CT ECL: an evaluation error is now `UNSUPPORTED_ECL` unless it is about a concept that does not exist (`INVALID_ECL`); it was classified by matching the error text, which filed `= true` as a syntax error. A concept id that is not 6-18 digits is rejected by the parser
82
+ - SNOMED CT expressions: the MRCM content type taken as "postcoordinated" was 723594008, which is *all precoordinated content* (postcoordinated is 723595009). So the body-structure part-of attributes (|Regional part of| etc., precoordinated only) were allowed in expressions, |Has ingredient| (postcoordinated only) was not checked, and for the attributes with a precoordinated-specific range that range was used in place of the general one
83
+
84
+ - SHL: every access to a live link (`/shl/access/{uuid}`) hung without a response - the access-logging callback was a plain `function`, so `this.db` was undefined in it and the request was never answered (since 0.3.0). A `GET` on a link is now the U flag (direct file) access the SMART Health Links spec defines: a link with one file returns it as a JWE (`application/jose`), and `recipient` is read from the query and logged (it was always `anonymous`); a link with more than one file, or a VHL, still gets the manifest / VHL bundle. The manifest now has `status` (`finalized`), file `location`s are always https (they followed `req.secure`, so http behind a proxy without trust proxy), and `embedded` is the JWE itself (it was the base64 of it, as stored)
85
+ - IPS: a VHL share no longer puts the U flag in its link payload - a VHL's link answers with the VHL bundle, not the file
86
+
87
+ ### Tx Conformance Statement
88
+
89
+ FHIRsmith v0.18.0 passed all 3860 HL7 terminology service tests (modes tx.fhir.org+omop+general+snomed+mimetypes+icd-11+icf+closure, tests v1.9.6, runner v7.0.3)
90
+
91
+ ## [0.17.0] - 2026-10-17
92
+
93
+ An extensive security review, which hardened many things, but no actual securty notifications needed. Plus improvemnts to text search in terminology filters
94
+
95
+ ### Security
96
+
97
+ - HTML pages (all modules using the shared page template): content containing `` $` `` or `$'` - which HTML escaping leaves alone - made the template insert a copy of part of itself into the page, garbling it. No way to inject script through it was found, but the page is now built without that substitution
98
+ - Packages: a tarball whose package.json version was not entirely semver (e.g. `1.0.0/../../x`) was accepted, and its mirror file written outside the mirror folder. The version must now be whole semver, and the mirror file must be in the mirror folder
99
+ - Rendered resources: links to `javascript:`, `data:` or `vbscript:` URLs taken from resource content are neutralised
100
+ - tx: values from loaded packages are escaped (and URL-encoded in links) on the problems page, the resource tabs, and the operations form's list of value sets
101
+ - tx: CodeSystems, ValueSets and ConceptMaps whose narrative (or a contained resource's) has active content - script, event handler attributes, `javascript:` URLs, frames, objects, forms, svg and the like - are not loaded, and a warning says what was found. The Original Narrative tab also refuses such a narrative. The check is in `library/narrative-check.js` for other uses. The package value set and concept map caches are renamed so that they are rebuilt and re-checked once
102
+ - xig: the database is downloaded over https (it was http, so it could theoretically have been replaced in transit, and its narratives are shown as they are), and a redirect off https is refused
103
+ - Extension tracker: the profiles page put the resource types from submissions into a script without escaping, so anyone could submit data that ran script on that page. They are now escaped
104
+ - Extension tracker: submissions must carry the token the IG Publisher sends (`token` in the ext-tracker config; without it, no token is needed), and must look like what the IG Publisher sends - the expected properties only, codes, URLs, resource types and element paths where those belong, and limited sizes. Anything else is refused, and the refusal logged
105
+ - Registry: server and registry URLs from registry lists (which other people host) were put into links without escaping, so a listed URL could add script to the registry's pages; and links were made from any URL, including `javascript:`. Links are now escaped, and made only from http(s) URLs
106
+ - Registry: the crawler limits each server's response to `maxResponseMB` (default 100) once unzipped, so a server can't exhaust the registry's memory
107
+ - xig: POST /xig/update, which re-downloads the database (about 3 GB), needed no authentication. It now needs the `updateToken` from the xig config in an `x-update-token` header (or the form now on /xig/stats, which starts an update and returns straight away), and is disabled when no updateToken is configured
108
+ - xig: a search parameter given more than once, or a very long one, is handled (the first is used, and each is capped at 200 characters) instead of failing the page
109
+ - OCL: the configured token can see the private repos of its account, and what it finds is served to anyone through the terminology server. Sources and collections are now served only when they are public (`public_access` View or Edit), whether they are found by discovery or by `$resolveReference`; anything that can't be confirmed public is left out. The token's account should still be one that can see only public repos
110
+ - OCL: a repo path returned by OCL is only used if it is a relative path on the OCL server, so a request carrying the token can't be sent to another host
111
+ - Packages: the crawler limits what a package can cost it: the download (`maxDownloadMB`, default 250), the total unzipped (`maxUnpackedMB`, 4096), the files it reads (`maxFileMB`, 50) and the number of files (`maxFiles`, 250000), all in the packages `crawler` config. Packages are now unpacked as a stream, keeping only package.json, .index.json, ig.ini and one .json file at a time, so a decompression bomb can't exhaust memory
112
+
113
+ ### Added
114
+
115
+ - `TerminologyCapabilities.expansion.textFilter` describes how text filters match
116
+ - OCL: with a `token=` on the `ocl:` source line, canonical URLs are resolved through OCL's `$resolveReference` (ConceptMap source systems, ValueSet collections and their compose sources), rather than by searching the listings. Without a token nothing changes
117
+ - OCL: a source's latest released version is served as its default version, as OCL itself does; HEAD is still available as version `HEAD` (needs the token)
118
+ - README: what to do after upgrading Node.js (`npm rebuild`, or reinstall)
119
+
120
+ ### Changed
121
+
122
+ - Text filters on code systems loaded from files and packages ignore case, and accents on Latin, Greek and Cyrillic letters (`colera` finds `Cólera`), and a search of several words matches when each word starts a word of the display or of one designation, in any order (`diabetes tipo 2` finds `Diabetes mellitus tipo 2`). These matches rank below matches of the whole text, shorter displays first.
123
+ - `$expand` with a text filter returns the best matches first on code systems loaded from files and packages, and on OCL code systems, as it already did on SNOMED CT
124
+ - OCL: sources and collections are discovered from the global `/sources/` and `/collections/` listings (falling back to each organisation's), and only organisation-owned ones are served - repos owned by a user are not
125
+ - OCL: a source's mappings are fetched from `{source}/mappings/` instead of walking every concept
126
+
127
+ ### Fixed
128
+
129
+ - LOINC: text filter matches came in reverse description order (Z to A) when the expansion asked for them sorted. They now come in description order either way
130
+ - LOINC: an `is-a` filter left out the concept itself, so it gave the same codes as `descendent-of`. It now includes the concept, as FHIR defines `is-a`
131
+ - OCL: `$translate` never returned anything from OCL concept maps. The maps declared OCL repo paths as their source and target scopes, which aren't value sets, so every translation failed with not-found; and `NARROWER-THAN` / `BROADER-THAN` mappings had relationship codes that aren't FHIR codes. The scopes are gone, the map types become `source-is-narrower-than-target` / `source-is-broader-than-target` (and `NOT-SAME-AS` becomes `not-related-to`), and `Q-AND-A` and `CONCEPT-SET` mappings, which aren't translations, are left out
132
+ - tx: a package value set or concept map cache whose build was cut short (the server stopped part way through) was used as it was, missing whatever hadn't been written. A cache is now marked complete when it's built, and one without the mark is rebuilt - so existing caches are rebuilt once, on the first start of this version
133
+
134
+ ### Tx Conformance Statement
135
+
136
+ FHIRsmith v0.17.0 passed all 3630 HL7 terminology service tests (modes tx.fhir.org+omop+general+snomed+mimetypes+icd-11+closure, tests v1.9.6, runner v7.0.2)
137
+
8
138
  ## [0.16.1] - 2026-10-16
9
139
 
10
140
  ### Added
package/README.md CHANGED
@@ -1,22 +1,37 @@
1
1
  # ![🔥](static/FHIRsmith64.png) FHIRsmith - FHIR Server toolkit
2
2
 
3
- This server provides a set of server-side services that are useful for the FHIR Community. The set of are two kinds of services:
3
+ This server provides a set of server-side services that are useful for the FHIR Community. Each is a module, turned on and configured in `config.json`. The modules are at different stages, and stability varies between them: a module in production can still be changing in places (and the terminology server's behaviour follows decisions about its content - ICD-11, SNOMED CT - as they are made).
4
4
 
5
- ## Modules useful to anyone in the community
5
+ ## In production
6
6
 
7
- * (Coming) R4/R6 interconverter
8
- * [terminology](tx/README.md) server (as running on [tx.fhir.org](http://tx.fhir.org/r4))
9
- * [SHL Server](shl/readme.md) - SHL/VHL support services (running on [healthintersections.com.au](http://www.healthintersections.com.au), supporting the ICVP demo and and the Patient Document Generator)
7
+ * [Terminology server](tx/README.md) (`tx`) - the reference terminology server for the FHIR community and the terminology ecosystem (as running on [tx.fhir.org](http://tx.fhir.org/r4))
8
+ * [TX Registry](registry/documentation/readme.md) (`registry`) - **Terminology System Registry** as [described by the terminology ecosystem specification](https://build.fhir.org/ig/HL7/fhir-tx-ecosystem-ig) (as running at http://tx.fhir.org/tx-reg, moving to https://ecosystem.hl7.org). Its API is described by an OpenAPI 3.1 spec at `/tx-reg/openapi.json` (and `.yaml`), with a browsable reference at `/tx-reg/openapi`
9
+ * [Package server](packages/readme.md) (`packages`) - **NPM-style FHIR package registry** with search, versioning, and downloads, consistent with the FHIR NPM Specification (as running at http://packages2.fhir.org/packages). Its API is described by an OpenAPI 3.1 spec at `/packages/openapi.json` (and `.yaml`), with a browsable reference at `/packages/openapi`
10
+ * Publisher (`publisher`) - FHIR publishing services (as running at [healthintersections.com.au](http://www.healthintersections.com.au/publisher))
11
+ * XIG server (`xig`) - **Comprehensive FHIR IG analytics** with resource breakdowns by version, authority, and realm (as running at http://packages2.fhir.org/packages)
12
+ * [SHL Server](shl/readme.md) (`shl`) - SHL/VHL support services (running on [healthintersections.com.au](http://www.healthintersections.com.au), supporting the ICVP demo and the Patient Document Generator)
10
13
 
11
- ## Services useful the community as a whole
14
+ ## In production, but still under development
12
15
 
13
- * [TX Registry](registry/readme.md) - **Terminology System Registry** as [described by the terminology ecosystem specification](https://build.fhir.org/ig/HL7/fhir-tx-ecosystem-ig) (as running at http://tx.fhir.org/tx-reg). Its API is described by an OpenAPI 3.1 spec at `/tx-reg/openapi.json` (and `.yaml`), with a browsable reference at `/tx-reg/openapi`
14
- * [Package server](packages/readme.md) - **NPM-style FHIR package registry** with search, versioning, and downloads, consistent with the FHIR NPM Specification (as running at http://packages2.fhir.org/packages). Its API is described by an OpenAPI 3.1 spec at `/packages/openapi.json` (and `.yaml`), with a browsable reference at `/packages/openapi`
15
- * [XIG server](xig/readme.md) - **Comprehensive FHIR IG analytics** with resource breakdowns by version, authority, and realm (as running at http://packages2.fhir.org/packages)
16
- * [Publisher](publisher/readme.md) - FHIR publishing services (as running at [healthintersections.com.au](http://www.healthintersections.com.au/publisher))
17
- * [Testing](testing/readme.md) - **TestReport repository**: receives FHIR TestReports from TxTester and other test tools, with a FHIR API and web pages (as running at https://testing.fhir.org/testing). Its API is described by an OpenAPI 3.1 spec at `/testing/openapi.json` (and `.yaml`), with a browsable reference at `/testing/openapi`
18
- * [VCL](vcl/readme.md) - **Parse VCL expressions** into FHIR ValueSet resources (as running at http://fhir.org/vcl)
19
- * (Coming) Token services
16
+ * [Extension tracker](extension-tracker/readme.md) (`ext-tracker`) - tracks extensions, profiles and extension usage across FHIR Implementation Guides, from data the IG Publisher submits
17
+ * [Folder](folder/readme.md) (`folder`) - serves static files from one or more directories, with optional authenticated upload
18
+ * [Testing](testing/readme.md) (`testing`) - **TestReport repository**: receives FHIR TestReports from TxTester and other test tools, with a FHIR API and web pages (as running at https://npm.fhir.org/testing). Its API is described by an OpenAPI 3.1 spec at `/testing/openapi.json` (and `.yaml`), with a browsable reference at `/testing/openapi`
19
+ * VCL (`vcl`) - **Parse VCL expressions** into FHIR ValueSet resources (as running at http://fhir.org/vcl)
20
+
21
+ ## Under development, prototyping
22
+
23
+ * [IPS](ips/readme.md) (`ips`) - collections of International Patient Summary documents, each with its own FHIR endpoint, processing (validation, terminology checks, translation, narrative, clinical profiles) and web pages
24
+
25
+ ## On the back-burner
26
+
27
+ These are not being actively developed at present.
28
+
29
+ * [NpmProjector](npmprojector/readme.md) (`npmprojector`) - watches a local npm package directory and serves its FHIR resources with FHIRPath-based search
30
+ * Token (`token`) - API key services
31
+
32
+ ## Planned
33
+
34
+ * R4/R6 interconverter
20
35
 
21
36
  ## Summary Statement
22
37
 
@@ -81,7 +96,7 @@ During development with a cloned repository, the data directory defaults to `[ro
81
96
  is provided by the host — see Deployment below.
82
97
 
83
98
  ### Prerequisites
84
- - Node.js 16+
99
+ - Node.js 24+
85
100
  - NPM or Yarn
86
101
  - Java 17+ (for FHIR validator, also for the test cases)
87
102
 
@@ -159,6 +174,58 @@ Add a `logging` section to `config.json` to control log behaviour. All fields ar
159
174
 
160
175
  Log files are written to the `logs/` subdirectory of the data directory as `server-YYYY-MM-DD.log`. A `server.log` symlink always points to the current day's file, so `tail -f data/logs/server.log` tracks the active log without needing to know the date.
161
176
 
177
+ ### Email and the Daily Report
178
+
179
+ The server can email a short markdown report on itself once a day. It needs two things in
180
+ `config.json`: an SMTP server to send through, and someone to send to:
181
+
182
+ ```json
183
+ {
184
+ "email": {
185
+ "host": "smtp.example.org",
186
+ "port": 587,
187
+ "user": "fhirsmith",
188
+ "password": "...",
189
+ "from": "FHIRsmith <fhirsmith@example.org>"
190
+ },
191
+ "dailyReport": {
192
+ "to": ["ops@example.org"]
193
+ }
194
+ }
195
+ ```
196
+
197
+ `user`/`password` are optional (an open relay); `secure` defaults to true for port 465 and
198
+ to STARTTLS otherwise. Without an `email` block nothing is emailed, and without
199
+ `dailyReport.to` there's no report (`dailyReport.enabled: false` turns it off explicitly).
200
+
201
+ The report, titled "`{hostName}` daily report", goes out at midnight (the server's local
202
+ time), or as soon as the server is up again if it was down at midnight. It has a paragraph
203
+ on the server's stability since the last report - how much of the time it was up, restarts,
204
+ the longest outage, and any upgrade (from the run history in the statistics database, so
205
+ `stats.enabled: false` leaves the uptime unknown); a paragraph from each running module that
206
+ has something to report (by default, none do); and a paragraph on the system - version,
207
+ Node.js and OS, how long this run has been up, the log files and the free disk space they're
208
+ drawing on, plus anything the npm audit check has found (packages with known vulnerabilities and
209
+ what to do about them, or a newer FHIRsmith release). If any background task's last run failed, a
210
+ final section lists them. When the last report was sent is kept in `daily-report.json` in the data
211
+ directory.
212
+
213
+ A module takes part by implementing `dailyReport({since, now})`, returning its paragraph as
214
+ markdown (or a promise of it); an empty return means it has nothing to say that day. The
215
+ request counts a module quotes come from `stats.countsSince(since, now)`, the per-operation
216
+ counts in the statistics database for the period. The modules that report:
217
+
218
+ | Module | Reports |
219
+ |---|---|
220
+ | Terminology server | how many of each operation were performed, over all its endpoints |
221
+ | Terminology server registry | client requests answered by the API, servers registered, and any server that answered none of the registry's scans in the period |
222
+ | Packages | packages served (tarball downloads) and new package versions found by the crawler |
223
+ | XIG | pages served, and whether the database could be accessed (and whether the last download failed) |
224
+ | Testing | pages served, test reports received, and the number held |
225
+ | SMART Health Links | QR codes created (SMART Health Links and Verifiable Health Links), link accesses, and the documents stored to serve them |
226
+ | IPS | pages served, IPS documents received, and the number stored |
227
+ | Publisher | publication runs started, and how many finished successfully |
228
+
162
229
  ### Start the Server
163
230
 
164
231
  ```bash
@@ -207,6 +274,15 @@ Or run it inline:
207
274
  FHIRSMITH_DATA_DIR=/var/lib/fhirsmith fhirsmith
208
275
  ```
209
276
 
277
+ ### Changing the Node.js version
278
+
279
+ Some of FHIRsmith's dependencies (the SQLite drivers) are native modules, built for the
280
+ version of Node.js that installed them. After upgrading Node.js, rebuild them - `npm rebuild`
281
+ in the FHIRsmith folder, or `npm rebuild -g fhirsmith` for a global install - or reinstall
282
+ FHIRsmith. Otherwise the server stops at startup with "compiled against a different Node.js
283
+ version". (With nvm, each Node.js version has its own global packages, so install FHIRsmith
284
+ again under the new version.) Docker images aren't affected.
285
+
210
286
  ### Docker Installation
211
287
 
212
288
  The server is available as a Docker image. Mount a host directory as the data directory:
@@ -339,30 +415,29 @@ GitHub Actions will automatically:
339
415
 
340
416
  or do it via a PR
341
417
 
342
- 4. Tag and push the release:
418
+ 4. Run the tx tests (`npx jest tests/tx/test-cases.test.js`), then tag and push the release:
343
419
  ```bash
344
- git tag vX.Y.Z
345
- git push origin vX.Y.Z
420
+ ./release.sh X.Y.Z
346
421
  ```
347
422
 
423
+ `gr.sh` checks that the release is ready - on `main` and pushed, the three versions in
424
+ `package.json` and `package-lock.json` match, `CHANGELOG.md` has a dated section with the
425
+ conformance statement, and `test-cases-report.json` passed, is from this version and is
426
+ newer than the last code change - then submits the report to
427
+ [npm.fhir.org](https://npm.fhir.org/testing) (set `FHIRSMITH_TESTING_TOKEN` if it needs a
428
+ token), and only if that is accepted, tags `vX.Y.Z` and pushes the tag.
429
+
430
+ The tx test run writes `test-cases-report.json` next to `test-cases-summary.txt` (both
431
+ are ignored by git). The report records the version in `package.json` when the tests
432
+ ran, so run the tx tests after step 2. To submit a report on its own (to a different
433
+ /testing server, say): `node utilities/submit-test-report.js [-server {url}] [-token {token}]`.
434
+
348
435
  5. Monitor the release:
349
436
  - Check [GitHub Actions](https://github.com/HealthIntersections/fhirsmith/actions) for the Release workflow
350
437
  - Verify the [GitHub Release](https://github.com/HealthIntersections/fhirsmith/releases) was created
351
438
  - Confirm Docker images are available at [GHCR](https://github.com/HealthIntersections/fhirsmith/pkgs/container/fhirsmith)
352
439
 
353
- 6. Submit the tx conformance report to [testing.fhir.org](https://testing.fhir.org/testing):
354
- ```bash
355
- node utilities/submit-test-report.js -token {token}
356
- ```
357
-
358
- The tx test run writes `test-cases-report.json` next to `test-cases-summary.txt` (both
359
- are ignored by git); this uploads it as a TestReport. The report records the version in
360
- `package.json` when the tests ran, so for it to name the release, run the tx tests
361
- after step 2 - the script warns if the report is from a different version, or from a
362
- snapshot. Use `-server {url}` to send it to a different /testing server; the token can
363
- also come from `FHIRSMITH_TESTING_TOKEN`.
364
-
365
- 7. Update `package.json` to have the next release version -SNAPSHOT
440
+ 6. Update `package.json` to have the next release version -SNAPSHOT
366
441
 
367
442
  **If a release fails:**
368
443
  - Delete the tag: `git tag -d vX.Y.Z && git push origin :refs/tags/vX.Y.Z`
@@ -27,6 +27,24 @@
27
27
  "enabled": true,
28
28
  "intervalHours": 24
29
29
  },
30
+ // Outbound email, over SMTP. Optional: without it nothing is emailed. user/password are
31
+ // optional too (an open relay); secure defaults to true for port 465, otherwise STARTTLS
32
+ "email": {
33
+ "host": "smtp.example.org",
34
+ "port": 587,
35
+ "user": "fhirsmith",
36
+ "password": "<!-- password -->",
37
+ "from": "FHIRsmith <fhirsmith@example.org>"
38
+ },
39
+ // A markdown report on the server - its uptime since the last report, restarts and
40
+ // upgrades, a paragraph from any module with something to say, and the version, logs
41
+ // and free disk space - emailed at midnight (local time), or as soon as the server is
42
+ // up again if it was down then. Titled "{hostName} daily report". Needs "email" above;
43
+ // the whole block is optional
44
+ "dailyReport": {
45
+ "enabled": true,
46
+ "to": ["ops@example.org"]
47
+ },
30
48
  "modules": {
31
49
  "shl": {
32
50
  "enabled": false,
@@ -61,7 +79,11 @@
61
79
  },
62
80
  "xig": {
63
81
  "enabled": false,
64
- "autoUpdate": true
82
+ "autoUpdate": true,
83
+ // shared secret for POST /xig/update, which re-downloads the database on demand. Send
84
+ // it in the x-update-token header. Omit this and the endpoint is disabled entirely -
85
+ // the scheduled update (autoUpdate) is unaffected either way.
86
+ "updateToken": "change-this-to-a-secure-random-string"
65
87
  },
66
88
  "packages": {
67
89
  "enabled": false,
@@ -73,7 +95,17 @@
73
95
  "crawlToken": "change-this-to-a-secure-random-string",
74
96
  "crawler": {
75
97
  "enabled": true,
76
- "schedule": "0 * * * *"
98
+ "schedule": "0 * * * *",
99
+ // limits on a package, so a hostile or broken one (a decompression bomb, say) costs
100
+ // a bounded amount. These are the defaults. maxDownloadMB: the package file;
101
+ // maxUnpackedMB: its files once unzipped (streamed, so this limits work, not
102
+ // memory); maxFileMB: a file the crawler reads (package.json, .index.json, ig.ini
103
+ // and each .json, for its url) - memory use is bounded by this; maxFiles: the number
104
+ // of files in a package
105
+ "maxDownloadMB": 250,
106
+ "maxUnpackedMB": 4096,
107
+ "maxFileMB": 50,
108
+ "maxFiles": 250000
77
109
  },
78
110
  // /packages/npm-check: checks any registry against the npm registry protocol. On by
79
111
  // default; the other settings are the defaults. "control": false stops the daily
@@ -92,7 +124,19 @@
92
124
  "crawlInterval": 30,
93
125
  "timeout": 30000,
94
126
  "userAgent": "YourServer/1.0",
95
- "apiKeys": {}
127
+ "apiKeys": {},
128
+ // the most a crawled server's response can be, once unzipped
129
+ "maxResponseMB": 100,
130
+ // how far back the software page reports each server's uptime, and how long scan outcomes are kept
131
+ "uptimeWindowDays": 30,
132
+ "uptimeRetentionDays": 90
133
+ // Securing the JSON API (the HTML pages stay open). Omit for an open registry. Any of the
134
+ // three can be configured, alone or together - see registry/documentation/security.md
135
+ // "security": {
136
+ // "apiToken": "one token every client sends",
137
+ // "users": { "database": "registry-auth.db", "adminUsername": "admin", "adminPassword": "...", "sessionSecret": "...", "cookieSecure": true },
138
+ // "oidc": { "issuer": "https://login.microsoftonline.com/{tenant}/v2.0", "audience": "api://ecosystem.hl7.org", "requiredScope": "registry.read" }
139
+ // }
96
140
  },
97
141
  "publisher": {
98
142
  "enabled": true,
@@ -209,6 +253,14 @@
209
253
  // several instances can route cached requests back to the instance that holds the
210
254
  // cache. Leave unset for a single server. See nginx.md.
211
255
  // "instanceCode": "tx1",
256
+ // The syndication feed (Terminology Syndication Feed IG) at <endpoint>/syndication. When
257
+ // it is on, the packages, resource files and published binaries the library loads are
258
+ // recorded in <data>/terminology-cache/syndication as they load - only from the time it
259
+ // is turned on - and the feed is built from that record. The full CPT database is never
260
+ // syndicated. Every endpoint shares the one store.
261
+ "syndication": {
262
+ "enabled": false
263
+ },
212
264
  // $closure: closure tables are kept on the server, across sessions and restarts, so
213
265
  // the operation is off unless it is turned on here. When it is on, only one server
214
266
  // instance may use the database - route every $closure request to it (see nginx.md).
@@ -271,7 +323,12 @@
271
323
  "ext-tracker": {
272
324
  "enabled": true,
273
325
  "database": "/var/fhir/data/extension-tracker.db",
274
- "url": "/ext-tracker"
326
+ "url": "/ext-tracker",
327
+ // the token submissions must carry (Authorization: Bearer ...). The IG Publisher sends
328
+ // 9D1C23C0-3915-4542-882E-2BCC55645DF8 - it's in the publisher's source, so it is not a
329
+ // secret, but it keeps out submissions that aren't from the publisher. Omit it and
330
+ // submissions need no token
331
+ "token": "9D1C23C0-3915-4542-882E-2BCC55645DF8"
275
332
  },
276
333
  // TestReports from TxTester and other tools, at /testing. See testing/readme.md
277
334
  "testing": {
@@ -297,6 +354,75 @@
297
354
  "rateLimit": { "windowMinutes": 1, "max": 60 }
298
355
  // delete reports received more than this many days ago. Leave it out to keep everything
299
356
  // , "retentionDays": 365
357
+ },
358
+ // International Patient Summary documents, in projects, at /ips. See ips/readme.md
359
+ "ips": {
360
+ "enabled": false,
361
+ // relative paths resolve under the data folder's databases directory
362
+ "database": "ips/ips-content.db",
363
+ // the administrator: creates and deletes projects and users, and says who manages
364
+ // which project. No adminPassword, no administrator
365
+ "adminUsername": "admin",
366
+ // "adminPassword": "<!-- secret -->",
367
+ // signs the login cookie; without it logins don't survive a restart
368
+ // "sessionSecret": "<!-- random string -->",
369
+ // the login cookie is HTTPS-only unless this is false (e.g. for local testing over http)
370
+ // "cookieSecure": true,
371
+ // failed-login attempts allowed per IP per 15 minutes (default 20)
372
+ "loginAttemptsPerWindow": 20,
373
+ // the biggest document accepted, in bytes (default 10MB)
374
+ "maxSize": 10485760,
375
+ // documents submitted per IP address per window; max 0 turns the limit off
376
+ "rateLimit": { "windowMinutes": 1, "max": 60 },
377
+ // the background loop that processes received documents
378
+ "processing": { "enabled": true, "intervalSeconds": 5 },
379
+ // the terminology server, for links to code systems and for analysing documents:
380
+ // an http(s) URL, or - when the tx module is enabled - one of its endpoint paths
381
+ // (modules.tx.endpoints[].path, e.g. /r4), to which requests are then routed
382
+ // in-process. Omit it for no links and no analysis
383
+ "txServer": "/tx/r4",
384
+ // "txServer": "https://tx.fhir.org/r4",
385
+ // (rateLimit also takes lookupMax: the editor's code lookups per window, 600)
386
+ // the AI assessment (the last processing step; a project turns it on in its settings).
387
+ // The server's default account, used by a project that has not set its own: provider
388
+ // is openai (any chat-completions service: OpenAI, OpenRouter, LiteLLM, Groq, Ollama...)
389
+ // or anthropic (the Messages API); baseUrl is optional (the provider's own by default;
390
+ // for an OpenAI-compatible service, up to the version, e.g. https://openrouter.ai/api/v1).
391
+ // dailyCap: assessments per project per day (50); maxBytes: the biggest document sent
392
+ // (300000); maxTokens: of the answer (16384); timeout: seconds (120); sendNarrative: send
393
+ // the narratives as submitted (false: the structured data only); a project can override
394
+ // all but maxBytes, maxTokens and timeout
395
+ // prices: USD per million tokens by model (or a prefix of the model name), to work out what
396
+ // each assessment cost, for services that don't say (OpenRouter does). Anthropic's current
397
+ // models are built in (ips/ai-client.js DEFAULT_PRICES); anything here overrides them
398
+ // "ai": { "provider": "anthropic", "model": "claude-sonnet-5-5", "apiKey": "...", "dailyCap": 50,
399
+ // "prices": { "claude-sonnet-5-5": { "input": 3, "output": 15 } } },
400
+ // the secret the projects' own AI keys are encrypted with in the database (else the
401
+ // sessionSecret is used; with neither, projects cannot keep their own accounts)
402
+ // "aiKeySecret": "<!-- random string -->",
403
+ // sharing a document as a QR code - a SMART Health Link, or a Verifiable Health Link
404
+ // signed as the GDHCN does it - through an SHL server: this server's own shl module
405
+ // ("server": "/shl"; the password is taken from modules.shl when left out) or another
406
+ // FHIRsmith's ("server": "https://host/shl", "password": its password). days: how long
407
+ // the shared copy lives (100); viewer: the SHL viewer the QR opens (viewer.tcpdev.org)
408
+ // "shl": { "server": "/shl", "days": 100 },
409
+ // the FHIR validator the processing pipeline validates documents with - the same
410
+ // validator service the shl module uses (fhir-validator-wrapper, the validator CLI jar in
411
+ // the data folder's bin directory), on its own port. Documents are validated against the
412
+ // IPS Bundle profile unless "profiles" says otherwise. Omit the block, or set enabled
413
+ // false, and documents are not validated (processing fails)
414
+ "validator": {
415
+ "enabled": true,
416
+ "version": "4.0.1",
417
+ "txServer": "http://tx.fhir.org/r4",
418
+ "txLog": "./ips-validator-tx.log",
419
+ "port": 8082,
420
+ "packages": ["hl7.fhir.uv.ips#1.1.0"],
421
+ "timeout": 120000,
422
+ // the validator's own IPS code checks (default true)
423
+ "checkIpsCodes": true
424
+ // , "profiles": ["http://hl7.org/fhir/uv/ips/StructureDefinition/Bundle-uv-ips"]
425
+ }
300
426
  }
301
427
  }
302
428
  }