fhirsmith 0.14.1 → 0.15.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 (74) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +3 -2
  3. package/config-template.json +9 -0
  4. package/library/changelog.js +57 -0
  5. package/library/fhir-openapi-schema.js +441 -0
  6. package/library/html-server.js +9 -3
  7. package/library/npm-audit.js +265 -0
  8. package/library/openapi-doc.js +557 -0
  9. package/library/utilities.js +21 -0
  10. package/package.json +10 -4
  11. package/packages/openapi.js +13 -0
  12. package/packages/openapi.yaml +603 -0
  13. package/packages/packages-template.html +4 -2
  14. package/packages/packages.js +286 -81
  15. package/packages/readme.md +120 -0
  16. package/registry/api.js +117 -41
  17. package/registry/crawler.js +41 -0
  18. package/registry/fhirsmith-releases.js +167 -0
  19. package/registry/model.js +13 -0
  20. package/registry/openapi.js +13 -0
  21. package/registry/openapi.yaml +311 -0
  22. package/registry/readme.md +107 -208
  23. package/registry/registry-template.html +4 -1
  24. package/registry/registry.js +198 -19
  25. package/server.js +19 -5
  26. package/testing/openapi-schemas.config.js +58 -0
  27. package/testing/openapi-schemas.json +1920 -0
  28. package/testing/openapi.js +15 -0
  29. package/testing/openapi.yaml +273 -0
  30. package/testing/readme.md +43 -1
  31. package/testing/search.js +1 -1
  32. package/testing/testing-template.html +2 -0
  33. package/testing/testing.js +24 -0
  34. package/translations/Messages.properties +1 -0
  35. package/tx/README.md +78 -4
  36. package/tx/cm/cm-api.js +25 -10
  37. package/tx/cm/cm-database.js +10 -5
  38. package/tx/cm/cm-package.js +61 -15
  39. package/tx/cs/cs-provider-api.js +22 -7
  40. package/tx/cs/cs-provider-list.js +10 -9
  41. package/tx/cs/cs-rxnorm.js +23 -6
  42. package/tx/html/tx-template.html +2 -0
  43. package/tx/library/canonical-resource.js +50 -1
  44. package/tx/library/codesystem.js +1 -0
  45. package/tx/library/conceptmap.js +1 -0
  46. package/tx/library/resource-ids.js +107 -0
  47. package/tx/library/valueset.js +16 -0
  48. package/tx/library.js +113 -26
  49. package/tx/ocl/cm-ocl.cjs +23 -15
  50. package/tx/ocl/cs-ocl.cjs +9 -10
  51. package/tx/ocl/vs-ocl.cjs +17 -17
  52. package/tx/openapi-operations.js +363 -0
  53. package/tx/openapi-schemas.config.js +118 -0
  54. package/tx/openapi-schemas.json +7903 -0
  55. package/tx/openapi.js +289 -0
  56. package/tx/openapi.yaml +151 -0
  57. package/tx/operation-context.js +15 -0
  58. package/tx/params.js +3 -12
  59. package/tx/provider.js +90 -14
  60. package/tx/tx-html.js +6 -0
  61. package/tx/tx.fhir.org.yml +16 -8
  62. package/tx/tx.js +62 -1
  63. package/tx/vs/vs-api.js +25 -10
  64. package/tx/vs/vs-database.js +14 -9
  65. package/tx/vs/vs-package.js +58 -31
  66. package/tx/vs/vs-vsac.js +20 -5
  67. package/tx/workers/expand.js +35 -16
  68. package/tx/workers/metadata.js +2 -14
  69. package/tx/workers/read.js +3 -18
  70. package/tx/workers/validate.js +65 -19
  71. package/tx/workers/worker.js +29 -8
  72. package/utilities/extract-schema-json.js +167 -0
  73. package/utilities/generate-openapi-schemas.js +49 -0
  74. package/xig/xig.js +2 -4
@@ -285,7 +285,10 @@ class ValueSetChecker {
285
285
  }
286
286
 
287
287
  seeValueSet() {
288
- this.worker.opContext.seeContext(this.valueSet.vurl);
288
+ const key = this.valueSet.contextKey !== undefined ? this.valueSet.contextKey : this.valueSet.vurl;
289
+ if (key) {
290
+ this.worker.opContext.seeContext(key);
291
+ }
289
292
  if (this.valueSet.jsonObj.compose && this.valueSet.jsonObj.compose.extension) {
290
293
  for (let ext of this.valueSet.jsonObj.compose.extension) {
291
294
  if (ext.url === 'http://hl7.org/fhir/StructureDefinition/valueset-expansion-parameter' || ext.url === 'http://hl7.org/fhir/tools/StructureDefinition/valueset-expansion-parameter') {
@@ -305,8 +308,22 @@ class ValueSetChecker {
305
308
  async prepare() {
306
309
  if (this.valueSet === null) {
307
310
  throw new Issue('error', 'not-found', null, null, 'Error Error: vs = nil', null, 422);
308
- } else {
309
- this.seeValueSet();
311
+ }
312
+ // circular reference detection: this value set is in the chain being processed until
313
+ // it's prepared (which prepares everything it imports)
314
+ this.seeValueSet();
315
+ try {
316
+ await this.prepareSeen();
317
+ } finally {
318
+ const key = this.valueSet.contextKey !== undefined ? this.valueSet.contextKey : this.valueSet.vurl;
319
+ if (key) {
320
+ this.worker.opContext.unseeContext(key);
321
+ }
322
+ }
323
+ }
324
+
325
+ async prepareSeen() {
326
+ {
310
327
  this.worker.opContext.addNote(this.valueSet, 'Analysing ' + this.valueSet.vurl + ' for validation purposes', this.indentCount);
311
328
  if (this.indentCount === 0) {
312
329
  this.worker.opContext.addNote(this.valueSet, 'Parameters: ' + this.params.summary(), this.indentCount);
@@ -569,9 +586,14 @@ class ValueSetChecker {
569
586
  if (msg) {
570
587
  op.addIssue(new Issue('information', 'informational', addToPath(path, 'code'), null, msg, 'process-note'));
571
588
  }
572
- inactive.value = await cs.isInactive(ctxt.context);
573
- inactive.path = path;
574
- vstatus.value = await cs.getStatus(ctxt.context);
589
+ // in a CodeableConcept a later coding must not clear the inactive status an earlier
590
+ // coding found, or the warning silently disappears when the inactive coding comes first
591
+ if (!inactive.value) {
592
+ inactive.value = await cs.isInactive(ctxt.context);
593
+ inactive.path = path;
594
+ inactive.code = code;
595
+ vstatus.value = await cs.getStatus(ctxt.context);
596
+ }
575
597
  }
576
598
  if (displays !== null) {
577
599
  await this.worker.listDisplaysFromCodeSystem(displays, cs, ctxt.context);
@@ -925,6 +947,7 @@ class ValueSetChecker {
925
947
  let pdisp;
926
948
  let psys;
927
949
  let pcode;
950
+ let ppath; // the path of the coding that is returned
928
951
  let pver;
929
952
 
930
953
 
@@ -1063,6 +1086,7 @@ class ValueSetChecker {
1063
1086
  if (pcode === undefined) {
1064
1087
  psys = c.system;
1065
1088
  pcode = c.code;
1089
+ ppath = path;
1066
1090
  if (ver.value) {
1067
1091
  pver = ver.value;
1068
1092
  }
@@ -1185,6 +1209,7 @@ class ValueSetChecker {
1185
1209
  if (!this.params.membershipOnly && !inactive.value && await prov.isInactive(ctxt.context)) {
1186
1210
  inactive.value = true;
1187
1211
  inactive.path = path;
1212
+ inactive.code = c.code;
1188
1213
  const st = await prov.getStatus(ctxt.context);
1189
1214
  if (st) {
1190
1215
  vstatus.value = st;
@@ -1340,9 +1365,14 @@ class ValueSetChecker {
1340
1365
  }
1341
1366
 
1342
1367
  if (inactive.value) {
1343
- result.addParamBool('inactive', inactive.value);
1344
- if (vstatus.value && vstatus.value !== 'inactive') {
1345
- result.addParamCode('status', vstatus.value);
1368
+ // the inactive and status parameters describe the code being returned. In a CodeableConcept
1369
+ // the inactive coding may be a different one (or none was returned at all): it still gets
1370
+ // its warning below, but the parameters are not claimed for the returned code
1371
+ if (mode !== 'codeableConcept' || (pcode && inactive.path === ppath)) {
1372
+ result.addParamBool('inactive', inactive.value);
1373
+ if (vstatus.value && vstatus.value !== 'inactive') {
1374
+ result.addParamCode('status', vstatus.value);
1375
+ }
1346
1376
  }
1347
1377
  let mpath = inactive.path;
1348
1378
  if (!mpath) {
@@ -1354,7 +1384,9 @@ class ValueSetChecker {
1354
1384
  mid1 = 'INACTIVE_CONCEPT_FOUND_ADD';
1355
1385
  mm1 = 'inactive';
1356
1386
  }
1357
- let m = this.worker.i18n.translate(mid1, this.params.HTTPLanguages, [vstatus.value, tcode, mm1]);
1387
+ // report the code that is actually inactive - in a CodeableConcept that isn't necessarily
1388
+ // the first coding (tcode), which is what this message used to name
1389
+ let m = this.worker.i18n.translate(mid1, this.params.HTTPLanguages, [vstatus.value, inactive.code || tcode, mm1]);
1358
1390
  msg(m);
1359
1391
  op.addIssue(new Issue('warning', 'business-rule', mpath, 'INACTIVE_CONCEPT_FOUND', m, 'code-comment'));
1360
1392
  } else if (vstatus.value && vstatus.value.toLowerCase() === 'deprecated') {
@@ -1548,6 +1580,7 @@ class ValueSetChecker {
1548
1580
  if (!this.params.membershipOnly && role !== 'not in') {
1549
1581
  inactive.value = true;
1550
1582
  inactive.path = path;
1583
+ inactive.code = code;
1551
1584
  if (inactive.value) {
1552
1585
  vstatus.value = await cs.getStatus(loc.context);
1553
1586
  }
@@ -1558,6 +1591,7 @@ class ValueSetChecker {
1558
1591
  if (role !== 'not in') {
1559
1592
  inactive.value = true;
1560
1593
  inactive.path = path;
1594
+ inactive.code = code;
1561
1595
  vstatus.value = await cs.getStatus(loc.context);
1562
1596
  let msg = this.worker.i18n.translate('STATUS_CODE_WARNING_CODE', this.params.HTTPLanguages, ['not active', code]);
1563
1597
  messages.push(msg);
@@ -1566,9 +1600,12 @@ class ValueSetChecker {
1566
1600
  } else {
1567
1601
  result = true;
1568
1602
  if (role !== 'not in') {
1569
- inactive.value = await cs.isInactive(loc.context);
1570
- inactive.path = path;
1571
- vstatus.value = await cs.getStatus(loc.context);
1603
+ if (!inactive.value) {
1604
+ inactive.value = await cs.isInactive(loc.context);
1605
+ inactive.path = path;
1606
+ inactive.code = code;
1607
+ vstatus.value = await cs.getStatus(loc.context);
1608
+ }
1572
1609
  if (vcc !== null) {
1573
1610
  if (!vcc.coding) {
1574
1611
  vcc.coding = [];
@@ -1605,6 +1642,7 @@ class ValueSetChecker {
1605
1642
  if (!this.params.membershipOnly) {
1606
1643
  inactive.value = true;
1607
1644
  inactive.path = path;
1645
+ inactive.code = code;
1608
1646
  vstatus.value = await cs.getStatus(loc);
1609
1647
  }
1610
1648
  } else {
@@ -1618,9 +1656,12 @@ class ValueSetChecker {
1618
1656
  } else if (Extensions.has(cc,'http://hl7.org/fhir/StructureDefinition/valueset-deprecated')) {
1619
1657
  op.addIssue(new Issue('warning', 'business-rule', addToPath(path, 'code'), 'CONCEPT_DEPRECATED_IN_VALUESET', this.worker.i18n.translate('CONCEPT_DEPRECATED_IN_VALUESET', this.params.HTTPLanguages, [cs.system(), code, 'deprecated', vs.vurl]), 'code-comment'));
1620
1658
  }
1621
- inactive.value = await cs.isInactive(loc);
1622
- inactive.path = path;
1623
- vstatus.value = await cs.getStatus(loc);
1659
+ if (!inactive.value) {
1660
+ inactive.value = await cs.isInactive(loc);
1661
+ inactive.path = path;
1662
+ inactive.code = code;
1663
+ vstatus.value = await cs.getStatus(loc);
1664
+ }
1624
1665
  result = true;
1625
1666
  return result;
1626
1667
  }
@@ -1669,6 +1710,7 @@ class ValueSetChecker {
1669
1710
  if (!this.params.membershipOnly) {
1670
1711
  inactive.value = true;
1671
1712
  inactive.path = path;
1713
+ inactive.code = code;
1672
1714
  vstatus.value = await cs.getStatus(loc);
1673
1715
  }
1674
1716
  } else {
@@ -1681,6 +1723,7 @@ class ValueSetChecker {
1681
1723
  if (await cs.isInactive(loc)) {
1682
1724
  inactive.value = true;
1683
1725
  inactive.path = path;
1726
+ inactive.code = code;
1684
1727
  // only replace a status we already have with a real one - a filter context
1685
1728
  // doesn't always carry the status (SNOMED, LOINC), and the assembly reads
1686
1729
  // vstatus for the message wording as well as for the status parameter
@@ -1718,9 +1761,12 @@ class ValueSetChecker {
1718
1761
  }
1719
1762
  } else {
1720
1763
  result = true;
1721
- inactive.value = await cs.isInactive(loc.context);
1722
- inactive.path = path;
1723
- vstatus.value = await cs.getStatus(loc.context);
1764
+ if (!inactive.value) {
1765
+ inactive.value = await cs.isInactive(loc.context);
1766
+ inactive.path = path;
1767
+ inactive.code = code;
1768
+ vstatus.value = await cs.getStatus(loc.context);
1769
+ }
1724
1770
  await this.worker.listDisplaysFromCodeSystem(displays, cs, loc.context);
1725
1771
  return result;
1726
1772
  }
@@ -379,14 +379,22 @@ class TerminologyWorker {
379
379
  return null;
380
380
  }
381
381
  if (url.startsWith("#")) {
382
+ // A local reference: per FHIR, #id is resolved against the contained resources of
383
+ // the resource being processed - which, when that is itself a contained value set,
384
+ // means its container's (contained resources are all siblings in one list)
382
385
  if (source) {
383
- if (source.jsonObj) {
384
- source = source.jsonObj;
385
- }
386
- for (const contained of source.contained || []) {
387
- if (contained.id === url.substring(1)) {
386
+ const container = source.container || source;
387
+ const json = container.jsonObj || container;
388
+ for (const contained of json.contained || []) {
389
+ if (contained && contained.id === url.substring(1)) {
390
+ if (contained.resourceType !== 'ValueSet') {
391
+ // the resource wrappers already reject this; this is the second line of defence
392
+ throw new Issue('error', 'not-supported', null, 'CONTAINED_RESOURCE_NOT_SUPPORTED',
393
+ `The reference '${url}' is to a contained ${contained.resourceType}, not a ValueSet`, 'not-supported', 400);
394
+ }
388
395
  const ret = this.wrapRawResource(contained);
389
396
  ret.isContained = true;
397
+ ret.container = container.jsonObj ? container : this.wrapRawResource(container);
390
398
  return ret;
391
399
  }
392
400
  }
@@ -477,11 +485,24 @@ class TerminologyWorker {
477
485
  * Note: this is the caller's parameters, not this.params - the worker's own
478
486
  * params is null when expanding an imported ValueSet
479
487
  */
488
+ /** The circular reference key seeValueSet tracks for a value set */
489
+ valueSetContextKey(vs) {
490
+ return vs.contextKey !== undefined ? vs.contextKey : (vs.url ? (vs.url + (vs.version ? '|' + vs.version : '')) : null);
491
+ }
492
+
493
+ /** Finished processing a value set: see OperationContext.unseeContext */
494
+ unseeValueSet(vs) {
495
+ const key = this.valueSetContextKey(vs);
496
+ if (key) {
497
+ this.opContext.unseeContext(key);
498
+ }
499
+ }
500
+
480
501
  seeValueSet(vs, params) {
481
502
  // Build canonical URL from url and version
482
- const vurl = vs.url ? (vs.url + (vs.version ? '|' + vs.version : '')) : null;
483
- if (vurl) {
484
- this.opContext.seeContext(vurl);
503
+ const key = this.valueSetContextKey(vs);
504
+ if (key) {
505
+ this.opContext.seeContext(key);
485
506
  }
486
507
  // Check for expansion parameter extensions on compose
487
508
  if (vs.jsonObj.compose && vs.jsonObj.compose.extension) {
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env node
2
+ //
3
+ // Copyright 2026, Health Intersections Pty Ltd (http://www.healthintersections.com.au)
4
+ //
5
+ // Licensed under BSD-3: https://opensource.org/license/bsd-3-clause
6
+ //
7
+
8
+ // The published FHIR specification has a page per resource for its JSON schema
9
+ // (e.g. testreport.schema.json.html), but not the schema itself as a file, so links to
10
+ // testreport.schema.json fail. This walks a tree of published specifications and, for each
11
+ // X.schema.json.html, takes the content of its <pre> element and writes it as X.schema.json
12
+ // beside it.
13
+ //
14
+ // node utilities/extract-schema-json.js <dir> [<dir> ...] [--dry-run] [--force] [--verbose]
15
+ //
16
+ // --dry-run report what would be written, but write nothing
17
+ // --force overwrite an existing X.schema.json whose content differs (by default it's
18
+ // left alone and reported)
19
+ // --verbose list every file written or skipped
20
+ // --no-recurse only look in the named directories themselves, not below them
21
+ //
22
+ // It can be stopped and run again: files already written are reported as up to date.
23
+ //
24
+ // A page is skipped (and reported) if it doesn't have exactly one <pre>, or if the content
25
+ // isn't valid JSON. Symbolic links are not followed. Exit status is 1 if anything was
26
+ // skipped for a problem, so it can be run in a script.
27
+
28
+ const fs = require('fs');
29
+ const path = require('path');
30
+
31
+ const SUFFIX = '.schema.json.html';
32
+
33
+ const NAMED_ENTITIES = { amp: '&', lt: '<', gt: '>', quot: '"', apos: '\'', nbsp: ' ' };
34
+
35
+ function decodeEntities(text) {
36
+ return text.replace(/&(#x[0-9a-f]+|#[0-9]+|[a-z]+);/gi, (m, e) => {
37
+ if (e[0] === '#') {
38
+ const code = e[1] === 'x' || e[1] === 'X' ? parseInt(e.substring(2), 16) : parseInt(e.substring(1), 10);
39
+ return Number.isFinite(code) ? String.fromCodePoint(code) : m;
40
+ }
41
+ const v = NAMED_ENTITIES[e.toLowerCase()];
42
+ return v === undefined ? m : v;
43
+ });
44
+ }
45
+
46
+ /**
47
+ * The text with the markup removed. Repeated until nothing changes, so that removing one tag
48
+ * can't leave another behind (<a<b>>).
49
+ */
50
+ function stripTags(html) {
51
+ let text = html;
52
+ let previous;
53
+ do {
54
+ previous = text;
55
+ text = text.replace(/<[^<>]*>/g, '');
56
+ } while (text !== previous);
57
+ return text;
58
+ }
59
+
60
+ /**
61
+ * The schema in a page: the text of its one <pre> element, with any markup removed and
62
+ * entities decoded.
63
+ *
64
+ * @param {string} html
65
+ * @returns {{json?: string, problem?: string}}
66
+ */
67
+ function extractSchema(html) {
68
+ const pres = [...html.matchAll(/<pre\b[^>]*>([\s\S]*?)<\/pre>/gi)];
69
+ if (pres.length !== 1) {
70
+ return { problem: pres.length === 0 ? 'no <pre> element' : `${pres.length} <pre> elements` };
71
+ }
72
+ // entities are decoded after the markup is gone: a decoded < is the schema's own text
73
+ const text = decodeEntities(stripTags(pres[0][1])).trim() + '\n';
74
+ try {
75
+ JSON.parse(text);
76
+ } catch (e) {
77
+ return { problem: 'the <pre> content is not valid JSON: ' + e.message };
78
+ }
79
+ return { json: text };
80
+ }
81
+
82
+ async function* walk(dir, recurse = true) {
83
+ let handle;
84
+ try {
85
+ handle = await fs.promises.opendir(dir);
86
+ } catch (e) {
87
+ console.error(`Cannot read ${dir}: ${e.message}`);
88
+ return;
89
+ }
90
+ for await (const entry of handle) {
91
+ const p = path.join(dir, entry.name);
92
+ if (entry.isDirectory()) {
93
+ if (recurse) {
94
+ yield* walk(p);
95
+ }
96
+ } else if (entry.isFile() && entry.name.endsWith(SUFFIX)) {
97
+ yield p;
98
+ }
99
+ }
100
+ }
101
+
102
+ async function run(roots, options = {}) {
103
+ const counts = { pages: 0, written: 0, unchanged: 0, different: 0, problems: 0 };
104
+ const log = (msg) => options.verbose && console.log(msg);
105
+ for (const root of roots) {
106
+ for await (const page of walk(root, !options.noRecurse)) {
107
+ counts.pages++;
108
+ const target = page.slice(0, -'.html'.length);
109
+ const { json, problem } = extractSchema(await fs.promises.readFile(page, 'utf8'));
110
+ if (problem) {
111
+ counts.problems++;
112
+ console.warn(`${page}: ${problem}`);
113
+ continue;
114
+ }
115
+ let existing = null;
116
+ try {
117
+ existing = await fs.promises.readFile(target, 'utf8');
118
+ } catch (e) {
119
+ // not there yet
120
+ }
121
+ if (existing === json) {
122
+ counts.unchanged++;
123
+ continue;
124
+ }
125
+ if (existing !== null && !options.force) {
126
+ counts.different++;
127
+ console.warn(`${target}: exists with different content - left alone (--force to overwrite)`);
128
+ continue;
129
+ }
130
+ if (!options.dryRun) {
131
+ await fs.promises.writeFile(target, json);
132
+ }
133
+ counts.written++;
134
+ log(`${options.dryRun ? 'would write' : 'wrote'} ${target}`);
135
+ }
136
+ }
137
+ return counts;
138
+ }
139
+
140
+ async function main(args) {
141
+ const options = {
142
+ dryRun: args.includes('--dry-run'),
143
+ force: args.includes('--force'),
144
+ verbose: args.includes('--verbose'),
145
+ noRecurse: args.includes('--no-recurse')
146
+ };
147
+ const roots = args.filter(a => !a.startsWith('--'));
148
+ if (roots.length === 0) {
149
+ console.error('usage: extract-schema-json.js <dir> [<dir> ...] [--dry-run] [--force] [--verbose] [--no-recurse]');
150
+ process.exit(2);
151
+ }
152
+ const start = Date.now();
153
+ const c = await run(roots, options);
154
+ console.log(`${c.pages} schema pages: ${c.written} ${options.dryRun ? 'to write' : 'written'}, ` +
155
+ `${c.unchanged} already up to date, ${c.different} existing and different (left alone), ` +
156
+ `${c.problems} with problems (${((Date.now() - start) / 1000).toFixed(1)}s)`);
157
+ process.exit(c.problems > 0 ? 1 : 0);
158
+ }
159
+
160
+ if (require.main === module) {
161
+ main(process.argv.slice(2)).catch((e) => {
162
+ console.error(e);
163
+ process.exit(1);
164
+ });
165
+ }
166
+
167
+ module.exports = { extractSchema, decodeEntities, run };
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+ //
3
+ // Copyright 2026, Health Intersections Pty Ltd (http://www.healthintersections.com.au)
4
+ //
5
+ // Licensed under BSD-3: https://opensource.org/license/bsd-3-clause
6
+ //
7
+
8
+ // Generates a module's FHIR component schemas for its OpenAPI description, from the
9
+ // StructureDefinitions in a FHIR package (see library/fhir-openapi-schema.js).
10
+ //
11
+ // node utilities/generate-openapi-schemas.js <module> [-package <dir>]
12
+ //
13
+ // reads <module>/openapi-schemas.config.js and writes <module>/openapi-schemas.json. The
14
+ // package is found in the server's terminology cache unless -package names the unpacked
15
+ // package directory.
16
+
17
+ const fs = require('fs');
18
+ const path = require('path');
19
+ const { generateSchemas } = require('../library/fhir-openapi-schema');
20
+
21
+ function packageDir(id) {
22
+ const folders = require('../library/folder-setup');
23
+ return folders.filePath('terminology-cache', id, 'package');
24
+ }
25
+
26
+ function main(args) {
27
+ const module = args[0];
28
+ if (!module) {
29
+ console.error('usage: generate-openapi-schemas.js <module> [-package <dir>]');
30
+ process.exit(2);
31
+ }
32
+ const moduleDir = path.join(__dirname, '..', module);
33
+ const config = require(path.join(moduleDir, 'openapi-schemas.config.js'));
34
+ const i = args.indexOf('-package');
35
+ const dir = i >= 0 ? args[i + 1] : packageDir(config.package);
36
+ if (!fs.existsSync(dir)) {
37
+ console.error(`The package ${config.package} isn't at ${dir}. Load it into the terminology cache, or use -package <dir>`);
38
+ process.exit(1);
39
+ }
40
+ const out = path.join(moduleDir, 'openapi-schemas.json');
41
+ fs.writeFileSync(out, JSON.stringify(generateSchemas(config, dir), null, 2) + '\n');
42
+ console.log(`Wrote ${out}`);
43
+ }
44
+
45
+ if (require.main === module) {
46
+ main(process.argv.slice(2));
47
+ }
48
+
49
+ module.exports = { packageDir };
package/xig/xig.js CHANGED
@@ -397,8 +397,7 @@ async function gatherPageStatistics() {
397
397
  downloadDate: downloadDate,
398
398
  totalResources: tableCounts.resources || 0,
399
399
  totalPackages: tableCounts.packages || 0,
400
- processingTime: processingTime,
401
- version: getMetadata('fhir-version') || '4.0.1'
400
+ processingTime: processingTime
402
401
  };
403
402
 
404
403
  } catch (error) {
@@ -411,8 +410,7 @@ async function gatherPageStatistics() {
411
410
  downloadDate: 'Error',
412
411
  totalResources: 0,
413
412
  totalPackages: 0,
414
- processingTime: processingTime,
415
- version: '4.0.1'
413
+ processingTime: processingTime
416
414
  };
417
415
  }
418
416
  }