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 000000000..a3faf4d23 Binary files /dev/null and b/local-dependencies/fhir-package-loader-1.0.0.tgz differ diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 3ea6c2563..891a70b44 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -15,7 +15,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", @@ -49,6 +49,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", @@ -871,6 +872,106 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "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-nB9Xy4oTJSJ4npcH/vS0X5ieqs36i9+bAs4xkWkHAl9b0UX4I75mt5zN0rXFlNF4Vc3RiPTWknyVA3Dw/RVydw==", "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.11.0", + "resolved": "https://registry.npmjs.org/sql.js/-/sql.js-1.11.0.tgz", + "integrity": "sha512-GsLUDU3vhOo14Pd5ME0y2te49JQyby6HuoCuadevEV+CGgTUjmYRrm7B7lhRyzOgrmcWmspUfyjNb6sOAEqdsA==" + }, "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-diffs.txt b/regression/repos-diffs.txt new file mode 100644 index 000000000..497b8f4ec --- /dev/null +++ b/regression/repos-diffs.txt @@ -0,0 +1,8 @@ +hl7-be/lab#master +hl7-it/cda2fhir#master +HL7/fhir-ipa#master +HL7/VhDir#master +hl7ch/ch-core#master +I-TECH-UW/smart-hiv-archived#main +IntelliSOFT-Consulting/HIV-FHIR-IG#main +mtnlotus/pco-ig#main diff --git a/regression/repos-errors.txt b/regression/repos-errors.txt new file mode 100644 index 000000000..8f3df80ae --- /dev/null +++ b/regression/repos-errors.txt @@ -0,0 +1,21 @@ +HL7/davinci-ecdx#master +HL7/davinci-ehrx#master +HL7/fhir-order-catalog#master +ahdis/ch-alis#master +cander2/epi-test#master +costateixeira/empty#main +costateixeira/LocalAdaptation#master +costateixeira/oops#main +HL7-cz/cz-lab#master +hl7-eu/xpandh-ps#master +HL7/davinci-crd#master +HL7/fhir-shc-vaccination-ig#master +HL7NZ/hpi#master +HL7NZ/mws-ig#main +HL7NZ/nes-ig#master +HL7NZ/nhi#master +IHE/QRPH.QORE#master +ritikarawlani/smart-ig-test#main +ritikarawlani/smart-ig-test-2#main +ritikarawlani/smart-ig-test-3#main +ritikarawlani/smart-ig-test-4#main diff --git a/regression/repos-warnings.txt b/regression/repos-warnings.txt new file mode 100644 index 000000000..2bb129b90 --- /dev/null +++ b/regression/repos-warnings.txt @@ -0,0 +1,16 @@ +hl7-eu/gravitate-health#master +ahdis/ch-ig#master +ansforge/IG-FHIR-EDS-SOCLE-COMMUN#main +CEOsys/cpg-on-ebm-on-fhir#master +hl7-eu/hdr#master +hl7-eu/xpandh-hdr#master +HL7/ebm#master +HL7/fhir-sdoh-clinicalcare#master +IHE/pharm-meow#main +IHE/pharm-supply#master +medizininformatik-initiative/kerndatensatz-bildgebung#main +openhie/NdovuClaims#master +TEHIK-EE/ig-ee-medication-scheme#master +TEHIK-EE/TerminologyServices#master +Uppsala-Monitoring-Centre/WHO-UMC-IDMP-Service#main +WorldHealthOrganization/smart-pcmt#main 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}`); + }, + recursive: true + }) + ) + .then(status => logger.info(`Load status for local resources: ${status}`)); // Check for StructureDefinition const structDef = defs.fishForFHIR('StructureDefinition', Type.Resource); @@ -296,6 +317,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..9c9f9668f 100644 --- a/src/fhirdefs/FHIRDefinitions.ts +++ b/src/fhirdefs/FHIRDefinitions.ts @@ -1,33 +1,22 @@ import { cloneDeep, flatten } from 'lodash'; -import { FHIRDefinitions as BaseFHIRDefinitions } from 'fhir-package-loader'; +import { FindResourceInfoOptions, PackageInfo, PackageLoader } 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'; 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 +24,43 @@ 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'] }; + if (fhirPackage) { + options.scope = fhirPackage; } + return cloneDeep(this.newFPL?.findResourceJSONs('*', options)); + } + + allPredefinedResources(makeClone = true): any[] { + // Return in reverse order to match previous SUSHI behavior + const pdResources = ( + this.newFPL?.findResourceJSONs('*', { scope: 'sushi-local' }) ?? [] + ).reverse(); + return makeClone ? pdResources.map(r => cloneDeep(r)) : pdResources; + } + + allPredefinedResourceMetadatas(): Metadata[] { + // Return in reverse order to match previous SUSHI behavior + return this.newFPL + ?.findResourceInfos('*', { scope: 'sushi-local' }) + .reverse() + .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: info.sdCharacteristics?.some(c => c === 'can-be-target'), + canBind: info.sdCharacteristics?.some(c => c === 'can-bind'), + resourcePath: info.resourcePath + }; + }); } add(definition: any): void { @@ -61,16 +81,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 +94,58 @@ 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' + }); + 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' + }); + 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: info.sdCharacteristics?.some(c => c === 'can-be-target'), + canBind: info.sdCharacteristics?.some(c => c === '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) + }); 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 +154,36 @@ 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) + }); + 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: info.sdCharacteristics?.some(c => c === 'can-be-target'), + canBind: info.sdCharacteristics?.some(c => c === '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 normalizeTypes(types: Type[]): Type[] { + if (types?.length) { + return types.sort((a, b) => FISHING_ORDER.indexOf(a) - FISHING_ORDER.indexOf(b)); + } else { + return 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..dcf5a1c62 100644 --- a/src/ig/IGExporter.ts +++ b/src/ig/IGExporter.ts @@ -28,7 +28,7 @@ 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 { logger, Type, stringOrElse, getFHIRVersionInfo } from '../utils'; import { FHIRDefinitions } 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,229 +947,161 @@ 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 predefinedResourceMetadatas = this.fhirDefs.allPredefinedResourceMetadatas(); + const predefinedResources = this.fhirDefs.allPredefinedResources(); + for (let i = 0; i < predefinedResources.length; i++) { + const resourceJSON: InstanceDefinition = predefinedResources[i]; + if (resourceJSON) { + const file = predefinedResourceMetadatas[i].resourcePath; + // 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); } } } } - if (deeplyNestedFiles.length) { - logger.warn( - 'The following files were not added to the ImplementationGuide JSON because they are not in one of the supported ' + - 'input folders or are nested too deep in one of those folders. While SUSHI automatically supports resources in ' + - 'sub-folders, the IG Publisher does not, unless the folder is explicitly added via the template or an IG parameter. ' + - 'To fix any issues you may encounter due to this, adjust your IG parameters or template accordingly or move these ' + - `files so they are directly under a supported input folder (e.g., input/resources, input/profiles, etc.):\n - ${deeplyNestedFiles.join( - '\n - ' - )}` - ); - } } /** 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..60f0873a1 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/**/*"