From fdca0ae36fe6e22742a5e57184797ff46ed92980 Mon Sep 17 00:00:00 2001 From: Chris Moesel Date: Thu, 31 Oct 2024 16:42:19 -0400 Subject: [PATCH] WIP --- .tool-versions | 2 +- .../fhir-package-loader-1.0.0.tgz | Bin 0 -> 28931 bytes npm-shrinkwrap.json | 437 ++++++++++--- package.json | 3 +- regression/repos-select.txt | 39 +- regression/run.ts | 2 +- src/app.ts | 38 +- src/export/StructureDefinitionExporter.ts | 2 +- src/fhirdefs/BaseFHIRDefinitions.ts | 600 ++++++++++++++++++ src/fhirdefs/FHIRDefinitions.ts | 226 ++++--- src/fhirdefs/impliedExtensions.ts | 44 +- src/fhirdefs/index.ts | 1 + src/fhirdefs/load.ts | 101 +-- src/fhirtypes/common.ts | 40 +- src/ig/IGExporter.ts | 357 +++++------ src/utils/Fishable.ts | 1 + src/utils/Processing.ts | 59 +- test/fhirdefs/FHIRDefinitions.test.ts | 12 - tsconfig.json | 8 +- 19 files changed, 1438 insertions(+), 534 deletions(-) create mode 100644 local-dependencies/fhir-package-loader-1.0.0.tgz create mode 100644 src/fhirdefs/BaseFHIRDefinitions.ts diff --git a/.tool-versions b/.tool-versions index 8f2e342a2..6df261cac 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -nodejs 18.18.0 +nodejs 18.20.1 diff --git a/local-dependencies/fhir-package-loader-1.0.0.tgz b/local-dependencies/fhir-package-loader-1.0.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..0ff7a1c36b6aa6b24678235d38d67715f04998b2 GIT binary patch literal 28931 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" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1385,6 +1486,15 @@ "node": ">= 8" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -1480,6 +1590,12 @@ "integrity": "sha512-uxpcuwWJGhe2AR1g8hD9F5OYGCqjqWnBUQFD8gMZsDbv8oPHzxJF6iMO6n8Tk0AdzlxoaaoQhOYlIg/PukVU8g==", "dev": true }, + "node_modules/@types/emscripten": { + "version": "1.39.13", + "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.39.13.tgz", + "integrity": "sha512-cFq+fO/isvhvmuP/+Sl4K4jtU6E23DoivtbO4r50e3odaxAiVdbfSYRDdJ4gCdxx+3aRjhphS5ZMwIH4hFy/Cw==", + "dev": true + }, "node_modules/@types/fs-extra": { "version": "11.0.4", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.4.tgz", @@ -1611,6 +1727,16 @@ "@types/node": "*" } }, + "node_modules/@types/sql.js": { + "version": "1.4.9", + "resolved": "https://registry.npmjs.org/@types/sql.js/-/sql.js-1.4.9.tgz", + "integrity": "sha512-ep8b36RKHlgWPqjNG9ToUrPiwkhwh0AEzy883mO5Xnd+cL6VBH1EvSjBAAuxLUFF2Vn/moE3Me6v9E1Lo+48GQ==", + "dev": true, + "dependencies": { + "@types/emscripten": "*", + "@types/node": "*" + } + }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", @@ -1962,7 +2088,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -2043,9 +2168,9 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/axios": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.5.tgz", - "integrity": "sha512-fZu86yCo+svH3uqJ/yTdQ0QHpQu5oL+/QE+QPSv6BZSkDAoky9vytxp7u5qk83OJFS3kEBcesWni9WTZAv3tSw==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -2177,7 +2302,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0" } @@ -2390,11 +2514,11 @@ } }, "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/ci-info": { @@ -2673,7 +2797,6 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -3037,6 +3160,11 @@ "node": ">=0.4.0" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, "node_modules/ejs": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", @@ -3073,8 +3201,7 @@ "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/enabled": { "version": "2.0.0", @@ -3576,32 +3703,27 @@ }, "node_modules/fhir-package-loader": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fhir-package-loader/-/fhir-package-loader-1.0.0.tgz", - "integrity": "sha512-x3VY3RY1wkJv8Fd7dA7fY3aw+6Vg7qeCU0pci7wUaEhnJ84k7Lnca6dfH00l36uzH1N5EwVX51iKuuwsS6RdlA==", + "resolved": "file:local-dependencies/fhir-package-loader-1.0.0.tgz", + "integrity": "sha512-c51UKQ68fjdW7FfYoR5DNkH5H/fkHsPykmfim9e5I4A1GcGCFK3jTWdtWgJwWgw4DDp3ZvkIFWmX387q+xR2xQ==", "dependencies": { - "axios": "^1.6.7", + "axios": "^1.7.7", "chalk": "^4.1.2", - "commander": "^11.1.0", + "commander": "^12.1.0", + "fhir": "^4.12.0", "fs-extra": "^11.2.0", - "https-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", "lodash": "^4.17.21", - "semver": "^7.5.4", - "tar": "^6.2.0", + "mnemonist": "^0.39.8", + "semver": "^7.6.3", + "sql.js": "^1.11.0", + "tar": "^7.4.3", "temp": "^0.9.1", - "winston": "^3.11.0" + "winston": "^3.14.2" }, "bin": { "fpl": "dist/app.js" } }, - "node_modules/fhir-package-loader/node_modules/commander": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", - "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", - "engines": { - "node": ">=16" - } - }, "node_modules/fhir/node_modules/inherits": { "version": "2.0.3", "inBundle": true, @@ -3766,6 +3888,21 @@ } } }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -3804,33 +3941,6 @@ "node": ">=14.14" } }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fs-minipass/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -4297,7 +4407,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "engines": { "node": ">=8" } @@ -4433,8 +4542,7 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", @@ -4502,6 +4610,20 @@ "node": ">=8" } }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jake": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", @@ -5699,7 +5821,6 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -5733,45 +5854,63 @@ } }, "node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" } }, "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.1.tgz", + "integrity": "sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==", "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" + "minipass": "^7.0.4", + "rimraf": "^5.0.5" }, "engines": { - "node": ">= 8" + "node": ">= 18" } }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "node_modules/minizlib/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dependencies": { - "yallist": "^4.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, - "engines": { - "node": ">=8" + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/minizlib/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "node_modules/minizlib/node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, "bin": { "mkdirp": "bin/cmd.js" }, @@ -5779,6 +5918,14 @@ "node": ">=10" } }, + "node_modules/mnemonist": { + "version": "0.39.8", + "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.39.8.tgz", + "integrity": "sha512-vyWo2K3fjrUw8YeeZ1zF0fy6Mu59RHokURlld8ymdUPjMlD9EC9ov1/YPqTgqRvUN9nTr3Gqfz29LYAmu0PHPQ==", + "dependencies": { + "obliterator": "^2.0.1" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -5938,6 +6085,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/obliterator": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.4.tgz", + "integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==" + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -6067,6 +6219,11 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==" + }, "node_modules/param-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", @@ -6136,7 +6293,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "engines": { "node": ">=8" } @@ -6147,6 +6303,26 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -6701,7 +6877,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -6713,7 +6888,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "engines": { "node": ">=8" } @@ -6722,7 +6896,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, "engines": { "node": ">=14" }, @@ -6816,6 +6989,11 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, + "node_modules/sql.js": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/sql.js/-/sql.js-1.12.0.tgz", + "integrity": "sha512-Bi+43yMx/tUFZVYD4AUscmdL6NHn3gYQ+CM+YheFWLftOmrEC/Mz6Yh7E96Y2WDHYz3COSqT+LP6Z79zgrwJlA==" + }, "node_modules/stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -6868,7 +7046,20 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -6882,7 +7073,18 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -6974,25 +7176,42 @@ } }, "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar/node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/tar/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "engines": { + "node": ">=18" + } }, "node_modules/temp": { "version": "0.9.4", @@ -7497,7 +7716,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -7665,6 +7883,23 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index 9dba3ebd9..38562bc52 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "@types/opener": "^1.4.3", "@types/readline-sync": "^1.4.8", "@types/sax": "^1.2.7", + "@types/sql.js": "^1.4.9", "@types/temp": "^0.9.4", "@types/text-table": "^0.2.5", "@types/valid-url": "^1.0.7", @@ -92,7 +93,7 @@ "chalk": "^4.1.2", "commander": "^12.1.0", "fhir": "^4.12.0", - "fhir-package-loader": "^1.0.0", + "fhir-package-loader": "file:local-dependencies/fhir-package-loader-1.0.0.tgz", "fs-extra": "^11.2.0", "html-minifier-terser": "5.1.1", "https-proxy-agent": "^7.0.5", diff --git a/regression/repos-select.txt b/regression/repos-select.txt index 4bec7b8da..71f945991 100644 --- a/regression/repos-select.txt +++ b/regression/repos-select.txt @@ -1,8 +1,33 @@ -HL7/fhir-mCODE-ig#master -hl7-eu/laboratory#master +HL7/davinci-crd#master HL7/fhir-sdoh-clinicalcare#master -HL7NZ/hpi#master -WorldHealthOrganization/smart-immunizations#main -aehrc/smart-health-checks-fhir-ig#master -HL7/davinci-epdx#master -openhie/covid19-casereporting#master +HL7/cqf-recommendations#master +bcgov/MOH-HCIM-FHIR#main +cqframework/cpg-example-chf#main +cqframework/cpg-example-hepb#main +EUVABECO/VCDS#main +fhir-fi/finnish-base-profiles#master +fut-infrastructure/implementation-guide#master +hl7-eu/xpandh-hdr#master +hl7-it/dossier-pharma#master +HL7/cql-ig#main +HL7/crmi-ig#master +HL7/davinci-ehrx#master +HL7/fhir-pacio-cognitive-status#master +HL7/fhir-pacio-functional-status#master +HL7/fhir-pacio-pfe#master +HL7/US-Core#master +HL7/VhDir#master +hl7ch/ch-core#master +hl7ch/ch-ems#master +HL7NZ/nzbase#master +IntelliSOFT-Consulting/HIV-FHIR-IG#main +Interop-Sante/FHIR-FR-Core#main +Interop-Sante/hl7.fhir.fr.core#main +mtnlotus/pco-ig#main +TEHIK-EE/ig-ee-medication-scheme#master +tewhatuora/hfc-ig#master +WorldHealthOrganization/smart-icvp#main +ansforge/IG-FHIR-EDS-SOCLE-COMMUN#main +cander2/epi-test#master +HL7/davinci-ecdx#master +medizininformatik-initiative/kerndatensatz-bildgebung#main \ No newline at end of file diff --git a/regression/run.ts b/regression/run.ts index 42e167987..c5e47ba50 100644 --- a/regression/run.ts +++ b/regression/run.ts @@ -366,7 +366,7 @@ async function runSUSHI(num: 1 | 2, repo: Repo, config: Config): Promise { + logMessage(level, `@@@ NEW FPL @@@ ${message}`); + } + }); + const defs = new FHIRDefinitions(false, newFPL); await loadExternalDependencies(defs, config); // Load custom resources. In current tank configuration (input/fsh), resources will be in input/ - loadCustomResources(path.join(input, '..'), originalInput, config.parameters, defs); + const localResourcePaths = getLocalResourcePaths( + path.join(input, '..'), + originalInput, + config.parameters + ); + await newFPL + .loadVirtualPackage( + new DiskBasedVirtualPackage({ name: 'sushi-local', version: 'LOCAL' }, localResourcePaths, { + log: (level: string, message: string) => { + logMessage(level, `@@@ NEW FPL @@@ ${message}`); + }, + allowNonResources: true, // support for logical instances + recursive: true + }) + ) + .then(status => logger.info(`Load status for local resources: ${status}`)); // Check for StructureDefinition const structDef = defs.fishForFHIR('StructureDefinition', Type.Resource); @@ -296,6 +318,14 @@ async function runBuild(input: string, program: OptionValues, helpText: string) process.exit(1); } + // const fplExport = await newFPL.exportDB(); + // if (fplExport.mimeType === 'application/x-sqlite3') { + // const exportPath = path.join(outDir, 'fsh-generated', 'FPL.sqlite'); + // fs.ensureDirSync(path.join(outDir, 'fsh-generated')); + // fs.writeFileSync(exportPath, fplExport.data); + // logger.info(`Exported FPL database to ${exportPath}`); + // } + logger.info('Converting FSH to FHIR resources...'); const outPackage = exportFHIR(tank, defs); const { skippedResources } = writeFHIRResources(outDir, outPackage, defs, program.snapshot); diff --git a/src/export/StructureDefinitionExporter.ts b/src/export/StructureDefinitionExporter.ts index 09ff205f9..bba486b85 100644 --- a/src/export/StructureDefinitionExporter.ts +++ b/src/export/StructureDefinitionExporter.ts @@ -984,7 +984,7 @@ export class StructureDefinitionExporter implements Fishable { } } } catch (e) { - logger.error(e.message, rule.sourceInfo); + logger.error(e.message ?? e, rule.sourceInfo); if (e.stack) { logger.debug(e.stack); } diff --git a/src/fhirdefs/BaseFHIRDefinitions.ts b/src/fhirdefs/BaseFHIRDefinitions.ts new file mode 100644 index 000000000..f642974e1 --- /dev/null +++ b/src/fhirdefs/BaseFHIRDefinitions.ts @@ -0,0 +1,600 @@ +// NOTE: This is the original FHIRDefinitions class from fhir-package-loader 1.0. +// This class was removed in fhir-package-loader 2.0, so we have it here as a +// temporary measure while we transition fully to fhir-package-loader 2.0. + +import { cloneDeep, isEqual, uniqWith, uniq } from 'lodash'; + +/** Class representing the FHIR definitions in one or more FHIR packages */ +export class BaseFHIRDefinitions { + protected resources: Map; + protected logicals: Map; + protected profiles: Map; + protected extensions: Map; + protected types: Map; + protected valueSets: Map; + protected codeSystems: Map; + protected implementationGuides: Map; + protected packageJsons: Map; + childFHIRDefs: BaseFHIRDefinitions[]; + package: string; + unsuccessfulPackageLoad: boolean; + + /** Create a FHIRDefinitions */ + constructor() { + this.package = ''; + this.resources = new DoubleMap(); + this.logicals = new DoubleMap(); + this.profiles = new DoubleMap(); + this.extensions = new DoubleMap(); + this.types = new DoubleMap(); + this.valueSets = new DoubleMap(); + this.codeSystems = new DoubleMap(); + this.implementationGuides = new DoubleMap(); + this.packageJsons = new DoubleMap(); + this.childFHIRDefs = []; + this.unsuccessfulPackageLoad = false; + } + + /** Get the total number of definitions */ + size(): number { + return ( + this.allResources().length + + this.allLogicals().length + + this.allProfiles().length + + this.allExtensions().length + + this.allTypes().length + + this.allValueSets().length + + this.allCodeSystems().length + + this.allImplementationGuides().length + ); + } + + // NOTE: These all return clones of the JSON to prevent the source values from being overwritten + + /** + * Get all resources. The array will not contain duplicates. + * @param {string} [fhirPackage] - The package (packageId#version) to search in. If not provided, searches all packages. + * @returns array of resources + */ + allResources(fhirPackage?: string): any[] { + if ( + (this.resources.size > 0 && this.childFHIRDefs.length > 0) || + this.childFHIRDefs.length > 1 + ) { + return uniqWith(this.collectResources(fhirPackage), isEqual); + } + return this.collectResources(fhirPackage); + } + + protected collectResources(fhirPackage?: string): any[] { + const childValues = this.childFHIRDefs + .map(def => def.collectResources(fhirPackage)) + .reduce((a, b) => a.concat(b), []); + let resources = this.resources; + if (fhirPackage) { + resources = new Map(); + if (this.package === fhirPackage) { + resources = this.resources; + } + } + return cloneJsonMapValues(resources).concat(childValues); + } + + /** + * Get all logicals. The array will not contain duplicates. + * @param {string} [fhirPackage] - The package (packageId#version) to search in. If not provided, searches all packages. + * @returns array of logicals + */ + allLogicals(fhirPackage?: string): any[] { + if ( + (this.logicals.size > 0 && this.childFHIRDefs.length > 0) || + this.childFHIRDefs.length > 1 + ) { + return uniqWith(this.collectLogicals(fhirPackage), isEqual); + } + return uniqWith(this.collectLogicals(fhirPackage), isEqual); + } + + protected collectLogicals(fhirPackage?: string): any[] { + const childValues = this.childFHIRDefs + .map(def => def.collectLogicals(fhirPackage)) + .reduce((a, b) => a.concat(b), []); + let logicals = this.logicals; + if (fhirPackage) { + logicals = new Map(); + if (this.package === fhirPackage) { + logicals = this.logicals; + } + } + return cloneJsonMapValues(logicals).concat(childValues); + } + + /** + * Get all profiles. The array will not contain duplicates. + * @param {string} [fhirPackage] - The package (packageId#version) to search in. If not provided, searches all packages. + * @returns array of profiles + */ + allProfiles(fhirPackage?: string): any[] { + if ( + (this.profiles.size > 0 && this.childFHIRDefs.length > 0) || + this.childFHIRDefs.length > 1 + ) { + return uniqWith(this.collectProfiles(fhirPackage), isEqual); + } + return this.collectProfiles(fhirPackage); + } + + protected collectProfiles(fhirPackage?: string): any[] { + const childValues = this.childFHIRDefs + .map(def => def.collectProfiles(fhirPackage)) + .reduce((a, b) => a.concat(b), []); + let profiles = this.profiles; + if (fhirPackage) { + profiles = new Map(); + if (this.package === fhirPackage) { + profiles = this.profiles; + } + } + return cloneJsonMapValues(profiles).concat(childValues); + } + + /** + * Get all extensions. The array will not contain duplicates. + * @param {string} [fhirPackage] - The package (packageId#version) to search in. If not provided, searches all packages. + * @returns array of extensions + */ + allExtensions(fhirPackage?: string): any[] { + if ( + (this.extensions.size > 0 && this.childFHIRDefs.length > 0) || + this.childFHIRDefs.length > 1 + ) { + return uniqWith(this.collectExtensions(fhirPackage), isEqual); + } + return this.collectExtensions(fhirPackage); + } + + protected collectExtensions(fhirPackage?: string): any[] { + const childValues = this.childFHIRDefs + .map(def => def.collectExtensions(fhirPackage)) + .reduce((a, b) => a.concat(b), []); + let extensions = this.extensions; + if (fhirPackage) { + extensions = new Map(); + if (this.package === fhirPackage) { + extensions = this.extensions; + } + } + return cloneJsonMapValues(extensions).concat(childValues); + } + + /** + * Get all types. The array will not contain duplicates. + * @param {string} [fhirPackage] - The package (packageId#version) to search in. If not provided, searches all packages. + * @returns array of types + */ + allTypes(fhirPackage?: string): any[] { + if ((this.types.size > 0 && this.childFHIRDefs.length > 0) || this.childFHIRDefs.length > 1) { + return uniqWith(this.collectTypes(fhirPackage), isEqual); + } + return this.collectTypes(fhirPackage); + } + + protected collectTypes(fhirPackage?: string): any[] { + const childValues = this.childFHIRDefs + .map(def => def.collectTypes(fhirPackage)) + .reduce((a, b) => a.concat(b), []); + let types = this.types; + if (fhirPackage) { + types = new Map(); + if (this.package === fhirPackage) { + types = this.types; + } + } + return cloneJsonMapValues(types).concat(childValues); + } + + /** + * Get all value sets. The array will not contain duplicates. + * @param {string} [fhirPackage] - The package (packageId#version) to search in. If not provided, searches all packages. + * @returns array of value sets + */ + allValueSets(fhirPackage?: string): any[] { + if ( + (this.valueSets.size > 0 && this.childFHIRDefs.length > 0) || + this.childFHIRDefs.length > 1 + ) { + return uniqWith(this.collectValueSets(fhirPackage), isEqual); + } + return this.collectValueSets(fhirPackage); + } + + protected collectValueSets(fhirPackage?: string): any[] { + const childValues = this.childFHIRDefs + .map(def => def.collectValueSets(fhirPackage)) + .reduce((a, b) => a.concat(b), []); + let valueSets = this.valueSets; + if (fhirPackage) { + valueSets = new Map(); + if (this.package === fhirPackage) { + valueSets = this.valueSets; + } + } + return cloneJsonMapValues(valueSets).concat(childValues); + } + + /** + * Get all code systems. The array will not contain duplicates. + * @param {string} [fhirPackage] - The package (packageId#version) to search in. If not provided, searches all packages. + * @returns array of code systems + */ + allCodeSystems(fhirPackage?: string): any[] { + if ( + (this.codeSystems.size > 0 && this.childFHIRDefs.length > 0) || + this.childFHIRDefs.length > 1 + ) { + return uniqWith(this.collectCodeSystems(fhirPackage), isEqual); + } + return this.collectCodeSystems(fhirPackage); + } + + protected collectCodeSystems(fhirPackage?: string): any[] { + const childValues = this.childFHIRDefs + .map(def => def.collectCodeSystems(fhirPackage)) + .reduce((a, b) => a.concat(b), []); + let codeSystems = this.codeSystems; + if (fhirPackage) { + codeSystems = new Map(); + if (this.package === fhirPackage) { + codeSystems = this.codeSystems; + } + } + return cloneJsonMapValues(codeSystems).concat(childValues); + } + + /** + * Get all implementation guides. The array will not contain duplicates. + * @param {string} [fhirPackage] - The package (packageId#version) to search in. If not provided, searches all packages. + * @returns array of implementation guides + */ + allImplementationGuides(fhirPackage?: string): any[] { + if ( + (this.implementationGuides.size > 0 && this.childFHIRDefs.length > 0) || + this.childFHIRDefs.length > 1 + ) { + return uniqWith(this.collectImplementationGuides(fhirPackage), isEqual); + } + return this.collectImplementationGuides(fhirPackage); + } + + protected collectImplementationGuides(fhirPackage?: string): any[] { + const childValues = this.childFHIRDefs + .map(def => def.collectImplementationGuides(fhirPackage)) + .reduce((a, b) => a.concat(b), []); + let implementationGuides = this.implementationGuides; + if (fhirPackage) { + implementationGuides = new Map(); + if (this.package === fhirPackage) { + implementationGuides = this.implementationGuides; + } + } + return cloneJsonMapValues(implementationGuides).concat(childValues); + } + + /** + * Get a list of packages that encountered errors while downloaded and were not loaded + * @param {string} [fhirPackage] - The package (packageId#version) to search in. If not provided, searches all packages. + * @returns array of packages (packageId#version) that were not successfully loaded + */ + allUnsuccessfulPackageLoads(fhirPackage?: string): string[] { + return uniq(this.collectUnsuccessfulPackageLoads(fhirPackage)); + } + + protected collectUnsuccessfulPackageLoads(fhirPackage?: string): string[] { + const childValues = this.childFHIRDefs + .map(def => def.collectUnsuccessfulPackageLoads(fhirPackage)) + .reduce((a, b) => a.concat(b), []); + if (fhirPackage) { + if (this.package === fhirPackage && this.unsuccessfulPackageLoad) { + return childValues.concat(this.package); + } + } else if (this.unsuccessfulPackageLoad) { + return childValues.concat(this.package); + } + return childValues; + } + + /** + * Get a list of all packages that are contained in this FHIRDefinitions + * @param {string} [fhirPackage] The package (packageId#version) to get all packages from. If not provided, all packages are returned. + * @returns array of packages (packageId#version) that are loaded + */ + allPackages(fhirPackage?: string): string[] { + return uniq(this.collectPackages(fhirPackage)); + } + + protected collectPackages(fhirPackage?: string): string[] { + const childValues = this.childFHIRDefs + .map(def => def.collectPackages(fhirPackage)) + .reduce((a, b) => a.concat(b), []); + if (fhirPackage) { + if (this.package === fhirPackage && this.package !== '') { + return childValues.concat(this.package); + } + } else if (this.package !== '') { + return childValues.concat(this.package); + } + return childValues; + } + + /** + * Add a definition + * @param definition - The definition to add + */ + add(definition: any): void { + if (definition.resourceType === 'StructureDefinition') { + if ( + definition.type === 'Extension' && + definition.baseDefinition !== 'http://hl7.org/fhir/StructureDefinition/Element' + ) { + addDefinitionToMap(definition, this.extensions); + } else if ( + definition.kind === 'primitive-type' || + definition.kind === 'complex-type' || + definition.kind === 'datatype' + ) { + addDefinitionToMap(definition, this.types); + } else if (definition.kind === 'resource') { + if (definition.derivation === 'constraint') { + addDefinitionToMap(definition, this.profiles); + } else { + addDefinitionToMap(definition, this.resources); + } + } else if (definition.kind === 'logical') { + if (definition.derivation === 'specialization') { + addDefinitionToMap(definition, this.logicals); + } else { + addDefinitionToMap(definition, this.profiles); + } + } + } else if (definition.resourceType === 'ValueSet') { + addDefinitionToMap(definition, this.valueSets); + } else if (definition.resourceType === 'CodeSystem') { + addDefinitionToMap(definition, this.codeSystems); + } else if (definition.resourceType === 'ImplementationGuide') { + addDefinitionToMap(definition, this.implementationGuides); + } + } + + /** + * Add a package.json + * @param {string} id - package id + * @param {string} definition - package JSON definition + */ + addPackageJson(id: string, definition: any): void { + this.packageJsons.set(id, definition); + } + + /** + * Get a package's package.json + * @param {string} id - package id + * @returns package.json definition + */ + getPackageJson(id: string): any { + return this.packageJsons.get(id); + } + + /** + * Private function for search through current FHIRDefinitions and all childFHIRDefs + * for a specified definition. Uses get for efficient retrieves. + * Breath-first search through childFHIRDefinitions for the item. + * @param item name, id, or url of definition to find + * @param map name of the map to search in + * @returns definition or undefined if it is not found + */ + private getDefinition(item: string, map: maps): any | undefined { + const defsToSearch: BaseFHIRDefinitions[] = [this]; + while (defsToSearch.length > 0) { + const currentFHIRDefs = defsToSearch.shift(); + const [base, ...versionParts] = item?.split('|') ?? ['', '']; + const version = versionParts.join('|') || null; + const def = currentFHIRDefs[map].get(base); + if (def) { + if (version == null || version === def?.version) { + // Only return the found definition if the version matches (if provided) + return def; + } + } + if (currentFHIRDefs.childFHIRDefs.length > 0) { + defsToSearch.push(...currentFHIRDefs.childFHIRDefs); + } + } + + return; + } + + /** + * Search for a definition based on the type it could be + * @param {string} item - the item to search for + * @param {Type[]} types - the possible type the item could be + * @returns the definition that is returned or undefined if none is found + */ + fishForFHIR(item: string, ...types: Type[]): any | undefined { + // No types passed in means to search ALL supported types + if (types.length === 0) { + types = FISHING_ORDER; + } else { + types.sort((a, b) => FISHING_ORDER.indexOf(a) - FISHING_ORDER.indexOf(b)); + } + + for (const type of types) { + let def; + switch (type) { + case Type.Resource: + def = cloneDeep(this.getDefinition(item, 'resources')); + break; + case Type.Logical: + def = cloneDeep(this.getDefinition(item, 'logicals')); + break; + case Type.Type: + def = cloneDeep(this.getDefinition(item, 'types')); + break; + case Type.Profile: + def = cloneDeep(this.getDefinition(item, 'profiles')); + break; + case Type.Extension: + def = cloneDeep(this.getDefinition(item, 'extensions')); + break; + case Type.ValueSet: + def = cloneDeep(this.getDefinition(item, 'valueSets')); + break; + case Type.CodeSystem: + def = cloneDeep(this.getDefinition(item, 'codeSystems')); + break; + case Type.Instance: // don't support resolving to FHIR instances + default: + break; + } + if (def) { + return def; + } + } + } +} + +function addDefinitionToMap(def: any, defMap: Map): void { + if (def.id) { + defMap.set(def.id, def); + } + if (def.url) { + defMap.set(def.url, def); + } + if (def.name) { + defMap.set(def.name, def); + } +} + +function cloneJsonMapValues(map: Map): any { + return Array.from(map.values()).map(v => cloneDeep(v)); +} + +export enum Type { + Profile = 'Profile', + Extension = 'Extension', + ValueSet = 'ValueSet', + CodeSystem = 'CodeSystem', + Instance = 'Instance', + Invariant = 'Invariant', // NOTE: only defined in FSHTanks, not FHIR defs + RuleSet = 'RuleSet', // NOTE: only defined in FSHTanks, not FHIR defs + Mapping = 'Mapping', // NOTE: only defined in FSHTanks, not FHIR defs + Resource = 'Resource', + Type = 'Type', // NOTE: only defined in FHIR defs, not FSHTanks + Logical = 'Logical' +} + +export const FISHING_ORDER = [ + Type.Resource, + Type.Logical, + Type.Type, + Type.Profile, + Type.Extension, + Type.ValueSet, + Type.CodeSystem +]; + +// Type to represent the names of the FHIRDefinition maps of definitions +type maps = + | 'resources' + | 'logicals' + | 'profiles' + | 'extensions' + | 'types' + | 'valueSets' + | 'codeSystems'; + +/** + * The DoubleMap is a Map that contains both forward and reverse mappings between keys and values. + * This allows the DoubleMap to easily provide a list of unique values, + * because each value in the internal forwardMap will be a key in the reverseMap. + * The reported size of a DoubleMap is the number of unique values, + * which is the number of keys in the reverseMap. + * + * Note that because DoubleMap.values() returns the keys from reverseMap, + * it may contain fewer elements than the other functions: keys(), entries(), forEach(), and the for-of iterator. + */ +export class DoubleMap implements Map { + private forwardMap: Map; + private reverseMap: Map>; + + constructor() { + this.forwardMap = new Map(); + this.reverseMap = new Map(); + } + + set(key: K, value: V): this { + if (this.forwardMap.get(key) === value) { + return this; + } + this.delete(key); + this.forwardMap.set(key, value); + if (this.reverseMap.has(value)) { + this.reverseMap.get(value).add(key); + } else { + this.reverseMap.set(value, new Set([key])); + } + return this; + } + + delete(key: K): boolean { + if (this.forwardMap.has(key)) { + const currentValue = this.forwardMap.get(key); + const currentKeys = this.reverseMap.get(currentValue); + currentKeys.delete(key); + if (currentKeys.size === 0) { + this.reverseMap.delete(currentValue); + } + this.forwardMap.delete(key); + return true; + } else { + return false; + } + } + + get(key: K): V { + return this.forwardMap.get(key); + } + + get size(): number { + return this.reverseMap.size; + } + + clear(): void { + this.forwardMap.clear(); + this.reverseMap.clear(); + } + + forEach(callbackfn: (value: V, key: K, map: Map) => void, thisArg?: any): void { + this.forwardMap.forEach(callbackfn, thisArg); + } + + has(key: K): boolean { + return this.forwardMap.has(key); + } + + [Symbol.iterator](): IterableIterator<[K, V]> { + return this.entries(); + } + + entries(): IterableIterator<[K, V]> { + return this.forwardMap.entries(); + } + + keys(): IterableIterator { + return this.forwardMap.keys(); + } + + values(): IterableIterator { + return this.reverseMap.keys(); + } + + [Symbol.toStringTag]: string; +} diff --git a/src/fhirdefs/FHIRDefinitions.ts b/src/fhirdefs/FHIRDefinitions.ts index 3ac06c77a..95e58eb7f 100644 --- a/src/fhirdefs/FHIRDefinitions.ts +++ b/src/fhirdefs/FHIRDefinitions.ts @@ -1,33 +1,31 @@ import { cloneDeep, flatten } from 'lodash'; -import { FHIRDefinitions as BaseFHIRDefinitions } from 'fhir-package-loader'; +import { + FindResourceInfoOptions, + PackageInfo, + PackageLoader, + ResourceInfo, + byLoadOrder, + byType +} from 'fhir-package-loader'; import { Type, Metadata, Fishable } from '../utils'; -import { IMPLIED_EXTENSION_REGEX, materializeImpliedExtension } from './impliedExtensions'; -import { R5_DEFINITIONS_NEEDED_IN_R4 } from './R5DefsForR4'; +import { BaseFHIRDefinitions, FISHING_ORDER } from './BaseFHIRDefinitions'; import { - LOGICAL_TARGET_EXTENSION, - TYPE_CHARACTERISTICS_EXTENSION, - findImposeProfiles -} from '../fhirtypes/common'; + IMPLIED_EXTENSION_REGEX, + materializeImpliedExtension, + materializeImpliedExtensionMetadata +} from './impliedExtensions'; + +const DEFAULT_SORT = [byType(...FISHING_ORDER), byLoadOrder(false)]; export class FHIRDefinitions extends BaseFHIRDefinitions implements Fishable { - private predefinedResources: Map; private supplementalFHIRDefinitions: Map; - constructor(public readonly isSupplementalFHIRDefinitions = false) { + constructor( + public readonly isSupplementalFHIRDefinitions = false, + public readonly newFPL?: PackageLoader + ) { super(); - this.predefinedResources = new Map(); this.supplementalFHIRDefinitions = new Map(); - // There are several R5 resources that are allowed for use in R4 and R4B. - // Add them first so they're always available. If a later version is loaded - // that has these definitions, it will overwrite them, so this should be safe. - if (!isSupplementalFHIRDefinitions) { - R5_DEFINITIONS_NEEDED_IN_R4.forEach(def => this.add(def)); - } - } - - // Expose the package.json files to support extracting the version when "latest" is used - allPackageJsons(): any[] { - return Array.from(this.packageJsons?.values() ?? []); } // This getter is only used in tests to verify what supplemental packages are loaded @@ -35,12 +33,49 @@ export class FHIRDefinitions extends BaseFHIRDefinitions implements Fishable { return flatten(Array.from(this.supplementalFHIRDefinitions.keys())); } - allPredefinedResources(makeClone = true): any[] { - if (makeClone) { - return Array.from(this.predefinedResources.values()).map(v => cloneDeep(v)); - } else { - return Array.from(this.predefinedResources.values()); + allImplementationGuides(fhirPackage?: string): any[] { + const options: FindResourceInfoOptions = { + type: ['ImplementationGuide'], + sort: DEFAULT_SORT + }; + if (fhirPackage) { + options.scope = fhirPackage; } + return cloneDeep(this.newFPL?.findResourceJSONs('*', options)); + } + + allPredefinedResources(makeClone = true): any[] { + // Return in FIFO order to match previous SUSHI behavior + const options = { + scope: 'sushi-local', + sort: [byLoadOrder(true)] + }; + const pdResources = this.newFPL?.findResourceJSONs('*', options) ?? []; + return makeClone ? pdResources.map(r => cloneDeep(r)) : pdResources; + } + + allPredefinedResourceMetadatas(): Metadata[] { + // Return in FIFO order to match previous SUSHI behavior + const options = { + scope: 'sushi-local', + sort: [byLoadOrder(true)] + }; + return this.newFPL?.findResourceInfos('*', options).map(info => { + return { + id: info.id, + name: info.name, + sdType: info.sdType, + url: info.url, + parent: info.sdBaseDefinition, + imposeProfiles: info.sdImposeProfiles, + abstract: info.sdAbstract, + version: info.version, + resourceType: info.resourceType, + canBeTarget: logicalCharacteristic(info, 'can-be-target'), + canBind: logicalCharacteristic(info, 'can-bind'), + resourcePath: info.resourcePath + }; + }); } add(definition: any): void { @@ -61,16 +96,9 @@ export class FHIRDefinitions extends BaseFHIRDefinitions implements Fishable { } } - addPredefinedResource(file: string, definition: any): void { - this.predefinedResources.set(file, definition); - } - + // eslint-disable-next-line @typescript-eslint/no-unused-vars getPredefinedResource(file: string): any { - return this.predefinedResources.get(file); - } - - resetPredefinedResources() { - this.predefinedResources = new Map(); + return null; } addSupplementalFHIRDefinitions(fhirPackage: string, definitions: FHIRDefinitions): void { @@ -81,42 +109,61 @@ export class FHIRDefinitions extends BaseFHIRDefinitions implements Fishable { return this.supplementalFHIRDefinitions.get(fhirPackage); } + fishForPackageInfos(name: string): PackageInfo[] { + return cloneDeep(this.newFPL?.findPackageInfos(name)); + } + fishForPredefinedResource(item: string, ...types: Type[]): any | undefined { - const resource = this.fishForFHIR(item, ...types); - if ( - resource && - this.allPredefinedResources(false).find( - predefResource => - predefResource.id === resource.id && - predefResource.resourceType === resource.resourceType && - predefResource.url === resource.url - ) - ) { - return resource; + const def = this.newFPL?.findResourceJSON(item, { + type: normalizeTypes(types), + scope: 'sushi-local', + sort: DEFAULT_SORT + }); + if (def) { + // TODO: Should FPL clone or leave that to FPL consumers? Or lock objects as READ-ONLY? + return cloneDeep(def); } } fishForPredefinedResourceMetadata(item: string, ...types: Type[]): Metadata | undefined { - const resource = this.fishForPredefinedResource(item, ...types); - if (resource) { + const info = this.newFPL?.findResourceInfo(item, { + type: normalizeTypes(types), + scope: 'sushi-local', + sort: DEFAULT_SORT + }); + if (info) { return { - id: resource.id as string, - name: resource.name as string, - sdType: resource.type as string, - url: resource.url as string, - parent: resource.baseDefinition as string, - imposeProfiles: findImposeProfiles(resource), - abstract: resource.abstract as boolean, - version: resource.version as string, - resourceType: resource.resourceType as string + id: info.id, + name: info.name, + sdType: info.sdType, + url: info.url, + parent: info.sdBaseDefinition, + imposeProfiles: info.sdImposeProfiles, + abstract: info.sdAbstract, + version: info.version, + resourceType: info.resourceType, + canBeTarget: logicalCharacteristic(info, 'can-be-target'), + canBind: logicalCharacteristic(info, 'can-bind'), + resourcePath: info.resourcePath }; } } + getPackageJson(id: string): any { + const [name, version] = id.split('#'); + const packageJSON = this.newFPL?.findPackageJSON(name, version); + if (packageJSON) { + return cloneDeep(packageJSON); + } + } + fishForFHIR(item: string, ...types: Type[]): any | undefined { - const def = super.fishForFHIR(item, ...types); + const def = this.newFPL?.findResourceJSON(item, { + type: normalizeTypes(types), + sort: DEFAULT_SORT + }); if (def) { - return def; + return cloneDeep(def); } // If it's an "implied extension", try to materialize it. See:http://hl7.org/fhir/versions.html#extensions if (IMPLIED_EXTENSION_REGEX.test(item) && types.some(t => t === Type.Extension)) { @@ -125,37 +172,40 @@ export class FHIRDefinitions extends BaseFHIRDefinitions implements Fishable { } fishForMetadata(item: string, ...types: Type[]): Metadata | undefined { - const result = this.fishForFHIR(item, ...types); - if (result) { - let canBeTarget: boolean; - let canBind: boolean; - if (result.resourceType === 'StructureDefinition' && result.kind === 'logical') { - canBeTarget = - result.extension?.some((ext: any) => { - return ( - (ext?.url === TYPE_CHARACTERISTICS_EXTENSION && ext?.valueCode === 'can-be-target') || - (ext?.url === LOGICAL_TARGET_EXTENSION && ext?.valueBoolean === true) - ); - }) ?? false; - canBind = - result.extension?.some( - (ext: any) => - ext?.url === TYPE_CHARACTERISTICS_EXTENSION && ext?.valueCode === 'can-bind' - ) ?? false; - } + const info = this.newFPL?.findResourceInfo(item, { + type: normalizeTypes(types), + sort: DEFAULT_SORT + }); + if (info) { return { - id: result.id as string, - name: result.name as string, - sdType: result.type as string, - url: result.url as string, - parent: result.baseDefinition as string, - imposeProfiles: findImposeProfiles(result), - abstract: result.abstract as boolean, - version: result.version as string, - resourceType: result.resourceType as string, - canBeTarget, - canBind + id: info.id, + name: info.name, + sdType: info.sdType, + url: info.url, + parent: info.sdBaseDefinition, + imposeProfiles: info.sdImposeProfiles, + abstract: info.sdAbstract, + version: info.version, + resourceType: info.resourceType, + canBeTarget: logicalCharacteristic(info, 'can-be-target'), + canBind: logicalCharacteristic(info, 'can-bind'), + resourcePath: info.resourcePath }; } + // If it's an "implied extension", try to materialize it. See:http://hl7.org/fhir/versions.html#extensions + if (IMPLIED_EXTENSION_REGEX.test(item) && types.some(t => t === Type.Extension)) { + return materializeImpliedExtensionMetadata(item, this); + } + } +} + +function logicalCharacteristic(info: ResourceInfo, characteristic: string) { + // only return a value for logicals, otherwise leave it undefined + if (info.sdFlavor === 'Logical') { + return info.sdCharacteristics?.some(c => c === characteristic); } } + +function normalizeTypes(types: Type[]): Type[] { + return types?.length ? types : FISHING_ORDER; +} diff --git a/src/fhirdefs/impliedExtensions.ts b/src/fhirdefs/impliedExtensions.ts index 5ef4c30f1..7f8c08f92 100644 --- a/src/fhirdefs/impliedExtensions.ts +++ b/src/fhirdefs/impliedExtensions.ts @@ -26,8 +26,13 @@ // - Once the extension has been "fished", it's used like any other extension. import { union } from 'lodash'; -import { logger, Type } from '../utils'; +import { logger, Metadata, Type } from '../utils'; import { ElementDefinition, ElementDefinitionType, StructureDefinition } from '../fhirtypes'; +import { + TYPE_CHARACTERISTICS_EXTENSION, + LOGICAL_TARGET_EXTENSION, + findImposeProfiles +} from '../fhirtypes/common'; import { FHIRDefinitions } from '../fhirdefs'; export const IMPLIED_EXTENSION_REGEX = @@ -165,6 +170,43 @@ export function materializeImpliedExtension(url: string, defs: FHIRDefinitions): return ext.toJSON(true); } +export function materializeImpliedExtensionMetadata( + url: string, + defs: FHIRDefinitions +): Metadata | undefined { + const result = materializeImpliedExtension(url, defs); + if (result) { + let canBeTarget: boolean; + let canBind: boolean; + if (result.resourceType === 'StructureDefinition' && result.kind === 'logical') { + canBeTarget = + result.extension?.some((ext: any) => { + return ( + (ext?.url === TYPE_CHARACTERISTICS_EXTENSION && ext?.valueCode === 'can-be-target') || + (ext?.url === LOGICAL_TARGET_EXTENSION && ext?.valueBoolean === true) + ); + }) ?? false; + canBind = + result.extension?.some( + (ext: any) => ext?.url === TYPE_CHARACTERISTICS_EXTENSION && ext?.valueCode === 'can-bind' + ) ?? false; + } + return { + id: result.id as string, + name: result.name as string, + sdType: result.type as string, + url: result.url as string, + parent: result.baseDefinition as string, + imposeProfiles: findImposeProfiles(result), + abstract: result.abstract as boolean, + version: result.version as string, + resourceType: result.resourceType as string, + canBeTarget, + canBind + }; + } +} + /** * Determines if the ElementDefinition can be represented using an implied extension. According to * the FHIR documentation, elements with type "Resource" cannot be represented as implied diff --git a/src/fhirdefs/index.ts b/src/fhirdefs/index.ts index ec20eaa87..9c24f9ae0 100644 --- a/src/fhirdefs/index.ts +++ b/src/fhirdefs/index.ts @@ -1,3 +1,4 @@ +export * from './BaseFHIRDefinitions'; export * from './FHIRDefinitions'; export * from './load'; export * from './R5DefsForR4'; diff --git a/src/fhirdefs/load.ts b/src/fhirdefs/load.ts index 7c6282c41..f727ac9c6 100644 --- a/src/fhirdefs/load.ts +++ b/src/fhirdefs/load.ts @@ -1,11 +1,9 @@ import { FHIRDefinitions } from './FHIRDefinitions'; -import { mergeDependency } from 'fhir-package-loader'; import fs from 'fs-extra'; import path from 'path'; -import junk from 'junk'; -import { logger, logMessage, getFilesRecursive } from '../utils'; -import { Fhir as FHIRConverter } from 'fhir/fhir'; +import { logger, logMessage } from '../utils'; import { ImplementationGuideDefinitionParameter } from '../fhirtypes'; +import { defaultPackageLoader } from 'fhir-package-loader'; /** * Loads custom resources defined in resourceDir into FHIRDefs @@ -13,14 +11,13 @@ import { ImplementationGuideDefinitionParameter } from '../fhirtypes'; * @param {string} projectDir - User's specified project directory * @param {ImplementationGuideDefinitionParameter[]} configParameters - optional, an array of config parameters in which to * determine if there are additional resource paths for predefined resource - * @param {FHIRDefinitions} defs - The FHIRDefinitions object to load definitions into + * @returns string[] list of paths to search for custom resources */ -export function loadCustomResources( +export function getLocalResourcePaths( resourceDir: string, projectDir: string = null, - configParameters: ImplementationGuideDefinitionParameter[] = null, - defs: FHIRDefinitions -): void { + configParameters: ImplementationGuideDefinitionParameter[] = null +): string[] { // Similar code for loading custom resources exists in IGExporter.ts addPredefinedResources() const pathEnds = [ 'capabilities', @@ -32,7 +29,9 @@ export function loadCustomResources( 'vocabulary', 'examples' ]; - const predefinedResourcePaths = pathEnds.map(pathEnd => path.join(resourceDir, pathEnd)); + const predefinedResourcePaths = pathEnds + .map(pathEnd => path.join(resourceDir, pathEnd)) + .filter(p => fs.existsSync(p)); if (configParameters && projectDir) { const pathResources = configParameters ?.filter(parameter => parameter.value && parameter.code === 'path-resource') @@ -42,70 +41,7 @@ export function loadCustomResources( .filter(directoryPath => fs.existsSync(directoryPath)); if (pathResourceDirectories) predefinedResourcePaths.push(...pathResourceDirectories); } - const converter = new FHIRConverter(); - let invalidFileCount = 0; - for (const dirPath of predefinedResourcePaths) { - let foundSpreadsheets = false; - if (fs.existsSync(dirPath)) { - const files = getFilesRecursive(dirPath); - for (const file of files) { - let resourceJSON: any; - try { - if (junk.is(file)) { - // Ignore "junk" files created by the OS, like .DS_Store on macOS and Thumbs.db on Windows - continue; - } else if (file.endsWith('.json')) { - resourceJSON = fs.readJSONSync(file); - } else if (file.endsWith('-spreadsheet.xml')) { - foundSpreadsheets = true; - continue; - } else if (file.endsWith('xml')) { - const xml = fs.readFileSync(file).toString(); - if (/<\?mso-application progid="Excel\.Sheet"\?>/m.test(xml)) { - foundSpreadsheets = true; - continue; - } - resourceJSON = converter.xmlToObj(xml); - } else { - invalidFileCount++; - logger.debug(`File not processed by SUSHI: ${file}`); - continue; - } - } catch (e) { - if (e.message.startsWith('Unknown resource type:')) { - // Skip unknown FHIR resource types. When we have instances of Logical Models, - // the resourceType will not be recognized as a known FHIR resourceType, but that's okay. - continue; - } - logger.error(`Loading ${file} failed with the following error:\n${e.message}`); - if (e.stack) { - logger.debug(e.stack); - } - continue; - } - // All resources are added to the predefined map, so that this map can later be used to - // access predefined resources in the IG Exporter - defs.addPredefinedResource(file, resourceJSON); - if (path.basename(dirPath) !== 'examples') { - // add() will only add resources of resourceType: - // StructureDefinition, ValueSet, CodeSystem, or ImplementationGuide - defs.add(resourceJSON); - } - } - } - if (foundSpreadsheets) { - logger.info( - `Found spreadsheets in directory ${dirPath}. SUSHI does not support spreadsheets, so any resources in the spreadsheets will be ignored.` - ); - } - } - if (invalidFileCount > 0) { - logger.info( - invalidFileCount > 1 - ? `Found ${invalidFileCount} files in input/* resource folders that were neither XML nor JSON. These files were not processed as resources by SUSHI. To see the unprocessed files in the logs, run SUSHI with the "--log-level debug" flag.` - : `Found ${invalidFileCount} file in an input/* resource folder that was neither XML nor JSON. This file was not processed as a resource by SUSHI. To see the unprocessed file in the logs, run SUSHI with the "--log-level debug" flag.` - ); - } + return [...predefinedResourcePaths]; } /** @@ -122,11 +58,20 @@ export async function loadSupplementalFHIRPackage( fhirPackage: string, defs: FHIRDefinitions ): Promise { - const supplementalDefs = new FHIRDefinitions(true); + const newFPL = await defaultPackageLoader({ + log: (level: string, message: string) => { + logMessage(level, `@@@ NEW FPL for ${fhirPackage} @@@ ${message}`); + } + }); + const supplementalDefs = new FHIRDefinitions(true, newFPL); const [fhirPackageId, fhirPackageVersion] = fhirPackage.split('#'); - return mergeDependency(fhirPackageId, fhirPackageVersion, supplementalDefs, undefined, logMessage) - .then((def: FHIRDefinitions) => defs.addSupplementalFHIRDefinitions(fhirPackage, def)) - .catch((e: Error) => { + await supplementalDefs.newFPL + ?.loadPackage(fhirPackageId, fhirPackageVersion) + .then(status => { + logger.info(`Load status for ${fhirPackage}: ${status}`); + defs.addSupplementalFHIRDefinitions(fhirPackage, supplementalDefs); + }) + .catch(e => { logger.error(`Failed to load supplemental FHIR package ${fhirPackage}: ${e.message}`); if (e.stack) { logger.debug(e.stack); diff --git a/src/fhirtypes/common.ts b/src/fhirtypes/common.ts index 84c6c9044..0c61f044f 100644 --- a/src/fhirtypes/common.ts +++ b/src/fhirtypes/common.ts @@ -992,26 +992,28 @@ export function replaceReferences( } else if (value instanceof FshCode) { // the version on a CodeSystem resource is not the same as the system's actual version out in the world. // so, they don't need to match. - const baseSystem = value.system?.split('|')[0]; - const codeSystemMeta = fisher.fishForMetadata(baseSystem, Type.CodeSystem); - if (codeSystemMeta) { - clone = cloneDeep(rule); - const assignedCode = clone.value as FshCode; - assignedCode.system = value.system.replace(/^[^|]+/, codeSystemMeta.url); + if (value.system != null) { + const baseSystem = value.system.split('|')[0]; + const codeSystemMeta = fisher.fishForMetadata(baseSystem, Type.CodeSystem); + if (codeSystemMeta) { + clone = cloneDeep(rule); + const assignedCode = clone.value as FshCode; + assignedCode.system = value.system.replace(/^[^|]+/, codeSystemMeta.url); - // Find the code system using the returned metadata to avoid duplicate warnings if version mismatches - const matchedCanonical = codeSystemMeta.url - ? `${codeSystemMeta.url}${codeSystemMeta.version ? `|${codeSystemMeta.version}` : ''}` - : value.system; - const codeSystem = fishInTankBestVersion( - tank, - matchedCanonical, - rule.sourceInfo, - Type.CodeSystem - ); - if (codeSystem && (codeSystem instanceof FshCodeSystem || codeSystem instanceof Instance)) { - // if a local system was used, check to make sure the code is actually in that system - listUndefinedLocalCodes(codeSystem, [assignedCode.code], tank, rule); + // Find the code system using the returned metadata to avoid duplicate warnings if version mismatches + const matchedCanonical = codeSystemMeta.url + ? `${codeSystemMeta.url}${codeSystemMeta.version ? `|${codeSystemMeta.version}` : ''}` + : value.system; + const codeSystem = fishInTankBestVersion( + tank, + matchedCanonical, + rule.sourceInfo, + Type.CodeSystem + ); + if (codeSystem && (codeSystem instanceof FshCodeSystem || codeSystem instanceof Instance)) { + // if a local system was used, check to make sure the code is actually in that system + listUndefinedLocalCodes(codeSystem, [assignedCode.code], tank, rule); + } } } } diff --git a/src/ig/IGExporter.ts b/src/ig/IGExporter.ts index aed50d878..5f682c159 100644 --- a/src/ig/IGExporter.ts +++ b/src/ig/IGExporter.ts @@ -28,8 +28,8 @@ import { } from '../fhirtypes'; import { CONFORMANCE_AND_TERMINOLOGY_RESOURCES } from '../fhirtypes/common'; import { ConfigurationMenuItem, ConfigurationResource } from '../fshtypes'; -import { logger, Type, getFilesRecursive, stringOrElse, getFHIRVersionInfo } from '../utils'; -import { FHIRDefinitions } from '../fhirdefs'; +import { logger, Type, stringOrElse, getFHIRVersionInfo } from '../utils'; +import { FHIRDefinitions, getLocalResourcePaths } from '../fhirdefs'; import { Configuration } from '../fshtypes'; import { parseCodeLexeme } from '../import'; @@ -271,15 +271,16 @@ export class IGExporter { } if (dependsOn.version === 'latest') { + // TODO: This assumes only a single version of a package is in scope const dependencyIG = igs.find(ig => ig.packageId === dependsOn.packageId); if (dependencyIG?.version != null) { dependsOn.version = dependencyIG.version; } else { - const packageJSON = this.fhirDefs - .allPackageJsons() - .find(p => p.name === dependsOn.packageId); - if (packageJSON?.version != null) { - dependsOn.version = packageJSON.version; + const packageInfos = this.fhirDefs + .fishForPackageInfos(dependsOn.packageId) + .filter(info => info.version != null); + if (packageInfos.length) { + dependsOn.version = packageInfos[0].version; } } } @@ -946,214 +947,170 @@ export class IGExporter { this.ig.definition.resource.push(newResource); } - /** - * Adds any user provided resource files to the ImplementationGuide JSON file. - * This includes definitions in: - * capabilities, extensions, models, operations, profiles, resources, vocabulary, examples - * Based on: https://build.fhir.org/ig/FHIR/ig-guidance/using-templates.html#root.input - * - * NOTE: This only includes files nested in subfolders when specified in the path-resource - * parameter, which is based on how the IG Publisher works. - * - * This function has similar operation to addResources, and both should be - * analyzed when making changes to either. - */ private addPredefinedResources(): void { - // Similar code for loading custom resources exists in load.ts loadCustomResources() - const pathEnds = [ - 'capabilities', - 'extensions', - 'models', - 'operations', - 'profiles', - 'resources', - 'vocabulary', - 'examples' - ]; - const predefinedResourcePaths = pathEnds.map(pathEnd => - path.join(this.inputPath, 'input', pathEnd) - ); - const pathResourceDirectories: string[] = []; - const pathResources = this.config.parameters - ?.filter(parameter => parameter.value && parameter.code === 'path-resource') - .map(parameter => parameter.value); - if (pathResources) { - pathResources.forEach(directoryPath => { - const fullPath = path.join(this.inputPath, ...directoryPath.split('/')); - if (existsSync(fullPath)) { - pathResourceDirectories.push(fullPath); - } else if (directoryPath.endsWith('/*') && existsSync(fullPath.slice(0, -2))) { - pathResourceDirectories.push( - ...readdirSync(fullPath.slice(0, -2), { withFileTypes: true, recursive: true }) - .filter(file => file.isDirectory()) - .map(dir => path.join(dir.path, dir.name)) - ); - } - }); - if (pathResourceDirectories) predefinedResourcePaths.push(...pathResourceDirectories); - } - const deeplyNestedFiles: string[] = []; const configuredBinaryResources = (this.config.resources ?? []).filter( resource => resource.reference?.reference?.startsWith('Binary/') && resource.extension?.some(e => IG_RESOURCE_FORMAT_EXTENSIONS.includes(e.url)) ); - for (const dirPath of predefinedResourcePaths) { - if (existsSync(dirPath)) { - const files = getFilesRecursive(dirPath); - for (const file of files) { + + const localResourcePaths = getLocalResourcePaths( + path.join(this.inputPath, 'input'), + this.inputPath, + this.config.parameters + ); + // BUG: predefinedResourceMetadatas and predefinedResources in different orders!!! + const predefinedResourceMetadatas = this.fhirDefs.allPredefinedResourceMetadatas(); + const predefinedResources = this.fhirDefs.allPredefinedResources(); + const deeplyNestedFiles: string[] = []; + for (let i = 0; i < predefinedResources.length; i++) { + // Since virtual resources start with 'virtual:{package}#{version}:', remove that part + const file = predefinedResourceMetadatas[i].resourcePath.replace(/^virtual:[^#]+#[^:]+:/, ''); + // If it's deeply nested, do not include it in the resource list + if (!localResourcePaths.includes(path.dirname(file))) { + deeplyNestedFiles.push(file); + continue; + } + const resourceJSON: InstanceDefinition = predefinedResources[i]; + if (resourceJSON) { + // For predefined examples of Logical Models, the user must provide an entry in config + // that specifies the reference as Binary/[id], the extension that specifies the resource format, + // and the exampleCanonical that references the LogicalModel the resource is an example of. + // Note: the exampleCanonical should reference the resourceType because the resourceType should + // be the absolute URL, but we previously supported using the logical model's id as the example's + // resourceType, so support having an exampleCanonical in either form for now. + // In that case, we do not want to add our own entry for the predefined resource - we just + // want to use the resource entry from the sushi-config.yaml + // For predefined examples of Logical Models that do not have a resourceType or id, + // a Binary resource reference based on the file name can be used, based on Zulip: + // https://chat.fhir.org/#narrow/stream/215610-shorthand/topic/How.20do.20I.20get.20SUSHI.20to.20ignore.20a.20binary.20JSON.20logical.20instance.3F/near/407861211 + const configuredBinaryReference = configuredBinaryResources.find( + resource => + (resource.reference?.reference === `Binary/${resourceJSON.id}` && + (resource.exampleCanonical === + `${this.config.canonical}/StructureDefinition/${resourceJSON.resourceType}` || + resource.exampleCanonical === resourceJSON.resourceType)) || + resource.reference?.reference === `Binary/${path.parse(file).name}` + ); + + if (configuredBinaryReference) { if ( - path.dirname(file) !== dirPath && - !pathResourceDirectories?.includes(path.dirname(file)) + configuredBinaryReference.extension?.some( + ext => ext.url === DEPRECATED_RESOURCE_FORMAT_EXTENSION + ) ) { - if (!deeplyNestedFiles.includes(file)) { - deeplyNestedFiles.push(file); - } - continue; - } - const resourceJSON: InstanceDefinition = this.fhirDefs.getPredefinedResource(file); - if (resourceJSON) { - // For predefined examples of Logical Models, the user must provide an entry in config - // that specifies the reference as Binary/[id], the extension that specifies the resource format, - // and the exampleCanonical that references the LogicalModel the resource is an example of. - // Note: the exampleCanonical should reference the resourceType because the resourceType should - // be the absolute URL, but we previously supported using the logical model's id as the example's - // resourceType, so support having an exampleCanonical in either form for now. - // In that case, we do not want to add our own entry for the predefined resource - we just - // want to use the resource entry from the sushi-config.yaml - // For predefined examples of Logical Models that do not have a resourceType or id, - // a Binary resource reference based on the file name can be used, based on Zulip: - // https://chat.fhir.org/#narrow/stream/215610-shorthand/topic/How.20do.20I.20get.20SUSHI.20to.20ignore.20a.20binary.20JSON.20logical.20instance.3F/near/407861211 - const configuredBinaryReference = configuredBinaryResources.find( - resource => - (resource.reference?.reference === `Binary/${resourceJSON.id}` && - (resource.exampleCanonical === - `${this.config.canonical}/StructureDefinition/${resourceJSON.resourceType}` || - resource.exampleCanonical === resourceJSON.resourceType)) || - resource.reference?.reference === `Binary/${path.parse(file).name}` + logger.warn( + `The extension ${DEPRECATED_RESOURCE_FORMAT_EXTENSION} has been deprecated. Update the configuration for ${configuredBinaryReference.reference?.reference ?? configuredBinaryReference.name} to use the current extension, ${CURRENT_RESOURCE_FORMAT_EXTENSION}.` ); + } + continue; + } - if (configuredBinaryReference) { - if ( - configuredBinaryReference.extension?.some( - ext => ext.url === DEPRECATED_RESOURCE_FORMAT_EXTENSION - ) - ) { - logger.warn( - `The extension ${DEPRECATED_RESOURCE_FORMAT_EXTENSION} has been deprecated. Update the configuration for ${configuredBinaryReference.reference?.reference ?? configuredBinaryReference.name} to use the current extension, ${CURRENT_RESOURCE_FORMAT_EXTENSION}.` - ); - } - continue; - } - - if (resourceJSON.resourceType == null || resourceJSON.id == null) { - logger.warn( - `Resource at ${file} is missing ${ - resourceJSON.resourceType == null ? 'resourceType' : '' - }${resourceJSON.resourceType == null && resourceJSON.id == null ? ' and ' : ''}${ - resourceJSON.id == null ? 'id' : '' - }.` - ); - continue; - } - - const referenceKey = `${resourceJSON.resourceType}/${resourceJSON.id}`; - const newResource: ImplementationGuideDefinitionResource = { - reference: { - reference: referenceKey - } - }; - const configResource = (this.config.resources ?? []).find( - resource => resource.reference?.reference == referenceKey - ); + if (resourceJSON.resourceType == null || resourceJSON.id == null) { + logger.warn( + `Resource at ${file} is missing ${ + resourceJSON.resourceType == null ? 'resourceType' : '' + }${resourceJSON.resourceType == null && resourceJSON.id == null ? ' and ' : ''}${ + resourceJSON.id == null ? 'id' : '' + }.` + ); + continue; + } - if (configResource?.omit !== true) { - const existingIndex = this.ig.definition.resource.findIndex( - r => r.reference.reference === referenceKey - ); - // If the user has provided a resource, it should override the generated resource. - // This can be helpful for working around cases where the generated resource has some incorrect values. - const existingResource = - existingIndex >= 0 ? this.ig.definition.resource[existingIndex] : null; - const existingIsExample = - existingResource?.exampleBoolean || existingResource?.exampleCanonical; - const existingName = existingIsExample ? existingResource.name : null; - const existingDescription = existingIsExample ? existingResource.description : null; - - const metaExtensionDescription = this.getMetaExtensionDescription(resourceJSON); - const metaExtensionName = this.getMetaExtensionName(resourceJSON); - // On some resources (Patient for example) title, name, and description can be objects, avoid using them when this is true - newResource.description = - configResource?.description ?? - metaExtensionDescription ?? - existingDescription ?? - stringOrElse(resourceJSON.description); - if (configResource?.fhirVersion) { - newResource.fhirVersion = configResource.fhirVersion; - } - if (configResource?.groupingId) { - newResource.groupingId = configResource.groupingId; - this.addGroup(newResource.groupingId); - } - if (path.basename(dirPath) === 'examples') { - newResource.name = - configResource?.name ?? - metaExtensionName ?? - existingName ?? - stringOrElse(resourceJSON.title) ?? - stringOrElse(resourceJSON.name) ?? - resourceJSON.id; - newResource._linkRef = resourceJSON.id; - // set exampleCanonical or exampleBoolean, preferring configured values - if (configResource?.exampleCanonical) { - newResource.exampleCanonical = configResource.exampleCanonical; - } else if (typeof configResource?.exampleBoolean === 'boolean') { - newResource.exampleBoolean = configResource.exampleBoolean; - } else { - const exampleUrl = resourceJSON.meta?.profile?.find(url => { - const [baseUrl, version] = url.split('|', 2); - const availableProfile = - this.pkg.fish(baseUrl, Type.Profile) ?? - this.fhirDefs.fishForFHIR(baseUrl, Type.Profile); - return ( - availableProfile != null && - (version == null || - version === (availableProfile.version ?? this.config.version)) - ); - }); - if (exampleUrl) { - newResource.exampleCanonical = exampleUrl.split('|', 1)[0]; - } else { - newResource.exampleBoolean = true; - } - } - } else { - if (configResource?.exampleCanonical) { - newResource.exampleCanonical = configResource.exampleCanonical; - } else if (typeof configResource?.exampleBoolean === 'boolean') { - newResource.exampleBoolean = configResource.exampleBoolean; - } else { - newResource.exampleBoolean = false; - } - newResource.name = - configResource?.name ?? - metaExtensionName ?? - existingResource?.name ?? - stringOrElse(resourceJSON.title) ?? - stringOrElse(resourceJSON.name) ?? - resourceJSON.id; - newResource._linkRef = stringOrElse(resourceJSON.name) ?? resourceJSON.id; - } - if (configResource?.extension?.length) { - newResource.extension = configResource.extension; - } + const referenceKey = `${resourceJSON.resourceType}/${resourceJSON.id}`; + const newResource: ImplementationGuideDefinitionResource = { + reference: { + reference: referenceKey + } + }; + const configResource = (this.config.resources ?? []).find( + resource => resource.reference?.reference == referenceKey + ); - if (existingIndex >= 0) { - this.ig.definition.resource[existingIndex] = newResource; + if (configResource?.omit !== true) { + const existingIndex = this.ig.definition.resource.findIndex( + r => r.reference.reference === referenceKey + ); + // If the user has provided a resource, it should override the generated resource. + // This can be helpful for working around cases where the generated resource has some incorrect values. + const existingResource = + existingIndex >= 0 ? this.ig.definition.resource[existingIndex] : null; + const existingIsExample = + existingResource?.exampleBoolean || existingResource?.exampleCanonical; + const existingName = existingIsExample ? existingResource.name : null; + const existingDescription = existingIsExample ? existingResource.description : null; + + const metaExtensionDescription = this.getMetaExtensionDescription(resourceJSON); + const metaExtensionName = this.getMetaExtensionName(resourceJSON); + // On some resources (Patient for example) title, name, and description can be objects, avoid using them when this is true + newResource.description = + configResource?.description ?? + metaExtensionDescription ?? + existingDescription ?? + stringOrElse(resourceJSON.description); + if (configResource?.fhirVersion) { + newResource.fhirVersion = configResource.fhirVersion; + } + if (configResource?.groupingId) { + newResource.groupingId = configResource.groupingId; + this.addGroup(newResource.groupingId); + } + if (path.basename(path.dirname(file)) === 'examples') { + newResource.name = + configResource?.name ?? + metaExtensionName ?? + existingName ?? + stringOrElse(resourceJSON.title) ?? + stringOrElse(resourceJSON.name) ?? + resourceJSON.id; + newResource._linkRef = resourceJSON.id; + // set exampleCanonical or exampleBoolean, preferring configured values + if (configResource?.exampleCanonical) { + newResource.exampleCanonical = configResource.exampleCanonical; + } else if (typeof configResource?.exampleBoolean === 'boolean') { + newResource.exampleBoolean = configResource.exampleBoolean; + } else { + const exampleUrl = resourceJSON.meta?.profile?.find(url => { + const [baseUrl, version] = url.split('|', 2); + const availableProfile = + this.pkg.fish(baseUrl, Type.Profile) ?? + this.fhirDefs.fishForFHIR(baseUrl, Type.Profile); + return ( + availableProfile != null && + (version == null || version === (availableProfile.version ?? this.config.version)) + ); + }); + if (exampleUrl) { + newResource.exampleCanonical = exampleUrl.split('|', 1)[0]; } else { - this.ig.definition.resource.push(newResource); + newResource.exampleBoolean = true; } } + } else { + if (configResource?.exampleCanonical) { + newResource.exampleCanonical = configResource.exampleCanonical; + } else if (typeof configResource?.exampleBoolean === 'boolean') { + newResource.exampleBoolean = configResource.exampleBoolean; + } else { + newResource.exampleBoolean = false; + } + newResource.name = + configResource?.name ?? + metaExtensionName ?? + existingResource?.name ?? + stringOrElse(resourceJSON.title) ?? + stringOrElse(resourceJSON.name) ?? + resourceJSON.id; + newResource._linkRef = stringOrElse(resourceJSON.name) ?? resourceJSON.id; + } + if (configResource?.extension?.length) { + newResource.extension = configResource.extension; + } + + if (existingIndex >= 0) { + this.ig.definition.resource[existingIndex] = newResource; + } else { + this.ig.definition.resource.push(newResource); } } } diff --git a/src/utils/Fishable.ts b/src/utils/Fishable.ts index ccbf19370..3c5898017 100644 --- a/src/utils/Fishable.ts +++ b/src/utils/Fishable.ts @@ -27,6 +27,7 @@ export interface Metadata { instanceUsage?: Instance['usage']; canBeTarget?: boolean; canBind?: boolean; + resourcePath?: string; } export interface Fishable { diff --git a/src/utils/Processing.ts b/src/utils/Processing.ts index 0f8083e52..b4dc1d0bb 100644 --- a/src/utils/Processing.ts +++ b/src/utils/Processing.ts @@ -6,11 +6,16 @@ import YAML from 'yaml'; import { execSync } from 'child_process'; import { YAMLMap, Collection } from 'yaml/types'; import { isPlainObject, padEnd, startCase, sortBy, upperFirst } from 'lodash'; -import { mergeDependency, FHIRDefinitions as BaseFHIRDefinitions } from 'fhir-package-loader'; import { EOL } from 'os'; import { AxiosResponse } from 'axios'; +import table from 'text-table'; +import { OptionValues } from 'commander'; import { logger, logMessage } from './FSHLogger'; -import { loadSupplementalFHIRPackage, FHIRDefinitions } from '../fhirdefs'; +import { + loadSupplementalFHIRPackage, + FHIRDefinitions, + R5_DEFINITIONS_NEEDED_IN_R4 +} from '../fhirdefs'; import { FSHTank, RawFSH, @@ -24,8 +29,7 @@ import { Configuration } from '../fshtypes'; import { axiosGet } from './axiosUtils'; import { ImplementationGuideDependsOn } from '../fhirtypes'; import { FHIRVersionName, getFHIRVersionInfo } from '../utils/FHIRVersionUtils'; -import table from 'text-table'; -import { OptionValues } from 'commander'; +import { InMemoryVirtualPackage } from 'fhir-package-loader'; const EXT_PKG_TO_FHIR_PKG_MAP: { [key: string]: string } = { 'hl7.fhir.extensions.r2': 'hl7.fhir.r2.core#1.0.2', @@ -368,9 +372,27 @@ export async function loadExternalDependencies( export async function loadAutomaticDependencies( fhirVersion: string, configuredDependencies: ImplementationGuideDependsOn[], - defs: BaseFHIRDefinitions + defs: FHIRDefinitions ): Promise { const fhirVersionName = getFHIRVersionInfo(fhirVersion).name; + + if (fhirVersionName === 'R4' || fhirVersionName === 'R4B') { + // There are several R5 resources that are allowed for use in R4 and R4B. + // Add them first so they're always available. + const R5forR4Map = new Map(); + R5_DEFINITIONS_NEEDED_IN_R4.forEach(def => R5forR4Map.set(def.id, def)); + const virtualR5forR4Package = new InMemoryVirtualPackage( + { name: 'sushi-r5forR4', version: '1.0.0' }, + R5forR4Map, + { + log: (level: string, message: string) => { + logMessage(level, `@@@ NEW FPL @@@ ${message}`); + } + } + ); + await defs.newFPL?.loadVirtualPackage(virtualR5forR4Package); + } + // Load dependencies serially so dependency loading order is predictable and repeatable for (const dep of AUTOMATIC_DEPENDENCIES) { // Skip dependencies not intended for this version of FHIR @@ -388,7 +410,9 @@ export async function loadAutomaticDependencies( }); if (!alreadyConfigured) { try { - await mergeDependency(dep.packageId, dep.version, defs, undefined, logMessage); + const status = await defs.newFPL?.loadPackage(dep.packageId, dep.version); + // TODO: This prints out "latest" and "1.2.x" when we want to know the real version + logger.info(`Load status for ${dep.packageId}#${dep.version}: ${status}`); } catch (e) { let message = `Failed to load automatically-provided ${dep.packageId}#${dep.version}`; if (process.env.FPL_REGISTRY) { @@ -444,16 +468,19 @@ async function loadConfiguredDependencies( ); await loadSupplementalFHIRPackage(EXT_PKG_TO_FHIR_PKG_MAP[dep.packageId], defs); } else { - await mergeDependency(dep.packageId, dep.version, defs, undefined, logMessage).catch(e => { - let message = `Failed to load ${dep.packageId}#${dep.version}: ${e.message}`; - if (/certificate/.test(e.message)) { - message += CERTIFICATE_MESSAGE; - } - logger.error(message); - if (e.stack) { - logger.debug(e.stack); - } - }); + await defs.newFPL + ?.loadPackage(dep.packageId, dep.version) + .then(status => logger.info(`Load status for ${dep.packageId}#${dep.version}: ${status}`)) + .catch(e => { + let message = `Failed to load ${dep.packageId}#${dep.version}: ${e.message}`; + if (/certificate/.test(e.message)) { + message += CERTIFICATE_MESSAGE; + } + logger.error(message); + if (e.stack) { + logger.debug(e.stack); + } + }); } } } diff --git a/test/fhirdefs/FHIRDefinitions.test.ts b/test/fhirdefs/FHIRDefinitions.test.ts index 208328523..7e1eaba83 100644 --- a/test/fhirdefs/FHIRDefinitions.test.ts +++ b/test/fhirdefs/FHIRDefinitions.test.ts @@ -1231,16 +1231,4 @@ describe('FHIRDefinitions', () => { ]); }); }); - - describe('#allPackageJSONs', () => { - it('should return all package jsons', () => { - const testDefs = new FHIRDefinitions(); - testDefs.addPackageJson('sushi.test.1', { name: 'sushi.test.1', version: '0.0.1' }); - testDefs.addPackageJson('sushi.test.2', { name: 'sushi.test.2', version: '0.0.2' }); - expect(testDefs.allPackageJsons()).toEqual([ - { name: 'sushi.test.1', version: '0.0.1' }, - { name: 'sushi.test.2', version: '0.0.2' } - ]); - }); - }); }); diff --git a/tsconfig.json b/tsconfig.json index 7c372fc2d..9ee2466d9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,9 +2,9 @@ "compilerOptions": { /* Basic Options */ // "incremental": true, /* Enable incremental compilation */ - "target": "ES2018", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ + "target": "ES2018", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ - "lib": ["ES2020"], /* Specify library files to be included in the compilation. */ + "lib": ["ES2020", "DOM"], /* Specify library files to be included in the compilation. */ "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ @@ -51,7 +51,7 @@ // "typeRoots": [], /* List of folders to include type definitions from. */ // "types": [], /* Type declaration files to be included in compilation. */ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ - "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ @@ -66,7 +66,7 @@ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ /* Advanced Options */ - "resolveJsonModule": true /* Include modules imported with '.json' extension */ + "resolveJsonModule": true /* Include modules imported with '.json' extension */ }, "include": [ "src/**/*"