diff --git a/package-lock.json b/package-lock.json index 02d2140..b95768b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "clinical-trial-matching-service", - "version": "0.0.13", + "version": "0.0.14", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "clinical-trial-matching-service", - "version": "0.0.13", + "version": "0.0.14", "license": "Apache-2.0", "dependencies": { "body-parser": "^1.19.0", @@ -16,24 +16,27 @@ "devDependencies": { "@istanbuljs/nyc-config-typescript": "^1.0.1", "@types/express": "^4.17.17", - "@types/fhir": "^0.0.37", + "@types/fhir": "^0.0.40", "@types/jasmine": "^5.1.0", "@types/mock-fs": "^4.13.0", - "@types/node": "^18.6.3", + "@types/node": "^20.11.6", "@types/node-fetch": "^2.6.6", - "@types/supertest": "^2.0.9", + "@types/supertest": "^6.0.2", "@typescript-eslint/eslint-plugin": "^6.1.0", "@typescript-eslint/parser": "^6.1.0", "eslint": "^8.29.0", "jasmine": "^5.0.2", - "memfs": "^4.2.0", "nock": "^13.0.4", "nyc": "^15.1.0", - "openapi-typescript-codegen": "^0.25.0", + "openapi-typescript-codegen": "^0.27.0", "source-map-support": "^0.5.19", "supertest": "^6.0.1", "ts-node": "^10.9.1", "typescript": "^5.1.6" + }, + "optionalDependencies": { + "sqlite": "^5.1.1", + "sqlite3": "^5.1.7" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -59,24 +62,31 @@ } }, "node_modules/@apidevtools/json-schema-ref-parser": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz", - "integrity": "sha512-GBD2Le9w2+lVFoc4vswGI/TjkNIZSVp7+9xPf+X3uidBfWnAeUWmquteSyt0+VCrhNMWj/FTABISQrD3Z/YA+w==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-10.1.0.tgz", + "integrity": "sha512-3e+viyMuXdrcK8v5pvP+SDoAQ77FH6OyRmuK48SZKmdHJRFm87RsSs8qm6kP39a/pOPURByJw+OXzQIqcfmKtA==", "dev": true, "dependencies": { "@jsdevtools/ono": "^7.1.3", - "@types/json-schema": "^7.0.6", - "call-me-maybe": "^1.0.1", - "js-yaml": "^4.1.0" + "@types/json-schema": "^7.0.11", + "@types/lodash.clonedeep": "^4.5.7", + "js-yaml": "^4.1.0", + "lodash.clonedeep": "^4.5.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/philsturgeon" } }, "node_modules/@babel/code-frame": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", - "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", "dev": true, "dependencies": { - "@babel/highlight": "^7.22.13", + "@babel/highlight": "^7.23.4", "chalk": "^2.4.2" }, "engines": { @@ -155,30 +165,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.2.tgz", - "integrity": "sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", + "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz", - "integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.9.tgz", + "integrity": "sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-module-transforms": "^7.23.0", - "@babel/helpers": "^7.23.2", - "@babel/parser": "^7.23.0", - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.2", - "@babel/types": "^7.23.0", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.23.9", + "@babel/parser": "^7.23.9", + "@babel/template": "^7.23.9", + "@babel/traverse": "^7.23.9", + "@babel/types": "^7.23.9", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -209,12 +219,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", - "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", "dev": true, "dependencies": { - "@babel/types": "^7.23.0", + "@babel/types": "^7.23.6", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -224,14 +234,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", - "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-validator-option": "^7.22.15", - "browserslist": "^4.21.9", + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -295,9 +305,9 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz", - "integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", @@ -338,9 +348,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", "dev": true, "engines": { "node": ">=6.9.0" @@ -356,32 +366,32 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", - "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz", - "integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.9.tgz", + "integrity": "sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==", "dev": true, "dependencies": { - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.2", - "@babel/types": "^7.23.0" + "@babel/template": "^7.23.9", + "@babel/traverse": "^7.23.9", + "@babel/types": "^7.23.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", - "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.22.20", @@ -464,9 +474,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", - "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", + "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -476,34 +486,34 @@ } }, "node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz", + "integrity": "sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", - "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.9.tgz", + "integrity": "sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.0", - "@babel/types": "^7.23.0", - "debug": "^4.1.0", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9", + "debug": "^4.3.1", "globals": "^11.1.0" }, "engines": { @@ -520,12 +530,12 @@ } }, "node_modules/@babel/types": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", - "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", + "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-string-parser": "^7.23.4", "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, @@ -571,18 +581,18 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.9.1.tgz", - "integrity": "sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -602,29 +612,79 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@eslint/js": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.52.0.tgz", - "integrity": "sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", + "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "optional": true + }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", - "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^2.0.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { "node": ">=10.10.0" } }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -639,9 +699,9 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", - "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", + "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", "dev": true }, "node_modules/@isaacs/cliui": { @@ -850,9 +910,9 @@ "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.20", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", - "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", + "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -900,6 +960,30 @@ "node": ">= 8" } }, + "node_modules/@npmcli/fs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", + "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", + "optional": true, + "dependencies": { + "@gar/promisify": "^1.0.1", + "semver": "^7.3.5" + } + }, + "node_modules/@npmcli/move-file": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", + "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "optional": true, + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -910,6 +994,15 @@ "node": ">=14" } }, + "node_modules/@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "optional": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -935,9 +1028,9 @@ "dev": true }, "node_modules/@types/body-parser": { - "version": "1.19.4", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.4.tgz", - "integrity": "sha512-N7UDG0/xiPQa2D/XrVJXjkWbpqHCd2sBaB32ggRF2l83RhPfamgKGF8gwwqyksS95qUS5ZYF9aF+lLPRlwI2UA==", + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", "dev": true, "dependencies": { "@types/connect": "*", @@ -945,24 +1038,24 @@ } }, "node_modules/@types/connect": { - "version": "3.4.37", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.37.tgz", - "integrity": "sha512-zBUSRqkfZ59OcwXon4HVxhx5oWCJmc0OtBTK05M+p0dYjgN6iTwIL2T/WbsQZrEsdnwaF9cWQ+azOnpPvIqY3Q==", + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/cookiejar": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.3.tgz", - "integrity": "sha512-LZ8SD3LpNmLMDLkG2oCBjZg+ETnx6XdCjydUE0HwojDmnDfDUnhMKKbtth1TZh+hzcqb03azrYWoXLS8sMXdqg==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", + "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", "dev": true }, "node_modules/@types/express": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.20.tgz", - "integrity": "sha512-rOaqlkgEvOW495xErXMsmyX3WKBInbhG5eqojXYi3cGUaLoRDlXa5d52fkfWZT963AZ3v2eZ4MbKE6WpDAGVsw==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", "dev": true, "dependencies": { "@types/body-parser": "*", @@ -972,9 +1065,9 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "4.17.39", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.39.tgz", - "integrity": "sha512-BiEUfAiGCOllomsRAZOiMFP7LAnrifHpt56pc4Z7l9K6ACyN06Ns1JLMBxwkfLOjJRlSf06NwWsT7yzfpaVpyQ==", + "version": "4.17.41", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz", + "integrity": "sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==", "dev": true, "dependencies": { "@types/node": "*", @@ -984,54 +1077,78 @@ } }, "node_modules/@types/fhir": { - "version": "0.0.37", - "resolved": "https://registry.npmjs.org/@types/fhir/-/fhir-0.0.37.tgz", - "integrity": "sha512-fR1y6tPfDmxYDWN4JkJhuI5F5QpbaFVSoNo3pu9A6nzuoojANqg0UBnNZTVegTz/MilV3PSjyvFe6/vO55geKA==", + "version": "0.0.40", + "resolved": "https://registry.npmjs.org/@types/fhir/-/fhir-0.0.40.tgz", + "integrity": "sha512-ae00uDa0GrgPl4sDsGpHEdUjxCeot0UEEhgO/4PljimMKrPMyEVMZpsiAjwCp+dARn7zybOflnLK+nfORLjxDw==", "dev": true }, "node_modules/@types/http-errors": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.3.tgz", - "integrity": "sha512-pP0P/9BnCj1OVvQR2lF41EkDG/lWWnDyA203b/4Fmi2eTyORnBtcDoKDwjWQthELrBvWkMOrvSOnZ8OVlW6tXA==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", "dev": true }, "node_modules/@types/jasmine": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-5.1.1.tgz", - "integrity": "sha512-qL4GoZHHJl1JQ0vK31OtXMfkfGxYJnysmYz9kk0E8j5W96ThKykBF90uD3PcVmQUAzulbsaus2eFiBhCH5itfw==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-5.1.4.tgz", + "integrity": "sha512-px7OMFO/ncXxixDe1zR13V1iycqWae0MxTaw62RpFlksUi5QuNWgQJFkTQjIOvrmutJbI7Fp2Y2N1F6D2R4G6w==", "dev": true }, "node_modules/@types/json-schema": { - "version": "7.0.14", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.14.tgz", - "integrity": "sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==", + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/lodash": { + "version": "4.14.202", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz", + "integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==", + "dev": true + }, + "node_modules/@types/lodash.clonedeep": { + "version": "4.5.9", + "resolved": "https://registry.npmjs.org/@types/lodash.clonedeep/-/lodash.clonedeep-4.5.9.tgz", + "integrity": "sha512-19429mWC+FyaAhOLzsS8kZUsI+/GmBAQ0HFiCPsKGU+7pBXOQWhyrY6xNNDwUSX8SMZMJvuFVMF9O5dQOlQK9Q==", + "dev": true, + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/methods": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", + "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==", "dev": true }, "node_modules/@types/mime": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.4.tgz", - "integrity": "sha512-1Gjee59G25MrQGk8bsNvC6fxNiRgUlGn2wlhGf95a59DrprnnHk80FIMMFG9XHMdrfsuA119ht06QPDXA1Z7tw==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", "dev": true }, "node_modules/@types/mock-fs": { - "version": "4.13.3", - "resolved": "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.13.3.tgz", - "integrity": "sha512-PeXnRqMVBkVjHNCxu1wzPBi9cv5uWVl6535XD11NXt8pasJXh2MHxWvJs6d7eyt/V6BGgHZ4O3LF71CVMdMasA==", + "version": "4.13.4", + "resolved": "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.13.4.tgz", + "integrity": "sha512-mXmM0o6lULPI8z3XNnQCpL0BGxPwx1Ul1wXYEPBGl4efShyxW2Rln0JOPEWGyZaYZMM6OVXM/15zUuFMY52ljg==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/node": { - "version": "18.18.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz", - "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==", - "dev": true + "version": "20.11.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.6.tgz", + "integrity": "sha512-+EOokTnksGVgip2PbYbr3xnR7kZigh4LbybAfBAw5BpnQ+FqBYUsvCEjYd70IXKlbohQ64mzEYmMtlWUY8q//Q==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } }, "node_modules/@types/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-lX17GZVpJ/fuCjguZ5b3TjEbSENxmEk1B2z02yoXSK9WMEWRivhdSY73wWMn6bpcCDAOh6qAdktpKHIlkDk2lg==", + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz", + "integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==", "dev": true, "dependencies": { "@types/node": "*", @@ -1039,27 +1156,27 @@ } }, "node_modules/@types/qs": { - "version": "6.9.9", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.9.tgz", - "integrity": "sha512-wYLxw35euwqGvTDx6zfY1vokBFnsK0HNrzc6xNHchxfO2hpuRg74GbkEW7e3sSmPvj0TjCDT1VCa6OtHXnubsg==", + "version": "6.9.11", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.11.tgz", + "integrity": "sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==", "dev": true }, "node_modules/@types/range-parser": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.6.tgz", - "integrity": "sha512-+0autS93xyXizIYiyL02FCY8N+KkKPhILhcUSA276HxzreZ16kl+cmwvV2qAM/PuCCwPXzOXOWhiPcw20uSFcA==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", "dev": true }, "node_modules/@types/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==", + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", "dev": true }, "node_modules/@types/send": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.3.tgz", - "integrity": "sha512-/7fKxvKUoETxjFUsuFlPB9YndePpxxRAOfGC/yJdc9kTjTeP5kRCTzfnE8kPUKCeyiyIZu0YQ76s50hCedI1ug==", + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", "dev": true, "dependencies": { "@types/mime": "^1", @@ -1067,9 +1184,9 @@ } }, "node_modules/@types/serve-static": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.4.tgz", - "integrity": "sha512-aqqNfs1XTF0HDrFdlY//+SGUxmdSUbjeRXb5iaZc3x0/vMbYmdw9qvOgHWOyyLFxSSRnUuP5+724zBgfw8/WAw==", + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz", + "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==", "dev": true, "dependencies": { "@types/http-errors": "*", @@ -1078,35 +1195,37 @@ } }, "node_modules/@types/superagent": { - "version": "4.1.20", - "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-4.1.20.tgz", - "integrity": "sha512-GfpwJgYSr3yO+nArFkmyqv3i0vZavyEG5xPd/o95RwpKYpsOKJYI5XLdxLpdRbZI3YiGKKdIOFIf/jlP7A0Jxg==", + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.3.tgz", + "integrity": "sha512-R/CfN6w2XsixLb1Ii8INfn+BT9sGPvw74OavfkW4SwY+jeUcAwLZv2+bXLJkndnimxjEBm0RPHgcjW9pLCa8cw==", "dev": true, "dependencies": { - "@types/cookiejar": "*", + "@types/cookiejar": "^2.1.5", + "@types/methods": "^1.1.4", "@types/node": "*" } }, "node_modules/@types/supertest": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-2.0.15.tgz", - "integrity": "sha512-jUCZZ/TMcpGzoSaed9Gjr8HCf3HehExdibyw3OHHEL1als1KmyzcOZZH4MjbObI8TkWsEr7bc7gsW0WTDni+qQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-6.0.2.tgz", + "integrity": "sha512-137ypx2lk/wTQbW6An6safu9hXmajAifU/s7szAHLN/FeIm5w7yR0Wkl9fdJMRSHwOn4HLAI0DaB2TOORuhPDg==", "dev": true, "dependencies": { - "@types/superagent": "*" + "@types/methods": "^1.1.4", + "@types/superagent": "^8.1.0" } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.8.0.tgz", - "integrity": "sha512-GosF4238Tkes2SHPQ1i8f6rMtG6zlKwMEB0abqSJ3Npvos+doIlc/ATG+vX1G9coDF3Ex78zM3heXHLyWEwLUw==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.19.1.tgz", + "integrity": "sha512-roQScUGFruWod9CEyoV5KlCYrubC/fvG8/1zXuT0WTcxX87GnMMmnksMwSg99lo1xiKrBzw2icsJPMAw1OtKxg==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.8.0", - "@typescript-eslint/type-utils": "6.8.0", - "@typescript-eslint/utils": "6.8.0", - "@typescript-eslint/visitor-keys": "6.8.0", + "@typescript-eslint/scope-manager": "6.19.1", + "@typescript-eslint/type-utils": "6.19.1", + "@typescript-eslint/utils": "6.19.1", + "@typescript-eslint/visitor-keys": "6.19.1", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -1132,15 +1251,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.8.0.tgz", - "integrity": "sha512-5tNs6Bw0j6BdWuP8Fx+VH4G9fEPDxnVI7yH1IAPkQH5RUtvKwRoqdecAPdQXv4rSOADAaz1LFBZvZG7VbXivSg==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.19.1.tgz", + "integrity": "sha512-WEfX22ziAh6pRE9jnbkkLGp/4RhTpffr2ZK5bJ18M8mIfA8A+k97U9ZyaXCEJRlmMHh7R9MJZWXp/r73DzINVQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.8.0", - "@typescript-eslint/types": "6.8.0", - "@typescript-eslint/typescript-estree": "6.8.0", - "@typescript-eslint/visitor-keys": "6.8.0", + "@typescript-eslint/scope-manager": "6.19.1", + "@typescript-eslint/types": "6.19.1", + "@typescript-eslint/typescript-estree": "6.19.1", + "@typescript-eslint/visitor-keys": "6.19.1", "debug": "^4.3.4" }, "engines": { @@ -1160,13 +1279,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.8.0.tgz", - "integrity": "sha512-xe0HNBVwCph7rak+ZHcFD6A+q50SMsFwcmfdjs9Kz4qDh5hWhaPhFjRs/SODEhroBI5Ruyvyz9LfwUJ624O40g==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.19.1.tgz", + "integrity": "sha512-4CdXYjKf6/6aKNMSly/BP4iCSOpvMmqtDzRtqFyyAae3z5kkqEjKndR5vDHL8rSuMIIWP8u4Mw4VxLyxZW6D5w==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.8.0", - "@typescript-eslint/visitor-keys": "6.8.0" + "@typescript-eslint/types": "6.19.1", + "@typescript-eslint/visitor-keys": "6.19.1" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -1177,13 +1296,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.8.0.tgz", - "integrity": "sha512-RYOJdlkTJIXW7GSldUIHqc/Hkto8E+fZN96dMIFhuTJcQwdRoGN2rEWA8U6oXbLo0qufH7NPElUb+MceHtz54g==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.19.1.tgz", + "integrity": "sha512-0vdyld3ecfxJuddDjACUvlAeYNrHP/pDeQk2pWBR2ESeEzQhg52DF53AbI9QCBkYE23lgkhLCZNkHn2hEXXYIg==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.8.0", - "@typescript-eslint/utils": "6.8.0", + "@typescript-eslint/typescript-estree": "6.19.1", + "@typescript-eslint/utils": "6.19.1", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -1204,9 +1323,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.8.0.tgz", - "integrity": "sha512-p5qOxSum7W3k+llc7owEStXlGmSl8FcGvhYt8Vjy7FqEnmkCVlM3P57XQEGj58oqaBWDQXbJDZxwUWMS/EAPNQ==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.19.1.tgz", + "integrity": "sha512-6+bk6FEtBhvfYvpHsDgAL3uo4BfvnTnoge5LrrCj2eJN8g3IJdLTD4B/jK3Q6vo4Ql/Hoip9I8aB6fF+6RfDqg==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -1217,16 +1336,17 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.8.0.tgz", - "integrity": "sha512-ISgV0lQ8XgW+mvv5My/+iTUdRmGspducmQcDw5JxznasXNnZn3SKNrTRuMsEXv+V/O+Lw9AGcQCfVaOPCAk/Zg==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.19.1.tgz", + "integrity": "sha512-aFdAxuhzBFRWhy+H20nYu19+Km+gFfwNO4TEqyszkMcgBDYQjmPJ61erHxuT2ESJXhlhrO7I5EFIlZ+qGR8oVA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.8.0", - "@typescript-eslint/visitor-keys": "6.8.0", + "@typescript-eslint/types": "6.19.1", + "@typescript-eslint/visitor-keys": "6.19.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", + "minimatch": "9.0.3", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" }, @@ -1244,17 +1364,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.8.0.tgz", - "integrity": "sha512-dKs1itdE2qFG4jr0dlYLQVppqTE+Itt7GmIf/vX6CSvsW+3ov8PbWauVKyyfNngokhIO9sKZeRGCUo1+N7U98Q==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.19.1.tgz", + "integrity": "sha512-JvjfEZuP5WoMqwh9SPAPDSHSg9FBHHGhjPugSRxu5jMfjvBpq5/sGTD+9M9aQ5sh6iJ8AY/Kk/oUYVEMAPwi7w==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.8.0", - "@typescript-eslint/types": "6.8.0", - "@typescript-eslint/typescript-estree": "6.8.0", + "@typescript-eslint/scope-manager": "6.19.1", + "@typescript-eslint/types": "6.19.1", + "@typescript-eslint/typescript-estree": "6.19.1", "semver": "^7.5.4" }, "engines": { @@ -1269,12 +1389,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.8.0.tgz", - "integrity": "sha512-oqAnbA7c+pgOhW2OhGvxm0t1BULX5peQI/rLsNDpGM78EebV3C9IGbX5HNZabuZ6UQrYveCLjKo8Iy/lLlBkkg==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.19.1.tgz", + "integrity": "sha512-gkdtIO+xSO/SmI0W68DBg4u1KElmIUo3vXzgHyGPs6cxgB0sa3TlptRAAE0hUY1hM6FcDKEv7aIwiTGm76cXfQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.8.0", + "@typescript-eslint/types": "6.19.1", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -1291,6 +1411,12 @@ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", "dev": true }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "optional": true + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -1304,9 +1430,9 @@ } }, "node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -1325,19 +1451,43 @@ } }, "node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", "dev": true, "engines": { "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agentkeepalive": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", + "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", + "optional": true, + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, + "devOptional": true, "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" @@ -1366,7 +1516,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -1398,16 +1548,35 @@ "node": ">=8" } }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "optional": true + }, "node_modules/archy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", "dev": true }, + "node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, "node_modules/arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", "dev": true }, "node_modules/argparse": { @@ -1446,7 +1615,47 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "devOptional": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "optional": true, + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "optional": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } }, "node_modules/body-parser": { "version": "1.20.2", @@ -1485,13 +1694,12 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "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", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "node_modules/braces": { @@ -1507,9 +1715,9 @@ } }, "node_modules/browserslist": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", - "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", + "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", "dev": true, "funding": [ { @@ -1526,9 +1734,9 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001541", - "electron-to-chromium": "^1.4.535", - "node-releases": "^2.0.13", + "caniuse-lite": "^1.0.30001565", + "electron-to-chromium": "^1.4.601", + "node-releases": "^2.0.14", "update-browserslist-db": "^1.0.13" }, "bin": { @@ -1538,6 +1746,30 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -1552,52 +1784,162 @@ "node": ">= 0.8" } }, - "node_modules/caching-transform": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", - "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", - "dev": true, + "node_modules/cacache": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", + "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", + "optional": true, "dependencies": { - "hasha": "^5.0.0", - "make-dir": "^3.0.0", - "package-hash": "^4.0.0", - "write-file-atomic": "^3.0.0" + "@npmcli/fs": "^1.0.0", + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" }, "engines": { - "node": ">=8" + "node": ">= 10" } }, - "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "node_modules/cacache/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "optional": true, "dependencies": { - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/cacache/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/call-me-maybe": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", - "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", - "dev": true - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, + "node_modules/cacache/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, "engines": { - "node": ">=6" + "node": ">=10" } }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "node_modules/cacache/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/cacache/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacache/node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "optional": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cacache/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, + "node_modules/caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "dev": true, + "dependencies": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "dependencies": { + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, "engines": { @@ -1605,9 +1947,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001553", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001553.tgz", - "integrity": "sha512-N0ttd6TrFfuqKNi+pMgWJTb9qrdJu4JSpgPFLe/lrD19ugC6fZgF0pUewRowDwzdDnb9V41mFcdlYgl/PyKf4A==", + "version": "1.0.30001580", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001580.tgz", + "integrity": "sha512-mtj5ur2FFPZcCEpXFy8ADXbDACuNFXg6mxVDqp7tqooX6l3zwm+d8EPoeOSIFRDvHs8qu7/SLFOGniULkcH2iA==", "dev": true, "funding": [ { @@ -1640,11 +1982,20 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "optional": true, + "engines": { + "node": ">=10" + } + }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, + "devOptional": true, "engines": { "node": ">=6" } @@ -1712,6 +2063,15 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "optional": true, + "bin": { + "color-support": "bin.js" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -1740,16 +2100,25 @@ "dev": true }, "node_modules/component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "devOptional": true + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "optional": true }, "node_modules/content-disposition": { "version": "0.5.4", @@ -1819,7 +2188,7 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, + "devOptional": true, "dependencies": { "ms": "2.1.2" }, @@ -1841,6 +2210,30 @@ "node": ">=0.10.0" } }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "optional": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "optional": true, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -1884,6 +2277,12 @@ "node": ">=0.4.0" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "optional": true + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -1901,6 +2300,15 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-libc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/dezalgo": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", @@ -1956,9 +2364,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { - "version": "1.4.563", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.563.tgz", - "integrity": "sha512-dg5gj5qOgfZNkPNeyKBZQAQitIQ/xwfIDmEQJHCbXaD9ebTZxwJXUsDYcBlAvZGZLi+/354l35J1wkmP6CqYaw==", + "version": "1.4.646", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.646.tgz", + "integrity": "sha512-vThkQ0JuF45qT/20KbRgM56lV7IuGt7SjhawQ719PDHzhP84KAO1WJoaxgCoAffKHK47FmVKP1Fqizx7CwK1SA==", "dev": true }, "node_modules/emoji-regex": { @@ -1975,6 +2383,51 @@ "node": ">= 0.8" } }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "optional": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "optional": true + }, "node_modules/es6-error": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", @@ -2008,15 +2461,15 @@ } }, "node_modules/eslint": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.52.0.tgz", - "integrity": "sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.52.0", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.56.0", "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -2090,6 +2543,28 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -2170,6 +2645,15 @@ "node": ">= 0.6" } }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "optional": true, + "engines": { + "node": ">=6" + } + }, "node_modules/express": { "version": "4.18.2", "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", @@ -2267,17 +2751,10 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, - "node_modules/fast-diff": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", - "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "dev": true, - "peer": true - }, "node_modules/fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -2321,9 +2798,9 @@ "dev": true }, "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz", + "integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==", "dev": true, "dependencies": { "reusify": "^1.0.4" @@ -2341,6 +2818,12 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "optional": true + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -2417,9 +2900,9 @@ } }, "node_modules/flat-cache": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.1.tgz", - "integrity": "sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, "dependencies": { "flatted": "^3.2.9", @@ -2427,7 +2910,7 @@ "rimraf": "^3.0.2" }, "engines": { - "node": ">=12.0.0" + "node": "^10.12.0 || >=12.0.0" } }, "node_modules/flatted": { @@ -2517,10 +3000,16 @@ } ] }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "optional": true + }, "node_modules/fs-extra": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", - "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", "dev": true, "dependencies": { "graceful-fs": "^4.2.0", @@ -2531,11 +3020,41 @@ "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==", + "optional": true, + "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==", + "optional": true, + "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==", + "optional": true + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "devOptional": true }, "node_modules/function-bind": { "version": "1.1.2", @@ -2545,6 +3064,51 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "optional": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/gauge/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==", + "optional": true + }, + "node_modules/gauge/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "optional": true + }, + "node_modules/gauge/node_modules/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==", + "optional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -2586,6 +3150,12 @@ "node": ">=8.0.0" } }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "optional": true + }, "node_modules/glob": { "version": "10.3.10", "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", @@ -2620,34 +3190,10 @@ "node": ">=10.13.0" } }, - "node_modules/glob/node_modules/brace-expansion": { - "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" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -2694,7 +3240,7 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true + "devOptional": true }, "node_modules/graphemer": { "version": "1.4.0", @@ -2765,6 +3311,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "optional": true + }, "node_modules/hasha": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", @@ -2816,6 +3368,12 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "optional": true + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -2831,13 +3389,40 @@ "node": ">= 0.8" } }, - "node_modules/hyperdyperid": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", - "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", - "dev": true, + "node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "optional": true, + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, "engines": { - "node": ">=10.18" + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "optional": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "optional": true, + "dependencies": { + "ms": "^2.0.0" } }, "node_modules/iconv-lite": { @@ -2851,10 +3436,30 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true + }, "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", + "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", "dev": true, "engines": { "node": ">= 4" @@ -2880,7 +3485,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.8.19" } @@ -2889,16 +3494,22 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } }, + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "optional": true + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, + "devOptional": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -2909,6 +3520,18 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "optional": true + }, + "node_modules/ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "optional": true + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -2930,7 +3553,7 @@ "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, + "devOptional": true, "engines": { "node": ">=8" } @@ -2947,6 +3570,12 @@ "node": ">=0.10.0" } }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "optional": true + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -2996,12 +3625,12 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "devOptional": true }, "node_modules/istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, "engines": { "node": ">=8" @@ -3189,50 +3818,6 @@ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, - "node_modules/json-joy": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/json-joy/-/json-joy-9.6.0.tgz", - "integrity": "sha512-vJtJD89T0OOZFMaENe95xKCOdibMev/lELkclTdhZxLplwbBPxneWNuctUPizk2nLqtGfBxwCXVO42G9LBoFBA==", - "dev": true, - "dependencies": { - "arg": "^5.0.2", - "hyperdyperid": "^1.2.0" - }, - "bin": { - "jj": "bin/jj.js", - "json-pack": "bin/json-pack.js", - "json-pack-test": "bin/json-pack-test.js", - "json-patch": "bin/json-patch.js", - "json-patch-test": "bin/json-patch-test.js", - "json-pointer": "bin/json-pointer.js", - "json-pointer-test": "bin/json-pointer-test.js", - "json-unpack": "bin/json-unpack.js" - }, - "engines": { - "node": ">=10.0" - }, - "funding": { - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "quill-delta": "^5", - "rxjs": "7", - "tslib": "2" - } - }, - "node_modules/json-schema-ref-parser": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz", - "integrity": "sha512-qcP2lmGy+JUoQJ4DOQeLaZDqH9qSkeGCK3suKWxJXS82dg728Mn3j97azDMaOUmJAN4uCq91LdPx4K7E8F1a7Q==", - "deprecated": "Please switch to @apidevtools/json-schema-ref-parser", - "dev": true, - "dependencies": { - "@apidevtools/json-schema-ref-parser": "9.0.9" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -3316,8 +3901,7 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/lodash.flattendeep": { "version": "4.4.0", @@ -3325,13 +3909,6 @@ "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", "dev": true }, - "node_modules/lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", - "dev": true, - "peer": true - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -3377,32 +3954,69 @@ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "node_modules/make-fetch-happen": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", + "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", + "optional": true, + "dependencies": { + "agentkeepalive": "^4.1.3", + "cacache": "^15.2.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^6.0.0", + "minipass": "^3.1.3", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^1.3.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.2", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^6.0.0", + "ssri": "^8.0.0" + }, "engines": { - "node": ">= 0.6" + "node": ">= 10" } }, - "node_modules/memfs": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.6.0.tgz", - "integrity": "sha512-I6mhA1//KEZfKRQT9LujyW6lRbX7RkC24xKododIDO3AGShcaFAMKElv1yFGWX8fD4UaSiwasr3NeQ5TdtHY1A==", - "dev": true, + "node_modules/make-fetch-happen/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, "dependencies": { - "json-joy": "^9.2.0", - "thingies": "^1.11.1" + "yallist": "^4.0.0" }, "engines": { - "node": ">= 4.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" + "node": ">=10" + } + }, + "node_modules/make-fetch-happen/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" }, - "peerDependencies": { - "tslib": "2" + "engines": { + "node": ">=8" + } + }, + "node_modules/make-fetch-happen/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" } }, "node_modules/merge-descriptors": { @@ -3470,23 +4084,38 @@ "node": ">= 0.6" } }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", "dev": true, "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, + "devOptional": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -3500,11 +4129,221 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-collect/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-collect/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, + "node_modules/minipass-fetch": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", + "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", + "optional": true, + "dependencies": { + "minipass": "^3.1.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "optionalDependencies": { + "encoding": "^0.1.12" + } + }, + "node_modules/minipass-fetch/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-fetch/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "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==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "optional": true + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "optional": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "optional": true + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "devOptional": true + }, + "node_modules/napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", + "optional": true }, "node_modules/natural-compare": { "version": "1.4.0", @@ -3527,9 +4366,9 @@ "dev": true }, "node_modules/nock": { - "version": "13.3.6", - "resolved": "https://registry.npmjs.org/nock/-/nock-13.3.6.tgz", - "integrity": "sha512-lT6YuktKroUFM+27mubf2uqQZVy2Jf+pfGzuh9N6VwdHlFoZqvi4zyxFTVR1w/ChPqGY6yxGehHp6C3wqCASCw==", + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/nock/-/nock-13.5.0.tgz", + "integrity": "sha512-9hc1eCS2HtOz+sE9W7JQw/tXJktg0zoPSu48s/pYe73e25JW9ywiowbqnUSd7iZPeVawLcVpPZeZS312fwSY+g==", "dev": true, "dependencies": { "debug": "^4.1.0", @@ -3540,6 +4379,27 @@ "node": ">= 10.13" } }, + "node_modules/node-abi": { + "version": "3.54.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.54.0.tgz", + "integrity": "sha512-p7eGEiQil0YUV3ItH4/tBb781L5impVmmx2E9FRKF7d18XXzp4PGT2tdYMFY6wQqgxD0IwNZOiSJ0/K0fSi/OA==", + "optional": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.0.tgz", + "integrity": "sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g==", + "optional": true, + "engines": { + "node": "^16 || ^18 || >= 20" + } + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -3559,6 +4419,72 @@ } } }, + "node_modules/node-gyp": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", + "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", + "optional": true, + "dependencies": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^9.1.0", + "nopt": "^5.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": ">= 10.12.0" + } + }, + "node_modules/node-gyp/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/node-gyp/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-gyp/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/node-preload": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", @@ -3572,11 +4498,41 @@ } }, "node_modules/node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", "dev": true }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "optional": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "optional": true, + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, "node_modules/nyc": { "version": "15.1.0", "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", @@ -3618,6 +4574,16 @@ "node": ">=8.9" } }, + "node_modules/nyc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/nyc/node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -3676,6 +4642,18 @@ "node": ">=8" } }, + "node_modules/nyc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/nyc/node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -3741,22 +4719,22 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, + "devOptional": true, "dependencies": { "wrappy": "1" } }, "node_modules/openapi-typescript-codegen": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/openapi-typescript-codegen/-/openapi-typescript-codegen-0.25.0.tgz", - "integrity": "sha512-nN/TnIcGbP58qYgwEEy5FrAAjePcYgfMaCe3tsmYyTgI3v4RR9v8os14L+LEWDvV50+CmqiyTzRkKKtJeb6Ybg==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/openapi-typescript-codegen/-/openapi-typescript-codegen-0.27.0.tgz", + "integrity": "sha512-QyQEod/vuel3zfnTRC3GgmYsqLPSBzB2OL4ojMYjO9hJmfYW02T+7tbQWEnuqWdhh2KSOBf3L8h59vLStr6vwA==", "dev": true, "dependencies": { + "@apidevtools/json-schema-ref-parser": "^10.1.0", "camelcase": "^6.3.0", - "commander": "^11.0.0", - "fs-extra": "^11.1.1", - "handlebars": "^4.7.7", - "json-schema-ref-parser": "^9.0.9" + "commander": "^11.1.0", + "fs-extra": "^11.2.0", + "handlebars": "^4.7.8" }, "bin": { "openapi": "bin/index.js" @@ -3890,7 +4868,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -3921,9 +4899,9 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz", - "integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", + "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==", "dev": true, "engines": { "node": "14 || >=16.14" @@ -4025,6 +5003,32 @@ "node": ">=8" } }, + "node_modules/prebuild-install": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", + "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -4046,6 +5050,25 @@ "node": ">=8" } }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "optional": true + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "optional": true, + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/propagate": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", @@ -4067,10 +5090,20 @@ "node": ">= 0.10" } }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "optional": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "engines": { "node": ">=6" @@ -4110,41 +5143,64 @@ } ] }, - "node_modules/quill-delta": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-5.1.0.tgz", - "integrity": "sha512-X74oCeRI4/p0ucjb5Ma8adTXd9Scumz367kkMK5V/IatcX6A0vlgLgKbzXWy5nZmCGeNJm2oQX0d2Eqj+ZIlCA==", - "dev": true, - "peer": true, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dependencies": { - "fast-diff": "^1.3.0", - "lodash.clonedeep": "^4.5.0", - "lodash.isequal": "^4.5.0" + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" }, "engines": { - "node": ">= 12.0.0" + "node": ">= 0.8" } }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "optional": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "optional": true, "engines": { - "node": ">= 0.6" + "node": ">=0.10.0" } }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "optional": true, "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" }, "engines": { - "node": ">= 0.8" + "node": ">= 6" } }, "node_modules/release-zalgo": { @@ -4183,6 +5239,15 @@ "node": ">=4" } }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "optional": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -4197,7 +5262,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, + "devOptional": true, "dependencies": { "glob": "^7.1.3" }, @@ -4208,11 +5273,21 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "devOptional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/rimraf/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, + "devOptional": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -4228,6 +5303,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "devOptional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -4251,16 +5338,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "peer": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -4289,7 +5366,7 @@ "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, + "devOptional": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -4304,7 +5381,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "devOptional": true, "dependencies": { "yallist": "^4.0.0" }, @@ -4316,7 +5393,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "devOptional": true }, "node_modules/send": { "version": "0.18.0", @@ -4377,17 +5454,18 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true + "devOptional": true }, "node_modules/set-function-length": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", - "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.0.tgz", + "integrity": "sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w==", "dependencies": { "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.2", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -4444,6 +5522,51 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true, + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -4453,6 +5576,44 @@ "node": ">=8" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "optional": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "optional": true, + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", + "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", + "optional": true, + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -4514,6 +5675,66 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, + "node_modules/sqlite": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/sqlite/-/sqlite-5.1.1.tgz", + "integrity": "sha512-oBkezXa2hnkfuJwUo44Hl9hS3er+YFtueifoajrgidvqsJRQFpc5fKoAkAor1O5ZnLoa28GBScfHXs8j0K358Q==", + "optional": true + }, + "node_modules/sqlite3": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.7.tgz", + "integrity": "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "bindings": "^1.5.0", + "node-addon-api": "^7.0.0", + "prebuild-install": "^7.1.1", + "tar": "^6.1.11" + }, + "optionalDependencies": { + "node-gyp": "8.x" + }, + "peerDependencies": { + "node-gyp": "8.x" + }, + "peerDependenciesMeta": { + "node-gyp": { + "optional": true + } + } + }, + "node_modules/ssri": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "optional": true, + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/ssri/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ssri/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -4522,6 +5743,15 @@ "node": ">= 0.8" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "optional": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -4591,7 +5821,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -4667,13 +5897,13 @@ } }, "node_modules/supertest": { - "version": "6.3.3", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.3.3.tgz", - "integrity": "sha512-EMCG6G8gDu5qEqRQ3JjjPs6+FYT1a7Hv5ApHvtSghmOFJYtsU5S+pSb6Y2EUeCEY3CmEL3mmQ8YWlPOzQomabA==", + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.3.4.tgz", + "integrity": "sha512-erY3HFDG0dPnhw4U+udPfrzXa4xhSG+n4rxfRuZWCUvjFWwKl+OxWf/7zk50s84/fAAs7vf5QAb9uRa0cCykxw==", "dev": true, "dependencies": { "methods": "^1.1.2", - "superagent": "^8.0.5" + "superagent": "^8.1.2" }, "engines": { "node": ">=6.4.0" @@ -4691,6 +5921,72 @@ "node": ">=8" } }, + "node_modules/tar": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", + "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "optional": true, + "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" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "optional": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-fs/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "optional": true + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "optional": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "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==", + "optional": true + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -4705,6 +6001,16 @@ "node": ">=8" } }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/test-exclude/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -4725,24 +6031,24 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, - "node_modules/thingies": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.12.0.tgz", - "integrity": "sha512-AiGqfYC1jLmJagbzQGuoZRM48JPsr9yB734a7K6wzr34NMhjUPrWSQrkF7ZBybf3yCerCL2Gcr02kMv4NmaZfA==", - "dev": true, - "engines": { - "node": ">=10.18" - }, - "peerDependencies": { - "tslib": "^2" - } - }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -4790,9 +6096,9 @@ } }, "node_modules/ts-node": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", @@ -4832,18 +6138,17 @@ } } }, - "node_modules/ts-node/node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true, - "peer": true + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "optional": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } }, "node_modules/type-check": { "version": "0.4.0", @@ -4891,9 +6196,9 @@ } }, "node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -4916,10 +6221,34 @@ "node": ">=0.8.0" } }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "optional": true, + "dependencies": { + "unique-slug": "^2.0.0" + } + }, + "node_modules/unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "optional": true, + "dependencies": { + "imurmurhash": "^0.1.4" + } + }, "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "engines": { "node": ">= 10.0.0" @@ -4972,6 +6301,12 @@ "punycode": "^2.1.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "optional": true + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -5021,7 +6356,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, + "devOptional": true, "dependencies": { "isexe": "^2.0.0" }, @@ -5038,6 +6373,35 @@ "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", "dev": true }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "optional": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wide-align/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==", + "optional": true + }, + "node_modules/wide-align/node_modules/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==", + "optional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", @@ -5142,7 +6506,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "devOptional": true }, "node_modules/write-file-atomic": { "version": "3.0.3", diff --git a/package.json b/package.json index b516f2d..89a2a86 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "clinical-trial-matching-service", - "version": "0.0.13", + "version": "0.0.14", "description": "Provides a core library for interacting with the clinical-trial-matching-engine", "homepage": "https://github.com/mcode/clinical-trial-matching-service", "bugs": "https://github.com/mcode/clinical-trial-matching-service/issues", @@ -38,23 +38,26 @@ "express": "^4.17.1", "node-fetch": "^2.7.0" }, + "optionalDependencies": { + "sqlite": "^5.1.1", + "sqlite3": "^5.1.7" + }, "devDependencies": { "@istanbuljs/nyc-config-typescript": "^1.0.1", "@types/express": "^4.17.17", - "@types/fhir": "^0.0.37", + "@types/fhir": "^0.0.40", "@types/jasmine": "^5.1.0", "@types/mock-fs": "^4.13.0", - "@types/node": "^18.6.3", + "@types/node": "^20.11.6", "@types/node-fetch": "^2.6.6", - "@types/supertest": "^2.0.9", + "@types/supertest": "^6.0.2", "@typescript-eslint/eslint-plugin": "^6.1.0", "@typescript-eslint/parser": "^6.1.0", "eslint": "^8.29.0", "jasmine": "^5.0.2", - "memfs": "^4.2.0", "nock": "^13.0.4", "nyc": "^15.1.0", - "openapi-typescript-codegen": "^0.25.0", + "openapi-typescript-codegen": "^0.27.0", "source-map-support": "^0.5.19", "supertest": "^6.0.1", "ts-node": "^10.9.1", diff --git a/spec/clinicaltrialsgov.spec.ts b/spec/clinicaltrialsgov.spec.ts index cc7a0ad..62ee3d4 100644 --- a/spec/clinicaltrialsgov.spec.ts +++ b/spec/clinicaltrialsgov.spec.ts @@ -3,10 +3,10 @@ import * as ctg from '../src/clinicaltrialsgov'; import * as fs from 'node:fs'; import * as path from 'node:path'; import * as nock from 'nock'; -import { Volume } from 'memfs'; +import * as sqlite from 'sqlite'; +import * as sqlite3 from 'sqlite3'; // Trial missing summary, inclusion/exclusion criteria, phase and study type -import * as trialMissing from './data/resource.json'; import { createClinicalStudy } from './support/clinicalstudy-factory'; import { createResearchStudy } from './support/researchstudy-factory'; import { PagedStudies, Study } from '../src/ctg-api'; @@ -28,8 +28,32 @@ describe('.isValidNCTNumber', () => { }); }); +describe('.parseNCTNumber', () => { + it('parses valid numbers', () => { + expect(ctg.parseNCTNumber('NCT12345678')).toEqual(12345678); + expect(ctg.parseNCTNumber('NCT00000000')).toEqual(0); + }); + it('rejects invalid numbers', () => { + expect(ctg.parseNCTNumber('NCT1234567')).toBeUndefined(); + expect(ctg.parseNCTNumber('NCT123456789')).toBeUndefined(); + expect(ctg.parseNCTNumber('blatantly wrong')).toBeUndefined(); + expect(ctg.parseNCTNumber('')).toBeUndefined(); + }); +}); + +describe('.formatNCTNumber', () => { + it('formats valid numbers', () => { + expect(ctg.formatNCTNumber(12345678)).toEqual('NCT12345678'); + expect(ctg.formatNCTNumber(1)).toEqual('NCT00000001'); + }); + it('rejects invalid numbers', () => { + expect(() => ctg.formatNCTNumber(-1)).toThrow(); + expect(() => ctg.formatNCTNumber(100000000)).toThrow(); + }); +}); + describe('.parseStudyJson', () => { - let loggerSpy: jasmine.Spy<(message: string, ...rest: unknown[])=>void>; + let loggerSpy: jasmine.Spy<(message: string, ...rest: unknown[]) => void>; let study: Study | null | undefined; beforeEach(() => { loggerSpy = jasmine.createSpy('log'); @@ -37,7 +61,7 @@ describe('.parseStudyJson', () => { }); const makeTests = (testData: string, loggerShouldBeCalled: boolean) => { - it ('without a logger', () => { + it('without a logger', () => { study = ctg.parseStudyJson(testData); }); @@ -49,7 +73,7 @@ describe('.parseStudyJson', () => { expect(loggerSpy).not.toHaveBeenCalled(); } }); - } + }; describe('parses valid study JSON', () => { const testJsonString = fs.readFileSync(specFilePath('NCT02513394.json'), 'utf8'); @@ -174,279 +198,62 @@ describe('parseStudyJson', () => { }); }); -describe('CacheEntry', () => { - // Constant start time - const startTime = new Date(2021, 0, 21, 12, 0, 0, 0); - // Create a dummy service - const service = new ctg.ClinicalTrialsGovService('/invalid', { cleanInterval: 0 }); - describe('createdAt', () => { - beforeAll(() => { - jasmine.clock().install(); - jasmine.clock().mockDate(startTime); - }); - afterAll(() => { - jasmine.clock().uninstall(); - }); - it('sets the created at time', () => { - const entry = new ctg.CacheEntry(service, 'test', {}); - expect(entry.createdAt).toEqual(startTime); - }); - it('clones the created at time', () => { - const entry = new ctg.CacheEntry(service, 'test', {}); - const createdAt = entry.createdAt; - expect(createdAt).toBeDefined(); - if (createdAt) createdAt.setMonth(5); - expect(entry.createdAt).toEqual(startTime); - }); - }); - describe('lastAccess', () => { - beforeAll(() => { - jasmine.clock().install(); - }); - beforeEach(() => { - // Reset the clock - jasmine.clock().mockDate(startTime); - }); - afterAll(() => { - jasmine.clock().uninstall(); - }); - it('sets the last access time', () => { - const entry = new ctg.CacheEntry(service, 'test', {}); - expect(entry.lastAccess).toEqual(startTime); - }); - it('clones the last access time', () => { - const entry = new ctg.CacheEntry(service, 'test', {}); - entry.lastAccess.setMonth(5); - expect(entry.lastAccess).toEqual(startTime); - }); - it('updates the last access time if accessed', () => { - const entry = new ctg.CacheEntry(service, 'test', {}); - // This spy exists to prevent an attempt from actually reading the file - spyOn(entry, 'readFile').and.callFake(() => { - return Promise.resolve(createClinicalStudy()); - }); - // Move forward a minute - jasmine.clock().tick(60000); - const promise = entry.load(); - // Last access should now be a minute after the start time - expect(entry.lastAccess).toEqual(new Date(2021, 0, 21, 12, 1, 0, 0)); - return expectAsync(promise).toBeResolved(); - }); - describe('#lastAccessBefore', () => { - it('determines if a date is before the last access time', () => { - const entry = new ctg.CacheEntry(service, 'test', {}); - expect(entry.lastAccessedBefore(new Date(2021, 0, 21, 11, 59, 59, 999))).toBeFalse(); - // Exactly the same time is not before - expect(entry.lastAccessedBefore(startTime)).toBeFalse(); - expect(entry.lastAccessedBefore(new Date(2021, 0, 21, 12, 0, 0, 1))).toBeTrue(); - }); - }); - }); - describe('pending', () => { - it('resolves a load once pending resolves', () => { - // This test is kind of weird, but first, create an entry in the pending state: - const entry = new ctg.CacheEntry(service, 'pending', { pending: true }); - const spy = spyOn(entry, 'readFile').and.callFake(() => Promise.resolve(createClinicalStudy())); - // Now, attempt to load it. This should do nothing as the entry is pending. - let shouldBeResolved = false; - const promise = entry.load(); - promise.then(() => { - // Check if we should be resolved at this point - expect(shouldBeResolved).toBeTrue(); - if (shouldBeResolved) { - // If it should be resolved, it should have called readFile - expect(spy).toHaveBeenCalledOnceWith(); - } - }); - // Set a timeout to happen "on next event loop" that marks the cache entry as ready - setTimeout(() => { - // Make sure the spy has not been called - yet. - expect(spy).not.toHaveBeenCalled(); - // Set the resolved flag to true - shouldBeResolved = true; - // And mark the entry ready, which should trigger the promise resolving - entry.ready(); - // And then mark it as ready again, which should NOT trigger a second resolve - entry.ready(); - }, 0); - // And now return that original Promise, expecting it to eventually resolve successfully - return expectAsync(promise).toBeResolved(); - }); - }); - describe('#load', () => { - it('uses the existing load promise if invoked twice', () => { - const entry = new ctg.CacheEntry(service, 'test', {pending: true}); - // This test involves calling load on an entry twice while it hasn't - // been resolved - // This spy is used to ensure everything resolves at the end - const testStudy = createClinicalStudy('01234567'); - spyOn(entry, 'readFile').and.callFake(() => Promise.resolve(testStudy)); - // Invoke load twice, as if it's coming from two different sources. - const promise1 = expectAsync(entry.load()).toBeResolvedTo(testStudy); - const promise2 = expectAsync(entry.load()).toBeResolvedTo(testStudy); - // Now flag the entry as ready - entry.ready(); - // This should make the two other promises resolve. Return them to Jasmine - // to end the test - return Promise.all([promise1, promise2]); - }); - }); - describe('#fail', () => { - // These tests are for instances when an entry fails between when a read has - // started but before it has ended. - let readSpy: jasmine.Spy; - let entry: ctg.CacheEntry; - beforeEach(() => { - entry = new ctg.CacheEntry(service, 'test', {pending: true}); - // This spy exists solely to create a promise that won't resolve - readSpy = spyOn(entry, 'readFile').and.callFake(() => Promise.reject('should not be invoked')); - }); - it('handles failures while waiting for a file to be read', () => { - const loadPromise = entry.load(); - // We have now triggered a load request, meaning that there is now a - // Promise waiting for the entry to be ready before attempting to read it - // Read spy should not be invoked - expect(readSpy).not.toHaveBeenCalled(); - // Now fail the entry - entry.fail('test error'); - return expectAsync(loadPromise).toBeRejectedWithError('test error'); - }); - it('rejects with the error object given', () => { - const loadPromise = entry.load(); - // We have now triggered a load request, meaning that there is now a - // Promise waiting for the entry to be ready before attempting to read it - // Read spy should not be invoked - expect(readSpy).not.toHaveBeenCalled(); - // Now fail the entry - const error = new Error('expected error object'); - entry.fail(error); - return expectAsync(loadPromise).toBeRejectedWith(error); - }); - }); - describe('#readFile()', () => { - it('rejects with an error if it fails', () => { - const readFileSpy = spyOn(fs, 'readFile') as unknown as jasmine.Spy< - (path: string, options: { encoding?: string }, callback: (err?: Error, data?: string) => void) => void - >; - readFileSpy.and.callFake((path, options, callback) => { - callback(new Error('Simulated error')); - }); - const testEntry = new ctg.CacheEntry(service, 'test', {}); - return expectAsync(testEntry.readFile()).toBeRejectedWithError('Simulated error'); - }); - it('resolves to null if the file is empty', () => { - const readFileSpy = spyOn(fs, 'readFile') as unknown as jasmine.Spy< - (path: string, options: { encoding?: string }, callback: (err?: Error, data?: string) => void) => void - >; - readFileSpy.and.callFake((path, options, callback) => { - callback(undefined, ''); - }); - const testEntry = new ctg.CacheEntry(service, 'test', {}); - return expectAsync(testEntry.readFile()).toBeResolvedTo(null); - }); - it('reads the file contents if the file exists', () => { - const expectedStudy = createClinicalStudy('73577357'); - const readFileSpy = spyOn(fs, 'readFile') as unknown as jasmine.Spy< - (path: string, options: { encoding?: string }, callback: (err?: Error, data?: string) => void) => void - >; - readFileSpy.and.callFake((path, options, callback) => { - callback(undefined, JSON.stringify(expectedStudy)); - }); - const testEntry = new ctg.CacheEntry(service, 'test', {}); - return expectAsync(testEntry.readFile()).toBeResolvedTo(expectedStudy); - }); - }); - describe('#remove()', () => { - let entry: ctg.CacheEntry; - let unlinkSpy: jasmine.Spy<(path: string, callback: (err?: Error) => void) => void>; - beforeEach(() => { - entry = new ctg.CacheEntry(service, 'NCT12345678.xml', {}); - unlinkSpy = spyOn(fs, 'unlink') as unknown as jasmine.Spy< - (path: string, callback: (err?: Error) => void) => void - >; - }); +async function createMemorySqliteDB(): Promise { + return await sqlite.open({ filename: ':memory:', driver: sqlite3.Database }); +} - it('deletes the underlying file', () => { - unlinkSpy.and.callFake((path, callback) => { - // Immediately invoke the callback - callback(); - }); - return expectAsync(entry.remove()) - .toBeResolved() - .then(() => { - expect(unlinkSpy).toHaveBeenCalledTimes(1); - expect(unlinkSpy.calls.first().args[0]).toEqual('NCT12345678.xml'); - }); - }); +/** + * Creates a ClinicalTrialsGovService that stores data in an in-memory database. + * @param options options object - if not given, sends a set of options that disables the clean timer + * @returns + */ +function createMemoryCTGovService(options?: ctg.ClinicalTrialsGovServiceOptions): ctg.ClinicalTrialsGovService { + return new ctg.ClinicalTrialsGovService(':memory:', options ?? { cleanInterval: 0 }); +} - it('rejects if an error occurs', () => { - unlinkSpy.and.callFake((path, callback) => { - // Immediately invoke the callback - callback(new Error('Simulated unlink error')); - }); - return expectAsync(entry.remove()).toBeRejectedWithError('Simulated unlink error'); - }); - }); -}); +async function insertTestStudy(db: sqlite.Database, nctId: string | number, study?: Study | null): Promise { + const id = typeof nctId === 'string' ? ctg.parseNCTNumber(nctId) : nctId; + if (id === undefined) { + throw new Error(`Internal test error: invalid NCT ID ${nctId}`); + } + await db.run( + 'INSERT INTO ctgov_studies (nct_id, study_json, created_at) VALUES (?, ?, ?)', + id, + study ? JSON.stringify(study) : study === null ? null : '{"dummyData":true}', + new Date().valueOf() + ); +} describe('ClinicalTrialsGovService', () => { - // The data dir path - const dataDirPath = '/ctg-cache'; - // Virtual directory that has more than one entry (with known times) - const multipleEntriesDataDir = '/ctg-cache-multi'; - // The date used for when the cache was first created, 2021-02-01T12:00:00.000 (picked arbitrarily) - const cacheStartTime = new Date(2021, 1, 1, 12, 0, 0, 0); - let study: ResearchStudy; - // Create our mock FS - const cacheVol = Volume.fromNestedJSON({ - '/ctg-cache/data/NCT02513394.json': fs.readFileSync(specFilePath('NCT02513394.json'), { encoding: 'utf8' }), - '/existing-file': 'Existing stub', - '/ctg-cache-multi/data': { - 'NCT00000001.json': 'Test File 1', - 'NCT00000002.json': 'Test File 2', - 'NCT00000003.json': 'Test File 3', - // Junk files: files that should be skipped on init - '.json': 'not really a JSON file, but a dot file called "json"', - 'invalid.json': 'not an NCT', - 'NCT1.json': 'not a valid NCT' - } - }); - // Force the type to FileSystem since the memfs types are wrong (well, too loose, compared to the proper types) - const cacheFS: ctg.FileSystem = cacheVol as ctg.FileSystem; - beforeAll(() => { - study = trialMissing.entry[0].resource as ResearchStudy; - const maybeNctID = ctg.findNCTNumber(study); - if (maybeNctID === null) { - // This indicates a failure in test cases - throw new Error('ResearchStudy has no NCT number'); - } - // Create an empty directory - cacheVol.mkdirSync('/ctg-cache-empty'); - // Update mtimes for files - // Mock file that was created 1 minute after cache was created - let time = new Date(cacheStartTime.getTime() + 60 * 1000); - cacheVol.utimesSync('/ctg-cache-multi/data/NCT00000001.json', time, time); - // Mock file that was created 1.5 minutes after cache was created - time = new Date(cacheStartTime.getTime() + 90 * 1000); - cacheVol.utimesSync('/ctg-cache-multi/data/NCT00000002.json', time, time); - // Mock file that was created 2 minutes after cache was created - time = new Date(cacheStartTime.getTime() + 2 * 60 * 1000); - cacheVol.utimesSync('/ctg-cache-multi/data/NCT00000003.json', time, time); - }); - - it('can set a custom logger', () => { - const customLogger = (): void => { - // Do nothing - }; - const instance = new ctg.ClinicalTrialsGovService(dataDirPath, { log: customLogger, fs: cacheFS }); - expect(instance['log']).toEqual(customLogger); + describe('constructor', () => { + it('can set a custom logger', () => { + const customLogger = (): void => { + // Do nothing + }; + const instance = createMemoryCTGovService({ log: customLogger }); + expect(instance['log']).toEqual(customLogger); + }); + it('sets defaults', () => { + const service = new ctg.ClinicalTrialsGovService(':memory:'); + expect(service.log).not.toBeNull(); + expect(service.expirationTimeout).toEqual(60 * 60 * 1000); + expect(service.cleanupInterval).toEqual(60 * 60 * 1000); + }); + it('sets options', () => { + const service = new ctg.ClinicalTrialsGovService(':memory:', { + expireAfter: 24 * 60 * 60 * 1000, + cleanInterval: 0 + }); + expect(service.log).not.toBeNull(); + expect(service.expirationTimeout).toEqual(24 * 60 * 60 * 1000); + expect(service.cleanupInterval).toEqual(0); + }); }); describe('#maxTrialsPerRequest', () => { let service: ctg.ClinicalTrialsGovService; beforeEach(() => { - // The service is never initialized so the temp directory isn't created - service = new ctg.ClinicalTrialsGovService(dataDirPath, { fs: cacheFS }); + service = createMemoryCTGovService(); }); it('does not allow values less than 1 to be set', () => { // First, set it to a known value - this also makes sure get works @@ -468,7 +275,7 @@ describe('ClinicalTrialsGovService', () => { describe('#expirationTimeout', () => { let service: ctg.ClinicalTrialsGovService; beforeEach(() => { - service = new ctg.ClinicalTrialsGovService(dataDirPath, { fs: cacheFS }); + service = createMemoryCTGovService(); }); it('ensures the value is at least 1000', () => { service.expirationTimeout = 5; @@ -483,7 +290,7 @@ describe('ClinicalTrialsGovService', () => { describe('#cleanupInterval', () => { let service: ctg.ClinicalTrialsGovService; beforeEach(() => { - service = new ctg.ClinicalTrialsGovService(dataDirPath, { fs: cacheFS }); + service = createMemoryCTGovService(); }); it('caps the value to 2^31', () => { // This is too large: it exceeds 2^31 @@ -507,100 +314,13 @@ describe('ClinicalTrialsGovService', () => { }); describe('#init', () => { - it('restores cache entries in an existing directory', () => { - const testService = new ctg.ClinicalTrialsGovService(multipleEntriesDataDir, { cleanInterval: 0, fs: cacheFS }); - return expectAsync(testService.init()) - .toBeResolved() - .then(() => { - // Restored cache should have only one key in it as it should have - // ignored the two invalid file names - expect(Array.from(testService['cache'].keys()).sort()).toEqual(['NCT00000001', 'NCT00000002', 'NCT00000003']); - // Make sure the cache entries were created properly with the stats - function expectDate(key: string, date: Date): void { - const entry = testService['cache'].get(key); - expect(entry).toBeDefined(); - if (entry) { - // Must have the entry to check it - expect(entry.lastAccess).toEqual(date); - } - } - expectDate('NCT00000001', new Date(2021, 1, 1, 12, 1, 0, 0)); - expectDate('NCT00000002', new Date(2021, 1, 1, 12, 1, 30, 0)); - expectDate('NCT00000003', new Date(2021, 1, 1, 12, 2, 0, 0)); - }); - }); - - it('handles the directory already existing but not being a directory', () => { - const testService = new ctg.ClinicalTrialsGovService('existing-file', { cleanInterval: 0, fs: cacheFS }); - return expectAsync(testService.init()).toBeRejected(); - }); - - it('creates the data directory if the cache directory exists but is empty', () => { - const testService = new ctg.ClinicalTrialsGovService('/ctg-cache-empty', { cleanInterval: 0, fs: cacheFS }); - return expectAsync(testService.init()) - .toBeResolved() - .then(() => { - // Make sure the mocked directory exists - because this is being mocked, - // just use sync fs functions - expect(() => { - cacheVol.readdirSync('/ctg-cache-empty/data'); - }).not.toThrow(); - }); - }); - - it("creates the directory if it doesn't exist", () => { - const testService = new ctg.ClinicalTrialsGovService('/new-ctg-cache', { cleanInterval: 0, fs: cacheFS }); - return expectAsync(testService.init()) - .toBeResolved() - .then(() => { - // Make sure the mocked directory exists - because this is being mocked, - // just use sync fs functions - expect(() => { - cacheVol.readdirSync('/new-ctg-cache'); - }).not.toThrow(); - }); - }); - - it('handles the directory creation failing', () => { - // because we don't override promisify, we need to "delete" the type data - const testService = new ctg.ClinicalTrialsGovService(dataDirPath, { cleanInterval: 0, fs: cacheFS }); - (spyOn(cacheFS, 'mkdir') as jasmine.Spy).and.callFake((path, callback) => { - expect(path).toEqual(dataDirPath); - callback(new Error('Simulated error')); - }); - return expectAsync(testService.init()).toBeRejectedWithError('Simulated error'); - }); - - it('rejects if the directory cannot be read', () => { - const testService = new ctg.ClinicalTrialsGovService(dataDirPath, { fs: cacheFS }); - (spyOn(cacheFS, 'readdir') as jasmine.Spy).and.callFake((path, callback) => { - callback(new Error('Simulated error')); - }); - return expectAsync(testService.init()).toBeRejectedWithError('Simulated error'); - }); - - it('rejects if a single file load fails', () => { - // FIXME (maybe): it's unclear to me if this is correct behavior - maybe the file should be skipped? - (spyOn(cacheFS, 'stat') as jasmine.Spy).and.callFake( - (filename, callback: (err?: Error, stats?: fs.Stats) => void) => { - callback(new Error('Simulated error')); - } - ); - const testService = new ctg.ClinicalTrialsGovService(dataDirPath, { fs: cacheFS }); - return expectAsync(testService.init()).toBeRejectedWithError('Simulated error'); - }); - - it('can have no options', () => { - expect(() => new ctg.ClinicalTrialsGovService(dataDirPath)).not.toThrowError(); - }); - describe('starts a timer', () => { const realTimeout = setTimeout; let testService: ctg.ClinicalTrialsGovService; let removeExpiredCacheEntries: jasmine.Spy<() => Promise>; beforeEach(() => { jasmine.clock().install(); - testService = new ctg.ClinicalTrialsGovService(dataDirPath, { cleanInterval: 60000, fs: cacheFS }); + testService = createMemoryCTGovService({ cleanInterval: 60000 }); removeExpiredCacheEntries = spyOn(testService, 'removeExpiredCacheEntries'); }); afterEach(() => { @@ -645,11 +365,67 @@ describe('ClinicalTrialsGovService', () => { }); it('does not start a timer if the interval is set to 0', async () => { - const testService = new ctg.ClinicalTrialsGovService(dataDirPath, { cleanInterval: 0, fs: cacheFS }); + const testService = createMemoryCTGovService({ cleanInterval: 0 }); const spy = jasmine.createSpy('setCleanupTimeout'); await testService.init(); expect(spy).not.toHaveBeenCalled(); }); + + it('initializes the database tables', async () => { + const db = await createMemorySqliteDB(); + await ctg.ClinicalTrialsGovService.create(db); + const tableNames = await db.all<{ name: string }[]>('SELECT name FROM sqlite_schema'); + expect(tableNames).toContain({ name: 'migrations' }); + expect(tableNames).toContain({ name: 'ctgov_studies' }); + }); + + it('disallows double calls', async () => { + const testService = createMemoryCTGovService(); + await testService.init(); + await expectAsync(testService.init()).toBeRejected(); + }); + + it('raises an exception on an invalid state', async () => { + const testService = createMemoryCTGovService(); + // The constructor shouldn't allow this but for typing reasons it needs to be checked anyway + testService['cacheDBPath'] = null; + await expectAsync(testService.init()).toBeRejected(); + }); + + describe('migrations', () => { + let db: sqlite.Database; + let service: ctg.ClinicalTrialsGovService; + + beforeEach(async () => { + db = await createMemorySqliteDB(); + service = new ctg.ClinicalTrialsGovService(db, { cleanInterval: 0 }); + }); + + it("doesn't rerun an already run migration", async () => { + const migration = jasmine.createSpy('up').and.callFake(() => Promise.resolve()); + const migrations = { + init: { up: migration } + }; + await service['migrateDB'](db, migrations); + expect(migration).toHaveBeenCalledTimes(1); + await service['migrateDB'](db, migrations); + expect(migration).toHaveBeenCalledTimes(1); + }); + + it('rolls back on a failed migration', async () => { + const migration = jasmine.createSpy('up').and.callFake(async (db: sqlite.Database): Promise => { + // Create a test table + await db.run('CREATE TABLE test (id INTEGER PRIMARY KEY)'); + throw new Error('Test error'); + }); + const migrations = { + init: { up: migration } + }; + await expectAsync(service['migrateDB'](db, migrations)).toBeRejected(); + // Make sure the test table doesn't exist + expect(await db.get<{ name: string }>("SELECT name FROM sqlite_schema WHERE name='test'")).toBeUndefined(); + }); + }); }); describe('#destroy', () => { @@ -661,7 +437,7 @@ describe('ClinicalTrialsGovService', () => { }); it('stops the timer', async () => { - const testService = new ctg.ClinicalTrialsGovService(dataDirPath, { cleanInterval: 60000, fs: cacheFS }); + const testService = createMemoryCTGovService({ cleanInterval: 60000 }); // This spy should never be invoked but provide the fake implementation so that if it is, the tests won't hang const removeExpiredCacheEntries = spyOn(testService, 'removeExpiredCacheEntries').and.callFake(() => { return Promise.resolve(); @@ -674,9 +450,70 @@ describe('ClinicalTrialsGovService', () => { expect(testService['cleanupTimeout']).toEqual(null); }); - it('does nothing if never started', () => { - const testService = new ctg.ClinicalTrialsGovService(dataDirPath, { cleanInterval: 60000, fs: cacheFS }); - return expectAsync(testService.destroy()).toBeResolved(); + it('closes the database if it opened it', async () => { + const testService = createMemoryCTGovService(); + await testService.init(); + // Need to spy on the private db field + const db = testService['cacheDB']; + expect(db).not.toBeNull(); + if (db != null) { + const closeSpy = spyOn(db, 'close').and.callThrough(); + await testService.destroy(); + expect(closeSpy).toHaveBeenCalled(); + } + }); + + it('does not close the database if passed a DB to use', async () => { + const db = await createMemorySqliteDB(); + const closeSpy = spyOn(db, 'close').and.callThrough(); + const testService = new ctg.ClinicalTrialsGovService(db, { cleanInterval: 0 }); + await testService.init(); + await testService.destroy(); + expect(closeSpy).not.toHaveBeenCalled(); + }); + + it('does nothing if never started', async () => { + const testService = createMemoryCTGovService({ cleanInterval: 60000 }); + await testService.destroy(); + // There's nothing to really test other than to ensure it worked + }); + }); + + describe('#getDB', () => { + // This is an internal private method intended to ensure that methods can't be invoked without the cache being ready + let service: ctg.ClinicalTrialsGovService; + beforeEach(() => { + service = createMemoryCTGovService(); + }); + + it('throws an exception if called before init()', () => { + expect(() => service['getDB']()).toThrow(); + }); + + it('throws an exception if called after destroy()', async () => { + await service.init(); + await service.destroy(); + expect(() => service['getDB']()).toThrow(); + }); + }); + + describe('#findCacheMisses', () => { + let db: sqlite.Database; + let service: ctg.ClinicalTrialsGovService; + beforeEach(async () => { + db = await createMemorySqliteDB(); + service = await ctg.ClinicalTrialsGovService.create(db, { cleanInterval: 0 }); + }); + + it('returns a list of IDs with existing IDs removed', async () => { + // First, insert dummy entries + await insertTestStudy(db, 1); + await insertTestStudy(db, 3); + expect(await service.findCacheMisses(['NCT00000001', 'NCT00000002', 'NCT00000003'])).toEqual(['NCT00000002']); + }); + + it('ignores invalid IDs', async () => { + expect(await service.findCacheMisses(['invalid'])).toEqual([]); }); }); @@ -684,16 +521,17 @@ describe('ClinicalTrialsGovService', () => { let service: ctg.ClinicalTrialsGovService; let downloadTrialsSpy: jasmine.Spy; - beforeEach(() => { + beforeEach(async () => { // The service is never initialized - service = new ctg.ClinicalTrialsGovService(dataDirPath, { cleanInterval: 0, fs: cacheFS }); + service = createMemoryCTGovService(); + await service.init(); // TypeScript won't allow us to install spies the "proper" way on private methods service['downloadTrials'] = downloadTrialsSpy = jasmine.createSpy('downloadTrials').and.callFake(() => { - return Promise.resolve('ignored'); + return Promise.resolve(true); }); }); - // These tests basically are only to ensure that all trials are properly visisted when given. + // These tests basically are only to ensure that all trials are properly visited when given. it('updates all the given studies', () => { // Our test studies contain the same NCT ID twice to make sure that works as expected, as well as a NCT ID that // download spy will return null for to indicate a failure. @@ -754,95 +592,31 @@ describe('ClinicalTrialsGovService', () => { }); }); + // this functionality is currently unimplemented - this test exists solely to "cover" the method describe('#removeExpiredCacheEntries', () => { - let service: ctg.ClinicalTrialsGovService; - let entry1: ctg.CacheEntry, entry2: ctg.CacheEntry, entry3: ctg.CacheEntry; - beforeEach(() => { - // These tests all involve mucking with time. - jasmine.clock().install(); - // Set the start date to the cache start time - jasmine.clock().mockDate(cacheStartTime); - return ctg - .createClinicalTrialsGovService(multipleEntriesDataDir, { expireAfter: 60000, fs: cacheFS }) - .then((newService) => { - service = newService; - // "Steal" the cache to grab entries for spying purposes - const cache = service['cache']; - function safeGet(key: string): ctg.CacheEntry { - const result = cache.get(key); - if (result) { - return result; - } else { - throw new Error('Missing cache entry for ' + key + ' (bailing before tests will fail)'); - } - } - entry1 = safeGet('NCT00000001'); - entry2 = safeGet('NCT00000002'); - entry3 = safeGet('NCT00000003'); - }); - }); - afterEach(() => { - // Stop playing with time - jasmine.clock().uninstall(); - }); - - function spyOnRemove(entry: ctg.CacheEntry): jasmine.Spy<() => Promise> { - // Need to include a default implementation or this will never work - const spy = spyOn(entry, 'remove').and.callFake(() => Promise.resolve()); - spy.and.identity = entry.filename + '.remove'; - return spy; - } - - it('removes entries when they expire', async () => { - // Create the spies on the remove methods - const removeSpy1 = spyOnRemove(entry1), - removeSpy2 = spyOnRemove(entry2), - removeSpy3 = spyOnRemove(entry3); - const cache = service['cache']; - await service.removeExpiredCacheEntries(); - // None of the spies should have been removed (technically we're "back in time" before the entires were created) - expect(removeSpy1).not.toHaveBeenCalled(); - expect(removeSpy2).not.toHaveBeenCalled(); - expect(removeSpy3).not.toHaveBeenCalled(); - // Advance to the point where the first entry should be removed - jasmine.clock().tick(2 * 60 * 1000 + 1); - await service.removeExpiredCacheEntries(); - expect(removeSpy1).toHaveBeenCalledTimes(1); - expect(removeSpy2).not.toHaveBeenCalled(); - expect(removeSpy3).not.toHaveBeenCalled(); - // Make sure the entry was actually removed - expect(cache.has('NCT00000001')).toBeFalse(); - // And that the others weren't - expect(cache.has('NCT00000002')).toBeTrue(); - expect(cache.has('NCT00000003')).toBeTrue(); - // And tick forward to where everything should be cleared - jasmine.clock().tick(5 * 60 * 1000); + it('does nothing', async () => { + const service = createMemoryCTGovService(); await service.removeExpiredCacheEntries(); - expect(removeSpy1).toHaveBeenCalledTimes(1); - expect(removeSpy2).toHaveBeenCalledTimes(1); - expect(removeSpy3).toHaveBeenCalledTimes(1); - // Cache should now be empty - expect(cache.size).toEqual(0); }); }); describe('#downloadTrials', () => { let scope: nock.Scope; let interceptor: nock.Interceptor; + let db: sqlite.Database; let downloader: ctg.ClinicalTrialsGovService; const nctIDs = ['NCT00000001', 'NCT00000002', 'NCT00000003']; beforeEach(async () => { scope = nock('https://clinicaltrials.gov'); interceptor = scope.get(`/api/v2/studies?filter.ids=${nctIDs.join(',')}&pageSize=128`); - // Need to intercept the writeFile method - spyOn(cacheFS, 'writeFile').and.callFake((_file, _data, _options, callback) => { - // For these, always pretend it succeeded - callback(null); - }); - downloader = await ctg.createClinicalTrialsGovService(dataDirPath, { cleanInterval: 0, fs: cacheFS }); + // Create the test DB as this needs to poke into it + db = await createMemorySqliteDB(); + // Initialize the downloaded + downloader = await ctg.ClinicalTrialsGovService.create(db, { cleanInterval: 0 }); }); - afterEach(() => { + afterEach(async () => { + await downloader.destroy(); scope.done(); }); @@ -853,14 +627,9 @@ describe('ClinicalTrialsGovService', () => { it('handles failure responses from the server', async () => { interceptor.reply(404, 'Unknown'); - // Pretend the middle entry exists - downloader['cache'].set(nctIDs[1], new ctg.CacheEntry(downloader, nctIDs[1] + '.json', {})); - expect(await downloader['downloadTrials'](nctIDs)).withContext('downloader indicates failure').toBeFalse(); - // Check to make sure the new cache entries do not still exist - the failure should remove them, but not the - // non-pending one - expect(downloader['cache'].has(nctIDs[0])).withContext('cache entry 0').toBeFalse(); - expect(downloader['cache'].has(nctIDs[1])).withContext('cache entry 1').toBeTrue(); - expect(downloader['cache'].has(nctIDs[2])).withContext('cache entry 2').toBeFalse(); + expect(await downloader['downloadTrials'](nctIDs)) + .withContext('downloader indicates failure') + .toBeFalse(); }); it('creates cache entries', async () => { @@ -878,69 +647,129 @@ describe('ClinicalTrialsGovService', () => { { 'Content-type': 'application/json' } ); // For this test, create an existing cache entry for one of the IDs - downloader['cache'].set(nctIDs[1], new ctg.CacheEntry(downloader, nctIDs[1] + '.xml', {})); + await insertTestStudy(db, 2); expect(await downloader['downloadTrials'](nctIDs)).toBeTrue(); // Should have created the two missing items which should be resolved - let entry = downloader['cache'].get(nctIDs[0]); - expect(entry?.pending).toBeFalse(); - entry = downloader['cache'].get(nctIDs[1]); - expect(entry?.pending).toBeFalse(); - entry = downloader['cache'].get(nctIDs[2]); - expect(entry?.pending).toBeFalse(); + for (const nctId of nctIDs) { + const result = await db.get<{ study_json: string }>( + 'SELECT study_json FROM ctgov_studies WHERE nct_id=?', + ctg.parseNCTNumber(nctId) + ); + expect(result?.study_json).toEqual( + `{"protocolSection":{"identificationModule":{"nctId":${JSON.stringify(nctId)}}}}` + ); + } }); - it('invalidates cache entries that were not found in the results', async () => { - interceptor.reply( - 200, - JSON.stringify({ - // Only include NCT ID 1 - studies: [ - { - protocolSection: { - identificationModule: { - nctId: nctIDs[1] - } - } - } - ] - } as PagedStudies), - { 'Content-type': 'application/json' } - ); - expect(await downloader['downloadTrials'](nctIDs)).toBeTrue(); - // The failed NCT IDs should be removed at this point - expect(downloader['cache'].has(nctIDs[0])).toBeFalse(); - expect(downloader['cache'].has(nctIDs[1])).toBeTrue(); - expect(downloader['cache'].has(nctIDs[2])).toBeFalse(); + it('rolls back on failure from addCacheEntry', async () => { + // Bunch of setup for this + const testNCT = 'NCT00000001'; + const study = createClinicalStudy(testNCT); + // Mock out the tryFetchStudies method + downloader['tryFetchStudies'] = async () => [study]; + // Make addCacheEntry throw an exception + downloader['addCacheEntry'] = async () => { + throw new Error('test error'); + }; + expect(await downloader['downloadTrials']([testNCT])).toBeFalse(); + expect(await db.get<{ nct_id: number }>('SELECT nct_id FROM ctgov_studies WHERE nct_id=1')).toBeUndefined(); + }); + + describe('multiple simultaneous calls', () => { + const realTimeout = setTimeout; + beforeEach(() => { + jasmine.clock().install(); + }); + afterEach(() => { + jasmine.clock().uninstall(); + }); + // Essentially, let internal promises resolve + function tickEventLoop(): Promise { + return new Promise((resolve) => { + realTimeout(resolve, 1); + }); + } + it('waits for the first to complete', async () => { + // This is kind of a weird set of tests since it involves ensuring things + // happen in a very specific order. + // First, mock out the db's "run" method to just resolve immediately + spyOn(db, 'run').and.callFake(() => Promise.resolve({} as sqlite.ISqlite.RunResult)); + // Mock tryFetchStudies to always return a test study after a brief + // timeout + const fetchStudiesSpy = (downloader['tryFetchStudies'] = jasmine + .createSpy('tryFetchStudies') + .and.callFake((ids: string[]) => { + return new Promise((resolve) => { + setTimeout(() => { + resolve(ids.map(createClinicalStudy)); + }, 1); + }); + })); + // Mock out addCacheEntry to again resolve after a specific timeout + const addCacheEntrySpy = (downloader['addCacheEntry'] = jasmine.createSpy('addCacheEntry').and.callFake(() => { + return new Promise((resolve) => { + setTimeout(resolve, 1); + }); + })); + let firstPromiseResolved = false; + const firstPromise = downloader['downloadTrials'](['NCT00000001']).then(() => { + firstPromiseResolved = true; + }); + expect(fetchStudiesSpy).toHaveBeenCalledTimes(1); + // First promise will not have resolved yet, it's waiting on the mock tryFetchStudies + // Tick forward + jasmine.clock().tick(1); + // Allow promises to resolve + await tickEventLoop(); + expect(firstPromiseResolved).toBeFalse(); + expect(addCacheEntrySpy).toHaveBeenCalledTimes(1); + // OK, so at this point, the first should have resolved tryFetchStudies, created the lock, and moved on to + // waiting to insert the one cache entry. Start the second promise now + let secondPromiseResolved = false; + const secondPromise = downloader['downloadTrials'](['NCT00000002']).then(() => { + secondPromiseResolved = true; + }); + expect(downloader['_downloadTrialsLock']).not.toBeNull(); + expect(fetchStudiesSpy).toHaveBeenCalledTimes(2); + expect(addCacheEntrySpy).toHaveBeenCalledTimes(1); + jasmine.clock().tick(1); + await tickEventLoop(); + // First promise should now resolve, having had a chance to finish + expect(firstPromiseResolved).toBeTrue(); + // Second promise should now be waiting on addCacheEntry + expect(secondPromiseResolved).toBeFalse(); + jasmine.clock().tick(1); + await tickEventLoop(); + expect(fetchStudiesSpy).toHaveBeenCalledTimes(2); + expect(addCacheEntrySpy).toHaveBeenCalledTimes(2); + // And finally just ensure everything cleans up properly + await Promise.all([firstPromise, secondPromise]); + }); }); }); describe('#getCachedClinicalStudy', () => { - let downloader: ctg.ClinicalTrialsGovService; - beforeEach(() => { - return ctg.createClinicalTrialsGovService(dataDirPath, { cleanInterval: 0, fs: cacheFS }).then((service) => { - downloader = service; - }); + let db: sqlite.Database; + let service: ctg.ClinicalTrialsGovService; + beforeEach(async () => { + db = await createMemorySqliteDB(); + service = await ctg.ClinicalTrialsGovService.create(db, { cleanInterval: 0 }); }); - it('handles a file that does not exist', () => { - // Intentionally call private method (this is a test after all) - return expectAsync(downloader.getCachedClinicalStudy('this is an invalid id')).toBeResolvedTo(null); + it('handles an invalid NCT ID', async () => { + expect(await service.getCachedClinicalStudy('not an NCT')).toBeNull(); }); - it('invokes the load method of the cache entry', () => { - // Force in a "fake" cache entry - const entry = new ctg.CacheEntry(downloader, 'test', {}); + it('loads the appropriate cache entry', async () => { const study = createClinicalStudy(); - const spy = spyOn(entry, 'load').and.callFake(() => { - return Promise.resolve(study); - }); - downloader['cache'].set('test', entry); - return expectAsync(downloader.getCachedClinicalStudy('test')) - .toBeResolvedTo(study) - .then(() => { - // Make sure the spy was called once - expect(spy).toHaveBeenCalledOnceWith(); - }); + await insertTestStudy(db, 1234, study); + expect(await service.getCachedClinicalStudy('NCT00001234')).toEqual(study); + }); + + it('returns null on cached failure', async () => { + // This currently can't happen as failed trials aren't currently marked as failed but... + await insertTestStudy(db, 7357, null); + expect(await service.getCachedClinicalStudy('NCT00007357')).toBeNull(); }); }); @@ -948,8 +777,9 @@ describe('ClinicalTrialsGovService', () => { // Most of these tests just pass through to downloadTrials let service: ctg.ClinicalTrialsGovService; let downloadTrials: jasmine.Spy<(ids: string[]) => Promise>; - beforeEach(() => { - service = new ctg.ClinicalTrialsGovService(dataDirPath, { fs: cacheFS }); + beforeEach(async () => { + service = createMemoryCTGovService(); + await service.init(); // Can't directly spy on within TypeScript because downloadTrials is protected const spy = jasmine.createSpy<(ids: string[]) => Promise>('downloadTrials'); // Jam it in @@ -958,136 +788,82 @@ describe('ClinicalTrialsGovService', () => { downloadTrials.and.callFake(() => Promise.resolve(true)); }); - it('excludes invalid NCT numbers in an array of strings', () => { - return expectAsync(service.ensureTrialsAvailable(['NCT00000001', 'NCT00000012', 'invalid', 'NCT01234567'])) - .toBeResolved() - .then(() => { - expect(downloadTrials).toHaveBeenCalledOnceWith(['NCT00000001', 'NCT00000012', 'NCT01234567']); - }); + it('excludes invalid NCT numbers in an array of strings', async () => { + await service.ensureTrialsAvailable(['NCT00000001', 'NCT00000012', 'invalid', 'NCT01234567']); + expect(downloadTrials).toHaveBeenCalledOnceWith(['NCT00000001', 'NCT00000012', 'NCT01234567']); }); - it('pulls NCT numbers out of given ResearchStudy objects', () => { - return expectAsync( - service.ensureTrialsAvailable([ - createResearchStudy('test1', 'NCT00000001'), - createResearchStudy('test2', 'NCT12345678'), - createResearchStudy('no-nct') - ]) - ) - .toBeResolved() - .then(() => { - expect(downloadTrials).toHaveBeenCalledOnceWith(['NCT00000001', 'NCT12345678']); - }); + it('pulls NCT numbers out of given ResearchStudy objects', async () => { + await service.ensureTrialsAvailable([ + createResearchStudy('test1', 'NCT00000001'), + createResearchStudy('test2', 'NCT12345678'), + createResearchStudy('no-nct') + ]); + expect(downloadTrials).toHaveBeenCalledOnceWith(['NCT00000001', 'NCT12345678']); }); }); describe('#addCacheEntry', () => { - // addCacheEntry is responsible for saving a single Study object to a file - // There is a matrix of three entry states (existing pending, exists non-pending, does not exist) and two stream - // cases (succeeds, fails) that needs to be handled. + // addCacheEntry is responsible for saving a single Study object to the cache + let db: sqlite.Database; let service: ctg.ClinicalTrialsGovService; - // Error to use - let writeError: Error | null; - let writeFileSpy: jasmine.Spy; - const mockNctNumber = 'NCT12345678'; - const testStudy: Study = { protocolSection: { identificationModule: { nctId: mockNctNumber } } }; - - beforeEach(() => { - service = new ctg.ClinicalTrialsGovService(dataDirPath, { cleanInterval: 0, fs: cacheFS }); - - writeError = null; - writeFileSpy = spyOn(cacheFS, 'writeFile').and.callFake((file, data, options, callback) => { - // Just need to invoke the callback to pretend this has completed. - callback(writeError); - }); - }); - - const makeTests = function () { - it('handles an error', async () => { - writeError = new Error('Test error'); - await expectAsync(service['addCacheEntry'](testStudy)).toBeRejected(); - }); - it('handles writing the entry', async () => { - writeError = null; - await service['addCacheEntry'](testStudy); - expect(writeFileSpy).toHaveBeenCalled(); - }); - }; - - it('with no entry resolves without writing anything', async () => { - // Make sure that nothing happens when doing this - await service['addCacheEntry'](testStudy); - expect(writeFileSpy).not.toHaveBeenCalled(); + beforeEach(async () => { + db = await createMemorySqliteDB(); + service = await ctg.ClinicalTrialsGovService.create(db, { cleanInterval: 0 }); }); - it('with a missing NCT number resolves without writing anything', async () => { + it('does not write an invalid cache entry', async () => { + const dbRunSpy = spyOn(db, 'run').and.callThrough(); // Everything in the object is optional - await service['addCacheEntry']({}); - expect(writeFileSpy).not.toHaveBeenCalled(); + await service['addCacheEntry'](db, {}); + expect(dbRunSpy).not.toHaveBeenCalled(); // For completeness sake: - await service['addCacheEntry']({ protocolSection: {} }); - expect(writeFileSpy).not.toHaveBeenCalled(); - await service['addCacheEntry']({ protocolSection: { identificationModule: {} } }); - expect(writeFileSpy).not.toHaveBeenCalled(); - // And finally assume something invalid was sent - await service['addCacheEntry']({ protocolSection: { identificationModule: { nctId: 12 as unknown as string } } }); - expect(writeFileSpy).not.toHaveBeenCalled(); - }); - - describe('with an existing pending entry', () => { - let entry: ctg.CacheEntry; - let readySpy: jasmine.Spy; - beforeEach(() => { - entry = new ctg.CacheEntry(service, mockNctNumber + '.json', { pending: true }); - // Add the entry - service['cache'].set(mockNctNumber, entry); - // We want to see if ready is invoked but also have it work as expected - readySpy = spyOn(entry, 'ready').and.callThrough(); - }); - afterEach(() => { - if (writeError != null) { - // Expect the cache entry to have been removed - expect(service['cache'].has(mockNctNumber)).toBeFalse(); - // Ready should not have been called in this case - expect(readySpy).not.toHaveBeenCalled(); - } else { - // Otherwise, expect the entry to be ready - expect(readySpy).toHaveBeenCalled(); - } + await service['addCacheEntry'](db, { protocolSection: {} }); + expect(dbRunSpy).not.toHaveBeenCalled(); + await service['addCacheEntry'](db, { protocolSection: { identificationModule: {} } }); + expect(dbRunSpy).not.toHaveBeenCalled(); + // Invalid type + await service['addCacheEntry'](db, { + protocolSection: { identificationModule: { nctId: 12 as unknown as string } } }); - - // And make the tests - makeTests(); + expect(dbRunSpy).not.toHaveBeenCalled(); + // Invalid ID + await service['addCacheEntry'](db, { protocolSection: { identificationModule: { nctId: 'not an NCT id' } } }); + expect(dbRunSpy).not.toHaveBeenCalled(); + }); + + it('inserts a new cache entry', async () => { + const study = createClinicalStudy('NCT12345678'); + await service['addCacheEntry'](db, study); + // Explicitly pull from the database + const result = await db.get<{ study_json: string }>( + 'SELECT study_json FROM ctgov_studies WHERE nct_id = 12345678' + ); + expect(result?.study_json).toEqual(JSON.stringify(study)); }); - describe('with an existing non-pending entry', () => { - let entry: ctg.CacheEntry; - let readySpy: jasmine.Spy; - beforeEach(() => { - entry = new ctg.CacheEntry(service, mockNctNumber + '.xml', {}); - // Add the entry - service['cache'].set(mockNctNumber, entry); - // In this case we just want to know if ready was not invoked as it shouldn't be - readySpy = spyOn(entry, 'ready').and.callThrough(); - }); - afterEach(() => { - if (writeError != null) { - // Expect the cache entry to remain - it's assumed the existing entry is still OK (this may be false?) - expect(service['cache'].has(mockNctNumber)).toBeTrue(); - } - // In either case, ready should not have been called - expect(readySpy).not.toHaveBeenCalled(); - }); - - // And make the tests - makeTests(); + it('upserts if the entry already exists', async () => { + // Insert invalid entry + await db.run( + 'INSERT INTO ctgov_studies (nct_id, study_json, created_at) VALUES (?, ?, ?)', + 12345678, + null, + new Date().valueOf() + ); + const study = createClinicalStudy('NCT12345678'); + await service['addCacheEntry'](db, study); + // Explicitly pull from the database + const result = await db.get<{ study_json: string }>( + 'SELECT study_json FROM ctgov_studies WHERE nct_id = 12345678' + ); + expect(result?.study_json).toEqual(JSON.stringify(study)); }); }); describe('#updateResearchStudy', () => { it('forwards to updateResearchStudyWithClinicalStudy', () => { - const service = new ctg.ClinicalTrialsGovService(dataDirPath, { cleanInterval: 0, fs: cacheFS }); + const service = createMemoryCTGovService(); const testResearchStudy = createResearchStudy('test'); const testClinicalStudy = createClinicalStudy(); service.updateResearchStudy(testResearchStudy, testClinicalStudy); diff --git a/spec/fhir-util.spec.ts b/spec/fhir-util.spec.ts index bcc8b33..c1cd84b 100644 --- a/spec/fhir-util.spec.ts +++ b/spec/fhir-util.spec.ts @@ -1,5 +1,5 @@ import { FhirResource } from 'fhir/r4'; -import { resourceContainsProfile } from '../src/fhir-util'; +import { FHIRDate, FHIRDateAccuracy, resourceContainsProfile } from '../src/fhir-util'; describe('#resourceContainsProfile', () => { it('handles resources with no meta', () => { @@ -20,8 +20,100 @@ describe('#resourceContainsProfile', () => { ).toBeFalse(); }); it('finds substrings using a RegExp', () => { - expect(resourceContainsProfile({ resourceType: 'Bundle', type: 'searchset', meta: { - profile: [ 'http://www.example.com/findme', 'http://www.example.com/extra' ] - } }, /example/)).toBeTrue(); + expect( + resourceContainsProfile( + { + resourceType: 'Bundle', + type: 'searchset', + meta: { + profile: ['http://www.example.com/findme', 'http://www.example.com/extra'] + } + }, + /example/ + ) + ).toBeTrue(); + }); +}); + +describe('FHIRDate', () => { + it('defaults to not being a leap second', () => { + expect(new FHIRDate(new Date(2023, 0, 1, 0, 0, 0), FHIRDateAccuracy.YEAR_MONTH_DAY_TIME).leapSecond).toBeFalse(); + }); + describe('.parse', () => { + it('parses a date containing the year', () => { + const actual = FHIRDate.parse('2024'); + expect(actual.date).toEqual(new Date(Date.UTC(2024, 0, 1, 0, 0, 0, 0))); + expect(actual.accuracy).toEqual(FHIRDateAccuracy.YEAR); + }); + it('parses a date containing the year and month', () => { + const actual = FHIRDate.parse('2024-02'); + expect(actual.date).toEqual(new Date(Date.UTC(2024, 1, 1, 0, 0, 0, 0))); + expect(actual.accuracy).toEqual(FHIRDateAccuracy.YEAR_MONTH); + }); + it('parses a date containing the year, month, and date', () => { + const actual = FHIRDate.parse('2024-03-02'); + expect(actual.date).toEqual(new Date(Date.UTC(2024, 2, 2, 0, 0, 0, 0))); + expect(actual.accuracy).toEqual(FHIRDateAccuracy.YEAR_MONTH_DAY); + }); + it('parses a date containing the year, month, date, and a time', () => { + let actual = FHIRDate.parse('2025-04-03T01:23:45.6789Z'); + expect(actual.date).toEqual(new Date(Date.UTC(2025, 3, 3, 1, 23, 45, 678))); + expect(actual.accuracy).toEqual(FHIRDateAccuracy.YEAR_MONTH_DAY_TIME); + actual = FHIRDate.parse('2025-04-03T01:23:45Z'); + expect(actual.date).toEqual(new Date(Date.UTC(2025, 3, 3, 1, 23, 45))); + expect(actual.accuracy).toEqual(FHIRDateAccuracy.YEAR_MONTH_DAY_TIME); + }); + it('parses a date containing the year, month, date, a time, and a time zone', () => { + let actual = FHIRDate.parse('2025-04-03T01:23:45.6789-04:00'); + expect(actual.date).toEqual(new Date(Date.UTC(2025, 3, 3, 5, 23, 45, 678))); + expect(actual.accuracy).toEqual(FHIRDateAccuracy.YEAR_MONTH_DAY_TIME); + actual = FHIRDate.parse('2025-04-03T01:23:45.6789+01:00'); + expect(actual.date).toEqual(new Date(Date.UTC(2025, 3, 3, 0, 23, 45, 678))); + expect(actual.accuracy).toEqual(FHIRDateAccuracy.YEAR_MONTH_DAY_TIME); + }); + it('handles leap seconds', () => { + // The FHIR spec allows it and this is the last time it happened: + const actual = FHIRDate.parse('2016-12-31T23:59:60Z'); + expect(actual.leapSecond).toEqual(true); + expect(actual.date).toEqual(new Date(Date.UTC(2016, 11, 31, 23, 59, 59))); + expect(actual.accuracy).toEqual(FHIRDateAccuracy.YEAR_MONTH_DAY_TIME); + }); + it('catches invalid dates', () => { + expect(() => { + FHIRDate.parse('2023-02-29'); + }).toThrowError('Invalid date 29 for month 2 in dateTime "2023-02-29"'); + expect(() => { + FHIRDate.parse('2024-02-29'); + }).not.toThrow(); + expect(() => { + FHIRDate.parse('2023-13-05'); + }).toThrowError('Invalid month 13 in dateTime "2023-13-05"'); + expect(() => { + FHIRDate.parse('2023-00-05'); + }).toThrowError('Invalid month 00 in dateTime "2023-00-05"'); + expect(() => { + FHIRDate.parse('2023-01-32'); + }).toThrowError('Invalid date 32 in dateTime "2023-01-32"'); + expect(() => { + FHIRDate.parse('2023-01-02T24:00:00Z'); + }).toThrowError('Invalid hours 24 in dateTime "2023-01-02T24:00:00Z"'); + expect(() => { + FHIRDate.parse('2023-01-02T12:60:00Z'); + }).toThrowError('Invalid minutes 60 in dateTime "2023-01-02T12:60:00Z"'); + expect(() => { + FHIRDate.parse('2023-01-02T12:00:61Z'); + }).toThrowError('Invalid seconds 61 in dateTime "2023-01-02T12:00:61Z"'); + expect(() => { + FHIRDate.parse('2023-01-02T24:00Z'); + }).toThrowError('Invalid FHIR dateTime "2023-01-02T24:00Z"'); + expect(() => { + FHIRDate.parse('Jun 01 2020'); + }).toThrowError('Invalid FHIR dateTime "Jun 01 2020"'); + }); + it('does not allow year 0', () => { + expect(() => { + FHIRDate.parse('0000-01-01'); + }).toThrowError('Invalid year 0 in dateTime "0000-01-01"'); + }); }); }); diff --git a/src/clinicaltrialsgov.ts b/src/clinicaltrialsgov.ts index 6d0dbea..8208e85 100644 --- a/src/clinicaltrialsgov.ts +++ b/src/clinicaltrialsgov.ts @@ -16,13 +16,12 @@ * This will fill out whatever can be filled out within the given studies. */ -import * as fs from 'node:fs'; -import { WriteFileOptions } from 'node:fs'; -import * as path from 'node:path'; import { debuglog } from 'util'; import { ResearchStudy } from 'fhir/r4'; import { ClinicalTrialsGovAPI, Study } from './clinicaltrialsgov-api'; import { updateResearchStudyWithClinicalStudy } from './study-fhir-converter'; +import * as sqlite from 'sqlite'; +import * as sqlite3 from 'sqlite3'; /** * Logger type from the NodeJS utilities. (The TypeScript definitions for Node @@ -51,6 +50,23 @@ export function isValidNCTNumber(nctNumber: string): boolean { return /^NCT[0-9]{8}$/.test(nctNumber); } +export function parseNCTNumber(nctNumber: string): number | undefined { + if (isValidNCTNumber(nctNumber)) { + return parseInt(nctNumber.substring(3)); + } else { + return undefined; + } +} + +export function formatNCTNumber(nctNumber: number): string { + // Make sure the NCT number is an integer + nctNumber = Math.floor(nctNumber); + if (nctNumber < 0 || nctNumber > 99999999) { + throw new Error(`Invalid NCT number ${nctNumber}`); + } + return `NCT${nctNumber.toFixed(0).padStart(8, '0')}`; +} + /** * Finds the NCT number specified for the given ResearchStudy, assuming there is * one. This requires an identifier on the given ResearchStudy that either @@ -133,258 +149,26 @@ export function parseStudyJson(fileContents: string, log?: Logger): Study | null } } -/** - * Subset of the Node.js fs module necessary to handle the cache file system, allowing it to be overridden if - * necessary. - */ -export interface FileSystem { - readFile: ( - path: fs.PathOrFileDescriptor, - options: { encoding: BufferEncoding; flag?: string }, - callback: (err: NodeJS.ErrnoException | null, data: string) => void - ) => void; - mkdir: (path: string, callback: (err: NodeJS.ErrnoException | null) => void) => void; - readdir: (path: string, callback: (err: NodeJS.ErrnoException | null, files: string[]) => void) => void; - stat: (path: string, callback: (err: NodeJS.ErrnoException | null, stat: fs.Stats) => void) => void; - writeFile: ( - file: string, - data: Buffer | string, - options: WriteFileOptions, - callback: (err: Error | null) => void - ) => void; - unlink: (path: string, callback: (err: NodeJS.ErrnoException | null) => void) => void; +// Currently this exists as a simple object with only one function but may be updated later +interface Migration { + up: (db: sqlite.Database) => Promise; } -interface PendingState { - promise: Promise; - resolve: () => void; - reject: (e: Error) => void; -} - -/** - * A cache entry. Cache entries basically operate in two modes: an entry that is pending being written based on a ZIP - * file, and a file that has a backing file ready to be loaded. - * - * The pending state is kind of weird because an entry is pending once a download has been requested for it, but cannot - * be properly fulfilled until the file data has been saved. There's no really good way to wrap that in a Promise so - * instead the resolve method is stored until the pending state can be resolved. - */ -export class CacheEntry { - private _cache: ClinicalTrialsGovService; - // Dates are, sadly, mutable via set methods. This makes it impractical to attempt to make properties involving them - // immutable. - private _createdAt: Date | null; - private _lastAccess: Date; - /** - * The pending status. Either a Promise (in which case it is not only pending, but has things actively waiting for - * it), or true, in which case it is pending but the promise is yet to be created, or false if pending but the promise - * has not been created. This is so that no Promise is created if nothing ever needs it, which avoids cases where the - * Promise won't have a catch handler attached to it. - */ - private _pending: PendingState | boolean = false; - /** - * Create a new cache entry. - * @param filename the file name of the entry - * @param options the file stats (if the file exists) or a flag indicating it's pending (being downloaded still) - */ - constructor( - cache: ClinicalTrialsGovService, - public filename: string, - options: { stats?: fs.Stats; pending?: boolean } - ) { - this._cache = cache; - if (options.stats) { - // Default to using the metadata from the fs.Stats object - this._createdAt = options.stats.ctime; - // Assume the last modified time was when the cache entry was fetched (it's close enough anyway) - this._lastAccess = options.stats.mtime; - } else { - // Otherwise, default to now - this._createdAt = new Date(); - this._lastAccess = new Date(); - } - if (options.pending) { - // In this mode, mark createdAt as null - this._createdAt = null; - this._pending = true; - } - } - // As the returned Date objects are mutable, return copies - /** - * Gets the time when this entry was initially created. If null, that indicates that the entry was never successfully - * created - it's still waiting for data to be downloaded. - */ - get createdAt(): Date | null { - return this._createdAt === null ? null : new Date(this._createdAt); - } - /** - * Gets the last time the cache entry was accessed. This is updated whenever - * #load() is called. - */ - get lastAccess(): Date { - return new Date(this._lastAccess); - } - /** - * Determine if the cache entry is still pending: data for it has not yet been saved. - */ - get pending(): boolean { - return this._pending !== false; - } - - /** - * Check if the last access time is before the given date - * @param date the date to check - * @returns true if the last access time was before the given date - */ - lastAccessedBefore(date: Date): boolean { - return this._lastAccess < date; - } - - /** - * Indicates that the entry has been located somewhere and that data for it is now being prepared. - */ - found(): void { - if (this._createdAt === null) { - this._createdAt = new Date(); - } - } - - /** - * Resolves the pending state (if the entry was pending), otherwise does nothing. This does not change the "found" - * status - if ready() is called without found(), createdAt remains null and the entry may be removed as pointing to - * a record that does not exist. - */ - ready(): void { - if (typeof this._pending === 'object') { - this._pending.resolve(); - } - this._pending = false; - } - - /** - * Forcibly fail the entry. - * @param e the error to fail the entry with - */ - fail(e: Error | string): void { - if (typeof this._pending === 'object') { - if (typeof e === 'string') { - e = new Error(e); - } - this._pending.reject(e); - } - this._pending = false; - } - - /** - * Loads the underlying file. If the entry is still pending, then the file is read once the entry is ready. This may - * resolve to null if the clinical study does not exist in the results. - */ - load(): Promise { - // Move last access to now - this._lastAccess = new Date(); - // If we're still pending, we have to wait for that to finish before we can - // read the underlying file. - if (this._pending) { - let promise: Promise; - if (this._pending === true) { - // This is the only case when we actually create the Promise - when still pending and something attempts a load. - // TODO (maybe): Add a timeout? - // There's no way to prove it to TypeScript, but the executor function in the Promise runs immediately when the - // Promise is created. So create useless no-op funcs to avoid "may be undefined" errors. - let resolveFunc: () => void = /* istanbul ignore next */ () => { - /* no-op */ - }, - rejectFunc: (e: Error) => void = /* istanbul ignore next */ () => { - /* no-op */ - }; - promise = new Promise((resolve, reject) => { - resolveFunc = resolve; - rejectFunc = reject; - }); - this._pending = { - promise: promise, - resolve: resolveFunc, - reject: rejectFunc - }; - } else { - promise = this._pending.promise; - } - return promise.then(() => { - return this.readFile(); - }); - } else { - // Otherwise we can just return immediately - return this.readFile(); +// Order matters in this +const MIGRATIONS: Record = { + init: { + up: async (db: sqlite.Database): Promise => { + // Create the cache table + await db.run(`CREATE TABLE ctgov_studies + ( + nct_id INTEGER PRIMARY KEY, + study_json TEXT, + error_message TEXT, + created_at NUMERIC NOT NULL + )`); } } - - /** - * Always attempt to read the file, regardless of whether or not the entry is pending. - */ - readFile(): Promise { - return new Promise((resolve, reject) => { - this._cache.fs.readFile(this.filename, { encoding: 'utf8' }, (err, data) => { - if (err) { - reject(err); - } else { - // Again bump last access to now since the access has completed - this._lastAccess = new Date(); - if (data.length === 0) { - // Sometimes files end up empty - this appears to be a bug? - // It's unclear what causes this to happen - this._cache.log('Warning: %s is empty on read', this.filename); - resolve(null); - } else { - resolve(parseStudyJson(data, this._cache.log)); - } - } - }); - }); - } - - /** - * Remove the entry from the cache. All this does is delete the underlying file. The returned Promise is more for - * error handling than anything else. - */ - remove(): Promise { - return new Promise((resolve, reject) => { - fs.unlink(this.filename, (err) => { - if (err) { - reject(err); - } else { - resolve(); - } - }); - }); - } -} - -/** - * Create a directory if it does not already exist. If it does exist (or some other file exists in its place), this - * still resolves rather than raising an error. This used to check if the paths were really directories, but since the - * final step is now reading the file entries from the directory anyway, that check ensures everything is a directory - * anyway. - * - * @param path the path to create (will be created recursively) - * @return a Promise that resolves as true if the directory was newly created or false if it existed - */ -export function mkdir(fs: FileSystem, path: string): Promise { - return new Promise((resolve, reject) => { - fs.mkdir(path, (err) => { - if (err) { - if (err.code === 'EEXIST') { - // This is fine - resolve false. We'll only get this if the final part of the path exists (although it will be - // EEXIST regardless of if it's a directory or a regular file) - resolve(false); - } else { - reject(err); - } - } else { - resolve(true); - } - }); - }); -} +}; export interface ClinicalTrialsGovServiceOptions { /** @@ -431,11 +215,6 @@ export interface ClinicalTrialsGovServiceOptions { * way to extract the clinical study data directly. */ export class ClinicalTrialsGovService { - /** - * Internal value to track temporary file names. - */ - private tempId = 0; - /** * Log used to log debug information. Either passed in at creation time or created via the Node.js util.debuglog * function. With the latter, activate by setting the NODE_DEBUG environment variable to "ctgovservice" (or include it @@ -504,34 +283,39 @@ export class ClinicalTrialsGovService { */ maxAllowedEntrySize = 128 * 1024 * 1024; - private cacheDataDir: string; - + private cacheDBPath: string | null; + private cacheDB: sqlite.Database | null; /** - * Actual cache of NCT IDs to their cached values. This is the "real cache" versus whatever's on the filesystem. - * (The NCT ID is in the form that returns true from isValidNCTNumber, so NCTnnnnnnnn where n are digits.) + * Internal flag indicating if init() has been called */ - private cache = new Map(); + private cacheReady = false; private cleanupTimeout: NodeJS.Timeout | null = null; readonly service: ClinicalTrialsGovAPI; - /** - * The filesystem being used by the cache. - */ - readonly fs: FileSystem; - /** * Creates a new instance. This will not initialize the cache or load anything, this merely creates the object. Use * the #init() method to initialize the service and load existing data. (Or use createClinicalTrialsGovService() do * construct and initialize at the same time.) * - * @param dataDir the directory to use for cache data + * The {@link #init} method must be used **even if** given a database object, as it will initialize the necessary + * tables within the database. If given a path name, calling {@link #destroy} will close the database. However, if + * given a database directly, {@link #destroy} **will not** close the database and it will be the responsibility of + * the code using the service to close it. + * + * @param db either the path to the database file to use, or the sqlite.Database to use directly. * @param log an optional function to receive debug log messages. By default this uses util.debuglog('ctgovservice') * meaning that the log can be activated by setting NODE_DEBUG to "ctgovservice" */ - constructor(public readonly dataDir: string, options?: ClinicalTrialsGovServiceOptions) { - this.cacheDataDir = path.join(dataDir, 'data'); + constructor(public readonly db: string | sqlite.Database, options?: ClinicalTrialsGovServiceOptions) { + if (typeof db === 'string') { + this.cacheDBPath = db; + this.cacheDB = null; + } else { + this.cacheDBPath = null; + this.cacheDB = db; + } const log = options ? options.log : undefined; // If no log was given, create it this.log = log ?? debuglog('ctgovservice'); @@ -539,30 +323,86 @@ export class ClinicalTrialsGovService { this.expirationTimeout = options?.expireAfter ?? 60 * 60 * 1000; // Default cleanup interval to an hour this.cleanupInterval = options?.cleanInterval ?? 60 * 60 * 1000; - this.fs = options?.fs ?? fs; this.service = new ClinicalTrialsGovAPI({ logger: this.log }); } /** - * Creates the necessary directories if they do not exist, and loads any existing data into the cache. Parent - * directories of the cache will not be created automatically, they must already exist. + * Initializes the service. This will open the SQLite database and run through any necessary data migrations for it. */ async init(): Promise { - this.log('Using %s as cache dir for clinicaltrials.gov data', this.dataDir); - const baseDirExisted = !(await mkdir(this.fs, this.dataDir)); - // The directory containing cached studies - const dataDirExisted = !(await mkdir(this.fs, this.cacheDataDir)); - if (baseDirExisted && dataDirExisted) { - // If both directories existed, it's necessary to restore the cache directory - await this.restoreCacheFromFS(); - this.log('Restored existing cache data.'); + if (this.cacheReady) { + throw new Error('init() has already been called'); + } + // Technically this isn't really ready yet, but prevent double-calls to init + this.cacheReady = true; + let db: sqlite.Database; + if (this.cacheDBPath === null) { + this.log('Using existing database object for clinicaltrials.gov data'); + if (this.cacheDB === null) { + throw new Error('Invalid internal state: both cache DB path and DB are null'); + } + db = this.cacheDB; } else { - this.log(baseDirExisted ? 'Created data directory for storing result' : 'Created new cache directory'); + this.log('Using %s as cache DB for clinicaltrials.gov data', this.cacheDBPath); + this.cacheDB = db = await sqlite.open({ driver: sqlite3.Database, filename: this.cacheDBPath }); } + await this.migrateDB(db, MIGRATIONS); // Once started, run the cache cleanup every _cleanupIntervalMillis this.setCleanupTimeout(); } + /** + * Migrate a database. The arguments exist mostly for typing/testing purposes. + * @param db the database to migrate + * @param migrations the migrations to run (they're run in key order). This is an argument for testing purposes. + */ + private async migrateDB(db: sqlite.Database, migrations: typeof MIGRATIONS): Promise { + this.log('Applying migrations...'); + // lock the database + await db.run('BEGIN TRANSACTION'); + try { + await db.run('CREATE TABLE IF NOT EXISTS migrations (id INTEGER PRIMARY KEY, name TEXT NOT NULL)'); + const appliedMigration = new Set( + (await db.all<{ id: number; name: string }[]>('SELECT name FROM migrations ORDER BY id ASC')).map( + (row) => row.name + ) + ); + for (const name in migrations) { + if (appliedMigration.has(name)) { + this.log('Migration %s already applied', name); + } else { + this.log('Applying migration %s...', name); + await migrations[name].up(db); + // Insert that this migration was run + await db.run('INSERT INTO migrations (name) VALUES (?)', name); + this.log('Migration %s completed.', name); + } + } + await db.run('COMMIT'); + this.log('All migrations applied successfully.'); + } catch (ex) { + this.log('Exception applying migrations: %o', ex); + // Rollback if we can + await db.run('ROLLBACK'); + this.log('Migrations were rolled back.'); + throw ex; + } + } + + /** + * Internal method to get the database object, throwing an exception if it's + * not available. + * @returns the database + */ + private getDB(): sqlite.Database { + if (this.cacheDB === null) { + throw new Error( + `Database not available (${this.cacheReady ? 'destroy() has been called' : 'init() has not been called'})` + ); + } + return this.cacheDB; + } + /** * Sets the timeout interval assuming it was set. */ @@ -586,71 +426,25 @@ export class ClinicalTrialsGovService { } /** - * Shuts down the service, doing any final necessary cleanup. At present this stops any running timers. The Promise - * returned currently resolves immediately, but in the future, it may resolve asynchronously when the service has been - * cleanly shut down. + * Shuts down the service, doing any final necessary cleanup. If given a database object when constructed, this + * **will not** close the database. However, future calls to this object will no longer function. */ - destroy(): Promise { + async destroy(): Promise { if (this.cleanupTimeout !== null) { clearTimeout(this.cleanupTimeout); // And blank it this.cleanupTimeout = null; } - return Promise.resolve(); - } - - /** - * This attempts to load the cache from the filesystem. - */ - private restoreCacheFromFS(): Promise { - return new Promise((resolve, reject) => { - this.log('Scanning %s for existing cache entries...', this.cacheDataDir); - this.fs.readdir(this.cacheDataDir, (err, files) => { - if (err) { - reject(err); - } else { - // Go through the files and create entries for them. - const promises: Promise[] = []; - for (const file of files) { - this.log('Checking %s', file); - // Split this file name into two parts: the extension and the base name - const dotIdx = file.lastIndexOf('.'); - if (dotIdx < 1) { - // Skip "bad" files - continue; - } - // FIXME: This is probably a bad idea. Right now the file name serves as the "master" name for files. It's - // probably possible to load the file and pull the NCT ID out that way, as well as clear out files that - // can't be parsed. - const baseName = file.substring(0, dotIdx); - const extension = file.substring(dotIdx + 1); - if (isValidNCTNumber(baseName) && extension === 'json') { - promises.push(this.createCacheEntry(baseName, path.join(this.cacheDataDir, file))); - } - } - Promise.all(promises).then(() => { - resolve(); - }, reject); - } - }); - }); - } - - private createCacheEntry(id: NCTNumber, filename: string): Promise { - // Restoring this involves getting the time the file was created so we know when the entry expires - return new Promise((resolve, reject) => { - this.fs.stat(filename, (err, stats) => { - if (err) { - // TODO (maybe): Instead of rejecting, just log - rejecting will caused Promise.all to immediately reject and - // ignore the rest of the Promises. However, stat failing is probably a "real" error. - reject(err); - } else { - this.cache.set(id, new CacheEntry(this, filename, { stats: stats })); - this.log('Restored cache entry for %s', filename); - resolve(); - } - }); - }); + if (this.cacheDBPath === null) { + // Always null out the database + this.cacheDB = null; + } else { + const db = this.cacheDB; + if (db) { + this.cacheDB = null; + await db.close(); + } + } } /** @@ -663,76 +457,79 @@ export class ClinicalTrialsGovService { * @returns a Promise that resolves when the studies are updated. It will resolve with the same array that was passed * in - this updates the given objects, it does not clone them and create new ones. */ - updateResearchStudies(studies: ResearchStudy[]): Promise { + async updateResearchStudies(studies: ResearchStudy[]): Promise { const nctIdMap = findNCTNumbers(studies); if (nctIdMap.size === 0) { // Nothing to do - return Promise.resolve(studies); - } else { - const nctIds = Array.from(nctIdMap.keys()); - // Make sure the NCT numbers are in the cache - return this.ensureTrialsAvailable(nctIds).then(() => { - const promises: Promise[] = []; - // Go through the NCT numbers we found and updated all matching trials - for (const entry of nctIdMap.entries()) { - const [nctId, study] = entry; - const promise = this.getCachedClinicalStudy(nctId); - promises.push(promise); - promise.then((clinicalStudy) => { - if (clinicalStudy !== null) { - // Make sure we have data to use - we may still get null if there was no data for a given NCT number - // Update whatever trials we have - if (Array.isArray(study)) { - for (const s of study) { - this.updateResearchStudy(s, clinicalStudy); - } - } else { - this.updateResearchStudy(study, clinicalStudy); - } - } - }); + return studies; + } + const nctIds = Array.from(nctIdMap.keys()); + this.log('Updating research studies with NCT IDs %j', nctIds); + // Make sure the NCT numbers are in the cache + await this.ensureTrialsAvailable(nctIds); + // Update the items in the list + await Promise.all( + Array.from(nctIdMap.entries()).map(([nctId, study]) => this.updateResearchStudyFromCache(nctId, study)) + ); + return studies; + } + + private async updateResearchStudyFromCache(nctId: string, originals: ResearchStudy | ResearchStudy[]): Promise { + const clinicalStudy = await this.getCachedClinicalStudy(nctId); + // Make sure we have data to use - cache can be missing NCT IDs even after requesting them if the NCT is missing + // from the origin service + if (clinicalStudy !== null) { + // Update whatever trials we have, which will either be an array or single object + if (Array.isArray(originals)) { + for (const s of originals) { + this.updateResearchStudy(s, clinicalStudy); } - // Finally resolve to the promises we were given - return Promise.all(promises).then(() => studies); - }); + } else { + this.updateResearchStudy(originals, clinicalStudy); + } } } /** - * Tells the cache to delete all expired cached files. The entries are removed from access immediately, but a Promise - * is returned that resolves when all the underlying data is cleaned up. This Promise is more for error handling than - * anything else. + * Tells the cache to delete all expired cached files. Currently this does nothing - entries never expire. It may + * make sense to clean up the database every once and a while, but for now, this is a no-op. */ - removeExpiredCacheEntries(): Promise { - const expiredEntries: CacheEntry[] = []; - const expiredIds: string[] = []; - // Go through the cache and find all expired entries - const expired = new Date(); - expired.setTime(expired.getTime() - this._expirationTimeout); - for (const [key, entry] of this.cache.entries()) { - if (entry.lastAccessedBefore(expired)) { - expiredIds.push(key); - expiredEntries.push(entry); + async removeExpiredCacheEntries(): Promise { + // For now, does nothing. + } + + /** + * Returns a list of NCT IDs that are valid and that are not in the cache. + * @param ids the NCT IDs to locate in the cache + * @return a list containing only NCT IDs that are valid and missing + */ + async findCacheMisses(ids: string[]): Promise { + const db = this.getDB(); + const checkIds: number[] = []; + const idSet = new Set(); + let sql = ''; + for (const id of ids) { + const nctNum = parseNCTNumber(id); + if (nctNum !== undefined) { + checkIds.push(nctNum); + idSet.add(id); + if (sql.length === 0) { + sql = 'SELECT nct_id FROM ctgov_studies WHERE nct_id IN (?'; + } else { + sql += ', ?'; + } } } - const expiredPromises: Promise[] = []; - // Now that the expired entries have been found, remove them - for (const key of expiredIds) { - this.cache.delete(key); + // No valid IDs + if (sql.length === 0) { + return []; } - // FIXME: It's unclear whether or not this creates a race condition where it may be possible for another request - // for an entry we just expired to be deleted before the new entry is ready. This is somewhat unlikely, but it's - // something that could probably be fixed by ensuring that each time we create a cache entry we use a unique - // filename. - for (const entry of expiredEntries) { - expiredPromises.push(entry.remove()); + const cacheHitIds = await db.all<{ nct_id: number }[]>(sql + ')', checkIds); + // Remove each hit ID + for (const row of cacheHitIds) { + idSet.delete(formatNCTNumber(row.nct_id)); } - - // The "then" essentially removes the array of undefined that will result - // into a single undefined. - return Promise.all(expiredPromises).then(() => { - /* do nothing */ - }); + return Array.from(idSet); } /** @@ -742,9 +539,9 @@ export class ClinicalTrialsGovService { */ ensureTrialsAvailable(ids: string[]): Promise; ensureTrialsAvailable(studies: ResearchStudy[]): Promise; - ensureTrialsAvailable(idsOrStudies: Array): Promise { + async ensureTrialsAvailable(idsOrStudies: Array): Promise { // We only want string IDs and we may end up filtering some of them out - const ids: string[] = []; + let ids: string[] = []; for (const o of idsOrStudies) { if (typeof o === 'string') { if (isValidNCTNumber(o)) ids.push(o); @@ -753,16 +550,20 @@ export class ClinicalTrialsGovService { if (id) ids.push(id); } } + // Find the IDs we need to fetch + ids = await this.findCacheMisses(ids); // Now that we have the IDs, we can split them into download requests - const promises: Promise[] = []; + // Run the download requests in series for (let start = 0; start < ids.length; start += this.maxTrialsPerRequest) { - promises.push(this.downloadTrials(ids.slice(start, Math.min(start + this.maxTrialsPerRequest, ids.length)))); + await this.downloadTrials(ids.slice(start, Math.min(start + this.maxTrialsPerRequest, ids.length))); } - return Promise.all(promises).then(() => { - // This exists solely to turn the result from an array of success/fails into a single nothing - }); } + /** + * A Promise used as a lock to ensure that only one downloadTrials is attempting to insert cache entries at a time. + */ + private _downloadTrialsLock: Promise | null = null; + /** * Downloads the given trials from ClinicalTrials.gov and stores them in the data directory. Note that this will * always replace trials if they exist. @@ -772,90 +573,74 @@ export class ClinicalTrialsGovService { * is otherwise silently eaten */ protected async downloadTrials(ids: string[]): Promise { - let success = false; - // Now that we're starting to download clinical trials, immediately create pending entries for them. - for (const id of ids) { - if (!this.cache.has(id)) { - this.cache.set(id, new CacheEntry(this, this.pathForNctNumber(id), { pending: true })); - } + this.log('Fetching studies with NCT IDs %j', ids); + const studies = await this.tryFetchStudies(ids); + // If the call failed, return false + if (studies === null) { + return false; + } + const db = this.getDB(); + // Some promise magic - if the lock exists, await it + while (this._downloadTrialsLock !== null) { + this.log('Waiting on transaction lock for NCT IDs %j', ids); + await this._downloadTrialsLock; } + // Now, create the lock. (Default value gives it a type. Promise always runs the function in the constructor + // immediately so resolveLock will be set to the resolve method, but TypeScript can't prove that.) + let resolveLock = /* istanbul ignore next */ () => {}; + this._downloadTrialsLock = new Promise((resolve) => { + resolveLock = resolve; + }); try { - const studies = await this.service.fetchStudies(ids, this._maxTrialsPerRequest); - for (const study of studies) { - await this.addCacheEntry(study); - } - success = true; - } catch (ex) { - // If an error occurred while fetching studies, every cache entry we just loaded may be invalid. - this.log('Error while fetching trials: %o', ex); - this.log('Invalidating cache entry IDs for: %s', ids); - for (const id of ids) { - const entry = this.cache.get(id); - if (entry && entry.pending) { - this.cache.delete(id); + // Surround this in a transaction (otherwise each insert would be a transaction on its own) + await db.run('BEGIN'); + try { + for (const study of studies) { + await this.addCacheEntry(db, study); } + await db.run('COMMIT'); + return true; + } catch (ex) { + this.log('Exception while adding cache entries: %j', ex); + await db.run('ROLLBACK'); + return false; } + } finally { + this._downloadTrialsLock = null; + resolveLock(); } - // Invalidate any IDs that are still pending, they weren't in the results - for (const id of ids) { - const entry = this.cache.get(id); - if (entry && entry.createdAt === null) { - this.log('Removing cache entry for %s: it was not in the downloaded bundle!', id); - entry.fail('Not found in bundle'); - this.cache.delete(id); - } - } - return success; } - /** - * Create a path to the data file that stores data about a cache entry. - * @param nctNumber the NCT number - */ - private pathForNctNumber(nctNumber: NCTNumber): string { - // FIXME: It's probably best not to use the NCT number as the sole part of the filename. See - // removeExpiredCacheEntries for details. - return path.join(this.cacheDataDir, nctNumber + '.json'); + private async tryFetchStudies(ids: string[]): Promise { + try { + return await this.service.fetchStudies(ids, this._maxTrialsPerRequest); + } catch (ex) { + this.log('Error fetching trials from server: %o', ex); + return null; + } } - private addCacheEntry(study: Study): Promise { + private async addCacheEntry(db: sqlite.Database, study: Study): Promise { // See if we can locate an NCT number for this study. const nctNumber = study.protocolSection?.identificationModule?.nctId; if (typeof nctNumber !== 'string') { this.log( 'Ignoring study object from server: unable to locate an NCT ID for it! (protocolSection.identificationModule.nctId missing or not a string)' ); - return Promise.resolve(); + return; } - const filename = this.pathForNctNumber(nctNumber); - // The cache entry should already exist - const entry = this.cache.get(nctNumber); - // Tell the entry that we are writing data - if (entry) { - entry.found(); - } else { - this.log('No cache entry for %s! NOT writing it to cache! (Got back a different NCT number?)', nctNumber); - return Promise.resolve(); + // Convert to the numeric ID sqlite will actually use + const rowId = parseNCTNumber(nctNumber); + if (rowId === undefined) { + this.log('Ignoring invalid study object from server: NCT ID %s is not valid!', nctNumber); + return; } - return new Promise((resolve, reject) => { - this.fs.writeFile(filename, JSON.stringify(study), 'utf8', (err) => { - if (err) { - this.log('Unable to create file [%s]: %o', filename, err); - // If the cache entry exists in pending mode, delete it - we failed to create this entry - if (entry && entry.pending) { - entry.fail('Unable to create file'); - this.cache.delete(nctNumber); - } - // TODO: Do we also need to delete the file? Or will the error prevent the file from existing? - reject(err); - } else { - if (entry && entry.pending) { - entry.ready(); - } - resolve(); - } - }); - }); + await db.run( + 'INSERT INTO ctgov_studies (nct_id, study_json, created_at) VALUES (?, ?, ?) ON CONFLICT(nct_id) DO UPDATE SET study_json=excluded.study_json', + rowId, + JSON.stringify(study), + new Date().valueOf() + ); } /** @@ -864,13 +649,20 @@ export class ClinicalTrialsGovService { * @param nctNumber the NCT number * @returns a Promise that resolves to either the parsed ClinicalStudy or to null if the ClinicalStudy does not exist */ - getCachedClinicalStudy(nctNumber: NCTNumber): Promise { - const entry = this.cache.get(nctNumber); - if (entry) { - return entry.load(); - } else { - return Promise.resolve(null); + async getCachedClinicalStudy(nctNumber: NCTNumber): Promise { + const db = this.getDB(); + const nctId = parseNCTNumber(nctNumber); + if (nctId) { + const result = await db.get<{ study_json: string } | null>( + 'SELECT study_json FROM ctgov_studies WHERE nct_id=?', + nctId + ); + // study_json can be NULL to indicate a cached failure + if (result && result.study_json) { + return JSON.parse(result.study_json); + } } + return null; } /** @@ -886,33 +678,29 @@ export class ClinicalTrialsGovService { } /** - * Creates and initializes a new service for retrieving data from http://clinicaltrials.gov/. This will automatically - * invoke the init method to create the directory if it doesn't exist and load any existing data if it does. Note that - * the init method will not create missing parent directories - the path to the cache directory must already exist - * minus the cache directory itself. If the cache directory cannot be created the Promise will be rejected with the - * error preventing it from being created. + * Creates and initializes a new service for retrieving data from http://clinicaltrials.gov/. + * + * This is essentially the same as constructing the object and then calling init() on it. + * + * Note the same behavior applies when `create()` is invoked with a database: the database object is nulled out when + * {@link #destroy} is called, but it isn't closed. When invoked with a database object, it is the caller's + * responsibility to close it. * - * @param dataDir the data directory + * @param db the path to the database file or the database to use directly * @param options additional options that can be set to further configure the trial service * @returns a Promise that resolves when the service is ready */ - static create(dataDir: string, options?: ClinicalTrialsGovServiceOptions): Promise { - const result = new ClinicalTrialsGovService(dataDir, options); - return result.init().then(() => result); + static async create( + db: string | sqlite.Database, + options?: ClinicalTrialsGovServiceOptions + ): Promise { + const result = new ClinicalTrialsGovService(db, options); + await result.init(); + return result; } } /** - * Creates and initializes a new service for retrieving data from http://clinicaltrials.gov/. This is the same as - * ClinicalTrialsGovService.create, see that method for details. - * - * @param dataDir the data directory - * @param options additional options that can be set to further configure the trial service - * @returns a Promise that resolves when the service is ready + * Alias of ClinicalTrialsGovService.create. */ -export function createClinicalTrialsGovService( - dataDir: string, - options?: ClinicalTrialsGovServiceOptions -): Promise { - return ClinicalTrialsGovService.create(dataDir, options); -} +export const createClinicalTrialsGovService = ClinicalTrialsGovService.create; diff --git a/src/fhir-util.ts b/src/fhir-util.ts index c5d66a1..173742a 100644 --- a/src/fhir-util.ts +++ b/src/fhir-util.ts @@ -1,5 +1,139 @@ import { FhirResource } from 'fhir/r4'; +export enum FHIRDateAccuracy { + YEAR, + YEAR_MONTH, + YEAR_MONTH_DAY, + YEAR_MONTH_DAY_TIME +} + +/** + * A FHIR date. A FHIR date may not have all components provided. + */ +export class FHIRDate { + /** + * The parsed date as a JavaScript date object. The date is parsed into UTC, + * so only the getUTC methods should be used to extract data from it. + */ + readonly date: Date; + /** + * The degree of accuracy provided for the date. Dates can be missing parts. + */ + readonly accuracy: FHIRDateAccuracy; + /** + * Whether or not the seconds was actually 60 and not 0. Leap seconds are + * allowed in the FHIR date time spec! If given a time that ends in a 60, + * the minute is left as-is, and the seconds are decreased to 59, and this + * flag is set. Note that milliseconds are not changed! For almost all use + * cases, it makes the most sense to ignore this flag. + */ + readonly leapSecond: boolean; + + constructor(date: Date, accuracy: FHIRDateAccuracy, leapSecond = false) { + this.date = date; + this.accuracy = accuracy; + this.leapSecond = leapSecond && date.getUTCSeconds() === 59; + } + + /** + * Parses a FHIR date. Throws an exception if the date is invalid. + * @param value + */ + static parse(value: string): FHIRDate { + // The overly complicated regexp that FHIR gives checks to ensure that + // values are in range, but that can be done after pulling them out. + const m = + /^([0-9]{1,4})(?:-([0-9]{2})(?:-([0-9]{2})(?:T([0-9]{2}):([0-9]{2}):([0-9]{2})(?:\.([0-9]+))?(Z|([+-][0-9]{1,2}):([0-9]{2})))?)?)?$/.exec( + value + ); + if (!m) { + throw new Error(`Invalid FHIR dateTime "${value}"`); + } + const year = parseInt(m[1]); + let monthIndex = 0, + date = 1, + hours = 0, + minutes = 0, + seconds = 0, + millis = 0, + leapSecond = false, + accuracy = FHIRDateAccuracy.YEAR; + if (year === 0) { + throw new Error(`Invalid year 0 in dateTime "${value}"`); + } + if (m[2]) { + monthIndex = parseInt(m[2]) - 1; + if (monthIndex < 0 || monthIndex > 11) { + throw new Error(`Invalid month ${m[2]} in dateTime "${value}"`); + } + if (m[3]) { + date = parseInt(m[3]); + if (date < 1 || date > 31) { + throw new Error(`Invalid date ${m[3]} in dateTime "${value}"`); + } + // So there's an issue here - 2023-02-29 becomes 2023-03-01 via the Date + // constructor, but checking for number of days in a month is + // problematic, because 2024-02-29 *is* a valid date. + // There *is* a way to deal with this, though... once we've made the + // date object, see if the month has changed. + if (m[4] && m[5] && m[6] && m[8]) { + hours = parseInt(m[4]); + if (hours < 0 || hours > 23) { + throw new Error(`Invalid hours ${m[4]} in dateTime "${value}"`); + } + minutes = parseInt(m[5]); + if (minutes < 0 || minutes > 59) { + throw new Error(`Invalid minutes ${m[5]} in dateTime "${value}"`); + } + seconds = parseInt(m[6]); + if (seconds < 0 || seconds > 60) { + throw new Error(`Invalid seconds ${m[6]} in dateTime "${value}"`); + } + if (seconds === 60) { + leapSecond = true; + seconds = 59; + } + // Only the first three digits are parsed, the rest are ignored. + // To make ".1" work, 0-pad the end, so it becomes ".100" or 100 + // milliseconds. + millis = m[7] === undefined ? 0 : parseInt(m[7].padEnd(3, '0').substring(0, 3)); + accuracy = FHIRDateAccuracy.YEAR_MONTH_DAY_TIME; + } else { + accuracy = FHIRDateAccuracy.YEAR_MONTH_DAY; + } + } else { + accuracy = FHIRDateAccuracy.YEAR_MONTH; + } + } + let dateObject = new Date(Date.UTC(year, monthIndex, date, hours, minutes, seconds, millis)); + // Do a couple of validation checks that the date object doesn't... + if (dateObject.getUTCMonth() != monthIndex) { + // Date was apparently invalid for that month + throw new Error(`Invalid date ${date} for month ${monthIndex + 1} in dateTime "${value}"`); + } + if (m[8] && m[8] !== 'Z') { + // There is a time zone so the offset needs to be applied. + const tzHours = parseInt(m[9].substring(1)), + tzMinutes = parseInt(m[10]); + // Calculate the offset in ms... + const offset = (tzHours * 60 + tzMinutes) * 60000; + // Apply the offset + let unixTime = dateObject.valueOf(); + if (m[9].startsWith('-')) { + // Offset is negative, meaning the time is *behind* UTC, and needs to be + // brought *forward*, so add the offset + unixTime += offset; + } else { + // Offset is positive, meaning the time is *ahead of* UTC, and needs to + // be brought *backwards*, so subtract the offset + unixTime -= offset; + } + dateObject = new Date(unixTime); + } + return new FHIRDate(dateObject, accuracy, leapSecond); + } +} + /** * Checks to see if a given resource contains a requested profile. * @param resource the FHIR resource to check @@ -21,6 +155,6 @@ export function resourceContainsProfile(resource: FhirResource, profile: string return profiles.includes(profile); } else { // RegExp, test each profile with it (useful for looser matching) - return profiles.some(p => profile.test(p)); + return profiles.some((p) => profile.test(p)); } }