fhirsmith 0.10.1 → 0.11.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 (53) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/changelog-template.md +22 -0
  3. package/library/logger.js +8 -1
  4. package/library/package-manager.js +40 -2
  5. package/npmprojector/watcher.js +1 -1
  6. package/package.json +3 -2
  7. package/packages/packages.js +28 -15
  8. package/publisher/publisher.js +8 -8
  9. package/registry/api.js +92 -31
  10. package/registry/crawler.js +15 -1
  11. package/registry/model.js +158 -8
  12. package/registry/readme.md +5 -13
  13. package/registry/registry.js +87 -46
  14. package/registry/test-runner.js +1 -1
  15. package/server.js +8 -1
  16. package/token/token.js +1 -1
  17. package/translations/Messages.properties +3 -1
  18. package/translations/rendering-phrases.properties +1227 -1218
  19. package/translations/rendering-phrases_ar.properties +1097 -1091
  20. package/translations/rendering-phrases_fr.properties +626 -624
  21. package/translations/rendering-phrases_nl.properties +976 -970
  22. package/translations/rendering-phrases_pt.properties +1026 -1020
  23. package/translations/rendering-phrases_ru.properties +1100 -1094
  24. package/tsconfig.eslint.json +9 -0
  25. package/tx/cs/cs-api.js +57 -4
  26. package/tx/cs/cs-cs.js +98 -25
  27. package/tx/cs/cs-hgvs.js +4 -0
  28. package/tx/cs/cs-lang.js +8 -0
  29. package/tx/cs/cs-mimetypes.js +4 -0
  30. package/tx/cs/cs-snomed.js +530 -75
  31. package/tx/cs/cs-uri.js +4 -0
  32. package/tx/data/snomed-subset.txt +40 -3
  33. package/tx/importers/import-sct.module.js +18 -0
  34. package/tx/library/designations.js +3 -5
  35. package/tx/library/renderer.js +227 -30
  36. package/tx/library.js +5 -1
  37. package/tx/operation-context.js +40 -3
  38. package/tx/provider.js +38 -9
  39. package/tx/sct/ecl.js +30 -11
  40. package/tx/sct/expressions.js +28 -1
  41. package/tx/sct/structures.js +5 -5
  42. package/tx/tx-html.js +24 -15
  43. package/tx/tx.fhir.org.yml +1 -4
  44. package/tx/vs/vs-package.js +52 -0
  45. package/tx/workers/batch-validate.js +125 -18
  46. package/tx/workers/cache-control.js +31 -2
  47. package/tx/workers/expand.js +50 -32
  48. package/tx/workers/metadata.js +27 -6
  49. package/tx/workers/search.js +7 -1
  50. package/tx/workers/validate.js +30 -18
  51. package/tx/workers/worker.js +30 -7
  52. package/tx/xversion/xv-valueset.js +5 -2
  53. package/xig/xig.js +3 -3
@@ -0,0 +1,9 @@
1
+ {
2
+ "compilerOptions": {
3
+ "allowJs": true, "checkJs": false, "noEmit": true,
4
+ "moduleResolution": "node", "target": "es2022", "module": "commonjs",
5
+ "strict": false, "skipLibCheck": true
6
+ },
7
+ "include": ["**/*.js"],
8
+ "exclude": ["node_modules", "static", "**/*.test.js", "tests", "coverage"]
9
+ }
package/tx/cs/cs-api.js CHANGED
@@ -118,6 +118,19 @@ class CodeSystemProvider {
118
118
  */
119
119
  totalCount() { throw new Error("Must override"); }
120
120
 
121
+ /**
122
+ * The number of codes an "all codes" expansion of this code system would
123
+ * contain, computed WITHOUT materialising them - used by the count=0 (count
124
+ * only) expansion fast path. `excludeInactive` mirrors the expansion's
125
+ * active-only handling. Defaults to totalCount(); providers that distinguish
126
+ * active from inactive override this to honour excludeInactive.
127
+ * @param {boolean} excludeInactive
128
+ * @returns {integer}
129
+ */
130
+ async countAllCodes(excludeInactive) { // eslint-disable-line no-unused-vars
131
+ return await this.totalCount();
132
+ }
133
+
121
134
  /**
122
135
  * @returns {CodeSystem.property[]} defined properties for the code system
123
136
  */
@@ -131,6 +144,18 @@ class CodeSystemProvider {
131
144
  return false;
132
145
  }
133
146
 
147
+ /**
148
+ * returns false if the code system cannot be iterated / enumerated in any
149
+ * meaningful sense - as distinct from isNotClosed(), which marks a code system
150
+ * that has a grammar whose base set may still be partially expanded. An
151
+ * expansion that would need to iterate a system for which this is false
152
+ * returns an OperationOutcome rather than attempting the iteration.
153
+ * @returns {boolean}
154
+ */
155
+ canBeExpanded() {
156
+ return true;
157
+ }
158
+
134
159
  /**
135
160
  * returns true if the code system is case sensitive when comparing codes.
136
161
  * this is true by default
@@ -372,11 +397,21 @@ class CodeSystemProvider {
372
397
  }
373
398
 
374
399
  /**
400
+ * List the designations for a concept, adding them to `displays`.
401
+ *
375
402
  * @param {string | CodeSystemProviderContext} code
376
- * @param {ConceptDesignations} designation list
403
+ * @param {ConceptDesignations} displays - designation list to add to
404
+ * @param {boolean} [significantOnly=false] - when true, emit only the
405
+ * designations that are significant for a value set expansion (the display,
406
+ * and - for code systems that distinguish them - the fully specified name
407
+ * and the preferred term), rather than every designation in every language.
408
+ * The $expand worker passes true; $lookup uses the default (false) to return
409
+ * the complete set. Providers that do not distinguish significant
410
+ * designations may ignore this parameter and return all designations either
411
+ * way.
377
412
  * @returns {Designation[]} whatever designations exist (in all languages)
378
413
  */
379
- async designations(code, displays) { return null; }
414
+ async designations(code, displays, significantOnly = false) { return null; }
380
415
 
381
416
  _listSupplementDesignations(code, displays) {
382
417
  assert(typeof code === 'string', 'code must be string');
@@ -582,9 +617,27 @@ class CodeSystemProvider {
582
617
  * @param {boolean} forIteration - whether this filter is going to be iterated
583
618
  * @param {String} prop
584
619
  * @param {ValueSetFilterOperator} op
585
- * @param {String} prop
620
+ * @param {String} value
621
+ * @param {Object} [fc] the ValueSet compose filter element
622
+ * (ValueSet.compose.include.filter) this filter was derived from. It is a stable
623
+ * sub-object of the (cached) ValueSet resource, so a provider MAY use it as a place
624
+ * to memoise the resolved analysis of this filter (e.g. the enumerated concept set),
625
+ * which is then reused on subsequent expand/validate calls for as long as the
626
+ * ValueSet stays cached - trading memory for the cost of re-preparing the filter.
627
+ *
628
+ * Two rules govern any such cache:
629
+ * - It MUST be pinned to the resolved code system VERSION. The same ValueSet can be
630
+ * resolved against different versions/editions, and a resolved filter (a set of
631
+ * version-specific concept references) is only valid for the version it was
632
+ * computed against.
633
+ * - It must NOT be pinned to forIteration. The same ValueSet is used both ways
634
+ * (iterated for $expand, non-iterated for validate), so if the iterated and
635
+ * non-iterated resolved forms differ, store BOTH rather than assuming one.
636
+ *
637
+ * Providers that gain nothing from this (the SQL-backed providers, and the trivial
638
+ * in-memory enumerations) simply ignore fc.
586
639
  **/
587
- async filter(filterContext, forIteration, prop, op, value) { throw new Error("Must override"); } // well, only if any filters are actually supported
640
+ async filter(filterContext, forIteration, prop, op, value, fc) { throw new Error("Must override"); } // well, only if any filters are actually supported
588
641
 
589
642
  /**
590
643
  * called once all the filters have been handled, and iteration is about to happen.
package/tx/cs/cs-cs.js CHANGED
@@ -8,6 +8,17 @@ const {Extensions} = require("../library/extensions");
8
8
  const {BaseCSServices} = require("./cs-base");
9
9
  const regexUtilities = require("../../library/regex-utilities");
10
10
 
11
+ // Symbol key under which resolved filter analysis is memoised directly on the
12
+ // ValueSet compose.include.filter element (`fc`). Symbol-keyed and defined
13
+ // non-enumerable, so it never serialises, clones (structuredClone), or spreads
14
+ // into a response - it simply lives and dies with the filter element (and thus
15
+ // the cached ValueSet). The value is Map(codeSystemVersionKey -> concepts[]):
16
+ // the resolved concept list is only valid for the code system version it was
17
+ // computed against. Only the expensive full-scan / subtree-walk filters are
18
+ // cached (see _isCacheableFilter); cheap ones (=, in, child-of) are not. cs-cs
19
+ // filter results do not depend on forIteration, so one form is stored per version.
20
+ const FILTER_ANALYSIS = Symbol('filterAnalysis');
21
+
11
22
  /**
12
23
  * Context class for FHIR CodeSystem provider concepts
13
24
  */
@@ -1218,40 +1229,70 @@ class FhirCodeSystemProvider extends BaseCSServices {
1218
1229
  * @param {string} value - Filter value
1219
1230
  * @returns {Promise<FhirCodeSystemProviderFilterContext>} Filter results
1220
1231
  */
1221
- async filter(filterContext, forIteration, prop, op, value) {
1222
-
1223
-
1232
+ async filter(filterContext, forIteration, prop, op, value, fc) {
1224
1233
  let results = null;
1225
1234
 
1226
- // Handle concept/code hierarchy filters
1227
- if ((prop === 'concept' || prop === 'code')) {
1228
- results = await this._handleConceptFilter(filterContext, prop, op, value);
1235
+ // Reuse previously resolved analysis when this same cached value set filter
1236
+ // is resolved again against the same code system version (see filter()/fc).
1237
+ // Only expensive filters are cached; the concept list is shared read-only,
1238
+ // wrapped in a fresh context so iteration state (the cursor) is per-use.
1239
+ // Caching needs a stable code system identity. When the code system has no
1240
+ // url (odd corner cases) _filterCacheVersionKey returns null and caching is
1241
+ // skipped entirely - there's no safe key to store or look up under.
1242
+ const vkey = fc ? this._filterCacheVersionKey() : null;
1243
+ const cacheable = vkey !== null && this._isCacheableFilter(prop, op);
1244
+ if (cacheable) {
1245
+ const byVersion = fc[FILTER_ANALYSIS];
1246
+ const cachedConcepts = byVersion ? byVersion.get(vkey) : null;
1247
+ if (cachedConcepts) {
1248
+ results = new FhirCodeSystemProviderFilterContext();
1249
+ results.concepts = cachedConcepts; // immutable: read-only from here
1250
+ }
1229
1251
  }
1230
1252
 
1231
- // Handle child existence filter
1232
- if (prop === 'child' && op === 'exists') {
1233
- results = await this._handleChildExistsFilter(filterContext, value);
1234
- }
1253
+ if (!results) {
1254
+ // Handle concept/code hierarchy filters
1255
+ if ((prop === 'concept' || prop === 'code')) {
1256
+ results = await this._handleConceptFilter(filterContext, prop, op, value);
1257
+ }
1235
1258
 
1236
- // Handle property-based filters
1237
- const propertyDefs = this.propertyDefinitions();
1238
- if (propertyDefs) {
1239
- const propertyDef = propertyDefs.find(p => p.code === prop);
1240
- if (propertyDef) {
1241
- results = await this._handlePropertyFilter(filterContext, propertyDef, op, value);
1259
+ // Handle child existence filter
1260
+ if (prop === 'child' && op === 'exists') {
1261
+ results = await this._handleChildExistsFilter(filterContext, value);
1242
1262
  }
1243
- }
1244
1263
 
1245
- // Handle known special properties
1246
- const knownProperties = ['notSelectable', 'status', 'inactive', 'deprecated'];
1247
- if (knownProperties.includes(prop)) {
1248
- results = await this._handleKnownPropertyFilter(filterContext, prop, op, value);
1249
- }
1264
+ // Handle property-based filters
1265
+ const propertyDefs = this.propertyDefinitions();
1266
+ if (propertyDefs) {
1267
+ const propertyDef = propertyDefs.find(p => p.code === prop);
1268
+ if (propertyDef) {
1269
+ results = await this._handlePropertyFilter(filterContext, propertyDef, op, value);
1270
+ }
1271
+ }
1250
1272
 
1251
- if (!results) {
1252
- throw new Issue('error', 'exception', null, 'FILTER_NOT_UNDERSTOOD',
1253
- this.opContext.i18n.translate('FILTER_NOT_UNDERSTOOD', this.opContext.langs, [prop, op, value]), 'vs-invalid', 422);
1273
+ // Handle known special properties
1274
+ const knownProperties = ['notSelectable', 'status', 'inactive', 'deprecated'];
1275
+ if (knownProperties.includes(prop)) {
1276
+ results = await this._handleKnownPropertyFilter(filterContext, prop, op, value);
1277
+ }
1278
+
1279
+ if (!results) {
1280
+ throw new Issue('error', 'exception', null, 'FILTER_NOT_UNDERSTOOD',
1281
+ this.opContext.i18n.translate('FILTER_NOT_UNDERSTOOD', this.opContext.langs, [prop, op, value]), 'vs-invalid', 422);
1282
+ }
1283
+
1284
+ // Store a private copy of the resolved concept list for reuse. A slice
1285
+ // isolates the cache from any later mutation of this request's context.
1286
+ if (cacheable) {
1287
+ let byVersion = fc[FILTER_ANALYSIS];
1288
+ if (!byVersion) {
1289
+ byVersion = new Map();
1290
+ Object.defineProperty(fc, FILTER_ANALYSIS, { value: byVersion, enumerable: false, configurable: true, writable: true });
1291
+ }
1292
+ byVersion.set(vkey, results.concepts.slice());
1293
+ }
1254
1294
  }
1295
+
1255
1296
  // Add to filter context
1256
1297
  if (!filterContext.filters) {
1257
1298
  filterContext.filters = [];
@@ -1261,6 +1302,38 @@ class FhirCodeSystemProvider extends BaseCSServices {
1261
1302
  return results;
1262
1303
  }
1263
1304
 
1305
+ /**
1306
+ * Whether a filter is expensive enough to be worth memoising: the full-scan
1307
+ * or subtree-walk families. Cheap lookups (=, in on a code list, child-of)
1308
+ * are not cached. Mirrors the dispatch in filter().
1309
+ * @private
1310
+ */
1311
+ _isCacheableFilter(prop, op) {
1312
+ if (prop === 'concept' || prop === 'code') {
1313
+ return op === 'is-a' || op === 'descendent-of' || op === 'is-not-a' || op === 'regex';
1314
+ }
1315
+ if (prop === 'child' && op === 'exists') {
1316
+ return true;
1317
+ }
1318
+ const propertyDefs = this.propertyDefinitions();
1319
+ if (propertyDefs && propertyDefs.find(p => p.code === prop)) {
1320
+ return true;
1321
+ }
1322
+ return ['notSelectable', 'status', 'inactive', 'deprecated'].includes(prop);
1323
+ }
1324
+
1325
+ /**
1326
+ * Cache key pinning resolved analysis to the code system identity + version.
1327
+ * @private
1328
+ */
1329
+ _filterCacheVersionKey() {
1330
+ const url = this.codeSystem.jsonObj.url;
1331
+ if (!url) {
1332
+ return null;
1333
+ }
1334
+ return url + '|' + (this.codeSystem.jsonObj.version || '');
1335
+ }
1336
+
1264
1337
  /**
1265
1338
  * Handle concept/code filters (is-a, descendent-of, etc.)
1266
1339
  * @param {FilterExecutionContext} filterContext - Filter context
package/tx/cs/cs-hgvs.js CHANGED
@@ -42,6 +42,10 @@ class HGVSServices extends CodeSystemProvider {
42
42
  return null;
43
43
  }
44
44
 
45
+ canBeExpanded() {
46
+ return false; // HGVS is a grammar validated externally; its codes cannot be iterated
47
+ }
48
+
45
49
  defaultToLatest() {
46
50
  return true;
47
51
  }
package/tx/cs/cs-lang.js CHANGED
@@ -430,6 +430,14 @@ class IETFLanguageCodeProvider extends CodeSystemProvider {
430
430
  return null;
431
431
  }
432
432
 
433
+ specialEnumeration() {
434
+ // The BCP-47 language grammar is unbounded, but a useful base expansion is
435
+ // the common languages enumerated by the THO "Languages" value set. A whole-
436
+ // code-system expansion returns that value set marked as an incomplete
437
+ // (unclosed) expansion, mirroring how UCUM uses its common-units value set.
438
+ return 'http://terminology.hl7.org/ValueSet/Languages';
439
+ }
440
+
433
441
  isNotClosed() {
434
442
  return true;
435
443
  }
@@ -188,6 +188,10 @@ class MimeTypeServices extends CodeSystemProvider {
188
188
  return true;
189
189
  }
190
190
 
191
+ canBeExpanded() {
192
+ return false; // MIME types cannot be iterated / enumerated
193
+ }
194
+
191
195
  }
192
196
 
193
197
  class MimeTypeServicesFactory extends CodeSystemFactoryProvider {