diff --git a/.gitignore b/.gitignore index bb1dae1..2545672 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ node_modules -/.env -/.DS_Store +.env +.DS_Store + /coverage /.github_old diff --git a/.nvmrc b/.nvmrc index b009dfb..2bf5ad0 100755 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -lts/* +stable diff --git a/eslint.config.js b/eslint.config.js index bbb9706..4bdceec 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -23,7 +23,8 @@ export default [ languageOptions: { globals: { ...globals.browser, - expect: 'readonly' + expect: 'readonly', + global: 'readonly' } }, plugins: { diff --git a/package-lock.json b/package-lock.json index 0626f44..a502c26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,7 +29,7 @@ "eslint-plugin-prettier": "5.2.1", "eslint-plugin-security": "3.0.1", "eslint-plugin-vitest": "0.5.4", - "fetch-mock": "11.1.5", + "fetch-mock": "12.0.0", "happy-dom": "15.7.4", "husky": "9.1.6", "lint-staged": "15.2.10", @@ -1495,224 +1495,208 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.3.tgz", - "integrity": "sha512-MmKSfaB9GX+zXl6E8z4koOr/xU63AMVleLEa64v7R0QF/ZloMs5vcD1sHgM64GXXS1csaJutG+ddtzcueI/BLg==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz", + "integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==", "cpu": [ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.3.tgz", - "integrity": "sha512-zrt8ecH07PE3sB4jPOggweBjJMzI1JG5xI2DIsUbkA+7K+Gkjys6eV7i9pOenNSDJH3eOr/jLb/PzqtmdwDq5g==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz", + "integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.3.tgz", - "integrity": "sha512-P0UxIOrKNBFTQaXTxOH4RxuEBVCgEA5UTNV6Yz7z9QHnUJ7eLX9reOd/NYMO3+XZO2cco19mXTxDMXxit4R/eQ==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz", + "integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.3.tgz", - "integrity": "sha512-L1M0vKGO5ASKntqtsFEjTq/fD91vAqnzeaF6sfNAy55aD+Hi2pBI5DKwCO+UNDQHWsDViJLqshxOahXyLSh3EA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz", + "integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.3.tgz", - "integrity": "sha512-btVgIsCjuYFKUjopPoWiDqmoUXQDiW2A4C3Mtmp5vACm7/GnyuprqIDPNczeyR5W8rTXEbkmrJux7cJmD99D2g==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz", + "integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==", "cpu": [ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.3.tgz", - "integrity": "sha512-zmjbSphplZlau6ZTkxd3+NMtE4UKVy7U4aVFMmHcgO5CUbw17ZP6QCgyxhzGaU/wFFdTfiojjbLG3/0p9HhAqA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz", + "integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==", "cpu": [ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.3.tgz", - "integrity": "sha512-nSZfcZtAnQPRZmUkUQwZq2OjQciR6tEoJaZVFvLHsj0MF6QhNMg0fQ6mUOsiCUpTqxTx0/O6gX0V/nYc7LrgPw==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz", + "integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.3.tgz", - "integrity": "sha512-MnvSPGO8KJXIMGlQDYfvYS3IosFN2rKsvxRpPO2l2cum+Z3exiExLwVU+GExL96pn8IP+GdH8Tz70EpBhO0sIQ==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz", + "integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.3.tgz", - "integrity": "sha512-+W+p/9QNDr2vE2AXU0qIy0qQE75E8RTwTwgqS2G5CRQ11vzq0tbnfBd6brWhS9bCRjAjepJe2fvvkvS3dno+iw==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz", + "integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==", "cpu": [ "ppc64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.3.tgz", - "integrity": "sha512-yXH6K6KfqGXaxHrtr+Uoy+JpNlUlI46BKVyonGiaD74ravdnF9BUNC+vV+SIuB96hUMGShhKV693rF9QDfO6nQ==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz", + "integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==", "cpu": [ "riscv64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.3.tgz", - "integrity": "sha512-R8cwY9wcnApN/KDYWTH4gV/ypvy9yZUHlbJvfaiXSB48JO3KpwSpjOGqO4jnGkLDSk1hgjYkTbTt6Q7uvPf8eg==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz", + "integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==", "cpu": [ "s390x" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.3.tgz", - "integrity": "sha512-kZPbX/NOPh0vhS5sI+dR8L1bU2cSO9FgxwM8r7wHzGydzfSjLRCFAT87GR5U9scj2rhzN3JPYVC7NoBbl4FZ0g==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz", + "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.3.tgz", - "integrity": "sha512-S0Yq+xA1VEH66uiMNhijsWAafffydd2X5b77eLHfRmfLsRSpbiAWiRHV6DEpz6aOToPsgid7TI9rGd6zB1rhbg==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz", + "integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.3.tgz", - "integrity": "sha512-9isNzeL34yquCPyerog+IMCNxKR8XYmGd0tHSV+OVx0TmE0aJOo9uw4fZfUuk2qxobP5sug6vNdZR6u7Mw7Q+Q==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz", + "integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.3.tgz", - "integrity": "sha512-nMIdKnfZfzn1Vsk+RuOvl43ONTZXoAPUUxgcU0tXooqg4YrAqzfKzVenqqk2g5efWh46/D28cKFrOzDSW28gTA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz", + "integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==", "cpu": [ "ia32" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.3.tgz", - "integrity": "sha512-fOvu7PCQjAj4eWDEuD8Xz5gpzFqXzGlxHZozHP4b9Jxv9APtdxL6STqztDzMLuRXEc4UpXGGhx029Xgm91QBeA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz", + "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" @@ -2202,11 +2186,10 @@ } }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true, - "license": "MIT" + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true }, "node_modules/@types/glob-to-regexp": { "version": "0.4.4", @@ -2837,6 +2820,15 @@ "node": ">=12" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -3334,6 +3326,21 @@ "dev": true, "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "12.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", @@ -3578,6 +3585,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cssstyle": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.1.0.tgz", + "integrity": "sha512-h66W1URKpBS5YMI/V8PyXvTMFT8SupJ1IzoIV8IeBC/ji8WVmrO8dGlTi+2dh6whmdk6BiKJLD/ZBkhWbcg6nA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "rrweb-cssom": "^0.7.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/dargs": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/dargs/-/dargs-8.1.0.tgz", @@ -3591,6 +3613,44 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-urls/node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=18" + } + }, "node_modules/debug": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", @@ -3609,6 +3669,15 @@ } } }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/deep-eql": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", @@ -3706,6 +3775,18 @@ "node": ">=8" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -4635,6 +4716,14 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/fast-equals": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.0.1.tgz", + "integrity": "sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", @@ -4696,26 +4785,44 @@ "reusify": "^1.0.4" } }, - "node_modules/fetch-mock": { - "version": "11.1.5", - "resolved": "https://registry.npmjs.org/fetch-mock/-/fetch-mock-11.1.5.tgz", - "integrity": "sha512-KHmZDnZ1ry0pCTrX4YG5DtThHi0MH+GNI9caESnzX/nMJBrvppUHMvLx47M0WY9oAtKOMiPfZDRpxhlHg89BOA==", + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/fetch-mock": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/fetch-mock/-/fetch-mock-12.0.0.tgz", + "integrity": "sha512-JSsjzoRN4rYqHa2/+8ushJGDsK9HGNTdBZo6Hrpu3KFN7Y03nRCt2VJ2WG4OUvyTUukOQ4TQIfjcFcEkMPGZ0Q==", + "dev": true, "dependencies": { "@types/glob-to-regexp": "^0.4.4", "dequal": "^2.0.3", "glob-to-regexp": "^0.4.1", - "is-subset": "^0.1.1", + "is-subset-of": "^3.1.10", "regexparam": "^3.0.0" }, "engines": { - "node": ">=8.0.0" - }, - "peerDependenciesMeta": { - "node-fetch": { - "optional": true - } + "node": ">=18.11.0" } }, "node_modules/figures": { @@ -4869,6 +4976,36 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/from2": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", @@ -5275,6 +5412,21 @@ "node": "^16.14.0 || >=18.0.0" } }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -5336,6 +5488,21 @@ "url": "https://github.com/sponsors/typicode" } }, + "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==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -5589,6 +5756,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -5602,12 +5778,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-subset": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", - "integrity": "sha512-6Ybun0IkarhmEqxXCNw/C0bna6Zb/TkfUX9UbwJtK6ObwAVCxmAP308WWTHviM/zAqXk05cdhYsUsZeGQh99iw==", + "node_modules/is-subset-of": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/is-subset-of/-/is-subset-of-3.1.10.tgz", + "integrity": "sha512-avvaYgVmYWyaZ1NDFiv4y9JGkrE2je3op1Po4VYKKJKR8H2qVPsg1GZuuXl5elCTxTlwAIsrAjWAs4BVrISFRw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", "dev": true, - "license": "MIT" + "dependencies": { + "typedescriptor": "3.0.2" + } }, "node_modules/is-text-path": { "version": "2.0.0", @@ -5776,6 +5955,76 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdom": { + "version": "25.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz", + "integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "cssstyle": "^4.1.0", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.12", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.7.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.0.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^2.11.2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/parse5": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.0.tgz", + "integrity": "sha512-ZkDsAOcxsUMZ4Lz5fVciOehNcJ+Gb8gTzcA4yl3wnc273BAybYWrQ+Ks/OjCjSEpjvQkDSeZbybK9qj2VHHdGA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "entities": "^4.5.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/jsdom/node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=18" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -6467,6 +6716,33 @@ "node": ">=16" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -6589,6 +6865,26 @@ "dev": true, "license": "MIT" }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, "node_modules/node-emoji": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.1.3.tgz", @@ -6605,6 +6901,25 @@ "node": ">=18" } }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, "node_modules/normalize-package-data": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", @@ -9188,6 +9503,15 @@ "inBundle": true, "license": "ISC" }, + "node_modules/nwsapi": { + "version": "2.2.13", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.13.tgz", + "integrity": "sha512-cTGB9ptp9dY9A5VbMSe7fQBcl/tt22Vcqdq8+eN93rblOuE0aCFu4aZ2vMwct/2t+lFnosm8RkQW1I0Omb1UtQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -10138,13 +10462,12 @@ } }, "node_modules/rollup": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.3.tgz", - "integrity": "sha512-7sqRtBNnEbcBtMeRVc6VRsJMmpI+JU1z9VTvW8D4gXIYQFz0aLcsE6rRkyghZkLfEgUZgVvOG7A5CVz/VW5GIA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz", + "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==", "dev": true, - "license": "MIT", "dependencies": { - "@types/estree": "1.0.5" + "@types/estree": "1.0.6" }, "bin": { "rollup": "dist/bin/rollup" @@ -10154,25 +10477,34 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.21.3", - "@rollup/rollup-android-arm64": "4.21.3", - "@rollup/rollup-darwin-arm64": "4.21.3", - "@rollup/rollup-darwin-x64": "4.21.3", - "@rollup/rollup-linux-arm-gnueabihf": "4.21.3", - "@rollup/rollup-linux-arm-musleabihf": "4.21.3", - "@rollup/rollup-linux-arm64-gnu": "4.21.3", - "@rollup/rollup-linux-arm64-musl": "4.21.3", - "@rollup/rollup-linux-powerpc64le-gnu": "4.21.3", - "@rollup/rollup-linux-riscv64-gnu": "4.21.3", - "@rollup/rollup-linux-s390x-gnu": "4.21.3", - "@rollup/rollup-linux-x64-gnu": "4.21.3", - "@rollup/rollup-linux-x64-musl": "4.21.3", - "@rollup/rollup-win32-arm64-msvc": "4.21.3", - "@rollup/rollup-win32-ia32-msvc": "4.21.3", - "@rollup/rollup-win32-x64-msvc": "4.21.3", + "@rollup/rollup-android-arm-eabi": "4.24.0", + "@rollup/rollup-android-arm64": "4.24.0", + "@rollup/rollup-darwin-arm64": "4.24.0", + "@rollup/rollup-darwin-x64": "4.24.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.24.0", + "@rollup/rollup-linux-arm-musleabihf": "4.24.0", + "@rollup/rollup-linux-arm64-gnu": "4.24.0", + "@rollup/rollup-linux-arm64-musl": "4.24.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0", + "@rollup/rollup-linux-riscv64-gnu": "4.24.0", + "@rollup/rollup-linux-s390x-gnu": "4.24.0", + "@rollup/rollup-linux-x64-gnu": "4.24.0", + "@rollup/rollup-linux-x64-musl": "4.24.0", + "@rollup/rollup-win32-arm64-msvc": "4.24.0", + "@rollup/rollup-win32-ia32-msvc": "4.24.0", + "@rollup/rollup-win32-x64-msvc": "4.24.0", "fsevents": "~2.3.2" } }, + "node_modules/rrweb-cssom": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", + "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -10223,6 +10555,30 @@ "regexp-tree": "~0.1.1" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/semantic-release": { "version": "24.1.2", "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-24.1.2.tgz", @@ -11311,6 +11667,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/synckit": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.1.tgz", @@ -11545,6 +11910,30 @@ "node": ">=14.0.0" } }, + "node_modules/tldts": { + "version": "6.1.53", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.53.tgz", + "integrity": "sha512-4uCStuOjPFaY2/LUjTSwdnJTC82W/gvSFL6FoTC9ehNOHboA9cyO3wX1erh2yGofVls37OdXr5sQLEfL5hS1TA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tldts-core": "^6.1.53" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.53", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.53.tgz", + "integrity": "sha512-IleS872aGdTB/UtocD2dSZBnQi/nqMIZxxezVgfcKKjw6+G2hJGzFw9buIDJO2MVJyEJe3rCAdyMTl2yvGMMrQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -11568,6 +11957,36 @@ "node": ">=8.0" } }, + "node_modules/tough-cookie": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz", + "integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "peer": true, + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/traverse": { "version": "0.6.8", "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.8.tgz", @@ -11626,6 +12045,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typedescriptor": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/typedescriptor/-/typedescriptor-3.0.2.tgz", + "integrity": "sha512-hyVbaCUd18UiXk656g/imaBLMogpdijIEpnhWYrSda9rhvO4gOU16n2nh7xG5lv/rjumnZzGOdz0CEGTmFe0fQ==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true + }, "node_modules/typescript": { "version": "5.6.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", @@ -11903,6 +12329,31 @@ } } }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -11913,6 +12364,21 @@ "node": ">=12" } }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/whatwg-mimetype": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", @@ -11923,6 +12389,22 @@ "node": ">=12" } }, + "node_modules/whatwg-url": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz", + "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -12114,6 +12596,51 @@ "dev": true, "license": "ISC" }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -12249,7 +12776,11 @@ "license": "MIT", "dependencies": { "@rxjs-collection/observables": "*", + "fast-equals": "^5.0.1", "rxjs": "7.8.1" + }, + "devDependencies": { + "node-fetch": "^3.3.2" } } } diff --git a/package.json b/package.json index ea9dfce..694d6d4 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "eslint-plugin-prettier": "5.2.1", "eslint-plugin-security": "3.0.1", "eslint-plugin-vitest": "0.5.4", - "fetch-mock": "11.1.5", + "fetch-mock": "12.0.0", "happy-dom": "15.7.4", "husky": "9.1.6", "lint-staged": "15.2.10", diff --git a/packages/observables/.releaserc b/packages/observables/.releaserc index a90e7ef..fc40687 100644 --- a/packages/observables/.releaserc +++ b/packages/observables/.releaserc @@ -39,15 +39,7 @@ } ], [ - "@semantic-release/github", - { - "assets": [ - { - "path": "./packages/observables/", - "label": "observables" - } - ] - } + "@semantic-release/github" ] ], "extends": "semantic-release-monorepo" diff --git a/packages/observables/src/fetch/request.http b/packages/observables/src/fetch/request.http deleted file mode 100644 index 286a6eb..0000000 --- a/packages/observables/src/fetch/request.http +++ /dev/null @@ -1,9 +0,0 @@ - -# @name post -GET https://dummyjson.com/posts/1 HTTP/1.1 -content-type: application/json - -### -@userId = {{post.response.body.userId}} -GET https://dummyjson.com/users/{{userId}} HTTP/1.1 -content-type: application/json diff --git a/packages/observables/src/fetch/request.js b/packages/observables/src/fetch/request.js deleted file mode 100644 index 95598a9..0000000 --- a/packages/observables/src/fetch/request.js +++ /dev/null @@ -1,8 +0,0 @@ -import { fromFetch } from 'rxjs/fetch'; - -export const requestObservable = request => { - return fromFetch(request) - .pipe - // add operators for default behaviour - (); -}; diff --git a/packages/observables/src/fetch/request.test.js b/packages/observables/src/fetch/request.test.js deleted file mode 100644 index ef16c3f..0000000 --- a/packages/observables/src/fetch/request.test.js +++ /dev/null @@ -1,34 +0,0 @@ -import fetchMock from 'fetch-mock'; -import { afterEach, test, describe, beforeEach, expect } from 'vitest'; - -import { requestObservable } from './request.js'; - -describe('request observable with default operators', function () { - beforeEach(function () { - fetchMock.get( - 'https://httpbin.org/my-url-fast', - new Response(JSON.stringify({ hello: 'fast world' }), { - status: 200, - headers: { - 'Content-type': 'application/json' - } - }), - { - delay: 1000 - } - ); - }); - - afterEach(function () { - fetchMock.restore(); - }); - - test('successfull request', () => - new Promise(done => { - requestObservable('https://httpbin.org/my-url-fast').subscribe(async e => { - expect(e.ok).equal(true); - expect(await e.json()).deep.equal({ hello: 'fast world' }); - done(); - }); - })); -}); diff --git a/packages/operators/fixtures/images/test_image.jpg b/packages/operators/fixtures/images/test_image.jpg new file mode 100644 index 0000000..2127b40 Binary files /dev/null and b/packages/operators/fixtures/images/test_image.jpg differ diff --git a/packages/operators/package.json b/packages/operators/package.json index bdb845f..7808bca 100644 --- a/packages/operators/package.json +++ b/packages/operators/package.json @@ -19,6 +19,10 @@ }, "dependencies": { "@rxjs-collection/observables": "*", + "fast-equals": "^5.0.1", "rxjs": "7.8.1" + }, + "devDependencies": { + "node-fetch": "^3.3.2" } } diff --git a/packages/operators/src/fetch/autoPagination.js b/packages/operators/src/fetch/autoPagination.js new file mode 100644 index 0000000..8ebd1ba --- /dev/null +++ b/packages/operators/src/fetch/autoPagination.js @@ -0,0 +1,21 @@ +import { concatMap, expand, filter, from, map } from 'rxjs'; + +import { download } from './download'; + +export const autoPagination = ({ resolveRoute }) => { + return source => + source.pipe( + concatMap(({ url }) => { + return from(resolveRoute(url)).pipe( + download(), + expand(resp => + from(resolveRoute(url, resp)).pipe( + filter(url => !!url), + download() + ) + ) + ); + }), + map(resp => resp.clone()) + ); +}; diff --git a/packages/operators/src/fetch/autoPagination.test.js b/packages/operators/src/fetch/autoPagination.test.js new file mode 100644 index 0000000..f0b4664 --- /dev/null +++ b/packages/operators/src/fetch/autoPagination.test.js @@ -0,0 +1,42 @@ +import { concatAll, map, of } from 'rxjs'; +import { beforeEach, describe, expect, test } from 'vitest'; + +import { log } from '../log'; +import { autoPagination } from './autoPagination'; +import { resolveJSON } from './resolve'; + +describe('auto pagination', function () { + beforeEach(function () { + // + }); + + test('auto pagination', async function () { + return new Promise(done => { + return of({ url: new URL('https://dummyjson.com/products') }) + .pipe( + autoPagination({ + resolveRoute: async (url, resp) => { + const data = (await resp?.json()) || { skip: -10, limit: 10 }; + + if (!data.total || data.total > data.skip + data.limit) { + const newUrl = new URL(`${url}`); + newUrl.searchParams.set('skip', data.skip + data.limit); + newUrl.searchParams.set('limit', data.limit); + newUrl.searchParams.set('select', 'title,price'); + return newUrl; + } + } + }), + log(false), + resolveJSON(), + log(false), + map(({ products }) => products), + concatAll() + ) + .subscribe({ + next: e => console.log(e), + complete: () => done() + }); + }); + }); +}); diff --git a/packages/operators/src/fetch/concurrentDownload.js b/packages/operators/src/fetch/concurrentDownload.js new file mode 100644 index 0000000..afcd4aa --- /dev/null +++ b/packages/operators/src/fetch/concurrentDownload.js @@ -0,0 +1,7 @@ +import { mergeMap, of } from 'rxjs'; + +import { download } from './download'; + +export const concurrentDownload = (concurrent = 1) => { + return source => source.pipe(mergeMap(url => of(url).pipe(download()), concurrent)); +}; diff --git a/packages/operators/src/fetch/concurrentDownload.test.js b/packages/operators/src/fetch/concurrentDownload.test.js new file mode 100644 index 0000000..acfd542 --- /dev/null +++ b/packages/operators/src/fetch/concurrentDownload.test.js @@ -0,0 +1,40 @@ +import { concatAll, map, of } from 'rxjs'; +import { beforeEach, describe, expect, test } from 'vitest'; + +import { log } from '../log'; +import { concurrentDownload } from './concurrentDownload'; +import { resolveJSON } from './resolve'; + +describe('multi fetch', function () { + beforeEach(function () { + // + }); + + test('request pagination', async function () { + return new Promise(done => { + of( + new URL('https://dummyjson.com/products?limit=10&skip=0&select=title,price'), + new URL('https://dummyjson.com/products?limit=10&skip=10&select=title,price'), + new URL('https://dummyjson.com/products?limit=10&skip=20&select=title,price'), + new URL('https://dummyjson.com/products?limit=10&skip=30&select=title,price'), + new URL('https://dummyjson.com/products?limit=10&skip=40&select=title,price'), + new URL('https://dummyjson.com/products?limit=10&skip=50&select=title,price'), + new URL('https://dummyjson.com/products?limit=10&skip=60&select=title,price'), + new URL('https://dummyjson.com/products?limit=10&skip=70&select=title,price'), + new URL('https://dummyjson.com/products?limit=10&skip=80&select=title,price') + ) + .pipe( + concurrentDownload(4), + log(false), + resolveJSON(), + log(false), + map(({ products }) => products), + concatAll() + ) + .subscribe({ + next: e => console.log(e), + complete: () => done() + }); + }); + }); +}); diff --git a/packages/operators/src/fetch/download.js b/packages/operators/src/fetch/download.js new file mode 100644 index 0000000..929d7cb --- /dev/null +++ b/packages/operators/src/fetch/download.js @@ -0,0 +1,18 @@ +import { request } from './request'; +import { resolveBlob, resolveJSON, resolveText } from './resolve'; + +export const download = () => { + return source => source.pipe(request()); +}; + +export const downloadJSON = () => { + return source => source.pipe(download(), resolveJSON()); +}; + +export const downloadText = () => { + return source => source.pipe(download(), resolveText()); +}; + +export const downloadBlob = () => { + return source => source.pipe(download(), resolveBlob()); +}; diff --git a/packages/operators/src/fetch/download.test.js b/packages/operators/src/fetch/download.test.js new file mode 100644 index 0000000..b777e4f --- /dev/null +++ b/packages/operators/src/fetch/download.test.js @@ -0,0 +1,46 @@ +import fetchMock from 'fetch-mock'; +import { of } from 'rxjs'; +import { afterEach, test, describe, beforeEach, expect } from 'vitest'; + +import { log } from '../log.js'; +import { download, downloadJSON } from './download.js'; +import { resolveJSON } from './resolve.js'; + +describe('download operator', function () { + beforeEach(function () { + fetchMock.mockGlobal().get( + 'https://httpbin.org/my-url-fast', + () => { + return new Response(JSON.stringify({ hello: 'fast world' }), { + status: 200, + headers: { 'Content-type': 'application/json' } + }); + }, + { delay: 1000 } + ); + }); + + afterEach(function () { + fetchMock.unmockGlobal(); + }); + + test('successfull download - indirect json resolve', () => + new Promise(done => { + of('https://httpbin.org/my-url-fast') + .pipe(download(), log(false), resolveJSON(), log(false)) + .subscribe({ + next: data => expect(data).deep.equal({ hello: 'fast world' }), + complete: () => done() + }); + })); + + test('successfull download - direct json resolve', () => + new Promise(done => { + of('https://httpbin.org/my-url-fast') + .pipe(downloadJSON(), log(false)) + .subscribe({ + next: data => expect(data).deep.equal({ hello: 'fast world' }), + complete: () => done() + }); + })); +}); diff --git a/packages/operators/src/fetch/lazyPagination.js b/packages/operators/src/fetch/lazyPagination.js new file mode 100644 index 0000000..15115ea --- /dev/null +++ b/packages/operators/src/fetch/lazyPagination.js @@ -0,0 +1,15 @@ +import { concatMap, map } from 'rxjs'; + +import { concurrentDownload } from './concurrentDownload'; + +export const lazyPagination = ({ resolveRoute }) => { + return source => + source.pipe( + concatMap(({ url, pager, concurrent }) => { + return pager.pipe( + map(options => resolveRoute(url, options)), + concurrentDownload(concurrent) + ); + }) + ); +}; diff --git a/packages/operators/src/fetch/lazyPagination.test.js b/packages/operators/src/fetch/lazyPagination.test.js new file mode 100644 index 0000000..7abacc2 --- /dev/null +++ b/packages/operators/src/fetch/lazyPagination.test.js @@ -0,0 +1,51 @@ +import { concatAll, map, of, Subject } from 'rxjs'; +import { beforeEach, describe, expect, test } from 'vitest'; + +import { log } from '../log'; +import { lazyPagination } from './lazyPagination'; +import { resolveJSON } from './resolve'; + +describe('lazy pagination operator', function () { + beforeEach(function () { + // + }); + + test('successfull lazy pagination', async function () { + const pager = new Subject(); + + return new Promise(done => { + of({ url: new URL('https://dummyjson.com/products'), pager, concurrent: 4 }) + .pipe( + lazyPagination({ + resolveRoute: (url, { value, limit = 10 }) => { + const newUrl = new URL(`${url}`); + newUrl.searchParams.set('skip', value * limit); + newUrl.searchParams.set('limit', limit); + newUrl.searchParams.set('select', 'title,price'); + return newUrl; + } + }), + log(false), + resolveJSON(), + log(false), + map(({ products }) => products), + concatAll(), + log(false) + ) + .subscribe({ + next: e => console.log(e), + complete: () => done() + }); + + pager.next({ value: 2 }); + pager.next({ value: 3 }); + pager.next({ value: 12 }); + pager.next({ value: 5 }); + pager.next({ value: 6 }); + pager.next({ value: 7 }); + pager.next({ value: 8 }); + pager.next({ value: 9 }); + pager.complete(); + }); + }); +}); diff --git a/packages/operators/src/fetch/polling.js b/packages/operators/src/fetch/polling.js new file mode 100644 index 0000000..fd4ccd1 --- /dev/null +++ b/packages/operators/src/fetch/polling.js @@ -0,0 +1,13 @@ +import { delay, expand, of } from 'rxjs'; + +import { download } from './download'; +import { distinctUntilResponseChanged } from './response'; + +export const polling = (timeout = 1000) => { + return source => + source.pipe( + download(), + expand(resp => of(resp.url).pipe(delay(timeout), download())), + distinctUntilResponseChanged() + ); +}; diff --git a/packages/operators/src/fetch/polling.test.js b/packages/operators/src/fetch/polling.test.js new file mode 100644 index 0000000..81725f0 --- /dev/null +++ b/packages/operators/src/fetch/polling.test.js @@ -0,0 +1,42 @@ +import fetchMock from 'fetch-mock'; +import { of, take } from 'rxjs'; +import { afterEach, beforeEach, describe, expect, test } from 'vitest'; + +import { log } from '../log'; +import { polling } from './polling'; +import { resolveJSON } from './resolve'; + +describe('polling', function () { + beforeEach(function () { + let counter = 0; + fetchMock.mockGlobal().get('https://httpbin.org/my-url-fast', () => { + if (counter++ < 2) { + return new Response(JSON.stringify({ hello: 'fast world' }), { + status: 200, + headers: { 'Content-type': 'application/json' } + }); + } + return new Response(JSON.stringify({ hello: 'faster world' }), { + status: 200, + headers: { 'Content-type': 'application/json' } + }); + }); + }); + + afterEach(function () { + fetchMock.unmockGlobal(); + }); + + test('auto polling', async function () { + const expected = [{ hello: 'fast world' }, { hello: 'faster world' }]; + + return new Promise(done => { + of(new URL('https://httpbin.org/my-url-fast')) + .pipe(polling(), log(false), resolveJSON(), log(false), take(2)) + .subscribe({ + next: e => expect(e).deep.include(expected.shift()), + complete: () => done() + }); + }); + }); +}); diff --git a/packages/operators/src/fetch/request.http b/packages/operators/src/fetch/request.http new file mode 100644 index 0000000..b6e83ff --- /dev/null +++ b/packages/operators/src/fetch/request.http @@ -0,0 +1,20 @@ +### Sample +# @name post +GET https://dummyjson.com/posts/1 HTTP/1.1 +content-type: application/json + +### Ref Sample +@userId = {{post.response.body.userId}} +GET https://dummyjson.com/users/{{userId}} HTTP/1.1 +content-type: application/json + +### Upload Sample +POST https://api.escuelajs.co/api/v1/files/upload HTTP/1.1 +Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gZ + +------WebKitFormBoundary7MA4YWxkTrZu0gZ +Content-Disposition: form-data; name="file"; filename="1.jpg" +Content-Type: image/jpeg + +< ../../fixtures/images/test_image.jpg +------WebKitFormBoundary7MA4YWxkTrZu0gZ-- diff --git a/packages/operators/src/fetch/request.js b/packages/operators/src/fetch/request.js new file mode 100644 index 0000000..e54f231 --- /dev/null +++ b/packages/operators/src/fetch/request.js @@ -0,0 +1,11 @@ +import { concatMap } from 'rxjs'; + +import { networkRetry } from './retry'; + +export const request = () => { + return source => + source.pipe( + concatMap(req => fetch(req)), + networkRetry() + ); +}; diff --git a/packages/operators/src/fetch/request.test.js b/packages/operators/src/fetch/request.test.js new file mode 100644 index 0000000..2d88eba --- /dev/null +++ b/packages/operators/src/fetch/request.test.js @@ -0,0 +1,36 @@ +import fetchMock from 'fetch-mock'; +import { of } from 'rxjs'; +import { afterEach, test, describe, beforeEach, expect } from 'vitest'; + +import { log } from '../log.js'; +import { request } from './request.js'; + +describe('request observable with default operators', function () { + beforeEach(function () { + let counter = 0; + fetchMock.mockGlobal().get( + 'https://httpbin.org/my-url-fast', + () => { + return new Response(JSON.stringify({ hello: 'fast world' }), { + status: ++counter > 2 ? 200 : 404, + headers: { 'Content-type': 'application/json' } + }); + }, + { delay: 0, repeat: 4 } + ); + }); + + afterEach(function () { + fetchMock.unmockGlobal(); + }); + + test('successfull request', () => + new Promise(done => { + of('https://httpbin.org/my-url-fast') + .pipe(request(), log(false)) + .subscribe({ + next: resp => expect(resp).deep.includes({ ok: true }), + complete: () => done() + }); + })); +}); diff --git a/packages/operators/src/fetch/resolve.js b/packages/operators/src/fetch/resolve.js new file mode 100644 index 0000000..776fce2 --- /dev/null +++ b/packages/operators/src/fetch/resolve.js @@ -0,0 +1,17 @@ +import { concatMap } from 'rxjs'; + +export const resolve = (type = 'json') => { + return source => source.pipe(concatMap(e => e[String(type)]())); +}; + +export const resolveJSON = () => { + return resolve('json'); +}; + +export const resolveText = () => { + return resolve('text'); +}; + +export const resolveBlob = () => { + return resolve('blob'); +}; diff --git a/packages/operators/src/fetch/resolve.test.js b/packages/operators/src/fetch/resolve.test.js new file mode 100644 index 0000000..1a64d5a --- /dev/null +++ b/packages/operators/src/fetch/resolve.test.js @@ -0,0 +1,37 @@ +import { of } from 'rxjs'; +import { afterEach, test, describe, beforeEach, expect } from 'vitest'; + +import { log } from '../log'; +import { resolveJSON, resolveText } from './resolve'; + +describe('resolver', function () { + beforeEach(function () { + // + }); + + afterEach(function () { + // + }); + + test('resolve json', () => { + return new Promise(done => { + of(new Response(JSON.stringify({ hello: 'world' }))) + .pipe(resolveJSON(), log(false)) + .subscribe({ + next: e => expect(e).includes({ hello: 'world' }), + complete: () => done() + }); + }); + }); + + test('resolve text', () => { + return new Promise(done => { + of(new Response('hello world')) + .pipe(resolveText(), log(false)) + .subscribe({ + next: e => expect(e).toBe('hello world'), + complete: () => done() + }); + }); + }); +}); diff --git a/packages/operators/src/fetch/response.js b/packages/operators/src/fetch/response.js new file mode 100644 index 0000000..c809d08 --- /dev/null +++ b/packages/operators/src/fetch/response.js @@ -0,0 +1,11 @@ +import { shallowEqual } from 'fast-equals'; +import { concatMap, distinctUntilChanged, map } from 'rxjs'; + +export const distinctUntilResponseChanged = () => { + return source => + source.pipe( + concatMap(async resp => [resp, await resp.clone().arrayBuffer()]), + distinctUntilChanged(([, a], [, b]) => shallowEqual(new Uint8Array(a), new Uint8Array(b))), + map(([resp]) => resp.clone()) + ); +}; diff --git a/packages/operators/src/fetch/response.test.js b/packages/operators/src/fetch/response.test.js new file mode 100644 index 0000000..d3c27bc --- /dev/null +++ b/packages/operators/src/fetch/response.test.js @@ -0,0 +1,39 @@ +import { of } from 'rxjs'; +import { afterEach, test, describe, beforeEach, expect } from 'vitest'; + +import { log } from '../log'; +import { resolveText } from './resolve'; +import { distinctUntilResponseChanged } from './response'; + +describe('response', function () { + beforeEach(function () { + // + }); + + afterEach(function () { + // + }); + + test('emit only changed responses', () => { + const triggerValues = [ + new Response('a'), + new Response('a'), + new Response('b'), + new Response('b'), + new Response('c'), + new Response('a'), + new Response('a'), + new Response('b') + ]; + const expectedValues = ['a', 'b', 'c', 'a', 'b']; + + return new Promise(done => { + of(...triggerValues) + .pipe(distinctUntilResponseChanged(), log(false), resolveText(), log(false)) + .subscribe({ + next: e => expect(e).toBe(expectedValues.shift()), + complete: () => done() + }); + }); + }); +}); diff --git a/packages/operators/src/request/retry.js b/packages/operators/src/fetch/retry.js similarity index 59% rename from packages/operators/src/request/retry.js rename to packages/operators/src/fetch/retry.js index ee50e93..81f67c7 100644 --- a/packages/operators/src/request/retry.js +++ b/packages/operators/src/fetch/retry.js @@ -20,28 +20,20 @@ export const networkRetry = ({ timeout = defaultTimeout, count } = {}) => { return source => { return source.pipe( - concatMap(resp => { - if (!resp.ok) { - return throwError(() => new Error('invalid request')); - } - return of(resp); - }), + concatMap(resp => (!resp.ok && throwError(() => new Error('invalid request'))) || of(resp)), retry({ count, - delay: () => { - return combineLatest([connectionObservable]).pipe( + delay: () => + combineLatest([connectionObservable]).pipe( + // all defined observables have to be valid map(values => values.every(v => v === true)), + // reset counter if one observable is invalid tap(valid => (counter = counter * valid)), + // continue only if all observables are valid filter(valid => valid), - tap(() => { - console.log(timeout(counter++)); - console.log( - `retry: request - next: ${counter + 1} in ${timeout(counter + 1) || timeout}s` - ); - }), + tap(() => console.log(`retry: request - next: ${counter} in ${timeout(counter)}s`)), delay(timeout(counter++) || timeout) - ); - } + ) }), catchError(e => console.error(e)) ); diff --git a/packages/operators/src/request/retry.test.js b/packages/operators/src/fetch/retry.test.js similarity index 69% rename from packages/operators/src/request/retry.test.js rename to packages/operators/src/fetch/retry.test.js index 3268ada..f9854b2 100644 --- a/packages/operators/src/request/retry.test.js +++ b/packages/operators/src/fetch/retry.test.js @@ -16,17 +16,10 @@ describe('request retry', function () { test('network retry', async function () { let counter = 0; - const mockObservable = of(null) - .pipe( - map(() => { - counter++; - if (counter < 3) { - return { ok: false }; - } - return { ok: true }; - }) - ) - .pipe(networkRetry({ timeout: () => 1000 })); + const mockObservable = of(null).pipe( + map(() => ({ ok: !(++counter < 3) })), + networkRetry({ timeout: () => 1000 }) + ); testScheduler.run(({ expectObservable }) => { expectObservable(mockObservable).toBe('2000ms (a|)', { diff --git a/packages/operators/src/fetch/upload.js b/packages/operators/src/fetch/upload.js new file mode 100644 index 0000000..5f8f023 --- /dev/null +++ b/packages/operators/src/fetch/upload.js @@ -0,0 +1,5 @@ +import { request } from './request'; + +export const upload = () => { + return source => source.pipe(request()); +}; diff --git a/packages/operators/src/fetch/upload.test.js b/packages/operators/src/fetch/upload.test.js new file mode 100644 index 0000000..74b91fe --- /dev/null +++ b/packages/operators/src/fetch/upload.test.js @@ -0,0 +1,51 @@ +import fetchMock from 'fetch-mock'; +import { readFile } from 'node:fs/promises'; +import { of } from 'rxjs'; +import { afterEach, test, describe, beforeEach, expect } from 'vitest'; + +import { log } from '../log.js'; +import { resolveJSON } from './resolve.js'; +import { upload } from './upload.js'; + +describe('request observable with default operators', function () { + beforeEach(function () { + // + }); + + afterEach(function () { + // + }); + + test('successfull request', async () => { + const formData = new FormData(); + formData.set( + 'file', + new File( + [ + new Blob([await readFile('./packages/operators/fixtures/images/test_image.jpg')], { + type: 'image/jpeg' + }) + ], + 'test_image.jpg' + ) + ); + + const req = new Request(new URL('https://api.escuelajs.co/api/v1/files/upload'), { + method: 'POST', + body: formData + }); + + return new Promise(done => { + of(req) + .pipe(upload(), log(false), resolveJSON(), log(false)) + .subscribe({ + next: e => { + expect(e) + .deep.includes({ originalname: 'test_image.jpg' }) + .have.all.keys('filename', 'location'); + }, + complete: () => done() + }); + }); + }); +}); diff --git a/packages/operators/src/log.js b/packages/operators/src/log.js new file mode 100644 index 0000000..535d267 --- /dev/null +++ b/packages/operators/src/log.js @@ -0,0 +1,26 @@ +import { Observable } from 'rxjs'; + +export const log = (active = true) => { + return source => { + if (active) { + return new Observable(observer => { + return source.subscribe( + val => { + console.log(val); + observer.next(val); + }, + err => { + console.error(err); + observer.error(err); + }, + () => { + console.log('%ccomplete', 'color: green'); + observer.complete(); + } + ); + }); + } else { + return source; + } + }; +}; diff --git a/packages/operators/vitest.config.js b/packages/operators/vitest.config.js index e3fd592..b34b1fe 100644 --- a/packages/operators/vitest.config.js +++ b/packages/operators/vitest.config.js @@ -2,6 +2,7 @@ import { defineProject } from 'vitest/config'; export default defineProject({ test: { + setupFiles: ['../../setup.js'], environment: 'happy-dom' } }); diff --git a/setup.js b/setup.js new file mode 100644 index 0000000..752a97b --- /dev/null +++ b/setup.js @@ -0,0 +1,17 @@ +import * as nFetch from 'node-fetch'; +import { afterAll, beforeAll } from 'vitest'; + +const backup = new Map([ + ['fetch', [global.fetch, nFetch.default]], + ['Blob', [global.Blob, nFetch.Blob]], + ['File', [global.File, nFetch.File]], + ['FormData', [global.FormData, nFetch.FormData]], + ['Request', [global.Request, nFetch.Request]] +]); + +beforeAll(() => { + Array.from(backup.entries()).forEach(([name, [, poly]]) => (global[String(name)] = poly)); +}); +afterAll(() => { + Array.from(backup.entries()).forEach(([name, [orig]]) => (global[String(name)] = orig)); +});