diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 1325fb02c2..12ffc89ea2 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -87,6 +87,16 @@ body: validations: required: true + - type: textarea + id: logs + attributes: + label: Relevant log output + description: Please copy and paste any relevant logs below. (code formatting is + enabled, no need for backticks) + render: shell + validations: + required: false + - type: textarea attributes: label: Additional information diff --git a/.github/workflows/docker-cleanup.yml b/.github/workflows/docker-cleanup.yml index fd404e84ef..4811579f74 100644 --- a/.github/workflows/docker-cleanup.yml +++ b/.github/workflows/docker-cleanup.yml @@ -35,7 +35,7 @@ jobs: steps: - name: Clean temporary images if: "${{ env.TOKEN != '' }}" - uses: stumpylog/image-cleaner-action/ephemeral@v0.5.0 + uses: stumpylog/image-cleaner-action/ephemeral@v0.6.0 with: token: "${{ env.TOKEN }}" owner: "immich-app" @@ -64,7 +64,7 @@ jobs: steps: - name: Clean untagged images if: "${{ env.TOKEN != '' }}" - uses: stumpylog/image-cleaner-action/untagged@v0.5.0 + uses: stumpylog/image-cleaner-action/untagged@v0.6.0 with: token: "${{ env.TOKEN }}" owner: "immich-app" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6a5df111d7..d74c961393 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,19 +10,6 @@ concurrency: cancel-in-progress: true jobs: - server-e2e-jobs: - name: Server (e2e-jobs) - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - submodules: 'recursive' - - - name: Run e2e tests - run: make server-e2e-jobs - doc-tests: name: Docs runs-on: ubuntu-latest diff --git a/Makefile b/Makefile index 55875e732b..ede70237fe 100644 --- a/Makefile +++ b/Makefile @@ -16,9 +16,6 @@ stage: pull-stage: docker compose -f ./docker/docker-compose.staging.yml pull -server-e2e-jobs: - docker compose -f ./server/e2e/docker-compose.server-e2e.yml up --renew-anon-volumes --abort-on-container-exit --exit-code-from immich-server --remove-orphans --build - .PHONY: e2e e2e: docker compose -f ./e2e/docker-compose.yml up --build -V --remove-orphans diff --git a/cli/package-lock.json b/cli/package-lock.json index 8f7b8f9c99..11154fec52 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -54,8 +54,8 @@ "@oazapfts/runtime": "^1.0.2" }, "devDependencies": { - "@types/node": "^20.12.4", - "typescript": "^5.4.4" + "@types/node": "^20.12.7", + "typescript": "^5.4.5" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -1193,12 +1193,6 @@ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true - }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -1230,9 +1224,9 @@ } }, "node_modules/@types/node": { - "version": "20.12.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.4.tgz", - "integrity": "sha512-E+Fa9z3wSQpzgYQdYmme5X3OTuejnnTx88A6p6vkkJosR3KBz+HpE3kqNm98VE6cfLFcISx7zW7MsJkH6KwbTw==", + "version": "20.12.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", + "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -1251,22 +1245,22 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.5.0.tgz", - "integrity": "sha512-HpqNTH8Du34nLxbKgVMGljZMG0rJd2O9ecvr2QLYp+7512ty1j42KnsFwspPXg1Vh8an9YImf6CokUBltisZFQ==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.6.0.tgz", + "integrity": "sha512-gKmTNwZnblUdnTIJu3e9kmeRRzV2j1a/LUO27KNNAnIC5zjy1aSvXSRp4rVNlmAoHlQ7HzX42NbKpcSr4jF80A==", "dev": true, "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "7.5.0", - "@typescript-eslint/type-utils": "7.5.0", - "@typescript-eslint/utils": "7.5.0", - "@typescript-eslint/visitor-keys": "7.5.0", + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.6.0", + "@typescript-eslint/type-utils": "7.6.0", + "@typescript-eslint/utils": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0", "debug": "^4.3.4", "graphemer": "^1.4.0", - "ignore": "^5.2.4", + "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -1286,15 +1280,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.5.0.tgz", - "integrity": "sha512-cj+XGhNujfD2/wzR1tabNsidnYRaFfEkcULdcIyVBYcXjBvBKOes+mpMBP7hMpOyk+gBcfXsrg4NBGAStQyxjQ==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.6.0.tgz", + "integrity": "sha512-usPMPHcwX3ZoPWnBnhhorc14NJw9J4HpSXQX4urF2TPKG0au0XhJoZyX62fmvdHONUkmyUe74Hzm1//XA+BoYg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "7.5.0", - "@typescript-eslint/types": "7.5.0", - "@typescript-eslint/typescript-estree": "7.5.0", - "@typescript-eslint/visitor-keys": "7.5.0", + "@typescript-eslint/scope-manager": "7.6.0", + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/typescript-estree": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0", "debug": "^4.3.4" }, "engines": { @@ -1314,13 +1308,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.5.0.tgz", - "integrity": "sha512-Z1r7uJY0MDeUlql9XJ6kRVgk/sP11sr3HKXn268HZyqL7i4cEfrdFuSSY/0tUqT37l5zT0tJOsuDP16kio85iA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.6.0.tgz", + "integrity": "sha512-ngttyfExA5PsHSx0rdFgnADMYQi+Zkeiv4/ZxGYUWd0nLs63Ha0ksmp8VMxAIC0wtCFxMos7Lt3PszJssG/E6w==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.5.0", - "@typescript-eslint/visitor-keys": "7.5.0" + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -1331,15 +1325,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.5.0.tgz", - "integrity": "sha512-A021Rj33+G8mx2Dqh0nMO9GyjjIBK3MqgVgZ2qlKf6CJy51wY/lkkFqq3TqqnH34XyAHUkq27IjlUkWlQRpLHw==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.6.0.tgz", + "integrity": "sha512-NxAfqAPNLG6LTmy7uZgpK8KcuiS2NZD/HlThPXQRGwz6u7MDBWRVliEEl1Gj6U7++kVJTpehkhZzCJLMK66Scw==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "7.5.0", - "@typescript-eslint/utils": "7.5.0", + "@typescript-eslint/typescript-estree": "7.6.0", + "@typescript-eslint/utils": "7.6.0", "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" + "ts-api-utils": "^1.3.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -1358,9 +1352,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.5.0.tgz", - "integrity": "sha512-tv5B4IHeAdhR7uS4+bf8Ov3k793VEVHd45viRRkehIUZxm0WF82VPiLgHzA/Xl4TGPg1ZD49vfxBKFPecD5/mg==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.6.0.tgz", + "integrity": "sha512-h02rYQn8J+MureCvHVVzhl69/GAfQGPQZmOMjG1KfCl7o3HtMSlPaPUAPu6lLctXI5ySRGIYk94clD/AUMCUgQ==", "dev": true, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -1371,19 +1365,19 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.5.0.tgz", - "integrity": "sha512-YklQQfe0Rv2PZEueLTUffiQGKQneiIEKKnfIqPIOxgM9lKSZFCjT5Ad4VqRKj/U4+kQE3fa8YQpskViL7WjdPQ==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.6.0.tgz", + "integrity": "sha512-+7Y/GP9VuYibecrCQWSKgl3GvUM5cILRttpWtnAu8GNL9j11e4tbuGZmZjJ8ejnKYyBRb2ddGQ3rEFCq3QjMJw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.5.0", - "@typescript-eslint/visitor-keys": "7.5.0", + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0", "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" + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -1399,18 +1393,18 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.5.0.tgz", - "integrity": "sha512-3vZl9u0R+/FLQcpy2EHyRGNqAS/ofJ3Ji8aebilfJe+fobK8+LbIFmrHciLVDxjDoONmufDcnVSF38KwMEOjzw==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.6.0.tgz", + "integrity": "sha512-x54gaSsRRI+Nwz59TXpCsr6harB98qjXYzsRxGqvA5Ue3kQH+FxS7FYU81g/omn22ML2pZJkisy6Q+ElK8pBCA==", "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": "7.5.0", - "@typescript-eslint/types": "7.5.0", - "@typescript-eslint/typescript-estree": "7.5.0", - "semver": "^7.5.4" + "@types/json-schema": "^7.0.15", + "@types/semver": "^7.5.8", + "@typescript-eslint/scope-manager": "7.6.0", + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/typescript-estree": "7.6.0", + "semver": "^7.6.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -1424,13 +1418,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.5.0.tgz", - "integrity": "sha512-mcuHM/QircmA6O7fy6nn2w/3ditQkj+SgtOc8DW3uQ10Yfj42amm2i+6F2K4YAOPNNTmE6iM1ynM6lrSwdendA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.6.0.tgz", + "integrity": "sha512-4eLB7t+LlNUmXzfOu1VAIAdkjbu5xNSerURS9X/S5TUKWFRpXRQZbmtPqgKmYx8bj3J0irtQXSiWAOY82v+cgw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.5.0", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "7.6.0", + "eslint-visitor-keys": "^3.4.3" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -1447,9 +1441,9 @@ "dev": true }, "node_modules/@vitest/coverage-v8": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.4.0.tgz", - "integrity": "sha512-4hDGyH1SvKpgZnIByr9LhGgCEuF9DKM34IBLCC/fVfy24Z3+PZ+Ii9hsVBsHvY1umM1aGPEjceRkzxCfcQ10wg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.5.0.tgz", + "integrity": "sha512-1igVwlcqw1QUMdfcMlzzY4coikSIBN944pkueGi0pawrX5I5Z+9hxdTR+w3Sg6Q3eZhvdMAs8ZaF9JuTG1uYOQ==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.1", @@ -1464,24 +1458,23 @@ "picocolors": "^1.0.0", "std-env": "^3.5.0", "strip-literal": "^2.0.0", - "test-exclude": "^6.0.0", - "v8-to-istanbul": "^9.2.0" + "test-exclude": "^6.0.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "1.4.0" + "vitest": "1.5.0" } }, "node_modules/@vitest/expect": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.4.0.tgz", - "integrity": "sha512-Jths0sWCJZ8BxjKe+p+eKsoqev1/T8lYcrjavEaz8auEJ4jAVY0GwW3JKmdVU4mmNPLPHixh4GNXP7GFtAiDHA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.5.0.tgz", + "integrity": "sha512-0pzuCI6KYi2SIC3LQezmxujU9RK/vwC1U9R0rLuGlNGcOuDWxqWKu6nUdFsX9tH1WU0SXtAxToOsEjeUn1s3hA==", "dev": true, "dependencies": { - "@vitest/spy": "1.4.0", - "@vitest/utils": "1.4.0", + "@vitest/spy": "1.5.0", + "@vitest/utils": "1.5.0", "chai": "^4.3.10" }, "funding": { @@ -1489,12 +1482,12 @@ } }, "node_modules/@vitest/runner": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.4.0.tgz", - "integrity": "sha512-EDYVSmesqlQ4RD2VvWo3hQgTJ7ZrFQ2VSJdfiJiArkCerDAGeyF1i6dHkmySqk573jLp6d/cfqCN+7wUB5tLgg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.5.0.tgz", + "integrity": "sha512-7HWwdxXP5yDoe7DTpbif9l6ZmDwCzcSIK38kTSIt6CFEpMjX4EpCgT6wUmS0xTXqMI6E/ONmfgRKmaujpabjZQ==", "dev": true, "dependencies": { - "@vitest/utils": "1.4.0", + "@vitest/utils": "1.5.0", "p-limit": "^5.0.0", "pathe": "^1.1.1" }, @@ -1530,9 +1523,9 @@ } }, "node_modules/@vitest/snapshot": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.4.0.tgz", - "integrity": "sha512-saAFnt5pPIA5qDGxOHxJ/XxhMFKkUSBJmVt5VgDsAqPTX6JP326r5C/c9UuCMPoXNzuudTPsYDZCoJ5ilpqG2A==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.5.0.tgz", + "integrity": "sha512-qpv3fSEuNrhAO3FpH6YYRdaECnnRjg9VxbhdtPwPRnzSfHVXnNzzrpX4cJxqiwgRMo7uRMWDFBlsBq4Cr+rO3A==", "dev": true, "dependencies": { "magic-string": "^0.30.5", @@ -1544,9 +1537,9 @@ } }, "node_modules/@vitest/spy": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.4.0.tgz", - "integrity": "sha512-Ywau/Qs1DzM/8Uc+yA77CwSegizMlcgTJuYGAi0jujOteJOUf1ujunHThYo243KG9nAyWT3L9ifPYZ5+As/+6Q==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.5.0.tgz", + "integrity": "sha512-vu6vi6ew5N5MMHJjD5PoakMRKYdmIrNJmyfkhRpQt5d9Ewhw9nZ5Aqynbi3N61bvk9UvZ5UysMT6ayIrZ8GA9w==", "dev": true, "dependencies": { "tinyspy": "^2.2.0" @@ -1556,9 +1549,9 @@ } }, "node_modules/@vitest/utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.4.0.tgz", - "integrity": "sha512-mx3Yd1/6e2Vt/PUC98DcqTirtfxUyAZ32uK82r8rZzbtBeBo+nqgnjx/LvqQdWsrvNtm14VmurNgcf4nqY5gJg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.5.0.tgz", + "integrity": "sha512-BDU0GNL8MWkRkSRdNFvCUCAVOeHaUlVJ9Tx0TYBZyXaaOTmGtUFObzchCivIBrIwKzvZA7A9sCejVhXM2aY98A==", "dev": true, "dependencies": { "diff-sequences": "^29.6.3", @@ -1909,12 +1902,6 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, "node_modules/core-js-compat": { "version": "3.36.0", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.0.tgz", @@ -3139,9 +3126,9 @@ } }, "node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -4258,9 +4245,9 @@ "dev": true }, "node_modules/tinypool": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.2.tgz", - "integrity": "sha512-SUszKYe5wgsxnNOVlBYO6IC+8VGWdVGZWAqUxp3UErNBtptZvWbwyUOyzNL59zigz2rCA92QiL3wvG+JDSdJdQ==", + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", + "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", "dev": true, "engines": { "node": ">=14.0.0" @@ -4368,9 +4355,9 @@ } }, "node_modules/typescript": { - "version": "5.4.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.4.tgz", - "integrity": "sha512-dGE2Vv8cpVvw28v8HCPqyb08EzbBURxDpuhJvTrusShUfGnhHBafDsLdS1EhhxyL6BJQE+2cT3dDPAv+MQ6oLw==", + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -4431,20 +4418,6 @@ "punycode": "^2.1.0" } }, - "node_modules/v8-to-istanbul": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", - "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -4511,9 +4484,9 @@ } }, "node_modules/vite-node": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.4.0.tgz", - "integrity": "sha512-VZDAseqjrHgNd4Kh8icYHWzTKSCZMhia7GyHfhtzLW33fZlG9SwsB6CEhgyVOWkJfJ2pFLrp/Gj1FSfAiqH9Lw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.5.0.tgz", + "integrity": "sha512-tV8h6gMj6vPzVCa7l+VGq9lwoJjW8Y79vst8QZZGiuRAfijU+EEWuc0kFpmndQrWhMMhet1jdSF+40KSZUqIIw==", "dev": true, "dependencies": { "cac": "^6.7.14", @@ -4552,16 +4525,16 @@ } }, "node_modules/vitest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.4.0.tgz", - "integrity": "sha512-gujzn0g7fmwf83/WzrDTnncZt2UiXP41mHuFYFrdwaLRVQ6JYQEiME2IfEjU3vcFL3VKa75XhI3lFgn+hfVsQw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.5.0.tgz", + "integrity": "sha512-d8UKgR0m2kjdxDWX6911uwxout6GHS0XaGH1cksSIVVG8kRlE7G7aBw7myKQCvDI5dT4j7ZMa+l706BIORMDLw==", "dev": true, "dependencies": { - "@vitest/expect": "1.4.0", - "@vitest/runner": "1.4.0", - "@vitest/snapshot": "1.4.0", - "@vitest/spy": "1.4.0", - "@vitest/utils": "1.4.0", + "@vitest/expect": "1.5.0", + "@vitest/runner": "1.5.0", + "@vitest/snapshot": "1.5.0", + "@vitest/spy": "1.5.0", + "@vitest/utils": "1.5.0", "acorn-walk": "^8.3.2", "chai": "^4.3.10", "debug": "^4.3.4", @@ -4573,9 +4546,9 @@ "std-env": "^3.5.0", "strip-literal": "^2.0.0", "tinybench": "^2.5.1", - "tinypool": "^0.8.2", + "tinypool": "^0.8.3", "vite": "^5.0.0", - "vite-node": "1.4.0", + "vite-node": "1.5.0", "why-is-node-running": "^2.2.2" }, "bin": { @@ -4590,8 +4563,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "1.4.0", - "@vitest/ui": "1.4.0", + "@vitest/browser": "1.5.0", + "@vitest/ui": "1.5.0", "happy-dom": "*", "jsdom": "*" }, diff --git a/docker/docker-compose.prod.yml b/docker/docker-compose.prod.yml index 4884a088a8..7f1f0f1a41 100644 --- a/docker/docker-compose.prod.yml +++ b/docker/docker-compose.prod.yml @@ -88,7 +88,7 @@ services: command: ['./run.sh', '-disable-reporting'] ports: - 3000:3000 - image: grafana/grafana:10.4.1-ubuntu@sha256:65e0e7d0f0b001cb0478bce5093bff917677dc308dd27a0aa4b3ac38e4fd877c + image: grafana/grafana:10.4.2-ubuntu@sha256:4f55071b556fb03f12b41423c98a185ed6695ed9ff2558e35805f0dd765fd958 volumes: - grafana-data:/var/lib/grafana diff --git a/docs/README.md b/docs/README.md index aaba2fa1e1..0c6c2c27be 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,6 @@ # Website -This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. +This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator. ### Installation diff --git a/docs/docs/FAQ.mdx b/docs/docs/FAQ.mdx index 452f1d91eb..033863bf51 100644 --- a/docs/docs/FAQ.mdx +++ b/docs/docs/FAQ.mdx @@ -6,7 +6,7 @@ The admin password can be reset by running the [reset-admin-password](/docs/administration/server-commands.md) command on the immich-server. -### How can I see list of all users in Immich? +### How can I see a list of all users in Immich? You can see the list of all users by running [list-users](/docs/administration/server-commands.md) Command on the Immich-server. @@ -24,14 +24,14 @@ You can see the list of all users by running [list-users](/docs/administration/s ### I cannot log into the application after an update. What can I do? -First, verify that the mobile app and server are both running the same version (major and minor). +Verify that the mobile app and server are both running the same version (major and minor). :::note -App store updates sometimes take longer because the stores (Google play store and Apple app store) -need to approve the update first which may take some time. +App store updates sometimes take longer because the stores (Google Play Store and Apple App Store) +need to approve the update first, and it can take some time. ::: -If you still cannot login to the app, try the following: +If you still cannot log in to the app, try the following: - Check the mobile logs - Make sure login credentials are correct by logging in on the web app @@ -40,6 +40,11 @@ If you still cannot login to the app, try the following: ## Assets +### Does Immich change the file? + +No, Immich does not touch the original file under any circumstances, +all edited metadata are saved in the companion sidecar file and the database. + ### Can I add my existing photo library? Yes, with an [External Library](/docs/features/libraries.md). @@ -50,11 +55,11 @@ Template changes will only apply to _new_ assets. To retroactively apply the tem ### Why are only photos and not videos being uploaded to Immich? -This often happens when using a reverse proxy (such as nginx or Cloudflare tunnel) in front of Immich. Make sure to set your reverse proxy to allow large `POST` requests. In `nginx`, set `client_max_body_size 50000M;` or similar. Also check the disk space of your reverse proxy, in some cases proxies cache requests to disk before passing them on, and if disk space runs out the request fails. +This often happens when using a reverse proxy (such as Nginx or Cloudflare tunnel) in front of Immich. Make sure to set your reverse proxy to allow large `POST` requests. In `nginx`, set `client_max_body_size 50000M;` or similar. Also, check the disk space of your reverse proxy. In some cases, proxies cache requests to disk before passing them on, and if disk space runs out, the request fails. ### Why are some photos stored in the file system with the wrong date? -There are a few different scenarios that can lead to this situation. The solution is to run the storage migration job again. The job is only _automatically_ run once per asset, after upload. If metadata extraction originally failed, the jobs were cleared/cancelled, etc. the job may not have run automatically the first time. +There are a few different scenarios that can lead to this situation. The solution is to rerun the storage migration job. The job is only automatically run once per asset after upload. If metadata extraction originally failed, the jobs were cleared/canceled, etc., the job may not have run automatically the first time. ### How can I hide photos from the timeline? @@ -68,23 +73,27 @@ See [Backup and Restore](/docs/administration/backup-and-restore.md). No, it currently does not. There is an [open feature request on GitHub](https://github.com/immich-app/immich/discussions/4348). -### Does Immich support filtering of NSFW images? +### Does Immich support the filtering of NSFW images? No, it currently does not. There is an [open feature request on Github](https://github.com/immich-app/immich/discussions/2451). ### Why are there so many thumbnail generation jobs? -There are three thubmanil jobs for each asset: +There are three thumbnail jobs for each asset: - Blurred (thumbhash) -- Small (webp) -- Large (jpeg) +- Preview (Webp) +- Thumbnail (Jpeg) Also, there are additional jobs for person (face) thumbnails. +### Why do files from WhatsApp not appear with the correct date? + +Files sent on WhatsApp are saved without metadata on the file. Therefore, Immich has no way of knowing the original date of the file when files are uploaded from WhatsApp, not the order of arrival on the device. [See #3527](https://github.com/immich-app/immich/issues/3527). + ### What happens if an asset exists in more than one account? -There are no requirements for assets to be unique across users. If multiple users upload the same image they are processed as if they were distinct assets and jobs run and thumbnails are generated accordingly. +There are no requirements for assets to be unique across users. If multiple users upload the same image, it is processed as if it were a distinct asset, and jobs run and thumbnails are generated accordingly. ### Why do HDR videos appear pale in Immich player but look normal after download? @@ -96,40 +105,36 @@ Immich always keeps your original files. Alongside that, it generates a transcod ### How can I delete transcoded videos without deleting the original? -The transcoded version of an asset can be deleted by setting a transcode policy that makes it unnecessary, then running a transcoding job for that asset. This can be done on a per-asset basis by starting a transcoding job for a single asset with the _Refresh encoded videos_ button in the asset viewer options, or for all assets by running transcoding jobs for all assets from the administration page. +The transcoded version of an asset can be deleted by setting a transcode policy that makes it unnecessary and then running a transcoding job for that asset. This can be done on a per-asset basis by starting a transcoding job for a single asset with the _Refresh encoded videos_ button in the asset viewer options or for all assets by running transcoding jobs for all assets from the administration page. To update the transcode policy, navigate to Administration > Video Transcoding Settings > Transcoding Policy and select a policy from the drop-down. This policy will determine whether an existing transcode will be deleted or overwritten in the transcoding job. If a video should be transcoded according to this policy, an existing transcode is overwritten. If not, then it is deleted. :::note -For example, say you have existing transcodes with the policy "Videos higher than normal resolution or not in the desired format" and switch to a narrower policy: "Videos not in the desired format". If an asset was only transcoded due to its resolution, then running a transcoding job for it will now delete the existing transcode. This is because resolution is no longer part of the transcode policy and the transcode is unnecessary as a result. Likewise, if you set the policy to "Don't transcode any videos" and run transcoding jobs for all assets, this will delete all existing transcodes as they are all unnecessary. +For example, say you have existing transcodes with the policy "Videos higher than normal resolution or not in the desired format" and switch to a narrower policy: "Videos not in the desired format." If an asset was only transcoded due to its resolution, running a transcoding job for it will delete the existing transcode. This is because resolution is no longer part of the transcode policy and the transcode is unnecessary. Likewise, if you set the policy to "Don't transcode any videos" and run transcoding jobs for all assets, this will delete all existing transcodes as they are unnecessary. ::: ### Is it possible to compress images during backup? -No. Our golden rule is that the original assets should always be untouched, so we don't think this feature is a good fit for Immich. +No. Our design principle is that the original assets should always be untouched. ### How can I move all data (photos, persons, albums) from one user to another? -This is not officially supported, but can be accomplished with some database updates. You can do this on the command line (in the PostgreSQL container using the `psql` command), or you can add for example an [Adminer](https://www.adminer.org/) container to the `docker-compose.yml` file, so that you can use a web-interface. - -:::warning -This is an advanced operation. If you can't do it with the steps described here, this is not for you. -::: +This is not officially supported but can be accomplished with some database updates. You can do this on the command line (in the PostgreSQL container using the `psql` command), or you can add, for example, an [Adminer](https://www.adminer.org/) container to the `docker-compose.yml` file so that you can use a web interface.
Steps 1. **MAKE A BACKUP** - See [backup and restore](/docs/administration/backup-and-restore.md). -2. Find the id of both the 'source' and the 'destination' user (it's the id column in the users table) +2. Find the ID of both the 'source' and the 'destination' user (it's the id column in the `users` table) 3. Three tables need to be updated: ```sql -// reassign albums +// Reassign albums UPDATE albums SET "ownerId" = '' WHERE "ownerId" = ''; -// reassign people +// Reassign people UPDATE person SET "ownerId" = '' WHERE "ownerId" = ''; // reassign assets @@ -159,7 +164,7 @@ No, not yet. For updates on this planned feature, follow the [GitHub discussion] ### Can I add an external library while keeping the existing album structure? -We haven't put in an official mechanism to create albums from external libraries at the moment, but there are some [workarounds from the community](https://github.com/immich-app/immich/discussions/4279) to help you achieve that. +We haven't implemented an official mechanism for creating albums from external libraries, but there are some [workarounds from the community](https://github.com/immich-app/immich/discussions/4279) to help you achieve that. ### What happens to duplicates in external libraries? @@ -171,7 +176,7 @@ Duplicate checking only exists for upload libraries, using the file hash. Furthe ### How does smart search work? -Immich uses CLIP models, for more information about CLIP and its capabilities read about it [here](https://openai.com/research/clip). +Immich uses CLIP models. For more information about CLIP and its capabilities, read about it [here](https://openai.com/research/clip). ### How does facial recognition work? @@ -189,33 +194,31 @@ However, disabling all jobs will not disable the machine learning service itself ### I'm getting errors about models being corrupt or failing to download. What do I do? -You can delete the model cache volume, which is where models are downloaded to. This will give the service a clean environment to download the model again. If models are failing to download entirely, you can manually download them from [Huggingface](https://huggingface.co/immich-app) and place them in the cache folder. - -### Why did Immich decide to remove object detection? - -The feature added keywords to images for metadata search, but wasn't used for smart search. Smart search made it unnecessary as it isn't limited to exact keywords. Combined with it causing crashes on some devices, using many dependencies and causing user confusion as to how search worked, it was better to remove the job altogether. -For more info see [here](https://github.com/immich-app/immich/pull/5903) +You can delete the model cache volume, where models are downloaded. This will give the service a clean environment to download the model again. If models are failing to download entirely, you can manually download them from [Huggingface][huggingface] and place them in the cache folder. ### Can I use a custom CLIP model? -No, this is not supported. Only models listed in the [Huggingface](https://huggingface.co/immich-app) page are compatible. Feel free to make a feature request if there's a model not listed here that you think should be added. +No, this is not supported. Only models listed in the [Huggingface][huggingface] page are compatible. Feel free to make a feature request if there's a model not listed here that you think should be added. ### I want to be able to search in other languages besides English. How can I do that? You can change to a multilingual model listed [here](https://huggingface.co/collections/immich-app/multilingual-clip-654eb08c2382f591eeb8c2a7) by going to Administration > Machine Learning Settings > Smart Search and replacing the name of the model. Be sure to re-run Smart Search on all assets after this change. You can then search in over 100 languages. :::note -Feel free to make a feature request if there's a model you want to use that isn't in [Immich Huggingface list](https://huggingface.co/immich-app). +Feel free to make a feature request if there's a model you want to use that isn't in [Immich Huggingface list][huggingface]. ::: -### Does Immich support Facial Recognition for videos ? +### Does Immich support Facial Recognition for videos? -Immich's machine learning feature operate on the generated thumbnail. If a face is visible in the video's thumbnail it will be picked up by facial recognition. +Immich's machine learning feature operates on the generated thumbnail. If a face is visible in the video's thumbnail it will be picked up by facial recognition. Scanning the entire video for faces may be implemented in the future. ### Does Immich have animal recognition? No. +:::tip +You can use [Smart Search](/docs/features/smart-search.md) for this to some extent. For example, if you have a Golden Retriever and a Chihuahua, type these words in the smart search and watch the results. +::: ### I'm getting a lot of "faces" that aren't faces, what can I do? @@ -248,8 +251,8 @@ The initial backup is the most intensive due to the number of jobs running. The - Lower the job concurrency for these jobs to 1. - Under Settings > Transcoding Settings > Threads, set the number of threads to a low number like 1 or 2. - Under Settings > Machine Learning Settings > Facial Recognition > Model Name, you can change the facial recognition model to `buffalo_s` instead of `buffalo_l`. The former is a smaller and faster model, albeit not as good. - - You _must_ re-run the Face Detection job for all images after this for facial recognition on new images to work properly. -- If these changes are not enough, see [below](/docs/FAQ#how-can-i-disable-machine-learning) for how you can disable machine learning. +- For facial recognition on new images to work properly, You must re-run the Face Detection job for all images after this. +- If these changes are not enough, see [below](/docs/FAQ#how-can-i-disable-machine-learning) for instructions on how to disable machine learning. ### Can I limit the amount of CPU and RAM usage? @@ -280,13 +283,17 @@ On a normal machine, 2 or 3 concurrent jobs can probably max the CPU. Beyond thi Do not exaggerate with the amount of jobs because you're probably thoroughly overloading the server. -More detail can be found [here](https://discord.com/channels/979116623879368755/994044917355663450/1174711719994605708) +More details can be found [here](https://discord.com/channels/979116623879368755/994044917355663450/1174711719994605708) ::: ### Why is Immich using so much of my CPU? -When a large amount of assets are uploaded to Immich it makes sense that the CPU and RAM will be heavily used due to machine learning work and creating image thumbnails. -Once this process completes, the percentage of CPU usage will drop to around 3-5% usage +When a large number of assets are uploaded to Immich, it makes sense that the CPU and RAM will be heavily used for machine learning work and creating image thumbnails. +Once this process is completed, the percentage of CPU usage will drop to around 3-5% usage + +### My server shows Server Status Offline | Version Unknown what can I do? + +You need to enable Websocket on your reverse proxy. --- @@ -311,7 +318,7 @@ For a further hardened system, you can add the following block to every containe ```yaml security_opt: - # Prevent escalation of privileges after container is started + # Prevent escalation of privileges after the container is started - no-new-privileges:true cap_drop: # Prevent access to raw network traffic @@ -322,7 +329,7 @@ cap_drop: Data for Immich comes in two forms: -1. **Metadata** stored in a postgres database, persisted via the `pg_data` volume +1. **Metadata** stored in a Postgres database, persisted via the `pg_data` volume 2. **Files** (originals, thumbs, profile, etc.), stored in the `UPLOAD_LOCATION` folder, more [info](/docs/administration/backup-and-restore#asset-types-and-storage-locations). To remove the **Metadata** you can stop Immich and delete the volume. @@ -359,3 +366,5 @@ If your version of Immich is below 1.92.0 and the crash occurs after logs about ### Why does Immich log migration errors on startup? Sometimes Immich logs errors such as "duplicate key value violates unique constraint" or "column (...) of relation (...) already exists". Because of Immich's container structure, this error can be seen when both immich and immich-microservices start at the same time and attempt to migrate or create the database structure. Since the database migration is run sequentially and inside of transactions, this error message does not cause harm to your installation of Immich and can safely be ignored. If needed, you can manually restart Immich by running `docker restart immich immich-microservices`. + +[huggingface]: https://huggingface.co/immich-app diff --git a/docs/docs/administration/postgres-standalone.md b/docs/docs/administration/postgres-standalone.md index be8d883a1c..57cb75a789 100644 --- a/docs/docs/administration/postgres-standalone.md +++ b/docs/docs/administration/postgres-standalone.md @@ -10,9 +10,11 @@ Running with a pre-existing Postgres server can unlock powerful administrative f ## Prerequisites -You must install pgvecto.rs using their [instructions](https://docs.pgvecto.rs/getting-started/installation.html). After installation, add `shared_preload_libraries = 'vectors.so'` to your `postgresql.conf`. If you already have some `shared_preload_libraries` set, you can separate each extension with a comma. For example, `shared_preload_libraries = 'pg_stat_statements, vectors.so'`. +You must install pgvecto.rs into your instance of Postgres using their [instructions][vectors-install]. After installation, add `shared_preload_libraries = 'vectors.so'` to your `postgresql.conf`. If you already have some `shared_preload_libraries` set, you can separate each extension with a comma. For example, `shared_preload_libraries = 'pg_stat_statements, vectors.so'`. :::note +Immich is known to work with Postgres versions 14, 15, and 16. Earlier versions are unsupported. + Make sure the installed version of pgvecto.rs is compatible with your version of Immich. For example, if your Immich version uses the dedicated database image `tensorchord/pgvecto-rs:pg14-v0.2.1`, you must install pgvecto.rs `>= 0.2.1, < 0.3.0`. ::: @@ -30,6 +32,10 @@ DB_URL='postgresql://immichdbusername:immichdbpassword@postgreshost:postgresport # DB_URL='postgresql://immichdbusername:immichdbpassword@postgreshost:postgresport/immichdatabasename?sslmode=require&sslmode=no-verify' ``` +:::info +When `DB_URL` is defined, the other database (`DB_*`) variables are ignored, with the exception of `DB_VECTOR_EXTENSION`. +::: + ## With superuser permission Typically Immich expects superuser permission in the database, which you can grant by running `ALTER USER WITH SUPERUSER;` at the `psql` console. If you prefer not to grant superuser permissions, follow the instructions in the next section. @@ -50,8 +56,7 @@ ALTER DATABASE OWNER TO ; CREATE EXTENSION vectors; CREATE EXTENSION earthdistance CASCADE; ALTER DATABASE SET search_path TO "$user", public, vectors; -GRANT USAGE ON SCHEMA vectors TO ; -ALTER DEFAULT PRIVILEGES IN SCHEMA vectors GRANT SELECT ON TABLES TO ; +ALTER SCHEMA vectors OWNER TO ; COMMIT; ``` @@ -64,3 +69,5 @@ When installing a new version of pgvecto.rs, you will need to manually update th #### Permission denied for view If you get the error `driverError: error: permission denied for view pg_vector_index_stat`, you can fix this by connecting to the Immich database and running `GRANT SELECT ON TABLE pg_vector_index_stat TO ;`. + +[vectors-install]: https://docs.pgvecto.rs/getting-started/installation.html diff --git a/docs/docs/administration/system-settings.md b/docs/docs/administration/system-settings.md index 21eeaaee89..e90f49fed3 100644 --- a/docs/docs/administration/system-settings.md +++ b/docs/docs/administration/system-settings.md @@ -37,9 +37,7 @@ You can set the scanning interval using the preset or cron format. For more info ## Logging -By default logs are set to record at the log level, the network administrator can choose a deeper or lower level of logs according to his decision or according to the needs required by the Immich support team. - -Here you can [learn about the different error levels](https://sematext.com/blog/logging-levels/). +The default Immich log level is `Log` (commonly known as `Info`). The Immich administrator can choose a higher or lower log level according to personal preference or as requested by the Immich support team. ## Machine Learning Settings diff --git a/docs/docs/community-guides.mdx b/docs/docs/community-guides.mdx new file mode 100644 index 0000000000..505ec93e77 --- /dev/null +++ b/docs/docs/community-guides.mdx @@ -0,0 +1,12 @@ +# Community Guides + +This page lists community guides that are written around Immich, but not officially supported by the development team. + +:::warning +This list comes with no guarantees about security, performance, reliability, or accuracy. Use at your own risk. +::: + +import CommunityGuides from '../src/components/community-guides.tsx'; +import React from 'react'; + + diff --git a/docs/docs/developer/pr-checklist.md b/docs/docs/developer/pr-checklist.md index 32d9b03507..372dd2b1eb 100644 --- a/docs/docs/developer/pr-checklist.md +++ b/docs/docs/developer/pr-checklist.md @@ -9,7 +9,7 @@ When contributing code through a pull request, please check the following: - [ ] `npm run check:svelte` (Type checking via SvelteKit) - [ ] `npm test` (unit tests) -:::tip +:::tip AIO Run all web checks with `npm run check:all` ::: @@ -20,10 +20,14 @@ Run all web checks with `npm run check:all` - [ ] `npm run check` (Type checking via `tsc`) - [ ] `npm test` (unit tests) -:::tip +:::tip AIO Run all server checks with `npm run check:all` ::: +:::info Auto Fix +You can use `npm run __:fix` to potentially correct some issues automatically for `npm run format` and `lint`. +::: + ## OpenAPI The OpenAPI client libraries need to be regenerated whenever there are changes to the `immich-openapi-specs.json` file. Note that you should not modify this file directly as it is auto-generated. See [OpenAPI](/docs/developer/open-api.md) for more details. diff --git a/docs/docs/developer/setup.md b/docs/docs/developer/setup.md index 6b1177fbc9..b80f11300c 100644 --- a/docs/docs/developer/setup.md +++ b/docs/docs/developer/setup.md @@ -66,7 +66,7 @@ The mobile app `(/mobile)` will required Flutter toolchain 3.13.x to be installe Please refer to the [Flutter's official documentation](https://flutter.dev/docs/get-started/install) for more information on setting up the toolchain on your machine. -The mobile app asks you what backend to connect to. You can utilize the demo backend (https://demo.immich.app/) if you don't need to change server code or can run the server yourself per the instructions above. +The mobile app asks you what backend to connect to. You can utilize the demo backend (https://demo.immich.app/) if you don't need to change server code or upload photos. Alternatively, you can run the server yourself per the instructions above. ## IDE setup diff --git a/docs/docs/developer/testing.md b/docs/docs/developer/testing.md index de080fbb52..fecb58f592 100644 --- a/docs/docs/developer/testing.md +++ b/docs/docs/developer/testing.md @@ -8,15 +8,24 @@ Unit are run by calling `npm run test` from the `server` directory. ### End to end tests -The backend has two end-to-end test suites that can be called with the following two commands from the project root directory: +The e2e tests can be run by first starting up a test production environment via: -- `make server-e2e-api` -- `make server-e2e-jobs` +```bash +make e2e +``` -#### API (e2e) +Once the test environment is running, the e2e tests can be run via: -The API e2e tests spin up a test database and execute http requests against the server, validating the expected response codes and functionality for API endpoints. +```bash +cd e2e/ +npm test +``` -#### Jobs (e2e) +The tests check various things including: -The Jobs e2e tests spin up a docker test environment where thumbnail generation, library scanning, and other _job_ workflows are validated. +- Authentication and authorization +- Query param, body, and url validation +- Response codes +- Thumbnail generation +- Metadata extraction +- Library scanning diff --git a/docs/docs/features/img/library-custom-scan-interval.png b/docs/docs/features/img/library-custom-scan-interval.png new file mode 100644 index 0000000000..3d2e8d3dbd Binary files /dev/null and b/docs/docs/features/img/library-custom-scan-interval.png differ diff --git a/docs/docs/features/libraries.md b/docs/docs/features/libraries.md index 28c68ced46..628bb880c1 100644 --- a/docs/docs/features/libraries.md +++ b/docs/docs/features/libraries.md @@ -161,7 +161,7 @@ The christmas trip library will now be scanned in the background. In the meantim - Click on Create External Library. -:::info Note +:::note If you get an error here, please rename the other external library to something else. This is a bug that will be fixed in a future release. ::: @@ -175,3 +175,14 @@ If you get an error here, please rename the other external library to something - Click on Scan Library Files Within seconds, the assets from the old-pics and videos folders should show up in the main timeline. + +### Set Custom Scan Interval + +:::note +Only an admin can do this. +::: + +You can define a custom interval for the trigger external library rescan under Administration -> Settings -> Library. +You can set the scanning interval using the preset or cron format. For more information you can refer to [Crontab Guru](https://crontab.guru/). + + diff --git a/docs/docs/features/ml-hardware-acceleration.md b/docs/docs/features/ml-hardware-acceleration.md index 1e646a0e7e..4b6012e71c 100644 --- a/docs/docs/features/ml-hardware-acceleration.md +++ b/docs/docs/features/ml-hardware-acceleration.md @@ -10,7 +10,7 @@ You do not need to redo any machine learning jobs after enabling hardware accele ## Supported Backends - ARM NN (Mali) -- CUDA (NVIDIA) +- CUDA (NVIDIA) Note: It is supported with [compute capability](https://developer.nvidia.com/cuda-gpus) 5.2 or higher - OpenVINO (Intel) ## Limitations diff --git a/docs/docs/features/supported-formats.md b/docs/docs/features/supported-formats.md index a2dc56b66a..ce3736db62 100644 --- a/docs/docs/features/supported-formats.md +++ b/docs/docs/features/supported-formats.md @@ -3,7 +3,7 @@ Immich supports a number of image and video formats, the most common of which are outlined here. :::note -For the full list, you can refer to the [Immich source code](https://github.com/immich-app/immich/blob/main/server/src/utils/mime-types.ts). +For the full list, refer to the [Immich source code](https://github.com/immich-app/immich/blob/main/server/src/utils/mime-types.ts). ::: ## Image formats diff --git a/docs/docs/guides/custom-locations.md b/docs/docs/guides/custom-locations.md new file mode 100644 index 0000000000..5898120f26 --- /dev/null +++ b/docs/docs/guides/custom-locations.md @@ -0,0 +1,50 @@ +# Files Custom Locations + +This guide explains storing generated and raw files with docker's volume mount in different locations. + +:::note Backup +It is important to remember to update the backup settings after following the guide to back up the new backup paths if using automatic backup tools. +::: + +In our `.env` file, we will define variables that will help us in the future when we want to move to a more advanced server in the future + +```diff title=".env" +# You can find documentation for all the supported env variables at https://immich.app/docs/install/environment-variables + +# Custom location where your uploaded, thumbnails, and transcoded video files are stored +- {UPLOAD_LOCATION}=./library ++ {UPLOAD_LOCATION}=/custom/location/on/your/system/ ++ {THUMB_LOCATION}=/custom/location/on/your/system/ ++ {ENCODED_VIDEO_LOCATION}=/custom/location/on/your/system/ +... +``` + +After defining the locations for these files, we will edit the `docker-compose.yml` file accordingly and add the new variables to the `immich-server` and `immich-microservices` containers. + +```diff title="docker-compose.yml" +services: + immich-server: + volumes: + - ${UPLOAD_LOCATION}:/usr/src/app/upload ++ - ${THUMB_LOCATION}:/usr/src/app/upload/thumbs ++ - ${ENCODED_VIDEO_LOCATION}:/usr/src/app/upload/encoded-video + - /etc/localtime:/etc/localtime:ro + +... + + immich-microservices: + volumes: + - ${UPLOAD_LOCATION}:/usr/src/app/upload ++ - ${THUMB_LOCATION}:/usr/src/app/upload/thumbs ++ - ${ENCODED_VIDEO_LOCATION}:/usr/src/app/upload/encoded-video + - /etc/localtime:/etc/localtime:ro +``` + +Restart Immich to register the changes. + +``` +docker compose down +docker compose up -d +``` + +Thanks to [Jrasm91](https://github.com/immich-app/immich/discussions/2110#discussioncomment-5477767) for writing the guide. diff --git a/docs/docs/guides/database-gui.md b/docs/docs/guides/database-gui.md index 1d5eedf990..1b0bd7931c 100644 --- a/docs/docs/guides/database-gui.md +++ b/docs/docs/guides/database-gui.md @@ -45,5 +45,5 @@ Open pgAdmin and click "Add New Server". Click on "Save" to connect to the Immich database. :::tip -View [Database Queries](https://immich.app/docs/guides/database-queries/) for common database queries. +View [Database Queries](/docs/guides/database-queries/) for common database queries. ::: diff --git a/docs/docs/guides/database-queries.md b/docs/docs/guides/database-queries.md index 45112849fa..a8748f65f9 100644 --- a/docs/docs/guides/database-queries.md +++ b/docs/docs/guides/database-queries.md @@ -37,15 +37,20 @@ SELECT * FROM "assets" WHERE "checksum" = decode('69de19c87658c4c15d9cacb9967b8e ``` ```sql title="Live photos" -SELECT * FROM "assets" where "livePhotoVideoId" IS NOT NULL; +SELECT * FROM "assets" WHERE "livePhotoVideoId" IS NOT NULL; ``` ```sql title="Without metadata" -SELECT "assets".* FROM "exif" LEFT JOIN "assets" ON "assets"."id" = "exif"."assetId" WHERE "exif"."assetId" IS NULL; +SELECT "assets".* FROM "exif" + LEFT JOIN "assets" ON "assets"."id" = "exif"."assetId" + WHERE "exif"."assetId" IS NULL; ``` ```sql title="size < 100,000 bytes, smallest to largest" -SELECT * FROM "assets" JOIN "exif" ON "assets"."id" = "exif"."assetId" WHERE "exif"."fileSizeInByte" < 100000 ORDER BY "exif"."fileSizeInByte" ASC; +SELECT * FROM "assets" + JOIN "exif" ON "assets"."id" = "exif"."assetId" + WHERE "exif"."fileSizeInByte" < 100000 + ORDER BY "exif"."fileSizeInByte" ASC; ``` ```sql title="Without thumbnails" @@ -62,8 +67,7 @@ SELECT "assets"."type", COUNT(*) FROM "assets" GROUP BY "assets"."type"; ``` ```sql title="Count by type (per user)" -SELECT "users"."email", "assets"."type", COUNT(*) - FROM "assets" +SELECT "users"."email", "assets"."type", COUNT(*) FROM "assets" JOIN "users" ON "assets"."ownerId" = "users"."id" GROUP BY "assets"."type", "users"."email" ORDER BY "users"."email"; @@ -75,7 +79,7 @@ SELECT * FROM "move_history"; ## Users -```sql title="List" +```sql title="List all users" SELECT * FROM "users"; ``` @@ -86,3 +90,9 @@ SELECT "key", "value" FROM "system_config"; ``` (Only used when not using the [config file](/docs/install/config-file)) + +## Persons + +```sql title="Delete person and unset it for the faces it was associated with" +DELETE FROM person WHERE name = 'PersonNameHere'; +``` diff --git a/docs/docs/guides/external-library.md b/docs/docs/guides/external-library.md index ba60e8b118..b1d4b67b2e 100644 --- a/docs/docs/guides/external-library.md +++ b/docs/docs/guides/external-library.md @@ -14,12 +14,6 @@ Edit `docker-compose.yml` to add two new mount points under `volumes:` - ${EXTERNAL_PATH}:/usr/src/app/external ``` -``` - immich-microservices: - volumes: - - ${EXTERNAL_PATH}:/usr/src/app/external -``` - Be sure to add exactly the same line to both `immich-server:` and `immich-microservices:`. Edit `.env` to define `EXTERNAL_PATH`, substituting in the correct path for your computer: diff --git a/docs/docs/guides/remote-machine-learning.md b/docs/docs/guides/remote-machine-learning.md index a5dbf5498d..e79f6b33e9 100644 --- a/docs/docs/guides/remote-machine-learning.md +++ b/docs/docs/guides/remote-machine-learning.md @@ -6,10 +6,14 @@ To alleviate [performance issues on low-memory systems](/docs/FAQ.mdx#why-is-imm - Copy the following `docker-compose.yml` to your ML system. - Start the container by running `docker compose up -d`. -:::note Info +:::info Starting with version v1.93.0 face detection work and face recognize were split. From now on face detection is done in the immich_machine_learning service, but facial recognition is done in the immich_microservices service. ::: +:::note +The [hwaccel.ml.yml](https://github.com/immich-app/immich/releases/latest/download/hwaccel.ml.yml) file also needs to be in the same folder if trying to use [hardware acceleration](/docs/features/ml-hardware-acceleration). +::: + ```yaml version: '3.8' diff --git a/docs/docs/install/docker-compose.mdx b/docs/docs/install/docker-compose.mdx index 60fcbbe8b8..9045891fd8 100644 --- a/docs/docs/install/docker-compose.mdx +++ b/docs/docs/install/docker-compose.mdx @@ -7,7 +7,8 @@ import ExampleEnv from '!!raw-loader!../../../docker/example.env'; # Docker Compose [Recommended] -Docker Compose is the recommended method to run Immich in production. Below are the steps to deploy Immich with Docker Compose. +Docker Compose is the recommended method to run Immich in production. Below are the steps to deploy Immich with Docker Compose. +Immich requires Docker Compose version 2.x. ### Step 1 - Download the required files @@ -66,7 +67,7 @@ docker compose up -d ``` :::info Docker version -If you get an error `unknown shorthand flag: 'd' in -d`, you are probably running the wrong Docker version. (This happens, for example, with the docker.io package in Ubuntu 22.04.3 LTS.) You can correct the problem by `apt remove`ing Ubuntu's docker.io package and installing docker and docker-compose via [Docker's official repository](https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository). +If you get an error `unknown shorthand flag: 'd' in -d`, you are probably running the wrong Docker version. (This happens, for example, with the docker.io package in Ubuntu 22.04.3 LTS.) You can correct the problem by `apt remove`ing Ubuntu's docker.io package and installing docker and docker-compose via [Docker's official repository][docker-repo]. Note that the correct command really is `docker compose`, not `docker-compose`. If you try the latter on vanilla Ubuntu 22.04 it will fail in a different way: @@ -83,27 +84,31 @@ For more information on how to use the application, please refer to the [Post In ::: :::note GitHub Authentication -Downloading container images might require you to authenticate to the GitHub Container Registry ([steps here](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry#authenticating-to-the-container-registry)). +Downloading container images might require you to authenticate to the GitHub Container Registry ([steps here][container-auth]). ::: ### Step 4 - Upgrading :::danger Breaking Changes -It is important to follow breaking updates to avoid problems. You can see versions that had breaking changes [here](https://github.com/immich-app/immich/discussions?discussions_q=label%3Abreaking-change+sort%3Adate_created). +It is important to follow breaking updates to avoid problems. You can see versions that had breaking changes [here][breaking]. ::: If `IMMICH_VERSION` is set, it will need to be updated to the latest or desired version. -When a new version of Immich is [released](https://github.com/immich-app/immich/releases), the application can be upgraded with the following commands, run in the directory with the `docker-compose.yml` file: +When a new version of Immich is [released][releases], the application can be upgraded with the following commands, run in the directory with the `docker-compose.yml` file: ```bash title="Upgrade Immich" docker compose pull && docker compose up -d ``` :::caution Automatic Updates -Immich is currently under heavy development, which means you can expect [breaking changes](https://github.com/immich-app/immich/discussions?discussions_q=label%3Abreaking-change+sort%3Adate_created) and bugs. Therefore, we recommend reading the release notes prior to updating and to take special care when using automated tools like [Watchtower][watchtower]. +Immich is currently under heavy development, which means you can expect [breaking changes][breaking] and bugs. Therefore, we recommend reading the release notes prior to updating and to take special care when using automated tools like [Watchtower][watchtower]. ::: [compose-file]: https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml [env-file]: https://github.com/immich-app/immich/releases/latest/download/example.env [watchtower]: https://containrrr.dev/watchtower/ +[breaking]: https://github.com/immich-app/immich/discussions?discussions_q=label%3Abreaking-change+sort%3Adate_created +[container-auth]: https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry#authenticating-to-the-container-registry +[releases]: https://github.com/immich-app/immich/releases +[docker-repo]: https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository diff --git a/docs/docs/install/environment-variables.md b/docs/docs/install/environment-variables.md index a8cbe6059f..dce9d4fa59 100644 --- a/docs/docs/install/environment-variables.md +++ b/docs/docs/install/environment-variables.md @@ -41,19 +41,20 @@ These environment variables are used by the `docker-compose.yml` file and do **N | `IMMICH_REVERSE_GEOCODING_ROOT` | Path of reverse geocoding dump directory | `/usr/src/resources` | microservices | :::tip -`TZ` should be set to a `TZ identifier` from [this list](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List). For example, `TZ="Etc/UTC"`. +`TZ` should be set to a `TZ identifier` from [this list][tz-list]. For example, `TZ="Etc/UTC"`. `TZ` is only used by `exiftool`, which is present in the microservices container, as a fallback in case the timezone cannot be determined from the image metadata. ::: ## Ports -| Variable | Description | Default | Services | -| :---------------------- | :-------------------- | :-------: | :--------------- | -| `SERVER_PORT` | Server Port | `3001` | server | -| `MICROSERVICES_PORT` | Microservices Port | `3002` | microservices | -| `MACHINE_LEARNING_HOST` | Machine Learning Host | `0.0.0.0` | machine learning | -| `MACHINE_LEARNING_PORT` | Machine Learning Port | `3003` | machine learning | +| Variable | Description | Default | Services | +| :---------------------- | :-------------------- | :-------: | :-------------------- | +| `HOST` | Host | `0.0.0.0` | server, microservices | +| `SERVER_PORT` | Server Port | `3001` | server | +| `MICROSERVICES_PORT` | Microservices Port | `3002` | microservices | +| `MACHINE_LEARNING_HOST` | Machine Learning Host | `0.0.0.0` | machine learning | +| `MACHINE_LEARNING_PORT` | Machine Learning Port | `3003` | machine learning | ## Database @@ -71,7 +72,7 @@ These environment variables are used by the `docker-compose.yml` file and do **N :::info -When `DB_URL` is defined, the other database (`DB_*`) variables are ignored. +When `DB_URL` is defined, the other database (`DB_*`) variables are ignored, with the exception of `DB_VECTOR_EXTENSION`. ::: @@ -90,7 +91,7 @@ When `DB_URL` is defined, the other database (`DB_*`) variables are ignored. :::info `REDIS_URL` must start with `ioredis://` and then include a `base64` encoded JSON string for the configuration. -More info can be found in the upstream [ioredis](https://ioredis.readthedocs.io/en/latest/API/) documentation. +More info can be found in the upstream [ioredis][redis-api] documentation. - When `REDIS_URL` is defined, the other redis (`REDIS_*`) variables are ignored. - When `REDIS_SOCKET` is defined, the other redis (`REDIS_*`) variables are ignored. @@ -158,7 +159,7 @@ Other machine learning parameters can be tuned from the admin UI. ## Docker Secrets -The following variables support the use of [Docker secrets](https://docs.docker.com/engine/swarm/secrets/) for additional security. +The following variables support the use of [Docker secrets][docker-secrets] for additional security. To use any of these, replace the regular environment variable with the equivalent `_FILE` environment variable. The value of the `_FILE` variable should be set to the path of a file containing the variable value. @@ -172,8 +173,14 @@ the `_FILE` variable should be set to the path of a file containing the variable | `DB_URL` | `DB_URL_FILE`\*1 | | `REDIS_PASSWORD` | `REDIS_PASSWORD_FILE`\*2 | -\*1: See the [official documentation](https://github.com/docker-library/docs/tree/master/postgres#docker-secrets) for +\*1: See the [official documentation][docker-secrets-docs] for details on how to use Docker Secrets in the Postgres image. -\*2: See [this comment](https://github.com/docker-library/redis/issues/46#issuecomment-335326234) for an example of how +\*2: See [this comment][docker-secrets-example] for an example of how to use use a Docker secret for the password in the Redis container. + +[tz-list]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List +[docker-secrets-example]: https://github.com/docker-library/redis/issues/46#issuecomment-335326234 +[docker-secrets-docs]: https://github.com/docker-library/docs/tree/master/postgres#docker-secrets +[docker-secrets]: https://docs.docker.com/engine/swarm/secrets/ +[redis-api]: https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository diff --git a/docs/docs/install/kubernetes.md b/docs/docs/install/kubernetes.md index 57db569128..d66f6193d1 100644 --- a/docs/docs/install/kubernetes.md +++ b/docs/docs/install/kubernetes.md @@ -6,7 +6,7 @@ sidebar_position: 40 You can deploy Immich on Kubernetes using [the official Helm chart](https://github.com/immich-app/immich-charts/tree/main/charts/immich). -If you want examples of how other people run Immich on Kubernetes, using the official chart or otherwise, you can find them at https://nanne.dev/k8s-at-home-search/#/immich. +You can view some [examples](https://kubesearch.dev/#/immich) of how other people run Immich on Kubernetes, using the official chart or otherwise. :::caution DNS in Alpine containers Immich makes use of Alpine container images. These can encounter [a DNS resolution bug](https://stackoverflow.com/a/65593511) on Kubernetes clusters if the host diff --git a/docs/docs/partials/_user-create.md b/docs/docs/partials/_user-create.md index 5d08591386..db99620677 100644 --- a/docs/docs/partials/_user-create.md +++ b/docs/docs/partials/_user-create.md @@ -1,3 +1,3 @@ -If you have friends or family members who want to use the application as well, you can create addition accounts. The default password is `password`, and the user can change their password after logging in to the application for the first time. +If you have friends or family members who want to use the application as well, you can create addition accounts. The default password is `password`, and the user has to change their password after logging in to the application for the first time. The system administrator can disable this option by unchecking the option "Require user to change password on first login" in the user registration interface. - + diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index afe9f0eecd..75513e3d3b 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -144,6 +144,10 @@ const config = { label: 'Discord', href: 'https://discord.com/invite/D8JsnBEuKb', }, + { + label: 'Reddit', + href: 'https://www.reddit.com/r/immich/', + }, ], }, { @@ -157,6 +161,10 @@ const config = { label: 'GitHub', href: 'https://github.com/immich-app/immich', }, + { + label: 'YouTube', + href: 'https://www.youtube.com/@immich-app', + }, ], }, ], diff --git a/docs/package-lock.json b/docs/package-lock.json index df80492263..6e898886cd 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -16125,9 +16125,9 @@ } }, "node_modules/typescript": { - "version": "5.4.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.4.tgz", - "integrity": "sha512-dGE2Vv8cpVvw28v8HCPqyb08EzbBURxDpuhJvTrusShUfGnhHBafDsLdS1EhhxyL6BJQE+2cT3dDPAv+MQ6oLw==", + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/docs/src/components/community-guides.tsx b/docs/src/components/community-guides.tsx new file mode 100644 index 0000000000..5a17e3b295 --- /dev/null +++ b/docs/src/components/community-guides.tsx @@ -0,0 +1,71 @@ +import Link from '@docusaurus/Link'; +import React from 'react'; + +interface CommunityGuidesProps { + title: string; + description: string; + url: string; +} + +const guides: CommunityGuidesProps[] = [ + { + title: 'Cloudflare Tunnels with SSO/OAuth', + description: `Setting up Cloudflare Tunnels and a SaaS App for immich.`, + url: 'https://github.com/immich-app/immich/discussions/8299', + }, + { + title: 'Database backup in Truenas', + description: `Create a database backup with pgAdmin in Truenas.`, + url: 'https://github.com/immich-app/immich/discussions/8809', + }, + { + title: 'Unraid backup scripts', + description: `Back up your assets in Unarid with a pre-prepared script.`, + url: 'https://github.com/immich-app/immich/discussions/8416', + }, + { + title: 'Sync folders with albums', + description: `synchronize folders in imported library with albums having the folders name.`, + url: 'https://github.com/immich-app/immich/discussions/3382', + }, + { + title: 'Podman/Quadlets Install', + description: 'Documentation for simple podman setup using quadlets.', + url: 'https://github.com/tbelway/immich-podman-quadlets/blob/main/docs/install/podman-quadlet.md', + }, +]; + +function CommunityGuide({ title, description, url }: CommunityGuidesProps): JSX.Element { + return ( +
+
+

+ {title} +

+ +

{description}

+

+ {url} +

+
+
+ + View Guide + +
+
+ ); +} + +export default function CommunityGuides(): JSX.Element { + return ( +
+ {guides.map((guides) => ( + + ))} +
+ ); +} diff --git a/docs/src/components/community-projects.tsx b/docs/src/components/community-projects.tsx index daf4138765..6d23b0dcfc 100644 --- a/docs/src/components/community-projects.tsx +++ b/docs/src/components/community-projects.tsx @@ -33,16 +33,31 @@ const projects: CommunityProjectProps[] = [ description: 'A Python script to create albums based on the folder structure of an external library.', url: 'https://github.com/Salvoxia/immich-folder-album-creator', }, - { - title: 'Podman/Quadlets Install', - description: 'Documentation for simple podman setup using quadlets.', - url: 'https://github.com/tbelway/immich-podman-quadlets/blob/main/docs/install/podman-quadlet.md', - }, { title: 'Lightroom Publisher: mi.Immich.Publisher', description: 'Lightroom plugin to publish photos from Lightroom collections to Immich albums.', url: 'https://github.com/midzelis/mi.Immich.Publisher', }, + { + title: 'Immich Duplicate Finder', + description: 'Webapp that uses machine learning to identify near-duplicate images.', + url: 'https://github.com/vale46n1/immich_duplicate_finder', + }, + { + title: 'Immich-Tiktok-Remover', + description: 'Script to search for and remove TikTok videos from your Immich library.', + url: 'https://github.com/mxc2/immich-tiktok-remover', + }, + { + title: 'Immich Android TV', + description: 'Unofficial Immich Android TV app.', + url: 'https://github.com/giejay/Immich-Android-TV', + }, + { + title: 'Powershell Module PSImmich', + description: 'Powershell Module for the Immich API', + url: 'https://github.com/hanpq/PSImmich', + }, ]; function CommunityProject({ title, description, url }: CommunityProjectProps): JSX.Element { diff --git a/docs/src/pages/milestones.tsx b/docs/src/pages/milestones.tsx index ea00f486cf..76e9b804d2 100644 --- a/docs/src/pages/milestones.tsx +++ b/docs/src/pages/milestones.tsx @@ -51,12 +51,22 @@ import { mdiVideo, mdiWeb, mdiScaleBalance, + mdiMagnifyScan, } from '@mdi/js'; import Layout from '@theme/Layout'; import React from 'react'; import Timeline, { DateType, Item } from '../components/timeline'; const items: Item[] = [ + { + icon: mdiMagnifyScan, + description: 'Advanced search with filters by date, location and more', + title: 'Search enhancement with advanced filters', + release: 'v1.95.0', + tag: 'v1.95.0', + date: new Date(2024, 1, 20), + dateType: DateType.RELEASE, + }, { icon: mdiScaleBalance, description: 'Immich switches to AGPLv3 license', diff --git a/e2e/package-lock.json b/e2e/package-lock.json index 41955e8ce5..6cd8dd90ec 100644 --- a/e2e/package-lock.json +++ b/e2e/package-lock.json @@ -33,6 +33,7 @@ "socket.io-client": "^4.7.4", "supertest": "^6.3.4", "typescript": "^5.3.3", + "utimes": "^5.2.1", "vitest": "^1.3.0" } }, @@ -87,8 +88,8 @@ "@oazapfts/runtime": "^1.0.2" }, "devDependencies": { - "@types/node": "^20.11.0", - "typescript": "^5.3.3" + "@types/node": "^20.12.7", + "typescript": "^5.4.5" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -872,6 +873,50 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "dev": true, + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -941,9 +986,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.0.tgz", - "integrity": "sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.3.tgz", + "integrity": "sha512-X9alQ3XM6I9IlSlmC8ddAvMSyG1WuHk5oUnXGw+yUBs3BFoTizmG1La/Gr8fVJvDWAq+zlYTZ9DBgrlKRVY06g==", "cpu": [ "arm" ], @@ -954,9 +999,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.0.tgz", - "integrity": "sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.3.tgz", + "integrity": "sha512-eQK5JIi+POhFpzk+LnjKIy4Ks+pwJ+NXmPxOCSvOKSNRPONzKuUvWE+P9JxGZVxrtzm6BAYMaL50FFuPe0oWMQ==", "cpu": [ "arm64" ], @@ -967,9 +1012,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.0.tgz", - "integrity": "sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.3.tgz", + "integrity": "sha512-Od4vE6f6CTT53yM1jgcLqNfItTsLt5zE46fdPaEmeFHvPs5SjZYlLpHrSiHEKR1+HdRfxuzXHjDOIxQyC3ptBA==", "cpu": [ "arm64" ], @@ -980,9 +1025,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.0.tgz", - "integrity": "sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.3.tgz", + "integrity": "sha512-0IMAO21axJeNIrvS9lSe/PGthc8ZUS+zC53O0VhF5gMxfmcKAP4ESkKOCwEi6u2asUrt4mQv2rjY8QseIEb1aw==", "cpu": [ "x64" ], @@ -993,9 +1038,22 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.0.tgz", - "integrity": "sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.3.tgz", + "integrity": "sha512-ge2DC7tHRHa3caVEoSbPRJpq7azhG+xYsd6u2MEnJ6XzPSzQsTKyXvh6iWjXRf7Rt9ykIUWHtl0Uz3T6yXPpKw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.14.3.tgz", + "integrity": "sha512-ljcuiDI4V3ySuc7eSk4lQ9wU8J8r8KrOUvB2U+TtK0TiW6OFDmJ+DdIjjwZHIw9CNxzbmXY39wwpzYuFDwNXuw==", "cpu": [ "arm" ], @@ -1006,9 +1064,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.0.tgz", - "integrity": "sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.3.tgz", + "integrity": "sha512-Eci2us9VTHm1eSyn5/eEpaC7eP/mp5n46gTRB3Aar3BgSvDQGJZuicyq6TsH4HngNBgVqC5sDYxOzTExSU+NjA==", "cpu": [ "arm64" ], @@ -1019,9 +1077,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.0.tgz", - "integrity": "sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.3.tgz", + "integrity": "sha512-UrBoMLCq4E92/LCqlh+blpqMz5h1tJttPIniwUgOFJyjWI1qrtrDhhpHPuFxULlUmjFHfloWdixtDhSxJt5iKw==", "cpu": [ "arm64" ], @@ -1031,10 +1089,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.3.tgz", + "integrity": "sha512-5aRjvsS8q1nWN8AoRfrq5+9IflC3P1leMoy4r2WjXyFqf3qcqsxRCfxtZIV58tCxd+Yv7WELPcO9mY9aeQyAmw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.0.tgz", - "integrity": "sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.3.tgz", + "integrity": "sha512-sk/Qh1j2/RJSX7FhEpJn8n0ndxy/uf0kI/9Zc4b1ELhqULVdTfN6HL31CDaTChiBAOgLcsJ1sgVZjWv8XNEsAQ==", "cpu": [ "riscv64" ], @@ -1044,10 +1115,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.3.tgz", + "integrity": "sha512-jOO/PEaDitOmY9TgkxF/TQIjXySQe5KVYB57H/8LRP/ux0ZoO8cSHCX17asMSv3ruwslXW/TLBcxyaUzGRHcqg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.0.tgz", - "integrity": "sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.3.tgz", + "integrity": "sha512-8ybV4Xjy59xLMyWo3GCfEGqtKV5M5gCSrZlxkPGvEPCGDLNla7v48S662HSGwRd6/2cSneMQWiv+QzcttLrrOA==", "cpu": [ "x64" ], @@ -1058,9 +1142,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.0.tgz", - "integrity": "sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.3.tgz", + "integrity": "sha512-s+xf1I46trOY10OqAtZ5Rm6lzHre/UiLA1J2uOhCFXWkbZrJRkYBPO6FhvGfHmdtQ3Bx793MNa7LvoWFAm93bg==", "cpu": [ "x64" ], @@ -1071,9 +1155,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.0.tgz", - "integrity": "sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.3.tgz", + "integrity": "sha512-+4h2WrGOYsOumDQ5S2sYNyhVfrue+9tc9XcLWLh+Kw3UOxAvrfOrSMFon60KspcDdytkNDh7K2Vs6eMaYImAZg==", "cpu": [ "arm64" ], @@ -1084,9 +1168,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.0.tgz", - "integrity": "sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.3.tgz", + "integrity": "sha512-T1l7y/bCeL/kUwh9OD4PQT4aM7Bq43vX05htPJJ46RTI4r5KNt6qJRzAfNfM+OYMNEVBWQzR2Gyk+FXLZfogGw==", "cpu": [ "ia32" ], @@ -1097,9 +1181,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.0.tgz", - "integrity": "sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.3.tgz", + "integrity": "sha512-/BypzV0H1y1HzgYpxqRaXGBRqfodgoBBCcsrujT6QRcakDQdfU+Lq9PENPh5jB4I44YWq+0C2eHsHya+nZY1sA==", "cpu": [ "x64" ], @@ -1133,12 +1217,6 @@ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true - }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -1158,9 +1236,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.12.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.4.tgz", - "integrity": "sha512-E+Fa9z3wSQpzgYQdYmme5X3OTuejnnTx88A6p6vkkJosR3KBz+HpE3kqNm98VE6cfLFcISx7zW7MsJkH6KwbTw==", + "version": "20.12.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", + "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -1173,9 +1251,9 @@ "dev": true }, "node_modules/@types/pg": { - "version": "8.11.4", - "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.11.4.tgz", - "integrity": "sha512-yw3Bwbda6vO+NvI1Ue/YKOwtl31AYvvd/e73O3V4ZkNzuGpTDndLSyc0dQRB2xrQqDePd20pEGIfqSp/GH3pRw==", + "version": "8.11.5", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.11.5.tgz", + "integrity": "sha512-2xMjVviMxneZHDHX5p5S6tsRRs7TpDHeeK7kTTMe/kAC/mRRNjWHjZg0rkiY+e17jXSZV3zJYDxXV8Cy72/Vuw==", "dev": true, "dependencies": { "@types/node": "*", @@ -1277,22 +1355,22 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.5.0.tgz", - "integrity": "sha512-HpqNTH8Du34nLxbKgVMGljZMG0rJd2O9ecvr2QLYp+7512ty1j42KnsFwspPXg1Vh8an9YImf6CokUBltisZFQ==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.6.0.tgz", + "integrity": "sha512-gKmTNwZnblUdnTIJu3e9kmeRRzV2j1a/LUO27KNNAnIC5zjy1aSvXSRp4rVNlmAoHlQ7HzX42NbKpcSr4jF80A==", "dev": true, "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "7.5.0", - "@typescript-eslint/type-utils": "7.5.0", - "@typescript-eslint/utils": "7.5.0", - "@typescript-eslint/visitor-keys": "7.5.0", + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.6.0", + "@typescript-eslint/type-utils": "7.6.0", + "@typescript-eslint/utils": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0", "debug": "^4.3.4", "graphemer": "^1.4.0", - "ignore": "^5.2.4", + "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -1312,15 +1390,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.5.0.tgz", - "integrity": "sha512-cj+XGhNujfD2/wzR1tabNsidnYRaFfEkcULdcIyVBYcXjBvBKOes+mpMBP7hMpOyk+gBcfXsrg4NBGAStQyxjQ==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.6.0.tgz", + "integrity": "sha512-usPMPHcwX3ZoPWnBnhhorc14NJw9J4HpSXQX4urF2TPKG0au0XhJoZyX62fmvdHONUkmyUe74Hzm1//XA+BoYg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "7.5.0", - "@typescript-eslint/types": "7.5.0", - "@typescript-eslint/typescript-estree": "7.5.0", - "@typescript-eslint/visitor-keys": "7.5.0", + "@typescript-eslint/scope-manager": "7.6.0", + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/typescript-estree": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0", "debug": "^4.3.4" }, "engines": { @@ -1340,13 +1418,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.5.0.tgz", - "integrity": "sha512-Z1r7uJY0MDeUlql9XJ6kRVgk/sP11sr3HKXn268HZyqL7i4cEfrdFuSSY/0tUqT37l5zT0tJOsuDP16kio85iA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.6.0.tgz", + "integrity": "sha512-ngttyfExA5PsHSx0rdFgnADMYQi+Zkeiv4/ZxGYUWd0nLs63Ha0ksmp8VMxAIC0wtCFxMos7Lt3PszJssG/E6w==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.5.0", - "@typescript-eslint/visitor-keys": "7.5.0" + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -1357,15 +1435,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.5.0.tgz", - "integrity": "sha512-A021Rj33+G8mx2Dqh0nMO9GyjjIBK3MqgVgZ2qlKf6CJy51wY/lkkFqq3TqqnH34XyAHUkq27IjlUkWlQRpLHw==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.6.0.tgz", + "integrity": "sha512-NxAfqAPNLG6LTmy7uZgpK8KcuiS2NZD/HlThPXQRGwz6u7MDBWRVliEEl1Gj6U7++kVJTpehkhZzCJLMK66Scw==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "7.5.0", - "@typescript-eslint/utils": "7.5.0", + "@typescript-eslint/typescript-estree": "7.6.0", + "@typescript-eslint/utils": "7.6.0", "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" + "ts-api-utils": "^1.3.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -1384,9 +1462,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.5.0.tgz", - "integrity": "sha512-tv5B4IHeAdhR7uS4+bf8Ov3k793VEVHd45viRRkehIUZxm0WF82VPiLgHzA/Xl4TGPg1ZD49vfxBKFPecD5/mg==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.6.0.tgz", + "integrity": "sha512-h02rYQn8J+MureCvHVVzhl69/GAfQGPQZmOMjG1KfCl7o3HtMSlPaPUAPu6lLctXI5ySRGIYk94clD/AUMCUgQ==", "dev": true, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -1397,19 +1475,19 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.5.0.tgz", - "integrity": "sha512-YklQQfe0Rv2PZEueLTUffiQGKQneiIEKKnfIqPIOxgM9lKSZFCjT5Ad4VqRKj/U4+kQE3fa8YQpskViL7WjdPQ==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.6.0.tgz", + "integrity": "sha512-+7Y/GP9VuYibecrCQWSKgl3GvUM5cILRttpWtnAu8GNL9j11e4tbuGZmZjJ8ejnKYyBRb2ddGQ3rEFCq3QjMJw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.5.0", - "@typescript-eslint/visitor-keys": "7.5.0", + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0", "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" + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -1434,9 +1512,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -1449,18 +1527,18 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.5.0.tgz", - "integrity": "sha512-3vZl9u0R+/FLQcpy2EHyRGNqAS/ofJ3Ji8aebilfJe+fobK8+LbIFmrHciLVDxjDoONmufDcnVSF38KwMEOjzw==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.6.0.tgz", + "integrity": "sha512-x54gaSsRRI+Nwz59TXpCsr6harB98qjXYzsRxGqvA5Ue3kQH+FxS7FYU81g/omn22ML2pZJkisy6Q+ElK8pBCA==", "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": "7.5.0", - "@typescript-eslint/types": "7.5.0", - "@typescript-eslint/typescript-estree": "7.5.0", - "semver": "^7.5.4" + "@types/json-schema": "^7.0.15", + "@types/semver": "^7.5.8", + "@typescript-eslint/scope-manager": "7.6.0", + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/typescript-estree": "7.6.0", + "semver": "^7.6.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -1474,13 +1552,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.5.0.tgz", - "integrity": "sha512-mcuHM/QircmA6O7fy6nn2w/3ditQkj+SgtOc8DW3uQ10Yfj42amm2i+6F2K4YAOPNNTmE6iM1ynM6lrSwdendA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.6.0.tgz", + "integrity": "sha512-4eLB7t+LlNUmXzfOu1VAIAdkjbu5xNSerURS9X/S5TUKWFRpXRQZbmtPqgKmYx8bj3J0irtQXSiWAOY82v+cgw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.5.0", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "7.6.0", + "eslint-visitor-keys": "^3.4.3" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -1497,9 +1575,9 @@ "dev": true }, "node_modules/@vitest/coverage-v8": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.4.0.tgz", - "integrity": "sha512-4hDGyH1SvKpgZnIByr9LhGgCEuF9DKM34IBLCC/fVfy24Z3+PZ+Ii9hsVBsHvY1umM1aGPEjceRkzxCfcQ10wg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.5.0.tgz", + "integrity": "sha512-1igVwlcqw1QUMdfcMlzzY4coikSIBN944pkueGi0pawrX5I5Z+9hxdTR+w3Sg6Q3eZhvdMAs8ZaF9JuTG1uYOQ==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.1", @@ -1514,24 +1592,23 @@ "picocolors": "^1.0.0", "std-env": "^3.5.0", "strip-literal": "^2.0.0", - "test-exclude": "^6.0.0", - "v8-to-istanbul": "^9.2.0" + "test-exclude": "^6.0.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "1.4.0" + "vitest": "1.5.0" } }, "node_modules/@vitest/expect": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.4.0.tgz", - "integrity": "sha512-Jths0sWCJZ8BxjKe+p+eKsoqev1/T8lYcrjavEaz8auEJ4jAVY0GwW3JKmdVU4mmNPLPHixh4GNXP7GFtAiDHA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.5.0.tgz", + "integrity": "sha512-0pzuCI6KYi2SIC3LQezmxujU9RK/vwC1U9R0rLuGlNGcOuDWxqWKu6nUdFsX9tH1WU0SXtAxToOsEjeUn1s3hA==", "dev": true, "dependencies": { - "@vitest/spy": "1.4.0", - "@vitest/utils": "1.4.0", + "@vitest/spy": "1.5.0", + "@vitest/utils": "1.5.0", "chai": "^4.3.10" }, "funding": { @@ -1539,12 +1616,12 @@ } }, "node_modules/@vitest/runner": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.4.0.tgz", - "integrity": "sha512-EDYVSmesqlQ4RD2VvWo3hQgTJ7ZrFQ2VSJdfiJiArkCerDAGeyF1i6dHkmySqk573jLp6d/cfqCN+7wUB5tLgg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.5.0.tgz", + "integrity": "sha512-7HWwdxXP5yDoe7DTpbif9l6ZmDwCzcSIK38kTSIt6CFEpMjX4EpCgT6wUmS0xTXqMI6E/ONmfgRKmaujpabjZQ==", "dev": true, "dependencies": { - "@vitest/utils": "1.4.0", + "@vitest/utils": "1.5.0", "p-limit": "^5.0.0", "pathe": "^1.1.1" }, @@ -1553,9 +1630,9 @@ } }, "node_modules/@vitest/snapshot": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.4.0.tgz", - "integrity": "sha512-saAFnt5pPIA5qDGxOHxJ/XxhMFKkUSBJmVt5VgDsAqPTX6JP326r5C/c9UuCMPoXNzuudTPsYDZCoJ5ilpqG2A==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.5.0.tgz", + "integrity": "sha512-qpv3fSEuNrhAO3FpH6YYRdaECnnRjg9VxbhdtPwPRnzSfHVXnNzzrpX4cJxqiwgRMo7uRMWDFBlsBq4Cr+rO3A==", "dev": true, "dependencies": { "magic-string": "^0.30.5", @@ -1567,9 +1644,9 @@ } }, "node_modules/@vitest/spy": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.4.0.tgz", - "integrity": "sha512-Ywau/Qs1DzM/8Uc+yA77CwSegizMlcgTJuYGAi0jujOteJOUf1ujunHThYo243KG9nAyWT3L9ifPYZ5+As/+6Q==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.5.0.tgz", + "integrity": "sha512-vu6vi6ew5N5MMHJjD5PoakMRKYdmIrNJmyfkhRpQt5d9Ewhw9nZ5Aqynbi3N61bvk9UvZ5UysMT6ayIrZ8GA9w==", "dev": true, "dependencies": { "tinyspy": "^2.2.0" @@ -1579,9 +1656,9 @@ } }, "node_modules/@vitest/utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.4.0.tgz", - "integrity": "sha512-mx3Yd1/6e2Vt/PUC98DcqTirtfxUyAZ32uK82r8rZzbtBeBo+nqgnjx/LvqQdWsrvNtm14VmurNgcf4nqY5gJg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.5.0.tgz", + "integrity": "sha512-BDU0GNL8MWkRkSRdNFvCUCAVOeHaUlVJ9Tx0TYBZyXaaOTmGtUFObzchCivIBrIwKzvZA7A9sCejVhXM2aY98A==", "dev": true, "dependencies": { "diff-sequences": "^29.6.3", @@ -1593,6 +1670,12 @@ "url": "https://opencollective.com/vitest" } }, + "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==", + "dev": true + }, "node_modules/acorn": { "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", @@ -1623,6 +1706,18 @@ "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==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -1660,6 +1755,25 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "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==", + "dev": true + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "dev": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -1895,6 +2009,15 @@ "node": "*" } }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/ci-info": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.0.0.tgz", @@ -1949,6 +2072,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==", + "dev": 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", @@ -1976,10 +2108,10 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "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==", "dev": true }, "node_modules/cookiejar": { @@ -2076,6 +2208,21 @@ "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==", + "dev": true + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/dezalgo": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", @@ -2125,6 +2272,12 @@ "integrity": "sha512-Ic85cOuXSP6h7KM0AIJ2hpJ98Bo4hyTUjc4yjMbkvD+8yTxEhfK9+8exT2KKYsSjnCn2tGsKVSZwE7ZgTORQCw==", "dev": true }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, "node_modules/engine.io-client": { "version": "6.5.3", "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz", @@ -2678,6 +2831,30 @@ "url": "https://ko-fi.com/tunnckoCore/commissions" } }, + "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==", + "dev": 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==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -2707,6 +2884,32 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "dev": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "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==", + "dev": true + }, "node_modules/get-func-name": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", @@ -2877,6 +3080,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==", + "dev": true + }, "node_modules/hasown": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", @@ -2919,6 +3128,19 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "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==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/human-signals": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", @@ -3029,6 +3251,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -3423,6 +3654,52 @@ "node": "*" } }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": 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==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/mlly": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.5.0.tgz", @@ -3465,12 +3742,53 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/node-addon-api": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", + "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", + "dev": true + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-releases": { "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==", + "dev": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -3519,6 +3837,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "dev": true, + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", @@ -3904,9 +4243,9 @@ } }, "node_modules/postcss": { - "version": "8.4.37", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.37.tgz", - "integrity": "sha512-7iB/v/r7Woof0glKLH8b1SPHrsX7uhdO+Geb41QpF/+mWZHU3uxxSlN+UXGVit1PawOYDToO+AbZzhBzWRDwbQ==", + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "dev": true, "funding": [ { @@ -4198,6 +4537,20 @@ "node": ">=8" } }, + "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==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/regexp-tree": { "version": "0.1.27", "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", @@ -4280,9 +4633,9 @@ } }, "node_modules/rollup": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.0.tgz", - "integrity": "sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.14.3.tgz", + "integrity": "sha512-ag5tTQKYsj1bhrFC9+OEWqb5O6VYgtQDO9hPDBMmIbePwhfSr+ExlcU741t8Dhw5DkPCQf6noz0jb36D6W9/hw==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -4295,19 +4648,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.13.0", - "@rollup/rollup-android-arm64": "4.13.0", - "@rollup/rollup-darwin-arm64": "4.13.0", - "@rollup/rollup-darwin-x64": "4.13.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.13.0", - "@rollup/rollup-linux-arm64-gnu": "4.13.0", - "@rollup/rollup-linux-arm64-musl": "4.13.0", - "@rollup/rollup-linux-riscv64-gnu": "4.13.0", - "@rollup/rollup-linux-x64-gnu": "4.13.0", - "@rollup/rollup-linux-x64-musl": "4.13.0", - "@rollup/rollup-win32-arm64-msvc": "4.13.0", - "@rollup/rollup-win32-ia32-msvc": "4.13.0", - "@rollup/rollup-win32-x64-msvc": "4.13.0", + "@rollup/rollup-android-arm-eabi": "4.14.3", + "@rollup/rollup-android-arm64": "4.14.3", + "@rollup/rollup-darwin-arm64": "4.14.3", + "@rollup/rollup-darwin-x64": "4.14.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.14.3", + "@rollup/rollup-linux-arm-musleabihf": "4.14.3", + "@rollup/rollup-linux-arm64-gnu": "4.14.3", + "@rollup/rollup-linux-arm64-musl": "4.14.3", + "@rollup/rollup-linux-powerpc64le-gnu": "4.14.3", + "@rollup/rollup-linux-riscv64-gnu": "4.14.3", + "@rollup/rollup-linux-s390x-gnu": "4.14.3", + "@rollup/rollup-linux-x64-gnu": "4.14.3", + "@rollup/rollup-linux-x64-musl": "4.14.3", + "@rollup/rollup-win32-arm64-msvc": "4.14.3", + "@rollup/rollup-win32-ia32-msvc": "4.14.3", + "@rollup/rollup-win32-x64-msvc": "4.14.3", "fsevents": "~2.3.2" } }, @@ -4334,6 +4690,26 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/semver": { "version": "7.6.0", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", @@ -4349,6 +4725,12 @@ "node": ">=10" } }, + "node_modules/set-blocking": { + "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 + }, "node_modules/set-function-length": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", @@ -4522,6 +4904,29 @@ "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", "dev": true }, + "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==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "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==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -4656,6 +5061,23 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": 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/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -4683,9 +5105,9 @@ "dev": true }, "node_modules/tinypool": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.2.tgz", - "integrity": "sha512-SUszKYe5wgsxnNOVlBYO6IC+8VGWdVGZWAqUxp3UErNBtptZvWbwyUOyzNL59zigz2rCA92QiL3wvG+JDSdJdQ==", + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", + "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", "dev": true, "engines": { "node": ">=14.0.0" @@ -4721,10 +5143,16 @@ "node": ">=8.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, "node_modules/ts-api-utils": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.1.tgz", - "integrity": "sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", "dev": true, "engines": { "node": ">=16" @@ -4773,9 +5201,9 @@ } }, "node_modules/typescript": { - "version": "5.4.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.4.tgz", - "integrity": "sha512-dGE2Vv8cpVvw28v8HCPqyb08EzbBURxDpuhJvTrusShUfGnhHBafDsLdS1EhhxyL6BJQE+2cT3dDPAv+MQ6oLw==", + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -4836,18 +5264,24 @@ "punycode": "^2.1.0" } }, - "node_modules/v8-to-istanbul": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", - "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", + "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==", + "dev": true + }, + "node_modules/utimes": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/utimes/-/utimes-5.2.1.tgz", + "integrity": "sha512-6S5mCapmzcxetOD/2UEjL0GF5e4+gB07Dh8qs63xylw5ay4XuyW6iQs70FOJo/puf10LCkvhp4jYMQSDUBYEFg==", "dev": true, + "hasInstallScript": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" + "@mapbox/node-pre-gyp": "^1.0.11", + "node-addon-api": "^4.3.0" }, "engines": { - "node": ">=10.12.0" + "node": ">=10.0.0" } }, "node_modules/validate-npm-package-license": { @@ -4861,13 +5295,13 @@ } }, "node_modules/vite": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.2.tgz", - "integrity": "sha512-FWZbz0oSdLq5snUI0b6sULbz58iXFXdvkZfZWR/F0ZJuKTSPO7v72QPXt6KqYeMFb0yytNp6kZosxJ96Nr/wDQ==", + "version": "5.2.9", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.9.tgz", + "integrity": "sha512-uOQWfuZBlc6Y3W/DTuQ1Sr+oIXWvqljLvS881SVmAj00d5RdgShLcuXWxseWPd4HXwiYBFW/vXHfKFeqj9uQnw==", "dev": true, "dependencies": { "esbuild": "^0.20.1", - "postcss": "^8.4.36", + "postcss": "^8.4.38", "rollup": "^4.13.0" }, "bin": { @@ -4916,9 +5350,9 @@ } }, "node_modules/vite-node": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.4.0.tgz", - "integrity": "sha512-VZDAseqjrHgNd4Kh8icYHWzTKSCZMhia7GyHfhtzLW33fZlG9SwsB6CEhgyVOWkJfJ2pFLrp/Gj1FSfAiqH9Lw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.5.0.tgz", + "integrity": "sha512-tV8h6gMj6vPzVCa7l+VGq9lwoJjW8Y79vst8QZZGiuRAfijU+EEWuc0kFpmndQrWhMMhet1jdSF+40KSZUqIIw==", "dev": true, "dependencies": { "cac": "^6.7.14", @@ -4952,16 +5386,16 @@ } }, "node_modules/vitest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.4.0.tgz", - "integrity": "sha512-gujzn0g7fmwf83/WzrDTnncZt2UiXP41mHuFYFrdwaLRVQ6JYQEiME2IfEjU3vcFL3VKa75XhI3lFgn+hfVsQw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.5.0.tgz", + "integrity": "sha512-d8UKgR0m2kjdxDWX6911uwxout6GHS0XaGH1cksSIVVG8kRlE7G7aBw7myKQCvDI5dT4j7ZMa+l706BIORMDLw==", "dev": true, "dependencies": { - "@vitest/expect": "1.4.0", - "@vitest/runner": "1.4.0", - "@vitest/snapshot": "1.4.0", - "@vitest/spy": "1.4.0", - "@vitest/utils": "1.4.0", + "@vitest/expect": "1.5.0", + "@vitest/runner": "1.5.0", + "@vitest/snapshot": "1.5.0", + "@vitest/spy": "1.5.0", + "@vitest/utils": "1.5.0", "acorn-walk": "^8.3.2", "chai": "^4.3.10", "debug": "^4.3.4", @@ -4973,9 +5407,9 @@ "std-env": "^3.5.0", "strip-literal": "^2.0.0", "tinybench": "^2.5.1", - "tinypool": "^0.8.2", + "tinypool": "^0.8.3", "vite": "^5.0.0", - "vite-node": "1.4.0", + "vite-node": "1.5.0", "why-is-node-running": "^2.2.2" }, "bin": { @@ -4990,8 +5424,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "1.4.0", - "@vitest/ui": "1.4.0", + "@vitest/browser": "1.5.0", + "@vitest/ui": "1.5.0", "happy-dom": "*", "jsdom": "*" }, @@ -5016,6 +5450,22 @@ } } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -5047,6 +5497,15 @@ "node": ">=8" } }, + "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==", + "dev": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/e2e/package.json b/e2e/package.json index 171b803b70..9023de8162 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -43,6 +43,7 @@ "socket.io-client": "^4.7.4", "supertest": "^6.3.4", "typescript": "^5.3.3", + "utimes": "^5.2.1", "vitest": "^1.3.0" } } diff --git a/e2e/src/api/specs/asset.e2e-spec.ts b/e2e/src/api/specs/asset.e2e-spec.ts index 40de8faef5..53c4c43468 100644 --- a/e2e/src/api/specs/asset.e2e-spec.ts +++ b/e2e/src/api/specs/asset.e2e-spec.ts @@ -816,15 +816,15 @@ describe('/asset', () => { }); it('should not include gps data for webp thumbnails', async () => { - const { status, body, type } = await request(app) - .get(`/asset/thumbnail/${locationAsset.id}?format=WEBP`) - .set('Authorization', `Bearer ${admin.accessToken}`); - await utils.waitForWebsocketEvent({ event: 'assetUpload', id: locationAsset.id, }); + const { status, body, type } = await request(app) + .get(`/asset/thumbnail/${locationAsset.id}?format=WEBP`) + .set('Authorization', `Bearer ${admin.accessToken}`); + expect(status).toBe(200); expect(body).toBeDefined(); expect(type).toBe('image/webp'); diff --git a/e2e/src/api/specs/library.e2e-spec.ts b/e2e/src/api/specs/library.e2e-spec.ts index 3e4f971cfd..18becec770 100644 --- a/e2e/src/api/specs/library.e2e-spec.ts +++ b/e2e/src/api/specs/library.e2e-spec.ts @@ -6,12 +6,13 @@ import { getAllLibraries, scanLibrary, } from '@immich/sdk'; -import { existsSync, rmdirSync } from 'node:fs'; +import { cpSync, existsSync } from 'node:fs'; import { Socket } from 'socket.io-client'; import { userDto, uuidDto } from 'src/fixtures'; import { errorDto } from 'src/responses'; import { app, asBearerAuth, testAssetDir, testAssetDirInternal, utils } from 'src/utils'; import request from 'supertest'; +import { utimes } from 'utimes'; import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest'; const scan = async (accessToken: string, id: string, dto: ScanLibraryDto = {}) => @@ -26,23 +27,21 @@ describe('/library', () => { beforeAll(async () => { await utils.resetDatabase(); admin = await utils.adminSetup(); + await utils.resetAdminConfig(admin.accessToken); user = await utils.userSetup(admin.accessToken, userDto.user1); library = await utils.createLibrary(admin.accessToken, { ownerId: admin.userId, type: LibraryType.External }); websocket = await utils.connectWebsocket(admin.accessToken); + utils.createImageFile(`${testAssetDir}/temp/directoryA/assetA.png`); + utils.createImageFile(`${testAssetDir}/temp/directoryB/assetB.png`); }); afterAll(() => { utils.disconnectWebsocket(websocket); + utils.resetTempFolder(); }); beforeEach(() => { utils.resetEvents(); - const tempDir = `${testAssetDir}/temp`; - if (existsSync(tempDir)) { - rmdirSync(tempDir, { recursive: true }); - } - utils.createImageFile(`${testAssetDir}/temp/directoryA/assetA.png`); - utils.createImageFile(`${testAssetDir}/temp/directoryB/assetB.png`); }); describe('GET /library', () => { @@ -357,95 +356,6 @@ describe('/library', () => { }); }); - describe('DELETE /library/:id', () => { - it('should require authentication', async () => { - const { status, body } = await request(app).delete(`/library/${uuidDto.notFound}`); - - expect(status).toBe(401); - expect(body).toEqual(errorDto.unauthorized); - }); - - it('should not delete the last upload library', async () => { - const libraries = await getAllLibraries( - { $type: LibraryType.Upload }, - { headers: asBearerAuth(admin.accessToken) }, - ); - - const adminLibraries = libraries.filter((library) => library.ownerId === admin.userId); - expect(adminLibraries.length).toBeGreaterThanOrEqual(1); - const lastLibrary = adminLibraries.pop() as LibraryResponseDto; - - // delete all but the last upload library - for (const library of adminLibraries) { - const { status } = await request(app) - .delete(`/library/${library.id}`) - .set('Authorization', `Bearer ${admin.accessToken}`); - expect(status).toBe(204); - } - - const { status, body } = await request(app) - .delete(`/library/${lastLibrary.id}`) - .set('Authorization', `Bearer ${admin.accessToken}`); - - expect(body).toEqual(errorDto.noDeleteUploadLibrary); - expect(status).toBe(400); - }); - - it('should delete an external library', async () => { - const library = await utils.createLibrary(admin.accessToken, { - ownerId: admin.userId, - type: LibraryType.External, - }); - - const { status, body } = await request(app) - .delete(`/library/${library.id}`) - .set('Authorization', `Bearer ${admin.accessToken}`); - - expect(status).toBe(204); - expect(body).toEqual({}); - - const libraries = await getAllLibraries({}, { headers: asBearerAuth(admin.accessToken) }); - expect(libraries).not.toEqual( - expect.arrayContaining([ - expect.objectContaining({ - id: library.id, - }), - ]), - ); - }); - - it('should delete an external library with assets', async () => { - const library = await utils.createLibrary(admin.accessToken, { - ownerId: admin.userId, - type: LibraryType.External, - importPaths: [`${testAssetDirInternal}/temp`], - }); - - await scan(admin.accessToken, library.id); - await utils.waitForWebsocketEvent({ event: 'assetUpload', total: 2 }); - - const { status, body } = await request(app) - .delete(`/library/${library.id}`) - .set('Authorization', `Bearer ${admin.accessToken}`); - - expect(status).toBe(204); - expect(body).toEqual({}); - - const libraries = await getAllLibraries({}, { headers: asBearerAuth(admin.accessToken) }); - expect(libraries).not.toEqual( - expect.arrayContaining([ - expect.objectContaining({ - id: library.id, - }), - ]), - ); - - // ensure no files get deleted - expect(existsSync(`${testAssetDir}/temp/directoryA/assetA.png`)).toBe(true); - expect(existsSync(`${testAssetDir}/temp/directoryB/assetB.png`)).toBe(true); - }); - }); - describe('GET /library/:id/statistics', () => { it('should require authentication', async () => { const { status, body } = await request(app).get(`/library/${uuidDto.notFound}/statistics`); @@ -549,6 +459,150 @@ describe('/library', () => { const { assets: newAssets } = await utils.metadataSearch(admin.accessToken, { libraryId: library.id }); expect(newAssets.count).toBe(3); + utils.removeImageFile(`${testAssetDir}/temp/directoryA/assetB.png`); + }); + + it('should offline missing files', async () => { + utils.createImageFile(`${testAssetDir}/temp/directoryA/assetB.png`); + const library = await utils.createLibrary(admin.accessToken, { + ownerId: admin.userId, + type: LibraryType.External, + importPaths: [`${testAssetDirInternal}/temp`], + }); + + await scan(admin.accessToken, library.id); + await utils.waitForQueueFinish(admin.accessToken, 'library'); + + utils.removeImageFile(`${testAssetDir}/temp/directoryA/assetB.png`); + + await scan(admin.accessToken, library.id); + await utils.waitForQueueFinish(admin.accessToken, 'library'); + + const { assets } = await utils.metadataSearch(admin.accessToken, { libraryId: library.id }); + + expect(assets.items).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + isOffline: true, + originalFileName: 'assetB.png', + }), + ]), + ); + }); + + it('should scan new files', async () => { + const library = await utils.createLibrary(admin.accessToken, { + ownerId: admin.userId, + type: LibraryType.External, + importPaths: [`${testAssetDirInternal}/temp`], + }); + + await scan(admin.accessToken, library.id); + await utils.waitForQueueFinish(admin.accessToken, 'library'); + + utils.createImageFile(`${testAssetDir}/temp/directoryA/assetC.png`); + + await scan(admin.accessToken, library.id); + await utils.waitForQueueFinish(admin.accessToken, 'library'); + + utils.removeImageFile(`${testAssetDir}/temp/directoryA/assetC.png`); + const { assets } = await utils.metadataSearch(admin.accessToken, { libraryId: library.id }); + + expect(assets.items).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + originalFileName: 'assetC.png', + }), + ]), + ); + }); + + describe('with refreshModifiedFiles=true', () => { + it('should reimport modified files', async () => { + const library = await utils.createLibrary(admin.accessToken, { + ownerId: admin.userId, + type: LibraryType.External, + importPaths: [`${testAssetDirInternal}/temp`], + }); + + utils.createImageFile(`${testAssetDir}/temp/directoryA/assetB.jpg`); + await utimes(`${testAssetDir}/temp/directoryA/assetB.jpg`, 447_775_200_000); + + await scan(admin.accessToken, library.id); + await utils.waitForQueueFinish(admin.accessToken, 'library'); + + cpSync(`${testAssetDir}/albums/nature/tanners_ridge.jpg`, `${testAssetDir}/temp/directoryA/assetB.jpg`); + await utimes(`${testAssetDir}/temp/directoryA/assetB.jpg`, 447_775_200_001); + + await scan(admin.accessToken, library.id, { refreshModifiedFiles: true }); + await utils.waitForQueueFinish(admin.accessToken, 'library'); + await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction'); + utils.removeImageFile(`${testAssetDir}/temp/directoryA/assetB.jpg`); + + const { assets } = await utils.metadataSearch(admin.accessToken, { + libraryId: library.id, + model: 'NIKON D750', + }); + expect(assets.count).toBe(1); + }); + + it('should not reimport unmodified files', async () => { + const library = await utils.createLibrary(admin.accessToken, { + ownerId: admin.userId, + type: LibraryType.External, + importPaths: [`${testAssetDirInternal}/temp`], + }); + + utils.createImageFile(`${testAssetDir}/temp/directoryA/assetB.jpg`); + await utimes(`${testAssetDir}/temp/directoryA/assetB.jpg`, 447_775_200_000); + + await scan(admin.accessToken, library.id); + await utils.waitForQueueFinish(admin.accessToken, 'library'); + + cpSync(`${testAssetDir}/albums/nature/tanners_ridge.jpg`, `${testAssetDir}/temp/directoryA/assetB.jpg`); + await utimes(`${testAssetDir}/temp/directoryA/assetB.jpg`, 447_775_200_000); + + await scan(admin.accessToken, library.id, { refreshModifiedFiles: true }); + await utils.waitForQueueFinish(admin.accessToken, 'library'); + await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction'); + utils.removeImageFile(`${testAssetDir}/temp/directoryA/assetB.jpg`); + + const { assets } = await utils.metadataSearch(admin.accessToken, { + libraryId: library.id, + model: 'NIKON D750', + }); + expect(assets.count).toBe(0); + }); + }); + + describe('with refreshAllFiles=true', () => { + it('should reimport all files', async () => { + const library = await utils.createLibrary(admin.accessToken, { + ownerId: admin.userId, + type: LibraryType.External, + importPaths: [`${testAssetDirInternal}/temp`], + }); + + utils.createImageFile(`${testAssetDir}/temp/directoryA/assetB.jpg`); + await utimes(`${testAssetDir}/temp/directoryA/assetB.jpg`, 447_775_200_000); + + await scan(admin.accessToken, library.id); + await utils.waitForQueueFinish(admin.accessToken, 'library'); + + cpSync(`${testAssetDir}/albums/nature/tanners_ridge.jpg`, `${testAssetDir}/temp/directoryA/assetB.jpg`); + await utimes(`${testAssetDir}/temp/directoryA/assetB.jpg`, 447_775_200_000); + + await scan(admin.accessToken, library.id, { refreshAllFiles: true }); + await utils.waitForQueueFinish(admin.accessToken, 'library'); + await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction'); + utils.removeImageFile(`${testAssetDir}/temp/directoryA/assetB.jpg`); + + const { assets } = await utils.metadataSearch(admin.accessToken, { + libraryId: library.id, + model: 'NIKON D750', + }); + expect(assets.count).toBe(1); + }); }); }); @@ -559,6 +613,72 @@ describe('/library', () => { expect(status).toBe(401); expect(body).toEqual(errorDto.unauthorized); }); + + it('should remove offline files', async () => { + const library = await utils.createLibrary(admin.accessToken, { + ownerId: admin.userId, + type: LibraryType.External, + importPaths: [`${testAssetDirInternal}/temp`], + }); + + utils.createImageFile(`${testAssetDir}/temp/directoryA/assetB.png`); + + await scan(admin.accessToken, library.id); + await utils.waitForQueueFinish(admin.accessToken, 'library'); + + const { assets: initialAssets } = await utils.metadataSearch(admin.accessToken, { + libraryId: library.id, + }); + expect(initialAssets.count).toBe(3); + + utils.removeImageFile(`${testAssetDir}/temp/directoryA/assetB.png`); + + await scan(admin.accessToken, library.id); + await utils.waitForQueueFinish(admin.accessToken, 'library'); + + const { assets: offlineAssets } = await utils.metadataSearch(admin.accessToken, { + libraryId: library.id, + isOffline: true, + }); + expect(offlineAssets.count).toBe(1); + + const { status } = await request(app) + .post(`/library/${library.id}/removeOffline`) + .set('Authorization', `Bearer ${admin.accessToken}`) + .send(); + expect(status).toBe(204); + await utils.waitForQueueFinish(admin.accessToken, 'library'); + await utils.waitForQueueFinish(admin.accessToken, 'backgroundTask'); + + const { assets } = await utils.metadataSearch(admin.accessToken, { libraryId: library.id }); + + expect(assets.count).toBe(2); + }); + + it('should not remove online files', async () => { + const library = await utils.createLibrary(admin.accessToken, { + ownerId: admin.userId, + type: LibraryType.External, + importPaths: [`${testAssetDirInternal}/temp`], + }); + + await scan(admin.accessToken, library.id); + await utils.waitForQueueFinish(admin.accessToken, 'library'); + + const { assets: assetsBefore } = await utils.metadataSearch(admin.accessToken, { libraryId: library.id }); + expect(assetsBefore.count).toBeGreaterThan(1); + + const { status } = await request(app) + .post(`/library/${library.id}/removeOffline`) + .set('Authorization', `Bearer ${admin.accessToken}`) + .send(); + expect(status).toBe(204); + await utils.waitForQueueFinish(admin.accessToken, 'library'); + + const { assets } = await utils.metadataSearch(admin.accessToken, { libraryId: library.id }); + + expect(assets).toEqual(assetsBefore); + }); }); describe('POST /library/:id/validate', () => { @@ -608,4 +728,93 @@ describe('/library', () => { }); }); }); + + describe('DELETE /library/:id', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).delete(`/library/${uuidDto.notFound}`); + + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should not delete the last upload library', async () => { + const libraries = await getAllLibraries( + { $type: LibraryType.Upload }, + { headers: asBearerAuth(admin.accessToken) }, + ); + + const adminLibraries = libraries.filter((library) => library.ownerId === admin.userId); + expect(adminLibraries.length).toBeGreaterThanOrEqual(1); + const lastLibrary = adminLibraries.pop() as LibraryResponseDto; + + // delete all but the last upload library + for (const library of adminLibraries) { + const { status } = await request(app) + .delete(`/library/${library.id}`) + .set('Authorization', `Bearer ${admin.accessToken}`); + expect(status).toBe(204); + } + + const { status, body } = await request(app) + .delete(`/library/${lastLibrary.id}`) + .set('Authorization', `Bearer ${admin.accessToken}`); + + expect(body).toEqual(errorDto.noDeleteUploadLibrary); + expect(status).toBe(400); + }); + + it('should delete an external library', async () => { + const library = await utils.createLibrary(admin.accessToken, { + ownerId: admin.userId, + type: LibraryType.External, + }); + + const { status, body } = await request(app) + .delete(`/library/${library.id}`) + .set('Authorization', `Bearer ${admin.accessToken}`); + + expect(status).toBe(204); + expect(body).toEqual({}); + + const libraries = await getAllLibraries({}, { headers: asBearerAuth(admin.accessToken) }); + expect(libraries).not.toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: library.id, + }), + ]), + ); + }); + + it('should delete an external library with assets', async () => { + const library = await utils.createLibrary(admin.accessToken, { + ownerId: admin.userId, + type: LibraryType.External, + importPaths: [`${testAssetDirInternal}/temp`], + }); + + await scan(admin.accessToken, library.id); + await utils.waitForWebsocketEvent({ event: 'assetUpload', total: 2 }); + + const { status, body } = await request(app) + .delete(`/library/${library.id}`) + .set('Authorization', `Bearer ${admin.accessToken}`); + + expect(status).toBe(204); + expect(body).toEqual({}); + + const libraries = await getAllLibraries({}, { headers: asBearerAuth(admin.accessToken) }); + expect(libraries).not.toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: library.id, + }), + ]), + ); + + // ensure no files get deleted + expect(existsSync(`${testAssetDir}/temp/directoryA/assetA.png`)).toBe(true); + expect(existsSync(`${testAssetDir}/temp/directoryB/assetB.png`)).toBe(true); + }); + }); }); diff --git a/e2e/src/api/specs/search.e2e-spec.ts b/e2e/src/api/specs/search.e2e-spec.ts index a613cdd288..d3274ed729 100644 --- a/e2e/src/api/specs/search.e2e-spec.ts +++ b/e2e/src/api/specs/search.e2e-spec.ts @@ -1,4 +1,4 @@ -import { AssetFileUploadResponseDto, LoginResponseDto, deleteAssets, updateAsset } from '@immich/sdk'; +import { AssetFileUploadResponseDto, LoginResponseDto, deleteAssets, getMapMarkers, updateAsset } from '@immich/sdk'; import { DateTime } from 'luxon'; import { readFile } from 'node:fs/promises'; import { join } from 'node:path'; @@ -32,6 +32,9 @@ describe('/search', () => { let assetGlarus: AssetFileUploadResponseDto; let assetSprings: AssetFileUploadResponseDto; let assetLast: AssetFileUploadResponseDto; + let cities: string[]; + let states: string[]; + let countries: string[]; beforeAll(async () => { await utils.resetDatabase(); @@ -79,7 +82,7 @@ describe('/search', () => { } // note: the coordinates here are not the actual coordinates of the images and are random for most of them - const cities = [ + const coordinates = [ { latitude: 48.853_41, longitude: 2.3488 }, // paris { latitude: 63.0695, longitude: -151.0074 }, // denali { latitude: 52.524_37, longitude: 13.410_53 }, // berlin @@ -101,7 +104,7 @@ describe('/search', () => { ]; const updates = assets.map((asset, i) => - updateAsset({ id: asset.id, updateAssetDto: cities[i] }, { headers: asBearerAuth(admin.accessToken) }), + updateAsset({ id: asset.id, updateAssetDto: coordinates[i] }, { headers: asBearerAuth(admin.accessToken) }), ); await Promise.all(updates); @@ -133,6 +136,12 @@ describe('/search', () => { assetLast = assets.at(-1) as AssetFileUploadResponseDto; await deleteAssets({ assetBulkDeleteDto: { ids: [assetSilver.id] } }, { headers: asBearerAuth(admin.accessToken) }); + + const mapMarkers = await getMapMarkers({}, { headers: asBearerAuth(admin.accessToken) }); + const nonTrashed = mapMarkers.filter((mark) => mark.id !== assetSilver.id); + cities = [...new Set(nonTrashed.map((mark) => mark.city).filter((entry): entry is string => !!entry))].sort(); + states = [...new Set(nonTrashed.map((mark) => mark.state).filter((entry): entry is string => !!entry))].sort(); + countries = [...new Set(nonTrashed.map((mark) => mark.country).filter((entry): entry is string => !!entry))].sort(); }, 30_000); afterAll(async () => { @@ -452,21 +461,7 @@ describe('/search', () => { const { status, body } = await request(app) .get('/search/suggestions?type=country') .set('Authorization', `Bearer ${admin.accessToken}`); - expect(body).toEqual([ - 'Cuba', - 'France', - 'Georgia', - 'Germany', - 'Ghana', - 'Japan', - 'Morocco', - "People's Republic of China", - 'Russian Federation', - 'Singapore', - 'Spain', - 'Switzerland', - 'United States of America', - ]); + expect(body).toEqual(countries); expect(status).toBe(200); }); @@ -474,23 +469,7 @@ describe('/search', () => { const { status, body } = await request(app) .get('/search/suggestions?type=state') .set('Authorization', `Bearer ${admin.accessToken}`); - expect(body).toEqual([ - 'Accra, Greater Accra', - 'Berlin', - 'Glarus, Glarus', - 'Havana', - 'Marrakech, Marrakesh-Safi', - 'Mesa County, Colorado', - 'Neshoba County, Mississippi', - 'New York', - 'Page County, Virginia', - 'Paris, Île-de-France', - 'Province of Córdoba, Andalusia', - 'Shanghai Municipality, Shanghai', - 'St.-Petersburg', - 'Tbilisi', - 'Tokyo', - ]); + expect(body).toEqual(states); expect(status).toBe(200); }); @@ -498,24 +477,7 @@ describe('/search', () => { const { status, body } = await request(app) .get('/search/suggestions?type=city') .set('Authorization', `Bearer ${admin.accessToken}`); - expect(body).toEqual([ - 'Accra', - 'Berlin', - 'Glarus', - 'Havana', - 'Marrakesh', - 'Montalbán de Córdoba', - 'New York City', - 'Palisade', - 'Paris', - 'Philadelphia', - 'Saint Petersburg', - 'Shanghai', - 'Singapore', - 'Stanley', - 'Tbilisi', - 'Tokyo', - ]); + expect(body).toEqual(cities); expect(status).toBe(200); }); diff --git a/e2e/src/utils.ts b/e2e/src/utils.ts index d8302a9e31..a46653eb11 100644 --- a/e2e/src/utils.ts +++ b/e2e/src/utils.ts @@ -1,4 +1,5 @@ import { + AllJobStatusResponseDto, AssetFileUploadResponseDto, AssetResponseDto, CreateAlbumDto, @@ -18,11 +19,14 @@ import { defaults, deleteAssets, getAllAssets, + getAllJobsStatus, getAssetInfo, + getConfigDefaults, login, searchMetadata, setAdminOnboarding, signUpAdmin, + updateConfig, validate, } from '@immich/sdk'; import { BrowserContext } from '@playwright/test'; @@ -31,6 +35,7 @@ import { createHash } from 'node:crypto'; import { existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import path, { dirname } from 'node:path'; +import { setTimeout as setAsyncTimeout } from 'node:timers/promises'; import { promisify } from 'node:util'; import pg from 'pg'; import { io, type Socket } from 'socket.io-client'; @@ -136,6 +141,7 @@ export const utils = { 'user_token', 'users', 'system_metadata', + 'system_config', ]; const sql: string[] = []; @@ -145,7 +151,12 @@ export const utils = { } for (const table of tables) { - sql.push(`DELETE FROM ${table} CASCADE;`); + if (table === 'system_metadata') { + // prevent reverse geocoder from being re-initialized + sql.push(`DELETE FROM "system_metadata" where "key" != 'reverse-geocoding-state';`); + } else { + sql.push(`DELETE FROM ${table} CASCADE;`); + } } await client.query(sql.join('\n')); @@ -209,35 +220,33 @@ export const utils = { } }, - waitForWebsocketEvent: async ({ event, id, total: count, timeout: ms }: WaitOptions): Promise => { - if (!id && !count) { - throw new Error('id or count must be provided for waitForWebsocketEvent'); - } - - const type = id ? `id=${id}` : `count=${count}`; - console.log(`Waiting for ${event} [${type}]`); - const set = events[event]; - if ((id && set.has(id)) || (count && set.size >= count)) { - return; - } - + waitForWebsocketEvent: ({ event, id, total: count, timeout: ms }: WaitOptions): Promise => { return new Promise((resolve, reject) => { + if (!id && !count) { + reject(new Error('id or count must be provided for waitForWebsocketEvent')); + } + const timeout = setTimeout(() => reject(new Error(`Timed out waiting for ${event} event`)), ms || 10_000); + const type = id ? `id=${id}` : `count=${count}`; + console.log(`Waiting for ${event} [${type}]`); + const set = events[event]; + const onId = () => { + clearTimeout(timeout); + resolve(); + }; + if ((id && set.has(id)) || (count && set.size >= count)) { + onId(); + return; + } if (id) { - idCallbacks[id] = () => { - clearTimeout(timeout); - resolve(); - }; + idCallbacks[id] = onId; } if (count) { countCallbacks[event] = { count, - callback: () => { - clearTimeout(timeout); - resolve(); - }, + callback: onId, }; } }); @@ -309,9 +318,7 @@ export const utils = { if (!existsSync(dirname(path))) { mkdirSync(dirname(path), { recursive: true }); } - if (!existsSync(path)) { - writeFileSync(path, makeRandomImage()); - } + writeFileSync(path, makeRandomImage()); }, removeImageFile: (path: string) => { @@ -406,6 +413,39 @@ export const utils = { }, ]), + resetTempFolder: () => { + rmSync(`${testAssetDir}/temp`, { recursive: true, force: true }); + mkdirSync(`${testAssetDir}/temp`, { recursive: true }); + }, + + resetAdminConfig: async (accessToken: string) => { + const defaultConfig = await getConfigDefaults({ headers: asBearerAuth(accessToken) }); + await updateConfig({ systemConfigDto: defaultConfig }, { headers: asBearerAuth(accessToken) }); + }, + + isQueueEmpty: async (accessToken: string, queue: keyof AllJobStatusResponseDto) => { + const queues = await getAllJobsStatus({ headers: asBearerAuth(accessToken) }); + const jobCounts = queues[queue].jobCounts; + return !jobCounts.active && !jobCounts.waiting; + }, + + waitForQueueFinish: (accessToken: string, queue: keyof AllJobStatusResponseDto, ms?: number) => { + return new Promise(async (resolve, reject) => { + const timeout = setTimeout(() => reject(new Error('Timed out waiting for queue to empty')), ms || 10_000); + + while (true) { + const done = await utils.isQueueEmpty(accessToken, queue); + if (done) { + break; + } + await setAsyncTimeout(200); + } + + clearTimeout(timeout); + resolve(); + }); + }, + cliLogin: async (accessToken: string) => { const key = await utils.createApiKey(accessToken); await immichCli(['login', app, `${key.secret}`]); diff --git a/e2e/vitest.config.ts b/e2e/vitest.config.ts index d7dcde4c38..9b9670c042 100644 --- a/e2e/vitest.config.ts +++ b/e2e/vitest.config.ts @@ -12,7 +12,7 @@ export default defineConfig({ test: { include: ['src/{api,cli}/specs/*.e2e-spec.ts'], globalSetup, - testTimeout: 10_000, + testTimeout: 15_000, poolOptions: { threads: { singleThread: true, diff --git a/machine-learning/README.md b/machine-learning/README.md index b30e18ceb4..d7d099a87e 100644 --- a/machine-learning/README.md +++ b/machine-learning/README.md @@ -7,7 +7,7 @@ This project uses [Poetry](https://python-poetry.org/docs/#installation), so be sure to install it first. Running `poetry install --no-root --with dev --with cpu` will install everything you need in an isolated virtual environment. -CUDA and OpenVINO are supported as acceleration APIs. To use them, you can replace `--with cpu` with either of `--with cuda` or `--with openvino`. +CUDA and OpenVINO are supported as acceleration APIs. To use them, you can replace `--with cpu` with either of `--with cuda` or `--with openvino`. In the case of CUDA, a [compute capability](https://developer.nvidia.com/cuda-gpus) of 5.2 or higher is required. To add or remove dependencies, you can use the commands `poetry add $PACKAGE_NAME` and `poetry remove $PACKAGE_NAME`, respectively. Be sure to commit the `poetry.lock` and `pyproject.toml` files with `poetry lock --no-update` to reflect any changes in dependencies. diff --git a/machine-learning/export/Dockerfile b/machine-learning/export/Dockerfile index 19857c4d78..92ec7b576a 100644 --- a/machine-learning/export/Dockerfile +++ b/machine-learning/export/Dockerfile @@ -1,4 +1,4 @@ -FROM mambaorg/micromamba:bookworm-slim@sha256:987299cff637df33d00a9969a577f2ac74d37fa070db6aa45b083346ba390959 as builder +FROM mambaorg/micromamba:bookworm-slim@sha256:4de614588f7b3d6598a6cd571297157b57ac85b1a8ff854cf10231bb0c50e3ac as builder ENV NODE_ENV=production \ TRANSFORMERS_CACHE=/cache \ diff --git a/machine-learning/poetry.lock b/machine-learning/poetry.lock index d7dcd94439..6b98b8f521 100644 --- a/machine-learning/poetry.lock +++ b/machine-learning/poetry.lock @@ -2847,28 +2847,28 @@ files = [ [[package]] name = "ruff" -version = "0.3.5" +version = "0.3.7" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.3.5-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:aef5bd3b89e657007e1be6b16553c8813b221ff6d92c7526b7e0227450981eac"}, - {file = "ruff-0.3.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:89b1e92b3bd9fca249153a97d23f29bed3992cff414b222fcd361d763fc53f12"}, - {file = "ruff-0.3.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e55771559c89272c3ebab23326dc23e7f813e492052391fe7950c1a5a139d89"}, - {file = "ruff-0.3.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dabc62195bf54b8a7876add6e789caae0268f34582333cda340497c886111c39"}, - {file = "ruff-0.3.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a05f3793ba25f194f395578579c546ca5d83e0195f992edc32e5907d142bfa3"}, - {file = "ruff-0.3.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:dfd3504e881082959b4160ab02f7a205f0fadc0a9619cc481982b6837b2fd4c0"}, - {file = "ruff-0.3.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87258e0d4b04046cf1d6cc1c56fadbf7a880cc3de1f7294938e923234cf9e498"}, - {file = "ruff-0.3.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:712e71283fc7d9f95047ed5f793bc019b0b0a29849b14664a60fd66c23b96da1"}, - {file = "ruff-0.3.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a532a90b4a18d3f722c124c513ffb5e5eaff0cc4f6d3aa4bda38e691b8600c9f"}, - {file = "ruff-0.3.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:122de171a147c76ada00f76df533b54676f6e321e61bd8656ae54be326c10296"}, - {file = "ruff-0.3.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d80a6b18a6c3b6ed25b71b05eba183f37d9bc8b16ace9e3d700997f00b74660b"}, - {file = "ruff-0.3.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a7b6e63194c68bca8e71f81de30cfa6f58ff70393cf45aab4c20f158227d5936"}, - {file = "ruff-0.3.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a759d33a20c72f2dfa54dae6e85e1225b8e302e8ac655773aff22e542a300985"}, - {file = "ruff-0.3.5-py3-none-win32.whl", hash = "sha256:9d8605aa990045517c911726d21293ef4baa64f87265896e491a05461cae078d"}, - {file = "ruff-0.3.5-py3-none-win_amd64.whl", hash = "sha256:dc56bb16a63c1303bd47563c60482a1512721053d93231cf7e9e1c6954395a0e"}, - {file = "ruff-0.3.5-py3-none-win_arm64.whl", hash = "sha256:faeeae9905446b975dcf6d4499dc93439b131f1443ee264055c5716dd947af55"}, - {file = "ruff-0.3.5.tar.gz", hash = "sha256:a067daaeb1dc2baf9b82a32dae67d154d95212080c80435eb052d95da647763d"}, + {file = "ruff-0.3.7-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0e8377cccb2f07abd25e84fc5b2cbe48eeb0fea9f1719cad7caedb061d70e5ce"}, + {file = "ruff-0.3.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:15a4d1cc1e64e556fa0d67bfd388fed416b7f3b26d5d1c3e7d192c897e39ba4b"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d28bdf3d7dc71dd46929fafeec98ba89b7c3550c3f0978e36389b5631b793663"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:379b67d4f49774ba679593b232dcd90d9e10f04d96e3c8ce4a28037ae473f7bb"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c060aea8ad5ef21cdfbbe05475ab5104ce7827b639a78dd55383a6e9895b7c51"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ebf8f615dde968272d70502c083ebf963b6781aacd3079081e03b32adfe4d58a"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d48098bd8f5c38897b03604f5428901b65e3c97d40b3952e38637b5404b739a2"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da8a4fda219bf9024692b1bc68c9cff4b80507879ada8769dc7e985755d662ea"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c44e0149f1d8b48c4d5c33d88c677a4aa22fd09b1683d6a7ff55b816b5d074f"}, + {file = "ruff-0.3.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3050ec0af72b709a62ecc2aca941b9cd479a7bf2b36cc4562f0033d688e44fa1"}, + {file = "ruff-0.3.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a29cc38e4c1ab00da18a3f6777f8b50099d73326981bb7d182e54a9a21bb4ff7"}, + {file = "ruff-0.3.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b15cc59c19edca917f51b1956637db47e200b0fc5e6e1878233d3a938384b0b"}, + {file = "ruff-0.3.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e491045781b1e38b72c91247cf4634f040f8d0cb3e6d3d64d38dcf43616650b4"}, + {file = "ruff-0.3.7-py3-none-win32.whl", hash = "sha256:bc931de87593d64fad3a22e201e55ad76271f1d5bfc44e1a1887edd0903c7d9f"}, + {file = "ruff-0.3.7-py3-none-win_amd64.whl", hash = "sha256:5ef0e501e1e39f35e03c2acb1d1238c595b8bb36cf7a170e7c1df1b73da00e74"}, + {file = "ruff-0.3.7-py3-none-win_arm64.whl", hash = "sha256:789e144f6dc7019d1f92a812891c645274ed08af6037d11fc65fcbc183b7d59f"}, + {file = "ruff-0.3.7.tar.gz", hash = "sha256:d5c1aebee5162c2226784800ae031f660c350e7a3402c4d1f8ea4e97e232e3ba"}, ] [[package]] diff --git a/mobile/assets/i18n/en-US.json b/mobile/assets/i18n/en-US.json index 3f2c0519b8..1a6ca76757 100644 --- a/mobile/assets/i18n/en-US.json +++ b/mobile/assets/i18n/en-US.json @@ -511,5 +511,7 @@ "version_announcement_overlay_title": "New Server Version Available \uD83C\uDF89", "viewer_remove_from_stack": "Remove from Stack", "viewer_stack_use_as_main_asset": "Use as Main Asset", - "viewer_unstack": "Un-Stack" -} \ No newline at end of file + "viewer_unstack": "Un-Stack", + "haptic_feedback_title": "Haptic Feedback", + "haptic_feedback_switch": "Enable haptic feedback" +} diff --git a/mobile/lib/modules/asset_viewer/views/gallery_viewer.dart b/mobile/lib/modules/asset_viewer/views/gallery_viewer.dart index 059c0c976d..33de70d757 100644 --- a/mobile/lib/modules/asset_viewer/views/gallery_viewer.dart +++ b/mobile/lib/modules/asset_viewer/views/gallery_viewer.dart @@ -20,6 +20,7 @@ import 'package:immich_mobile/modules/asset_viewer/ui/gallery_app_bar.dart'; import 'package:immich_mobile/modules/asset_viewer/views/video_viewer_page.dart'; import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart'; import 'package:immich_mobile/modules/settings/services/app_settings.service.dart'; +import 'package:immich_mobile/shared/providers/haptic_feedback.provider.dart'; import 'package:immich_mobile/shared/ui/immich_image.dart'; import 'package:immich_mobile/shared/ui/immich_thumbnail.dart'; import 'package:immich_mobile/shared/ui/photo_view/photo_view_gallery.dart'; @@ -303,7 +304,9 @@ class GalleryViewerPage extends HookConsumerWidget { scrollDirection: Axis.horizontal, onPageChanged: (value) async { final next = currentIndex.value < value ? value + 1 : value - 1; - HapticFeedback.selectionClick(); + + ref.read(hapticFeedbackProvider.notifier).selectionClick(); + currentIndex.value = value; stackIndex.value = -1; isPlayingVideo.value = false; diff --git a/mobile/lib/modules/backup/ui/album_info_card.dart b/mobile/lib/modules/backup/ui/album_info_card.dart index 5380360ff1..a274f1c5e8 100644 --- a/mobile/lib/modules/backup/ui/album_info_card.dart +++ b/mobile/lib/modules/backup/ui/album_info_card.dart @@ -1,13 +1,13 @@ import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/backup/models/available_album.model.dart'; import 'package:immich_mobile/modules/backup/providers/backup.provider.dart'; import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/shared/providers/haptic_feedback.provider.dart'; import 'package:immich_mobile/shared/ui/immich_toast.dart'; class AlbumInfoCard extends HookConsumerWidget { @@ -21,6 +21,7 @@ class AlbumInfoCard extends HookConsumerWidget { ref.watch(backupProvider).selectedBackupAlbums.contains(album); final bool isExcluded = ref.watch(backupProvider).excludedBackupAlbums.contains(album); + final isDarkTheme = context.isDarkTheme; ColorFilter selectedFilter = ColorFilter.mode( @@ -78,7 +79,7 @@ class AlbumInfoCard extends HookConsumerWidget { return GestureDetector( onTap: () { - HapticFeedback.selectionClick(); + ref.read(hapticFeedbackProvider.notifier).selectionClick(); if (isSelected) { ref.read(backupProvider.notifier).removeAlbumForBackup(album); @@ -87,7 +88,7 @@ class AlbumInfoCard extends HookConsumerWidget { } }, onDoubleTap: () { - HapticFeedback.selectionClick(); + ref.read(hapticFeedbackProvider.notifier).selectionClick(); if (isExcluded) { // Remove from exclude album list diff --git a/mobile/lib/modules/backup/ui/album_info_list_tile.dart b/mobile/lib/modules/backup/ui/album_info_list_tile.dart index dcf0923a11..40fdfa8897 100644 --- a/mobile/lib/modules/backup/ui/album_info_list_tile.dart +++ b/mobile/lib/modules/backup/ui/album_info_list_tile.dart @@ -1,6 +1,5 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -8,6 +7,7 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/backup/models/available_album.model.dart'; import 'package:immich_mobile/modules/backup/providers/backup.provider.dart'; import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/shared/providers/haptic_feedback.provider.dart'; import 'package:immich_mobile/shared/ui/immich_toast.dart'; class AlbumInfoListTile extends HookConsumerWidget { @@ -68,7 +68,7 @@ class AlbumInfoListTile extends HookConsumerWidget { return GestureDetector( onDoubleTap: () { - HapticFeedback.selectionClick(); + ref.watch(hapticFeedbackProvider.notifier).selectionClick(); if (isExcluded) { // Remove from exclude album list @@ -93,7 +93,7 @@ class AlbumInfoListTile extends HookConsumerWidget { tileColor: buildTileColor(), contentPadding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), onTap: () { - HapticFeedback.selectionClick(); + ref.read(hapticFeedbackProvider.notifier).selectionClick(); if (isSelected) { ref.read(backupProvider.notifier).removeAlbumForBackup(album); } else { diff --git a/mobile/lib/modules/home/ui/asset_grid/group_divider_title.dart b/mobile/lib/modules/home/ui/asset_grid/group_divider_title.dart index 0c06fc1a1b..03236e3930 100644 --- a/mobile/lib/modules/home/ui/asset_grid/group_divider_title.dart +++ b/mobile/lib/modules/home/ui/asset_grid/group_divider_title.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart'; import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart'; import 'package:immich_mobile/modules/settings/services/app_settings.service.dart'; +import 'package:immich_mobile/shared/providers/haptic_feedback.provider.dart'; class GroupDividerTitle extends HookConsumerWidget { const GroupDividerTitle({ @@ -38,7 +38,7 @@ class GroupDividerTitle extends HookConsumerWidget { ); void handleTitleIconClick() { - HapticFeedback.heavyImpact(); + ref.read(hapticFeedbackProvider.notifier).heavyImpact(); if (selected) { onDeselect(); } else { diff --git a/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid_view.dart b/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid_view.dart index 4c520fe6fc..5ece42d5cf 100644 --- a/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid_view.dart +++ b/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid_view.dart @@ -6,7 +6,7 @@ import 'package:collection/collection.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; -import 'package:flutter/services.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/collection_extensions.dart'; import 'package:immich_mobile/modules/asset_viewer/providers/scroll_notifier.provider.dart'; @@ -15,6 +15,7 @@ import 'package:immich_mobile/modules/home/ui/asset_grid/thumbnail_image.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/thumbnail_placeholder.dart'; import 'package:immich_mobile/modules/home/ui/control_bottom_app_bar.dart'; import 'package:immich_mobile/shared/models/asset.dart'; +import 'package:immich_mobile/shared/providers/haptic_feedback.provider.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import 'asset_grid_data_structure.dart'; @@ -27,7 +28,7 @@ typedef ImmichAssetGridSelectionListener = void Function( Set, ); -class ImmichAssetGridView extends StatefulWidget { +class ImmichAssetGridView extends ConsumerStatefulWidget { final RenderList renderList; final int assetsPerRow; final double margin; @@ -69,12 +70,12 @@ class ImmichAssetGridView extends StatefulWidget { }); @override - State createState() { + createState() { return ImmichAssetGridViewState(); } } -class ImmichAssetGridViewState extends State { +class ImmichAssetGridViewState extends ConsumerState { final ItemScrollController _itemScrollController = ItemScrollController(); final ScrollOffsetController _scrollOffsetController = ScrollOffsetController(); @@ -314,7 +315,7 @@ class ImmichAssetGridViewState extends State { final now = Timeline.now; if (now > (_hapticFeedbackTS + feedbackInterval)) { _hapticFeedbackTS = now; - HapticFeedback.mediumImpact(); + ref.read(hapticFeedbackProvider.notifier).mediumImpact(); } } } diff --git a/mobile/lib/modules/home/ui/asset_grid/thumbnail_image.dart b/mobile/lib/modules/home/ui/asset_grid/thumbnail_image.dart index a194bc2ade..f06be0289b 100644 --- a/mobile/lib/modules/home/ui/asset_grid/thumbnail_image.dart +++ b/mobile/lib/modules/home/ui/asset_grid/thumbnail_image.dart @@ -1,14 +1,15 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/shared/models/asset.dart'; +import 'package:immich_mobile/shared/providers/haptic_feedback.provider.dart'; import 'package:immich_mobile/shared/ui/immich_thumbnail.dart'; import 'package:immich_mobile/utils/storage_indicator.dart'; import 'package:isar/isar.dart'; -class ThumbnailImage extends StatelessWidget { +class ThumbnailImage extends ConsumerWidget { final Asset asset; final int index; final Asset Function(int index) loadAsset; @@ -37,7 +38,7 @@ class ThumbnailImage extends StatelessWidget { }); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { final assetContainerColor = context.isDarkTheme ? Colors.blueGrey : context.themeData.primaryColorLight; @@ -186,7 +187,7 @@ class ThumbnailImage extends StatelessWidget { }, onLongPress: () { onSelect?.call(); - HapticFeedback.heavyImpact(); + ref.read(hapticFeedbackProvider.notifier).heavyImpact(); }, child: Stack( children: [ diff --git a/mobile/lib/modules/login/providers/authentication.provider.dart b/mobile/lib/modules/login/providers/authentication.provider.dart index e010024332..03d06bd140 100644 --- a/mobile/lib/modules/login/providers/authentication.provider.dart +++ b/mobile/lib/modules/login/providers/authentication.provider.dart @@ -2,7 +2,6 @@ import 'dart:io'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_udid/flutter_udid.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/modules/album/providers/album.provider.dart'; @@ -92,7 +91,6 @@ class AuthenticationNotifier extends StateNotifier { serverUrl: serverUrl, ); } catch (e) { - HapticFeedback.vibrate(); debugPrint("Error logging in $e"); return false; } diff --git a/mobile/lib/modules/memories/ui/memory_lane.dart b/mobile/lib/modules/memories/ui/memory_lane.dart index eb72c15e8e..d48785a78b 100644 --- a/mobile/lib/modules/memories/ui/memory_lane.dart +++ b/mobile/lib/modules/memories/ui/memory_lane.dart @@ -1,10 +1,10 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/thumbnail_placeholder.dart'; import 'package:immich_mobile/modules/memories/providers/memory.provider.dart'; import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/shared/providers/haptic_feedback.provider.dart'; import 'package:immich_mobile/shared/ui/immich_image.dart'; class MemoryLane extends HookConsumerWidget { @@ -33,7 +33,9 @@ class MemoryLane extends HookConsumerWidget { return GestureDetector( onTap: () { - HapticFeedback.heavyImpact(); + ref + .read(hapticFeedbackProvider.notifier) + .heavyImpact(); context.pushRoute( MemoryRoute( memories: memories, diff --git a/mobile/lib/modules/memories/views/memory_page.dart b/mobile/lib/modules/memories/views/memory_page.dart index aa968303b3..9a7032f828 100644 --- a/mobile/lib/modules/memories/views/memory_page.dart +++ b/mobile/lib/modules/memories/views/memory_page.dart @@ -9,6 +9,7 @@ import 'package:immich_mobile/modules/memories/ui/memory_card.dart'; import 'package:immich_mobile/modules/memories/ui/memory_epilogue.dart'; import 'package:immich_mobile/modules/memories/ui/memory_progress_indicator.dart'; import 'package:immich_mobile/shared/models/asset.dart'; +import 'package:immich_mobile/shared/providers/haptic_feedback.provider.dart'; import 'package:immich_mobile/shared/ui/immich_image.dart'; @RoutePage() @@ -127,7 +128,7 @@ class MemoryPage extends HookConsumerWidget { } Future onAssetChanged(int otherIndex) async { - HapticFeedback.selectionClick(); + ref.read(hapticFeedbackProvider.notifier).selectionClick(); currentAssetPage.value = otherIndex; updateProgressText(); // Wait for page change animation to finish @@ -169,7 +170,7 @@ class MemoryPage extends HookConsumerWidget { scrollDirection: Axis.vertical, controller: memoryPageController, onPageChanged: (pageNumber) { - HapticFeedback.mediumImpact(); + ref.read(hapticFeedbackProvider.notifier).mediumImpact(); if (pageNumber < memories.length) { currentMemoryIndex.value = pageNumber; currentMemory.value = memories[pageNumber]; diff --git a/mobile/lib/modules/settings/services/app_settings.service.dart b/mobile/lib/modules/settings/services/app_settings.service.dart index 98e8464425..b7be3ca5e3 100644 --- a/mobile/lib/modules/settings/services/app_settings.service.dart +++ b/mobile/lib/modules/settings/services/app_settings.service.dart @@ -58,6 +58,7 @@ enum AppSettingsEnum { null, false, ), + enableHapticFeedback(StoreKey.enableHapticFeedback, null, true), ; const AppSettingsEnum(this.storeKey, this.hiveKey, this.defaultValue); diff --git a/mobile/lib/modules/settings/ui/language_settings.dart b/mobile/lib/modules/settings/ui/language_settings.dart index c3cdde6859..ebe8673135 100644 --- a/mobile/lib/modules/settings/ui/language_settings.dart +++ b/mobile/lib/modules/settings/ui/language_settings.dart @@ -23,36 +23,46 @@ class LanguageSettings extends HookConsumerWidget { return ListView( padding: const EdgeInsets.all(16), children: [ - DropdownMenu( - inputDecorationTheme: InputDecorationTheme( - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(20), - ), - contentPadding: const EdgeInsets.only(left: 16), - ), - menuStyle: MenuStyle( - shape: MaterialStatePropertyAll( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(15), - ), - ), - ), - menuHeight: context.height * 0.5, - hintText: "Languages", - label: const Text('Languages'), - dropdownMenuEntries: locales.keys - .map( - (countryName) => DropdownMenuEntry( - value: locales[countryName], - label: countryName, + LayoutBuilder( + builder: (context, constraints) { + return DropdownMenu( + width: constraints.maxWidth, + inputDecorationTheme: InputDecorationTheme( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(20), ), - ) - .toList(), - controller: textController, - onSelected: (value) { - if (value != null) { - selectedLocale.value = value; - } + contentPadding: const EdgeInsets.only(left: 16), + ), + menuStyle: MenuStyle( + shape: MaterialStatePropertyAll( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(15), + ), + ), + backgroundColor: MaterialStatePropertyAll( + context.isDarkTheme + ? Colors.grey[900]! + : context.scaffoldBackgroundColor, + ), + ), + menuHeight: context.height * 0.5, + hintText: "Languages", + label: const Text('Languages'), + dropdownMenuEntries: locales.keys + .map( + (countryName) => DropdownMenuEntry( + value: locales[countryName], + label: countryName, + ), + ) + .toList(), + controller: textController, + onSelected: (value) { + if (value != null) { + selectedLocale.value = value; + } + }, + ); }, ), const SizedBox(height: 16), diff --git a/mobile/lib/modules/settings/ui/preference_settings/haptic_setting.dart b/mobile/lib/modules/settings/ui/preference_settings/haptic_setting.dart new file mode 100644 index 0000000000..290dd5aafa --- /dev/null +++ b/mobile/lib/modules/settings/ui/preference_settings/haptic_setting.dart @@ -0,0 +1,38 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/modules/settings/services/app_settings.service.dart'; +import 'package:immich_mobile/modules/settings/ui/settings_sub_title.dart'; +import 'package:immich_mobile/modules/settings/ui/settings_switch_list_tile.dart'; +import 'package:immich_mobile/modules/settings/utils/app_settings_update_hook.dart'; + +class HapticSetting extends HookConsumerWidget { + const HapticSetting({ + super.key, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final hapticFeedbackSetting = + useAppSettingsState(AppSettingsEnum.enableHapticFeedback); + final isHapticFeedbackEnabled = + useValueNotifier(hapticFeedbackSetting.value); + + onHapticFeedbackChange(bool isEnabled) { + hapticFeedbackSetting.value = isEnabled; + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SettingsSubTitle(title: "haptic_feedback_title".tr()), + SettingsSwitchListTile( + valueNotifier: isHapticFeedbackEnabled, + title: 'haptic_feedback_switch'.tr(), + onChanged: onHapticFeedbackChange, + ), + ], + ); + } +} diff --git a/mobile/lib/modules/settings/ui/preference_settings/preference_setting.dart b/mobile/lib/modules/settings/ui/preference_settings/preference_setting.dart index f75891437c..ccc0e5b161 100644 --- a/mobile/lib/modules/settings/ui/preference_settings/preference_setting.dart +++ b/mobile/lib/modules/settings/ui/preference_settings/preference_setting.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:immich_mobile/modules/settings/ui/preference_settings/haptic_setting.dart'; import 'package:immich_mobile/modules/settings/ui/preference_settings/theme_setting.dart'; import 'package:immich_mobile/modules/settings/ui/settings_sub_page_scaffold.dart'; @@ -11,6 +12,7 @@ class PreferenceSetting extends StatelessWidget { Widget build(BuildContext context) { const preferenceSettings = [ ThemeSetting(), + HapticSetting(), ]; return const SettingsSubPageScaffold(settings: preferenceSettings); diff --git a/mobile/lib/shared/models/store.dart b/mobile/lib/shared/models/store.dart index b1f28ec0f0..233f6231af 100644 --- a/mobile/lib/shared/models/store.dart +++ b/mobile/lib/shared/models/store.dart @@ -191,6 +191,7 @@ enum StoreKey { selectedAlbumSortReverse(123, type: bool), mapThemeMode(124, type: int), mapwithPartners(125, type: bool), + enableHapticFeedback(126, type: bool), ; const StoreKey( diff --git a/mobile/lib/shared/providers/haptic_feedback.provider.dart b/mobile/lib/shared/providers/haptic_feedback.provider.dart new file mode 100644 index 0000000000..47373a67e9 --- /dev/null +++ b/mobile/lib/shared/providers/haptic_feedback.provider.dart @@ -0,0 +1,56 @@ +import 'package:flutter/services.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart'; +import 'package:immich_mobile/modules/settings/services/app_settings.service.dart'; + +final hapticFeedbackProvider = + StateNotifierProvider((ref) { + return HapticNotifier(ref); +}); + +class HapticNotifier extends StateNotifier { + void build() {} + final Ref _ref; + + HapticNotifier(this._ref) : super(null); + + selectionClick() { + if (_ref + .read(appSettingsServiceProvider) + .getSetting(AppSettingsEnum.enableHapticFeedback)) { + HapticFeedback.selectionClick(); + } + } + + lightImpact() { + if (_ref + .read(appSettingsServiceProvider) + .getSetting(AppSettingsEnum.enableHapticFeedback)) { + HapticFeedback.lightImpact(); + } + } + + mediumImpact() { + if (_ref + .read(appSettingsServiceProvider) + .getSetting(AppSettingsEnum.enableHapticFeedback)) { + HapticFeedback.mediumImpact(); + } + } + + heavyImpact() { + if (_ref + .read(appSettingsServiceProvider) + .getSetting(AppSettingsEnum.enableHapticFeedback)) { + HapticFeedback.heavyImpact(); + } + } + + vibrate() { + if (_ref + .read(appSettingsServiceProvider) + .getSetting(AppSettingsEnum.enableHapticFeedback)) { + HapticFeedback.vibrate(); + } + } +} diff --git a/mobile/lib/shared/views/tab_controller_page.dart b/mobile/lib/shared/views/tab_controller_page.dart index 40de493d0b..e1f6fde1ad 100644 --- a/mobile/lib/shared/views/tab_controller_page.dart +++ b/mobile/lib/shared/views/tab_controller_page.dart @@ -1,13 +1,13 @@ import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/asset_viewer/providers/scroll_notifier.provider.dart'; import 'package:immich_mobile/modules/home/providers/multiselect.provider.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/shared/providers/asset.provider.dart'; +import 'package:immich_mobile/shared/providers/haptic_feedback.provider.dart'; import 'package:immich_mobile/shared/providers/tab.provider.dart'; @RoutePage() @@ -53,7 +53,7 @@ class TabControllerPage extends HookConsumerWidget { scrollToTopNotifierProvider.scrollToTop(); } - HapticFeedback.selectionClick(); + ref.read(hapticFeedbackProvider.notifier).selectionClick(); tabsRouter.setActiveIndex(index); ref.read(tabProvider.notifier).state = TabEnum.values[index]; }, @@ -107,7 +107,7 @@ class TabControllerPage extends HookConsumerWidget { scrollToTopNotifierProvider.scrollToTop(); } - HapticFeedback.selectionClick(); + ref.read(hapticFeedbackProvider.notifier).selectionClick(); tabsRouter.setActiveIndex(index); ref.read(tabProvider.notifier).state = TabEnum.values[index]; }, diff --git a/mobile/openapi/.openapi-generator/FILES b/mobile/openapi/.openapi-generator/FILES index cc27eba627..83380c5e77 100644 --- a/mobile/openapi/.openapi-generator/FILES +++ b/mobile/openapi/.openapi-generator/FILES @@ -26,6 +26,7 @@ doc/AssetBulkUploadCheckDto.md doc/AssetBulkUploadCheckItem.md doc/AssetBulkUploadCheckResponseDto.md doc/AssetBulkUploadCheckResult.md +doc/AssetDeltaSyncResponseDto.md doc/AssetFaceResponseDto.md doc/AssetFaceUpdateDto.md doc/AssetFaceUpdateItem.md @@ -151,6 +152,7 @@ doc/SharedLinkType.md doc/SignUpDto.md doc/SmartInfoResponseDto.md doc/SmartSearchDto.md +doc/SyncApi.md doc/SystemConfigApi.md doc/SystemConfigDto.md doc/SystemConfigFFmpegDto.md @@ -221,6 +223,7 @@ lib/api/person_api.dart lib/api/search_api.dart lib/api/server_info_api.dart lib/api/shared_link_api.dart +lib/api/sync_api.dart lib/api/system_config_api.dart lib/api/tag_api.dart lib/api/timeline_api.dart @@ -253,6 +256,7 @@ lib/model/asset_bulk_upload_check_dto.dart lib/model/asset_bulk_upload_check_item.dart lib/model/asset_bulk_upload_check_response_dto.dart lib/model/asset_bulk_upload_check_result.dart +lib/model/asset_delta_sync_response_dto.dart lib/model/asset_face_response_dto.dart lib/model/asset_face_update_dto.dart lib/model/asset_face_update_item.dart @@ -435,6 +439,7 @@ test/asset_bulk_upload_check_dto_test.dart test/asset_bulk_upload_check_item_test.dart test/asset_bulk_upload_check_response_dto_test.dart test/asset_bulk_upload_check_result_test.dart +test/asset_delta_sync_response_dto_test.dart test/asset_face_response_dto_test.dart test/asset_face_update_dto_test.dart test/asset_face_update_item_test.dart @@ -560,6 +565,7 @@ test/shared_link_type_test.dart test/sign_up_dto_test.dart test/smart_info_response_dto_test.dart test/smart_search_dto_test.dart +test/sync_api_test.dart test/system_config_api_test.dart test/system_config_dto_test.dart test/system_config_f_fmpeg_dto_test.dart diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 417093a89f..da96fc2c08 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -192,6 +192,8 @@ Class | Method | HTTP request | Description *SharedLinkApi* | [**removeSharedLink**](doc//SharedLinkApi.md#removesharedlink) | **DELETE** /shared-link/{id} | *SharedLinkApi* | [**removeSharedLinkAssets**](doc//SharedLinkApi.md#removesharedlinkassets) | **DELETE** /shared-link/{id}/assets | *SharedLinkApi* | [**updateSharedLink**](doc//SharedLinkApi.md#updatesharedlink) | **PATCH** /shared-link/{id} | +*SyncApi* | [**getAllForUserFullSync**](doc//SyncApi.md#getallforuserfullsync) | **GET** /sync/full-sync | +*SyncApi* | [**getDeltaSync**](doc//SyncApi.md#getdeltasync) | **GET** /sync/delta-sync | *SystemConfigApi* | [**getConfig**](doc//SystemConfigApi.md#getconfig) | **GET** /system-config | *SystemConfigApi* | [**getConfigDefaults**](doc//SystemConfigApi.md#getconfigdefaults) | **GET** /system-config/defaults | *SystemConfigApi* | [**getMapStyle**](doc//SystemConfigApi.md#getmapstyle) | **GET** /system-config/map/style.json | @@ -243,6 +245,7 @@ Class | Method | HTTP request | Description - [AssetBulkUploadCheckItem](doc//AssetBulkUploadCheckItem.md) - [AssetBulkUploadCheckResponseDto](doc//AssetBulkUploadCheckResponseDto.md) - [AssetBulkUploadCheckResult](doc//AssetBulkUploadCheckResult.md) + - [AssetDeltaSyncResponseDto](doc//AssetDeltaSyncResponseDto.md) - [AssetFaceResponseDto](doc//AssetFaceResponseDto.md) - [AssetFaceUpdateDto](doc//AssetFaceUpdateDto.md) - [AssetFaceUpdateItem](doc//AssetFaceUpdateItem.md) diff --git a/mobile/openapi/doc/AssetDeltaSyncResponseDto.md b/mobile/openapi/doc/AssetDeltaSyncResponseDto.md new file mode 100644 index 0000000000..527203a4b8 --- /dev/null +++ b/mobile/openapi/doc/AssetDeltaSyncResponseDto.md @@ -0,0 +1,17 @@ +# openapi.model.AssetDeltaSyncResponseDto + +## Load the model package +```dart +import 'package:openapi/api.dart'; +``` + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**deleted** | **List** | | [default to const []] +**needsFullSync** | **bool** | | +**upserted** | [**List**](AssetResponseDto.md) | | [default to const []] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/mobile/openapi/doc/SyncApi.md b/mobile/openapi/doc/SyncApi.md new file mode 100644 index 0000000000..1b28e10c8c --- /dev/null +++ b/mobile/openapi/doc/SyncApi.md @@ -0,0 +1,135 @@ +# openapi.api.SyncApi + +## Load the API package +```dart +import 'package:openapi/api.dart'; +``` + +All URIs are relative to */api* + +Method | HTTP request | Description +------------- | ------------- | ------------- +[**getAllForUserFullSync**](SyncApi.md#getallforuserfullsync) | **GET** /sync/full-sync | +[**getDeltaSync**](SyncApi.md#getdeltasync) | **GET** /sync/delta-sync | + + +# **getAllForUserFullSync** +> List getAllForUserFullSync(limit, updatedUntil, lastCreationDate, lastId, userId) + + + +### Example +```dart +import 'package:openapi/api.dart'; +// TODO Configure API key authorization: cookie +//defaultApiClient.getAuthentication('cookie').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('cookie').apiKeyPrefix = 'Bearer'; +// TODO Configure API key authorization: api_key +//defaultApiClient.getAuthentication('api_key').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('api_key').apiKeyPrefix = 'Bearer'; +// TODO Configure HTTP Bearer authorization: bearer +// Case 1. Use String Token +//defaultApiClient.getAuthentication('bearer').setAccessToken('YOUR_ACCESS_TOKEN'); +// Case 2. Use Function which generate token. +// String yourTokenGeneratorFunction() { ... } +//defaultApiClient.getAuthentication('bearer').setAccessToken(yourTokenGeneratorFunction); + +final api_instance = SyncApi(); +final limit = 56; // int | +final updatedUntil = 2013-10-20T19:20:30+01:00; // DateTime | +final lastCreationDate = 2013-10-20T19:20:30+01:00; // DateTime | +final lastId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String | +final userId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String | + +try { + final result = api_instance.getAllForUserFullSync(limit, updatedUntil, lastCreationDate, lastId, userId); + print(result); +} catch (e) { + print('Exception when calling SyncApi->getAllForUserFullSync: $e\n'); +} +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **limit** | **int**| | + **updatedUntil** | **DateTime**| | + **lastCreationDate** | **DateTime**| | [optional] + **lastId** | **String**| | [optional] + **userId** | **String**| | [optional] + +### Return type + +[**List**](AssetResponseDto.md) + +### Authorization + +[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer) + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **getDeltaSync** +> AssetDeltaSyncResponseDto getDeltaSync(updatedAfter, userIds) + + + +### Example +```dart +import 'package:openapi/api.dart'; +// TODO Configure API key authorization: cookie +//defaultApiClient.getAuthentication('cookie').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('cookie').apiKeyPrefix = 'Bearer'; +// TODO Configure API key authorization: api_key +//defaultApiClient.getAuthentication('api_key').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('api_key').apiKeyPrefix = 'Bearer'; +// TODO Configure HTTP Bearer authorization: bearer +// Case 1. Use String Token +//defaultApiClient.getAuthentication('bearer').setAccessToken('YOUR_ACCESS_TOKEN'); +// Case 2. Use Function which generate token. +// String yourTokenGeneratorFunction() { ... } +//defaultApiClient.getAuthentication('bearer').setAccessToken(yourTokenGeneratorFunction); + +final api_instance = SyncApi(); +final updatedAfter = 2013-10-20T19:20:30+01:00; // DateTime | +final userIds = []; // List | + +try { + final result = api_instance.getDeltaSync(updatedAfter, userIds); + print(result); +} catch (e) { + print('Exception when calling SyncApi->getDeltaSync: $e\n'); +} +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **updatedAfter** | **DateTime**| | + **userIds** | [**List**](String.md)| | [default to const []] + +### Return type + +[**AssetDeltaSyncResponseDto**](AssetDeltaSyncResponseDto.md) + +### Authorization + +[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer) + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index e479ed6670..fe7282a27a 100644 --- a/mobile/openapi/lib/api.dart +++ b/mobile/openapi/lib/api.dart @@ -46,6 +46,7 @@ part 'api/person_api.dart'; part 'api/search_api.dart'; part 'api/server_info_api.dart'; part 'api/shared_link_api.dart'; +part 'api/sync_api.dart'; part 'api/system_config_api.dart'; part 'api/tag_api.dart'; part 'api/timeline_api.dart'; @@ -71,6 +72,7 @@ part 'model/asset_bulk_upload_check_dto.dart'; part 'model/asset_bulk_upload_check_item.dart'; part 'model/asset_bulk_upload_check_response_dto.dart'; part 'model/asset_bulk_upload_check_result.dart'; +part 'model/asset_delta_sync_response_dto.dart'; part 'model/asset_face_response_dto.dart'; part 'model/asset_face_update_dto.dart'; part 'model/asset_face_update_item.dart'; diff --git a/mobile/openapi/lib/api/sync_api.dart b/mobile/openapi/lib/api/sync_api.dart new file mode 100644 index 0000000000..fdfd8b9ac7 --- /dev/null +++ b/mobile/openapi/lib/api/sync_api.dart @@ -0,0 +1,150 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.12 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class SyncApi { + SyncApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Performs an HTTP 'GET /sync/full-sync' operation and returns the [Response]. + /// Parameters: + /// + /// * [int] limit (required): + /// + /// * [DateTime] updatedUntil (required): + /// + /// * [DateTime] lastCreationDate: + /// + /// * [String] lastId: + /// + /// * [String] userId: + Future getAllForUserFullSyncWithHttpInfo(int limit, DateTime updatedUntil, { DateTime? lastCreationDate, String? lastId, String? userId, }) async { + // ignore: prefer_const_declarations + final path = r'/sync/full-sync'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (lastCreationDate != null) { + queryParams.addAll(_queryParams('', 'lastCreationDate', lastCreationDate)); + } + if (lastId != null) { + queryParams.addAll(_queryParams('', 'lastId', lastId)); + } + queryParams.addAll(_queryParams('', 'limit', limit)); + queryParams.addAll(_queryParams('', 'updatedUntil', updatedUntil)); + if (userId != null) { + queryParams.addAll(_queryParams('', 'userId', userId)); + } + + const contentTypes = []; + + + return apiClient.invokeAPI( + path, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Parameters: + /// + /// * [int] limit (required): + /// + /// * [DateTime] updatedUntil (required): + /// + /// * [DateTime] lastCreationDate: + /// + /// * [String] lastId: + /// + /// * [String] userId: + Future?> getAllForUserFullSync(int limit, DateTime updatedUntil, { DateTime? lastCreationDate, String? lastId, String? userId, }) async { + final response = await getAllForUserFullSyncWithHttpInfo(limit, updatedUntil, lastCreationDate: lastCreationDate, lastId: lastId, userId: userId, ); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Performs an HTTP 'GET /sync/delta-sync' operation and returns the [Response]. + /// Parameters: + /// + /// * [DateTime] updatedAfter (required): + /// + /// * [List] userIds (required): + Future getDeltaSyncWithHttpInfo(DateTime updatedAfter, List userIds,) async { + // ignore: prefer_const_declarations + final path = r'/sync/delta-sync'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + queryParams.addAll(_queryParams('', 'updatedAfter', updatedAfter)); + queryParams.addAll(_queryParams('multi', 'userIds', userIds)); + + const contentTypes = []; + + + return apiClient.invokeAPI( + path, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Parameters: + /// + /// * [DateTime] updatedAfter (required): + /// + /// * [List] userIds (required): + Future getDeltaSync(DateTime updatedAfter, List userIds,) async { + final response = await getDeltaSyncWithHttpInfo(updatedAfter, userIds,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AssetDeltaSyncResponseDto',) as AssetDeltaSyncResponseDto; + + } + return null; + } +} diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index a4260edff1..57349e2383 100644 --- a/mobile/openapi/lib/api_client.dart +++ b/mobile/openapi/lib/api_client.dart @@ -220,6 +220,8 @@ class ApiClient { return AssetBulkUploadCheckResponseDto.fromJson(value); case 'AssetBulkUploadCheckResult': return AssetBulkUploadCheckResult.fromJson(value); + case 'AssetDeltaSyncResponseDto': + return AssetDeltaSyncResponseDto.fromJson(value); case 'AssetFaceResponseDto': return AssetFaceResponseDto.fromJson(value); case 'AssetFaceUpdateDto': diff --git a/mobile/openapi/lib/model/asset_delta_sync_response_dto.dart b/mobile/openapi/lib/model/asset_delta_sync_response_dto.dart new file mode 100644 index 0000000000..5d7679e734 --- /dev/null +++ b/mobile/openapi/lib/model/asset_delta_sync_response_dto.dart @@ -0,0 +1,116 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.12 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetDeltaSyncResponseDto { + /// Returns a new [AssetDeltaSyncResponseDto] instance. + AssetDeltaSyncResponseDto({ + this.deleted = const [], + required this.needsFullSync, + this.upserted = const [], + }); + + List deleted; + + bool needsFullSync; + + List upserted; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetDeltaSyncResponseDto && + _deepEquality.equals(other.deleted, deleted) && + other.needsFullSync == needsFullSync && + _deepEquality.equals(other.upserted, upserted); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (deleted.hashCode) + + (needsFullSync.hashCode) + + (upserted.hashCode); + + @override + String toString() => 'AssetDeltaSyncResponseDto[deleted=$deleted, needsFullSync=$needsFullSync, upserted=$upserted]'; + + Map toJson() { + final json = {}; + json[r'deleted'] = this.deleted; + json[r'needsFullSync'] = this.needsFullSync; + json[r'upserted'] = this.upserted; + return json; + } + + /// Returns a new [AssetDeltaSyncResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetDeltaSyncResponseDto? fromJson(dynamic value) { + if (value is Map) { + final json = value.cast(); + + return AssetDeltaSyncResponseDto( + deleted: json[r'deleted'] is Iterable + ? (json[r'deleted'] as Iterable).cast().toList(growable: false) + : const [], + needsFullSync: mapValueOfType(json, r'needsFullSync')!, + upserted: AssetResponseDto.listFromJson(json[r'upserted']), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetDeltaSyncResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetDeltaSyncResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetDeltaSyncResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetDeltaSyncResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'deleted', + 'needsFullSync', + 'upserted', + }; +} + diff --git a/mobile/openapi/test/asset_delta_sync_response_dto_test.dart b/mobile/openapi/test/asset_delta_sync_response_dto_test.dart new file mode 100644 index 0000000000..20104c08c6 --- /dev/null +++ b/mobile/openapi/test/asset_delta_sync_response_dto_test.dart @@ -0,0 +1,37 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.12 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +import 'package:openapi/api.dart'; +import 'package:test/test.dart'; + +// tests for AssetDeltaSyncResponseDto +void main() { + // final instance = AssetDeltaSyncResponseDto(); + + group('test AssetDeltaSyncResponseDto', () { + // List deleted (default value: const []) + test('to test the property `deleted`', () async { + // TODO + }); + + // bool needsFullSync + test('to test the property `needsFullSync`', () async { + // TODO + }); + + // List upserted (default value: const []) + test('to test the property `upserted`', () async { + // TODO + }); + + + }); + +} diff --git a/mobile/openapi/test/sync_api_test.dart b/mobile/openapi/test/sync_api_test.dart new file mode 100644 index 0000000000..ad9ef0f92f --- /dev/null +++ b/mobile/openapi/test/sync_api_test.dart @@ -0,0 +1,31 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.12 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +import 'package:openapi/api.dart'; +import 'package:test/test.dart'; + + +/// tests for SyncApi +void main() { + // final instance = SyncApi(); + + group('tests for SyncApi', () { + //Future> getAllForUserFullSync(int limit, DateTime updatedUntil, { DateTime lastCreationDate, String lastId, String userId }) async + test('test getAllForUserFullSync', () async { + // TODO + }); + + //Future getDeltaSync(DateTime updatedAfter, List userIds) async + test('test getDeltaSync', () async { + // TODO + }); + + }); +} diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 6f0f03b573..b0dd2f6b61 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -12,7 +12,8 @@ dependencies: sdk: flutter path_provider_ios: - # TODO: change to stable after 3.16 support + # TODO: upgrade to stable after 3.0.1 is released. 3.0.0 is broken + # https://github.com/fluttercandies/flutter_photo_manager/pull/990#issuecomment-2058066427 photo_manager: ^3.0.0-dev.5 photo_manager_image_provider: ^2.1.0 flutter_hooks: ^0.20.4 diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index ac567e54cc..b1b3fb1a8f 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -5619,6 +5619,140 @@ ] } }, + "/sync/delta-sync": { + "get": { + "operationId": "getDeltaSync", + "parameters": [ + { + "name": "updatedAfter", + "required": true, + "in": "query", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "name": "userIds", + "required": true, + "in": "query", + "schema": { + "format": "uuid", + "type": "array", + "items": { + "type": "string" + } + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AssetDeltaSyncResponseDto" + } + } + }, + "description": "" + } + }, + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ], + "tags": [ + "Sync" + ] + } + }, + "/sync/full-sync": { + "get": { + "operationId": "getAllForUserFullSync", + "parameters": [ + { + "name": "lastCreationDate", + "required": false, + "in": "query", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "name": "lastId", + "required": false, + "in": "query", + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "name": "limit", + "required": true, + "in": "query", + "schema": { + "type": "integer" + } + }, + { + "name": "updatedUntil", + "required": true, + "in": "query", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "name": "userId", + "required": false, + "in": "query", + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/AssetResponseDto" + }, + "type": "array" + } + } + }, + "description": "" + } + }, + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ], + "tags": [ + "Sync" + ] + } + }, "/system-config": { "get": { "operationId": "getConfig", @@ -7419,6 +7553,31 @@ ], "type": "object" }, + "AssetDeltaSyncResponseDto": { + "properties": { + "deleted": { + "items": { + "type": "string" + }, + "type": "array" + }, + "needsFullSync": { + "type": "boolean" + }, + "upserted": { + "items": { + "$ref": "#/components/schemas/AssetResponseDto" + }, + "type": "array" + } + }, + "required": [ + "deleted", + "needsFullSync", + "upserted" + ], + "type": "object" + }, "AssetFaceResponseDto": { "properties": { "boundingBoxX1": { diff --git a/open-api/typescript-sdk/package-lock.json b/open-api/typescript-sdk/package-lock.json index 9b338d1122..8def6adffd 100644 --- a/open-api/typescript-sdk/package-lock.json +++ b/open-api/typescript-sdk/package-lock.json @@ -22,18 +22,18 @@ "integrity": "sha512-V33FjR6V+AkGRWYQW3XPm5BLn2loGl2ujSeja1TzdjjEn2zjGgl3ve0dcFf/jEwPZEOqQZl6YwIgIB/clXVqWw==" }, "node_modules/@types/node": { - "version": "20.12.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.4.tgz", - "integrity": "sha512-E+Fa9z3wSQpzgYQdYmme5X3OTuejnnTx88A6p6vkkJosR3KBz+HpE3kqNm98VE6cfLFcISx7zW7MsJkH6KwbTw==", + "version": "20.12.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", + "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==", "dev": true, "dependencies": { "undici-types": "~5.26.4" } }, "node_modules/typescript": { - "version": "5.4.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.4.tgz", - "integrity": "sha512-dGE2Vv8cpVvw28v8HCPqyb08EzbBURxDpuhJvTrusShUfGnhHBafDsLdS1EhhxyL6BJQE+2cT3dDPAv+MQ6oLw==", + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", "dev": true, "bin": { "tsc": "bin/tsc", diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts index a9c768deb3..01e5d3556c 100644 --- a/open-api/typescript-sdk/src/fetch-client.ts +++ b/open-api/typescript-sdk/src/fetch-client.ts @@ -844,6 +844,11 @@ export type AssetIdsResponseDto = { error?: Error2; success: boolean; }; +export type AssetDeltaSyncResponseDto = { + deleted: string[]; + needsFullSync: boolean; + upserted: AssetResponseDto[]; +}; export type SystemConfigFFmpegDto = { accel: TranscodeHWAccel; acceptedAudioCodecs: AudioCodec[]; @@ -2527,6 +2532,40 @@ export function addSharedLinkAssets({ id, key, assetIdsDto }: { body: assetIdsDto }))); } +export function getDeltaSync({ updatedAfter, userIds }: { + updatedAfter: string; + userIds: string[]; +}, opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchJson<{ + status: 200; + data: AssetDeltaSyncResponseDto; + }>(`/sync/delta-sync${QS.query(QS.explode({ + updatedAfter, + userIds + }))}`, { + ...opts + })); +} +export function getAllForUserFullSync({ lastCreationDate, lastId, limit, updatedUntil, userId }: { + lastCreationDate?: string; + lastId?: string; + limit: number; + updatedUntil: string; + userId?: string; +}, opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchJson<{ + status: 200; + data: AssetResponseDto[]; + }>(`/sync/full-sync${QS.query(QS.explode({ + lastCreationDate, + lastId, + limit, + updatedUntil, + userId + }))}`, { + ...opts + })); +} export function getConfig(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ status: 200; diff --git a/server/.eslintrc.js b/server/.eslintrc.js index 0339cfb4c5..79c48c4015 100644 --- a/server/.eslintrc.js +++ b/server/.eslintrc.js @@ -10,7 +10,6 @@ module.exports = { root: true, env: { node: true, - jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { diff --git a/server/Dockerfile b/server/Dockerfile index 3644f0a6ea..8ed5344395 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -1,5 +1,5 @@ # dev build -FROM ghcr.io/immich-app/base-server-dev:20240326@sha256:d945aba864051b30888617f36446f86b72c4bc7ad6476b9dd2aaa0c4c4e3c945 as dev +FROM ghcr.io/immich-app/base-server-dev:20240416@sha256:ff2aadf54298e8ceca94031c6fed143236d8d82640fbbf422e0a9d2978e45923 as dev RUN apt-get install --no-install-recommends -yqq tini WORKDIR /usr/src/app @@ -41,7 +41,7 @@ RUN npm run build # prod build -FROM ghcr.io/immich-app/base-server-prod:20240326@sha256:28ad98fed8d746b5f92de49ff776cfdff7399df163ebeda2f37a01f473965841 +FROM ghcr.io/immich-app/base-server-prod:20240416@sha256:138f4d6fb74b282256583070339eaba6f39fcffa3569ae05b6823d5c37098242 WORKDIR /usr/src/app ENV NODE_ENV=production \ diff --git a/server/bin/immich-test b/server/bin/immich-test deleted file mode 100755 index 93b104f136..0000000000 --- a/server/bin/immich-test +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -node /usr/src/app/node_modules/.bin/jest --config e2e/"$1"/jest-e2e.json --runInBand diff --git a/server/e2e/client/asset-api.ts b/server/e2e/client/asset-api.ts deleted file mode 100644 index f32d586115..0000000000 --- a/server/e2e/client/asset-api.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { AssetResponseDto } from 'src/dtos/asset-response.dto'; -import request from 'supertest'; - -export const assetApi = { - getAllAssets: async (server: any, accessToken: string) => { - const { body, status } = await request(server).get(`/asset/`).set('Authorization', `Bearer ${accessToken}`); - expect(status).toBe(200); - return body as AssetResponseDto[]; - }, -}; diff --git a/server/e2e/client/auth-api.ts b/server/e2e/client/auth-api.ts deleted file mode 100644 index a8cfe4660a..0000000000 --- a/server/e2e/client/auth-api.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { LoginResponseDto } from 'src/dtos/auth.dto'; -import { UserResponseDto } from 'src/dtos/user.dto'; -import request from 'supertest'; -import { adminSignupStub, loginResponseStub, loginStub } from 'test/fixtures/auth.stub'; - -export const authApi = { - adminSignUp: async (server: any) => { - const { status, body } = await request(server).post('/auth/admin-sign-up').send(adminSignupStub); - - expect(status).toBe(201); - - return body as UserResponseDto; - }, - adminLogin: async (server: any) => { - const { status, body } = await request(server).post('/auth/login').send(loginStub.admin); - - expect(body).toEqual(loginResponseStub.admin.response); - expect(body).toMatchObject({ accessToken: expect.any(String) }); - expect(status).toBe(201); - - return body as LoginResponseDto; - }, -}; diff --git a/server/e2e/client/index.ts b/server/e2e/client/index.ts deleted file mode 100644 index 41418ddcc0..0000000000 --- a/server/e2e/client/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { assetApi } from 'e2e/client/asset-api'; -import { authApi } from 'e2e/client/auth-api'; -import { libraryApi } from 'e2e/client/library-api'; - -export const api = { - authApi, - assetApi, - libraryApi, -}; diff --git a/server/e2e/client/library-api.ts b/server/e2e/client/library-api.ts deleted file mode 100644 index 70c8c4c360..0000000000 --- a/server/e2e/client/library-api.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { CreateLibraryDto, LibraryResponseDto, ScanLibraryDto } from 'src/dtos/library.dto'; -import request from 'supertest'; - -export const libraryApi = { - getAll: async (server: any, accessToken: string) => { - const { body, status } = await request(server).get(`/library/`).set('Authorization', `Bearer ${accessToken}`); - expect(status).toBe(200); - return body as LibraryResponseDto[]; - }, - create: async (server: any, accessToken: string, dto: CreateLibraryDto) => { - const { body, status } = await request(server) - .post(`/library/`) - .set('Authorization', `Bearer ${accessToken}`) - .send(dto); - expect(status).toBe(201); - return body as LibraryResponseDto; - }, - setImportPaths: async (server: any, accessToken: string, id: string, importPaths: string[]) => { - const { body, status } = await request(server) - .put(`/library/${id}`) - .set('Authorization', `Bearer ${accessToken}`) - .send({ importPaths }); - expect(status).toBe(200); - return body as LibraryResponseDto; - }, - scanLibrary: async (server: any, accessToken: string, id: string, dto: ScanLibraryDto = {}) => { - const { status } = await request(server) - .post(`/library/${id}/scan`) - .set('Authorization', `Bearer ${accessToken}`) - .send(dto); - expect(status).toBe(204); - }, -}; diff --git a/server/e2e/docker-compose.server-e2e.yml b/server/e2e/docker-compose.server-e2e.yml deleted file mode 100644 index 61b38e1ee0..0000000000 --- a/server/e2e/docker-compose.server-e2e.yml +++ /dev/null @@ -1,33 +0,0 @@ -version: '3.8' - -name: 'immich-test-e2e' - -services: - immich-server: - image: immich-server-dev:latest - build: - context: ../../ - dockerfile: server/Dockerfile - target: dev - command: ['/usr/src/app/bin/immich-test', 'jobs'] - volumes: - - /usr/src/app/node_modules - - ../test/assets:/usr/src/app/test/assets:ro - environment: - - DB_HOSTNAME=database - - DB_USERNAME=postgres - - DB_PASSWORD=postgres - - DB_DATABASE_NAME=e2e_test - - IMMICH_METRICS=true - depends_on: - - database - - database: - image: tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0 - command: -c fsync=off -c shared_preload_libraries=vectors.so - environment: - POSTGRES_PASSWORD: postgres - POSTGRES_USER: postgres - POSTGRES_DB: e2e_test - logging: - driver: none diff --git a/server/e2e/jobs/config/library-watcher-e2e-config.json b/server/e2e/jobs/config/library-watcher-e2e-config.json deleted file mode 100644 index 9f7420ca5a..0000000000 --- a/server/e2e/jobs/config/library-watcher-e2e-config.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "reverseGeocoding": { - "enabled": false - }, - "machineLearning": { - "enabled": false - }, - "logging": { - "enabled": false, - "level": "debug" - }, - "library": { - "watch": { - "enabled": true - } - } -} diff --git a/server/e2e/jobs/immich-e2e-config.json b/server/e2e/jobs/immich-e2e-config.json deleted file mode 100644 index 4f018dc164..0000000000 --- a/server/e2e/jobs/immich-e2e-config.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "reverseGeocoding": { - "enabled": false - }, - "machineLearning": { - "enabled": false - }, - "logging": { - "enabled": false, - "level": "debug" - } -} diff --git a/server/e2e/jobs/jest-e2e.json b/server/e2e/jobs/jest-e2e.json deleted file mode 100644 index b7e62d5f46..0000000000 --- a/server/e2e/jobs/jest-e2e.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "moduleFileExtensions": ["js", "json", "ts"], - "modulePaths": [""], - "rootDir": "../..", - "globalSetup": "/e2e/jobs/setup.ts", - "testEnvironment": "node", - "testMatch": ["**/e2e/jobs/specs/*.e2e-spec.[tj]s"], - "testTimeout": 10000, - "transform": { - "^.+\\.(t|j)s$": "ts-jest" - }, - "collectCoverageFrom": [ - "/src/**/*.(t|j)s", - "!/src/**/*.spec.(t|s)s", - "!/src/migrations/**" - ], - "coverageDirectory": "./coverage", - "moduleNameMapper": { - "^test(|/.*)$": "/test/$1", - "^src(|/.*)$": "/src/$1" - } -} diff --git a/server/e2e/jobs/setup.ts b/server/e2e/jobs/setup.ts deleted file mode 100644 index d1f566d372..0000000000 --- a/server/e2e/jobs/setup.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { PostgreSqlContainer } from '@testcontainers/postgresql'; -import { access } from 'fs/promises'; -import path from 'path'; - -export default async () => { - let IMMICH_TEST_ASSET_PATH: string = ''; - - if (process.env.IMMICH_TEST_ASSET_PATH === undefined) { - IMMICH_TEST_ASSET_PATH = path.normalize(`${__dirname}/../../test/assets/`); - process.env.IMMICH_TEST_ASSET_PATH = IMMICH_TEST_ASSET_PATH; - } else { - IMMICH_TEST_ASSET_PATH = process.env.IMMICH_TEST_ASSET_PATH; - } - - const directoryExists = async (dirPath: string) => - await access(dirPath) - .then(() => true) - .catch(() => false); - - if (!(await directoryExists(`${IMMICH_TEST_ASSET_PATH}/albums`))) { - throw new Error( - `Test assets not found. Please checkout https://github.com/immich-app/test-assets into ${IMMICH_TEST_ASSET_PATH} before testing`, - ); - } - - if (process.env.DB_HOSTNAME === undefined) { - // DB hostname not set which likely means we're not running e2e through docker compose. Start a local postgres container. - const pg = await new PostgreSqlContainer('tensorchord/pgvecto-rs:pg14-v0.2.0') - .withExposedPorts(5432) - .withDatabase('immich') - .withUsername('postgres') - .withPassword('postgres') - .withReuse() - .start(); - - process.env.DB_URL = pg.getConnectionUri(); - } - - process.env.NODE_ENV = 'development'; - process.env.IMMICH_CONFIG_FILE = path.normalize(`${__dirname}/immich-e2e-config.json`); - process.env.TZ = 'Z'; -}; diff --git a/server/e2e/jobs/specs/library-watcher.e2e-spec.ts b/server/e2e/jobs/specs/library-watcher.e2e-spec.ts deleted file mode 100644 index 20a9d32020..0000000000 --- a/server/e2e/jobs/specs/library-watcher.e2e-spec.ts +++ /dev/null @@ -1,231 +0,0 @@ -import { api } from 'e2e/client'; -import fs from 'node:fs/promises'; -import path from 'node:path'; -import { LoginResponseDto } from 'src/dtos/auth.dto'; -import { LibraryResponseDto } from 'src/dtos/library.dto'; -import { AssetType } from 'src/entities/asset.entity'; -import { LibraryType } from 'src/entities/library.entity'; -import { StorageEventType } from 'src/interfaces/storage.interface'; -import { LibraryService } from 'src/services/library.service'; -import { - IMMICH_TEST_ASSET_PATH, - IMMICH_TEST_ASSET_TEMP_PATH, - restoreTempFolder, - testApp, - waitForEvent, -} from 'test/utils'; - -describe(`Library watcher (e2e)`, () => { - let server: any; - let admin: LoginResponseDto; - let libraryService: LibraryService; - const configFilePath = process.env.IMMICH_CONFIG_FILE; - - beforeAll(async () => { - process.env.IMMICH_CONFIG_FILE = path.normalize(`${__dirname}/../config/library-watcher-e2e-config.json`); - - const app = await testApp.create(); - server = app.getHttpServer(); - libraryService = testApp.get(LibraryService); - }); - - beforeEach(async () => { - await testApp.reset(); - await restoreTempFolder(); - await api.authApi.adminSignUp(server); - admin = await api.authApi.adminLogin(server); - }); - - afterEach(async () => { - await libraryService.teardown(); - }); - - afterAll(async () => { - await testApp.teardown(); - await restoreTempFolder(); - process.env.IMMICH_CONFIG_FILE = configFilePath; - }); - - describe('Event handling', () => { - describe('Single import path', () => { - beforeEach(async () => { - await api.libraryApi.create(server, admin.accessToken, { - ownerId: admin.userId, - type: LibraryType.EXTERNAL, - importPaths: [`${IMMICH_TEST_ASSET_TEMP_PATH}`], - }); - }); - - it('should import a new file', async () => { - await fs.copyFile( - `${IMMICH_TEST_ASSET_PATH}/albums/nature/el_torcal_rocks.jpg`, - `${IMMICH_TEST_ASSET_TEMP_PATH}/file.jpg`, - ); - - await waitForEvent(libraryService, StorageEventType.ADD); - - const afterAssets = await api.assetApi.getAllAssets(server, admin.accessToken); - expect(afterAssets.length).toEqual(1); - }); - - it('should import new files with case insensitive extensions', async () => { - await fs.copyFile( - `${IMMICH_TEST_ASSET_PATH}/albums/nature/el_torcal_rocks.jpg`, - `${IMMICH_TEST_ASSET_TEMP_PATH}/file2.JPG`, - ); - - await fs.copyFile( - `${IMMICH_TEST_ASSET_PATH}/albums/nature/el_torcal_rocks.jpg`, - `${IMMICH_TEST_ASSET_TEMP_PATH}/file3.Jpg`, - ); - - await fs.copyFile( - `${IMMICH_TEST_ASSET_PATH}/albums/nature/el_torcal_rocks.jpg`, - `${IMMICH_TEST_ASSET_TEMP_PATH}/file4.jpG`, - ); - - await fs.copyFile( - `${IMMICH_TEST_ASSET_PATH}/albums/nature/el_torcal_rocks.jpg`, - `${IMMICH_TEST_ASSET_TEMP_PATH}/file5.jPg`, - ); - - await waitForEvent(libraryService, StorageEventType.ADD, 4); - - const afterAssets = await api.assetApi.getAllAssets(server, admin.accessToken); - expect(afterAssets.length).toEqual(4); - }); - - it('should update a changed file', async () => { - await fs.copyFile( - `${IMMICH_TEST_ASSET_PATH}/albums/nature/el_torcal_rocks.jpg`, - `${IMMICH_TEST_ASSET_TEMP_PATH}/file.jpg`, - ); - - await waitForEvent(libraryService, StorageEventType.ADD); - - const originalAssets = await api.assetApi.getAllAssets(server, admin.accessToken); - expect(originalAssets.length).toEqual(1); - - await fs.copyFile( - `${IMMICH_TEST_ASSET_PATH}/albums/nature/prairie_falcon.jpg`, - `${IMMICH_TEST_ASSET_TEMP_PATH}/file.jpg`, - ); - - await waitForEvent(libraryService, StorageEventType.CHANGE); - - const afterAssets = await api.assetApi.getAllAssets(server, admin.accessToken); - expect(afterAssets).toEqual([ - expect.objectContaining({ - // Make sure we keep the original asset id - id: originalAssets[0].id, - type: AssetType.IMAGE, - exifInfo: expect.objectContaining({ - make: 'Canon', - model: 'Canon EOS R5', - exifImageWidth: 800, - exifImageHeight: 533, - exposureTime: '1/4000', - }), - }), - ]); - }); - }); - - describe('Multiple import paths', () => { - beforeEach(async () => { - await fs.mkdir(`${IMMICH_TEST_ASSET_TEMP_PATH}/dir1`, { recursive: true }); - await fs.mkdir(`${IMMICH_TEST_ASSET_TEMP_PATH}/dir2`, { recursive: true }); - await fs.mkdir(`${IMMICH_TEST_ASSET_TEMP_PATH}/dir3`, { recursive: true }); - - await api.libraryApi.create(server, admin.accessToken, { - ownerId: admin.userId, - type: LibraryType.EXTERNAL, - importPaths: [ - `${IMMICH_TEST_ASSET_TEMP_PATH}/dir1`, - `${IMMICH_TEST_ASSET_TEMP_PATH}/dir2`, - `${IMMICH_TEST_ASSET_TEMP_PATH}/dir3`, - ], - }); - }); - - it('should add new files in multiple import paths', async () => { - await fs.copyFile( - `${IMMICH_TEST_ASSET_PATH}/albums/nature/el_torcal_rocks.jpg`, - `${IMMICH_TEST_ASSET_TEMP_PATH}/dir1/file2.jpg`, - ); - - await fs.copyFile( - `${IMMICH_TEST_ASSET_PATH}/albums/nature/polemonium_reptans.jpg`, - `${IMMICH_TEST_ASSET_TEMP_PATH}/dir2/file3.jpg`, - ); - - await fs.copyFile( - `${IMMICH_TEST_ASSET_PATH}/albums/nature/tanners_ridge.jpg`, - `${IMMICH_TEST_ASSET_TEMP_PATH}/dir3/file4.jpg`, - ); - - await waitForEvent(libraryService, StorageEventType.ADD, 3); - - const assets = await api.assetApi.getAllAssets(server, admin.accessToken); - expect(assets.length).toEqual(3); - }); - - it('should offline a removed file', async () => { - await fs.copyFile( - `${IMMICH_TEST_ASSET_PATH}/albums/nature/polemonium_reptans.jpg`, - `${IMMICH_TEST_ASSET_TEMP_PATH}/dir1/file.jpg`, - ); - - await waitForEvent(libraryService, StorageEventType.ADD); - - const addedAssets = await api.assetApi.getAllAssets(server, admin.accessToken); - expect(addedAssets.length).toEqual(1); - - await fs.unlink(`${IMMICH_TEST_ASSET_TEMP_PATH}/dir1/file.jpg`); - - await waitForEvent(libraryService, StorageEventType.UNLINK); - - const afterAssets = await api.assetApi.getAllAssets(server, admin.accessToken); - expect(afterAssets[0].isOffline).toEqual(true); - }); - }); - }); - - describe('Configuration', () => { - let library: LibraryResponseDto; - - beforeEach(async () => { - library = await api.libraryApi.create(server, admin.accessToken, { - ownerId: admin.userId, - type: LibraryType.EXTERNAL, - importPaths: [ - `${IMMICH_TEST_ASSET_TEMP_PATH}/dir1`, - `${IMMICH_TEST_ASSET_TEMP_PATH}/dir2`, - `${IMMICH_TEST_ASSET_TEMP_PATH}/dir3`, - ], - }); - - await fs.mkdir(`${IMMICH_TEST_ASSET_TEMP_PATH}/dir1`, { recursive: true }); - await fs.mkdir(`${IMMICH_TEST_ASSET_TEMP_PATH}/dir2`, { recursive: true }); - await fs.mkdir(`${IMMICH_TEST_ASSET_TEMP_PATH}/dir3`, { recursive: true }); - }); - - it('should use an updated import path', async () => { - await fs.mkdir(`${IMMICH_TEST_ASSET_TEMP_PATH}/dir4`, { recursive: true }); - - await api.libraryApi.setImportPaths(server, admin.accessToken, library.id, [ - `${IMMICH_TEST_ASSET_TEMP_PATH}/dir4`, - ]); - - await fs.copyFile( - `${IMMICH_TEST_ASSET_PATH}/albums/nature/polemonium_reptans.jpg`, - `${IMMICH_TEST_ASSET_TEMP_PATH}/dir4/file.jpg`, - ); - - await waitForEvent(libraryService, StorageEventType.ADD); - - const afterAssets = await api.assetApi.getAllAssets(server, admin.accessToken); - expect(afterAssets.length).toEqual(1); - }); - }); -}); diff --git a/server/e2e/jobs/specs/library.e2e-spec.ts b/server/e2e/jobs/specs/library.e2e-spec.ts deleted file mode 100644 index 3ae27e631e..0000000000 --- a/server/e2e/jobs/specs/library.e2e-spec.ts +++ /dev/null @@ -1,305 +0,0 @@ -import { api } from 'e2e/client'; -import fs from 'node:fs'; -import { LibraryController } from 'src/controllers/library.controller'; -import { LoginResponseDto } from 'src/dtos/auth.dto'; -import { LibraryType } from 'src/entities/library.entity'; -import request from 'supertest'; -import { errorStub } from 'test/fixtures/error.stub'; -import { uuidStub } from 'test/fixtures/uuid.stub'; -import { IMMICH_TEST_ASSET_PATH, IMMICH_TEST_ASSET_TEMP_PATH, restoreTempFolder, testApp } from 'test/utils'; -import { utimes } from 'utimes'; - -describe(`${LibraryController.name} (e2e)`, () => { - let server: any; - let admin: LoginResponseDto; - - beforeAll(async () => { - const app = await testApp.create(); - server = app.getHttpServer(); - }); - - beforeEach(async () => { - await testApp.reset(); - await restoreTempFolder(); - await api.authApi.adminSignUp(server); - admin = await api.authApi.adminLogin(server); - }); - - afterAll(async () => { - await testApp.teardown(); - await restoreTempFolder(); - }); - - describe('POST /library/:id/scan', () => { - it('should offline missing files', async () => { - await fs.promises.cp(`${IMMICH_TEST_ASSET_PATH}/albums/nature`, `${IMMICH_TEST_ASSET_TEMP_PATH}/albums/nature`, { - recursive: true, - }); - - const library = await api.libraryApi.create(server, admin.accessToken, { - ownerId: admin.userId, - type: LibraryType.EXTERNAL, - importPaths: [`${IMMICH_TEST_ASSET_TEMP_PATH}`], - }); - - await api.libraryApi.scanLibrary(server, admin.accessToken, library.id); - - const onlineAssets = await api.assetApi.getAllAssets(server, admin.accessToken); - expect(onlineAssets.length).toBeGreaterThan(1); - - await restoreTempFolder(); - - await api.libraryApi.scanLibrary(server, admin.accessToken, library.id); - - const assets = await api.assetApi.getAllAssets(server, admin.accessToken); - - expect(assets).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - isOffline: true, - originalFileName: 'el_torcal_rocks.jpg', - }), - expect.objectContaining({ - isOffline: true, - originalFileName: 'tanners_ridge.jpg', - }), - ]), - ); - }); - - it('should scan new files', async () => { - const library = await api.libraryApi.create(server, admin.accessToken, { - ownerId: admin.userId, - type: LibraryType.EXTERNAL, - importPaths: [`${IMMICH_TEST_ASSET_TEMP_PATH}`], - }); - - await fs.promises.cp( - `${IMMICH_TEST_ASSET_PATH}/albums/nature/silver_fir.jpg`, - `${IMMICH_TEST_ASSET_TEMP_PATH}/silver_fir.jpg`, - ); - - await api.libraryApi.scanLibrary(server, admin.accessToken, library.id); - - await fs.promises.cp( - `${IMMICH_TEST_ASSET_PATH}/albums/nature/el_torcal_rocks.jpg`, - `${IMMICH_TEST_ASSET_TEMP_PATH}/el_torcal_rocks.jpg`, - ); - - await api.libraryApi.scanLibrary(server, admin.accessToken, library.id); - - const assets = await api.assetApi.getAllAssets(server, admin.accessToken); - - expect(assets).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - originalFileName: 'el_torcal_rocks.jpg', - }), - expect.objectContaining({ - originalFileName: 'silver_fir.jpg', - }), - ]), - ); - }); - - describe('with refreshModifiedFiles=true', () => { - it('should reimport modified files', async () => { - const library = await api.libraryApi.create(server, admin.accessToken, { - ownerId: admin.userId, - type: LibraryType.EXTERNAL, - importPaths: [`${IMMICH_TEST_ASSET_TEMP_PATH}`], - }); - - await fs.promises.cp( - `${IMMICH_TEST_ASSET_PATH}/albums/nature/el_torcal_rocks.jpg`, - `${IMMICH_TEST_ASSET_TEMP_PATH}/el_torcal_rocks.jpg`, - ); - - await utimes(`${IMMICH_TEST_ASSET_TEMP_PATH}/el_torcal_rocks.jpg`, 447_775_200_000); - - await api.libraryApi.scanLibrary(server, admin.accessToken, library.id); - - await fs.promises.cp( - `${IMMICH_TEST_ASSET_PATH}/albums/nature/tanners_ridge.jpg`, - `${IMMICH_TEST_ASSET_TEMP_PATH}/el_torcal_rocks.jpg`, - ); - - await utimes(`${IMMICH_TEST_ASSET_TEMP_PATH}/el_torcal_rocks.jpg`, 447_775_200_001); - - await api.libraryApi.scanLibrary(server, admin.accessToken, library.id, { refreshModifiedFiles: true }); - - const assets = await api.assetApi.getAllAssets(server, admin.accessToken); - expect(assets.length).toBe(1); - - expect(assets[0]).toEqual( - expect.objectContaining({ - originalFileName: 'el_torcal_rocks.jpg', - exifInfo: expect.objectContaining({ - dateTimeOriginal: '2023-09-25T08:33:30.880Z', - exifImageHeight: 534, - exifImageWidth: 800, - exposureTime: '1/15', - fNumber: 22, - fileSizeInByte: 114_225, - focalLength: 35, - iso: 1000, - make: 'NIKON CORPORATION', - model: 'NIKON D750', - }), - }), - ); - }); - - it('should not reimport unmodified files', async () => { - const library = await api.libraryApi.create(server, admin.accessToken, { - ownerId: admin.userId, - type: LibraryType.EXTERNAL, - importPaths: [`${IMMICH_TEST_ASSET_TEMP_PATH}`], - }); - - await fs.promises.cp( - `${IMMICH_TEST_ASSET_PATH}/albums/nature/el_torcal_rocks.jpg`, - `${IMMICH_TEST_ASSET_TEMP_PATH}/el_torcal_rocks.jpg`, - ); - - await utimes(`${IMMICH_TEST_ASSET_TEMP_PATH}/el_torcal_rocks.jpg`, 447_775_200_000); - - await api.libraryApi.scanLibrary(server, admin.accessToken, library.id); - - await fs.promises.cp( - `${IMMICH_TEST_ASSET_PATH}/albums/nature/tanners_ridge.jpg`, - `${IMMICH_TEST_ASSET_TEMP_PATH}/el_torcal_rocks.jpg`, - ); - - await utimes(`${IMMICH_TEST_ASSET_TEMP_PATH}/el_torcal_rocks.jpg`, 447_775_200_000); - - await api.libraryApi.scanLibrary(server, admin.accessToken, library.id, { refreshModifiedFiles: true }); - - const assets = await api.assetApi.getAllAssets(server, admin.accessToken); - expect(assets.length).toBe(1); - - expect(assets[0]).toEqual( - expect.objectContaining({ - originalFileName: 'el_torcal_rocks.jpg', - exifInfo: expect.objectContaining({ - dateTimeOriginal: '2012-08-05T11:39:59.000Z', - }), - }), - ); - }); - }); - - describe('with refreshAllFiles=true', () => { - it('should reimport all files', async () => { - const library = await api.libraryApi.create(server, admin.accessToken, { - ownerId: admin.userId, - type: LibraryType.EXTERNAL, - importPaths: [`${IMMICH_TEST_ASSET_TEMP_PATH}`], - }); - - await fs.promises.cp( - `${IMMICH_TEST_ASSET_PATH}/albums/nature/el_torcal_rocks.jpg`, - `${IMMICH_TEST_ASSET_TEMP_PATH}/el_torcal_rocks.jpg`, - ); - - await utimes(`${IMMICH_TEST_ASSET_TEMP_PATH}/el_torcal_rocks.jpg`, 447_775_200_000); - - await api.libraryApi.scanLibrary(server, admin.accessToken, library.id); - - await fs.promises.cp( - `${IMMICH_TEST_ASSET_PATH}/albums/nature/tanners_ridge.jpg`, - `${IMMICH_TEST_ASSET_TEMP_PATH}/el_torcal_rocks.jpg`, - ); - - await utimes(`${IMMICH_TEST_ASSET_TEMP_PATH}/el_torcal_rocks.jpg`, 447_775_200_000); - - await api.libraryApi.scanLibrary(server, admin.accessToken, library.id, { refreshAllFiles: true }); - - const assets = await api.assetApi.getAllAssets(server, admin.accessToken); - expect(assets.length).toBe(1); - - expect(assets[0]).toEqual( - expect.objectContaining({ - originalFileName: 'el_torcal_rocks.jpg', - exifInfo: expect.objectContaining({ - exifImageHeight: 534, - exifImageWidth: 800, - exposureTime: '1/15', - fNumber: 22, - fileSizeInByte: 114_225, - focalLength: 35, - iso: 1000, - make: 'NIKON CORPORATION', - model: 'NIKON D750', - }), - }), - ); - }); - }); - }); - - describe('POST /library/:id/removeOffline', () => { - it('should require authentication', async () => { - const { status, body } = await request(server).post(`/library/${uuidStub.notFound}/removeOffline`).send({}); - - expect(status).toBe(401); - expect(body).toEqual(errorStub.unauthorized); - }); - - it('should remove offline files', async () => { - await fs.promises.cp(`${IMMICH_TEST_ASSET_PATH}/albums/nature`, `${IMMICH_TEST_ASSET_TEMP_PATH}/albums/nature`, { - recursive: true, - }); - - const library = await api.libraryApi.create(server, admin.accessToken, { - ownerId: admin.userId, - type: LibraryType.EXTERNAL, - importPaths: [`${IMMICH_TEST_ASSET_TEMP_PATH}`], - }); - - await api.libraryApi.scanLibrary(server, admin.accessToken, library.id); - - const onlineAssets = await api.assetApi.getAllAssets(server, admin.accessToken); - expect(onlineAssets.length).toBeGreaterThan(1); - - await restoreTempFolder(); - - await api.libraryApi.scanLibrary(server, admin.accessToken, library.id); - - const { status } = await request(server) - .post(`/library/${library.id}/removeOffline`) - .set('Authorization', `Bearer ${admin.accessToken}`) - .send(); - expect(status).toBe(204); - - const assets = await api.assetApi.getAllAssets(server, admin.accessToken); - - expect(assets).toEqual([]); - }); - - it('should not remove online files', async () => { - const library = await api.libraryApi.create(server, admin.accessToken, { - ownerId: admin.userId, - type: LibraryType.EXTERNAL, - importPaths: [`${IMMICH_TEST_ASSET_PATH}/albums/nature`], - }); - - await api.libraryApi.scanLibrary(server, admin.accessToken, library.id); - - const assetsBefore = await api.assetApi.getAllAssets(server, admin.accessToken); - expect(assetsBefore.length).toBeGreaterThan(1); - - await api.libraryApi.scanLibrary(server, admin.accessToken, library.id); - - const { status } = await request(server) - .post(`/library/${library.id}/removeOffline`) - .set('Authorization', `Bearer ${admin.accessToken}`) - .send(); - expect(status).toBe(204); - - const assetsAfter = await api.assetApi.getAllAssets(server, admin.accessToken); - - expect(assetsAfter).toEqual(assetsBefore); - }); - }); -}); diff --git a/server/package-lock.json b/server/package-lock.json index 66afc1d1b9..286f1006b9 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -9,7 +9,6 @@ "version": "1.101.0", "license": "GNU Affero General Public License version 3", "dependencies": { - "@babel/runtime": "^7.22.11", "@nestjs/bullmq": "^10.0.1", "@nestjs/common": "^10.2.2", "@nestjs/config": "^3.0.0", @@ -25,7 +24,6 @@ "@opentelemetry/exporter-prometheus": "^0.50.0", "@opentelemetry/sdk-node": "^0.50.0", "@socket.io/postgres-adapter": "^0.3.1", - "@types/picomatch": "^2.3.3", "archiver": "^7.0.0", "async-lock": "^1.4.0", "bcrypt": "^5.1.1", @@ -48,11 +46,12 @@ "luxon": "^3.4.2", "mnemonist": "^0.39.8", "nest-commander": "^3.11.1", + "nestjs-cls": "^4.3.0", "nestjs-otel": "^5.1.5", "openid-client": "^5.4.3", "pg": "^8.11.3", "picomatch": "^4.0.0", - "reflect-metadata": "^0.1.13", + "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1", "sanitize-filename": "^1.6.3", "sharp": "^0.33.0", @@ -65,6 +64,7 @@ "@nestjs/cli": "^10.1.16", "@nestjs/schematics": "^10.0.2", "@nestjs/testing": "^10.2.2", + "@swc/core": "^1.4.14", "@testcontainers/postgresql": "^10.2.1", "@types/archiver": "^6.0.0", "@types/async-lock": "^1.4.2", @@ -73,38 +73,32 @@ "@types/express": "^4.17.17", "@types/fluent-ffmpeg": "^2.1.21", "@types/imagemin": "^8.0.1", - "@types/jest": "29.5.12", - "@types/jest-when": "^3.5.2", "@types/js-yaml": "^4.0.9", "@types/lodash": "^4.14.197", "@types/mock-fs": "^4.13.1", "@types/multer": "^1.4.7", "@types/node": "^20.5.7", - "@types/supertest": "^6.0.0", + "@types/picomatch": "^2.3.3", "@types/ua-parser-js": "^0.7.36", "@typescript-eslint/eslint-plugin": "^7.0.0", "@typescript-eslint/parser": "^7.0.0", + "@vitest/coverage-v8": "^1.5.0", "dotenv": "^16.3.1", "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-unicorn": "^52.0.0", - "jest": "^29.6.4", - "jest-when": "^3.6.0", "mock-fs": "^5.2.0", "prettier": "^3.0.2", "prettier-plugin-organize-imports": "^3.2.3", "rimraf": "^5.0.1", "source-map-support": "^0.5.21", "sql-formatter": "^15.0.0", - "supertest": "^6.3.3", - "testcontainers": "^10.2.1", - "ts-jest": "^29.1.1", - "ts-loader": "^9.4.4", - "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.3.3", - "utimes": "^5.2.1" + "unplugin-swc": "^1.4.5", + "utimes": "^5.2.1", + "vitest": "^1.5.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -314,271 +308,21 @@ } }, "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.24.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", + "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", "dependencies": { - "@babel/highlight": "^7.22.13", - "chalk": "^2.4.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/code-frame/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/code-frame/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/code-frame/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.20.tgz", - "integrity": "sha512-BQYjKbpXjoXwFW5jGqiizJQQT/aC7pFm9Ok1OWssonuguICi264lbgMzRp2ZMmRSlfkX6DsWDDcsrctK8Rwfiw==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.0.tgz", - "integrity": "sha512-97z/ju/Jy1rZmDxybphrBuI+jtJjFVoz7Mr9yUQVVVi+DNZE333uFQeMOqcCIy1x3WYBIbWftUSLmbNXNT7qFQ==", - "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.0", - "@babel/parser": "^7.23.0", - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.0", - "@babel/types": "^7.23.0", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/generator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.3.tgz", - "integrity": "sha512-keeZWAV4LU3tW0qRi19HRpabC/ilM0HRBBzf9/k8FFiG4KVpiv0FIy4hHfLfFQZNhziCTPTmd59zoyv6DNISzg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.23.3", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "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==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-validator-option": "^7.22.15", - "browserslist": "^4.21.9", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "dev": true, - "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.15" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "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==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.20" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" + "@babel/highlight": "^7.24.2", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, "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.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", + "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", "dev": true, "engines": { "node": ">=6.9.0" @@ -592,37 +336,15 @@ "node": ">=6.9.0" } }, - "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==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.23.1", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.1.tgz", - "integrity": "sha512-chNpneuK18yW5Oxsr+t553UZzzAs3aZnFm4bxhebsNTeshrC95yA7l5yl7GBAG+JG1rF0F7zzD2EixK9mWSDoA==", - "dev": true, - "dependencies": { - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.0", - "@babel/types": "^7.23.0" - }, - "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.24.2", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", + "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", "dependencies": { "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" @@ -693,9 +415,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.3.tgz", - "integrity": "sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz", + "integrity": "sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -704,245 +426,13 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", - "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", - "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz", - "integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.3.tgz", - "integrity": "sha512-+K0yF1/9yR0oHdE0StHuEj3uTPzwwbrLGfNOndVJVV2TqA5+j3oljJUb4nmB954FLGjNem976+B+eDuLIjesiQ==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.3", - "@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.3", - "@babel/types": "^7.23.3", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/types": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.3.tgz", - "integrity": "sha512-OZnvoH2l8PK5eUvEcUyCt/sXgr/h+UWpVuBbOljwcrAgUl6lpchoQ++PHGyQy1AtYnVA6CEq3y5xeEI10brpXw==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", + "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", "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" }, @@ -976,7 +466,8 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "devOptional": true, + "optional": true, + "peer": true, "dependencies": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -988,7 +479,8 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "devOptional": true, + "optional": true, + "peer": true, "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -1003,6 +495,374 @@ "tslib": "^2.4.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -1810,105 +1670,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", @@ -1918,205 +1679,6 @@ "node": ">=8" } }, - "node_modules/@jest/console": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", - "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", - "dev": true, - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/core": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", - "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", - "dev": true, - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/environment": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", - "dev": true, - "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", - "dev": true, - "dependencies": { - "expect": "^29.7.0", - "jest-snapshot": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", - "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", - "dev": true, - "dependencies": { - "jest-get-type": "^29.6.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", - "dev": true, - "dependencies": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/globals": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", - "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/types": "^29.6.3", - "jest-mock": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/reporters": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", - "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", - "dev": true, - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/reporters/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, - "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/@jest/schemas": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", @@ -2129,102 +1691,15 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/source-map": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", - "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-result": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", - "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", - "dev": true, - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", - "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", - "dev": true, - "dependencies": { - "@jest/test-result": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" @@ -2240,9 +1715,9 @@ } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, "engines": { "node": ">=6.0.0" @@ -2265,9 +1740,9 @@ "devOptional": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.22", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", - "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -2611,9 +2086,9 @@ } }, "node_modules/@nestjs/config": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-3.2.1.tgz", - "integrity": "sha512-tFZyLJKanSAu51ygQ6ZBSpx95pRcwS6qSpJDW6FFgRQzkOaOUXpL8qD8yMNoYoYxuJCxph+waiBaWKgFWxn3sw==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-3.2.2.tgz", + "integrity": "sha512-vGICPOui5vE6kPz1iwQ7oCnp3qWgqxldPmBQ9onkVoKlBtyc83KJCr7CjuVtf4OdovMAVcux1d8Q6jglU2ZphA==", "dependencies": { "dotenv": "16.4.5", "dotenv-expand": "10.0.0", @@ -4134,6 +3609,254 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", + "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/@rollup/pluginutils/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.3.tgz", + "integrity": "sha512-X9alQ3XM6I9IlSlmC8ddAvMSyG1WuHk5oUnXGw+yUBs3BFoTizmG1La/Gr8fVJvDWAq+zlYTZ9DBgrlKRVY06g==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.3.tgz", + "integrity": "sha512-eQK5JIi+POhFpzk+LnjKIy4Ks+pwJ+NXmPxOCSvOKSNRPONzKuUvWE+P9JxGZVxrtzm6BAYMaL50FFuPe0oWMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.3.tgz", + "integrity": "sha512-Od4vE6f6CTT53yM1jgcLqNfItTsLt5zE46fdPaEmeFHvPs5SjZYlLpHrSiHEKR1+HdRfxuzXHjDOIxQyC3ptBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.3.tgz", + "integrity": "sha512-0IMAO21axJeNIrvS9lSe/PGthc8ZUS+zC53O0VhF5gMxfmcKAP4ESkKOCwEi6u2asUrt4mQv2rjY8QseIEb1aw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.3.tgz", + "integrity": "sha512-ge2DC7tHRHa3caVEoSbPRJpq7azhG+xYsd6u2MEnJ6XzPSzQsTKyXvh6iWjXRf7Rt9ykIUWHtl0Uz3T6yXPpKw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.14.3.tgz", + "integrity": "sha512-ljcuiDI4V3ySuc7eSk4lQ9wU8J8r8KrOUvB2U+TtK0TiW6OFDmJ+DdIjjwZHIw9CNxzbmXY39wwpzYuFDwNXuw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.3.tgz", + "integrity": "sha512-Eci2us9VTHm1eSyn5/eEpaC7eP/mp5n46gTRB3Aar3BgSvDQGJZuicyq6TsH4HngNBgVqC5sDYxOzTExSU+NjA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.3.tgz", + "integrity": "sha512-UrBoMLCq4E92/LCqlh+blpqMz5h1tJttPIniwUgOFJyjWI1qrtrDhhpHPuFxULlUmjFHfloWdixtDhSxJt5iKw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.3.tgz", + "integrity": "sha512-5aRjvsS8q1nWN8AoRfrq5+9IflC3P1leMoy4r2WjXyFqf3qcqsxRCfxtZIV58tCxd+Yv7WELPcO9mY9aeQyAmw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.3.tgz", + "integrity": "sha512-sk/Qh1j2/RJSX7FhEpJn8n0ndxy/uf0kI/9Zc4b1ELhqULVdTfN6HL31CDaTChiBAOgLcsJ1sgVZjWv8XNEsAQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.3.tgz", + "integrity": "sha512-jOO/PEaDitOmY9TgkxF/TQIjXySQe5KVYB57H/8LRP/ux0ZoO8cSHCX17asMSv3ruwslXW/TLBcxyaUzGRHcqg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.3.tgz", + "integrity": "sha512-8ybV4Xjy59xLMyWo3GCfEGqtKV5M5gCSrZlxkPGvEPCGDLNla7v48S662HSGwRd6/2cSneMQWiv+QzcttLrrOA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.3.tgz", + "integrity": "sha512-s+xf1I46trOY10OqAtZ5Rm6lzHre/UiLA1J2uOhCFXWkbZrJRkYBPO6FhvGfHmdtQ3Bx793MNa7LvoWFAm93bg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.3.tgz", + "integrity": "sha512-+4h2WrGOYsOumDQ5S2sYNyhVfrue+9tc9XcLWLh+Kw3UOxAvrfOrSMFon60KspcDdytkNDh7K2Vs6eMaYImAZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.3.tgz", + "integrity": "sha512-T1l7y/bCeL/kUwh9OD4PQT4aM7Bq43vX05htPJJ46RTI4r5KNt6qJRzAfNfM+OYMNEVBWQzR2Gyk+FXLZfogGw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.3.tgz", + "integrity": "sha512-/BypzV0H1y1HzgYpxqRaXGBRqfodgoBBCcsrujT6QRcakDQdfU+Lq9PENPh5jB4I44YWq+0C2eHsHya+nZY1sA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@sideway/address": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", @@ -4158,24 +3881,6 @@ "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true }, - "node_modules/@sinonjs/commons": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", - "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^3.0.0" - } - }, "node_modules/@socket.io/component-emitter": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", @@ -4203,38 +3908,255 @@ "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz", "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==" }, + "node_modules/@swc/core": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.4.14.tgz", + "integrity": "sha512-tHXg6OxboUsqa/L7DpsCcFnxhLkqN/ht5pCwav1HnvfthbiNIJypr86rNx4cUnQDJepETviSqBTIjxa7pSpGDQ==", + "devOptional": true, + "hasInstallScript": true, + "dependencies": { + "@swc/counter": "^0.1.2", + "@swc/types": "^0.1.5" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.4.14", + "@swc/core-darwin-x64": "1.4.14", + "@swc/core-linux-arm-gnueabihf": "1.4.14", + "@swc/core-linux-arm64-gnu": "1.4.14", + "@swc/core-linux-arm64-musl": "1.4.14", + "@swc/core-linux-x64-gnu": "1.4.14", + "@swc/core-linux-x64-musl": "1.4.14", + "@swc/core-win32-arm64-msvc": "1.4.14", + "@swc/core-win32-ia32-msvc": "1.4.14", + "@swc/core-win32-x64-msvc": "1.4.14" + }, + "peerDependencies": { + "@swc/helpers": "^0.5.0" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.4.14.tgz", + "integrity": "sha512-8iPfLhYNspBl836YYsfv6ErXwDUqJ7IMieddV3Ey/t/97JAEAdNDUdtTKDtbyP0j/Ebyqyn+fKcqwSq7rAof0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.4.14.tgz", + "integrity": "sha512-9CqSj8uRZ92cnlgAlVaWMaJJBdxtNvCzJxaGj5KuIseeG6Q0l1g+qk8JcU7h9dAsH9saHTNwNFBVGKQo0W0ujg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.4.14.tgz", + "integrity": "sha512-mfd5JArPITTzMjcezH4DwMw+BdjBV1y25Khp8itEIpdih9ei+fvxOOrDYTN08b466NuE2dF2XuhKtRLA7fXArQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.4.14.tgz", + "integrity": "sha512-3Lqlhlmy8MVRS9xTShMaPAp0oyUt0KFhDs4ixJsjdxKecE0NJSV/MInuDmrkij1C8/RQ2wySRlV9np5jK86oWw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.4.14.tgz", + "integrity": "sha512-n0YoCa64TUcJrbcXIHIHDWQjdUPdaXeMHNEu7yyBtOpm01oMGTKP3frsUXIABLBmAVWtKvqit4/W1KVKn5gJzg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.4.14.tgz", + "integrity": "sha512-CGmlwLWbfG1dB4jZBJnp2IWlK5xBMNLjN7AR5kKA3sEpionoccEnChOEvfux1UdVJQjLRKuHNV9yGyqGBTpxfQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.4.14.tgz", + "integrity": "sha512-xq4npk8YKYmNwmr8fbvF2KP3kUVdZYfXZMQnW425gP3/sn+yFQO8Nd0bGH40vOVQn41kEesSe0Z5O/JDor2TgQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.4.14.tgz", + "integrity": "sha512-imq0X+gU9uUe6FqzOQot5gpKoaC00aCUiN58NOzwp0QXEupn8CDuZpdBN93HiZswfLruu5jA1tsc15x6v9p0Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.4.14.tgz", + "integrity": "sha512-cH6QpXMw5D3t+lpx6SkErHrxN0yFzmQ0lgNAJxoDRiaAdDbqA6Col8UqUJwUS++Ul6aCWgNhCdiEYehPaoyDPA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.4.14.tgz", + "integrity": "sha512-FmZ4Tby4wW65K/36BKzmuu7mlq7cW5XOxzvufaSNVvQ5PN4OodAlqPjToe029oma4Av+ykJiif64scMttyNAzg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "devOptional": true + }, + "node_modules/@swc/types": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.6.tgz", + "integrity": "sha512-/JLo/l2JsT/LRd80C3HfbmVpxOAJ11FO2RCEslFrgzLltoP9j8XIbsyDcfCt2WWyX+CM96rBoNM+IToAkFOugg==", + "devOptional": true, + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, "node_modules/@testcontainers/postgresql": { - "version": "10.8.1", - "resolved": "https://registry.npmjs.org/@testcontainers/postgresql/-/postgresql-10.8.1.tgz", - "integrity": "sha512-/9zlwfjK6l6iGo1gWQVO/bly4CsikM6Z9UVjkcKlPvzfzkfMr//eeUbDiHTDPu3tB0Idakqany4V5S4LIgz38A==", + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/@testcontainers/postgresql/-/postgresql-10.8.2.tgz", + "integrity": "sha512-UYbcHlK2XnP5DkwIOB8v+atz/ZfSpyEXR//FFhvAKNlLetMTv/8W73Hbhmvv50TCd2J3biFnatyo//uJm9e7ng==", "dev": true, "dependencies": { - "testcontainers": "^10.8.1" + "testcontainers": "^10.8.2" } }, "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "devOptional": true + "optional": true, + "peer": true }, "node_modules/@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "devOptional": true + "optional": true, + "peer": true }, "node_modules/@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "devOptional": true + "optional": true, + "peer": true }, "node_modules/@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "devOptional": true + "optional": true, + "peer": true }, "node_modules/@turf/boolean-point-in-polygon": { "version": "6.5.0", @@ -4295,47 +4217,6 @@ "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.122.tgz", "integrity": "sha512-vBkIh9AY22kVOCEKo5CJlyCgmSWvasC+SWUxL/x/vOwRobMpI/HG1xp/Ae3AqmSiZeLUbOhW0FCD3ZjqqUxmXw==" }, - "node_modules/@types/babel__core": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.2.tgz", - "integrity": "sha512-pNpr1T1xLUc2l3xJKuPtsEky3ybxN3m4fJkknfIpTCTfIZCDW57oAg+EfCgIIp2rvCe0Wn++/FfodDS4YXxBwA==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.5.tgz", - "integrity": "sha512-h9yIuWbJKdOPLJTbmSpPzkF67e659PbQDba7ifWm5BJ8xTv+sDmS7rFmywkWOvXedGTivCdeGSIIX8WLcRTz8w==", - "dev": true, - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.2.tgz", - "integrity": "sha512-/AVzPICMhMOMYoSx9MoKpGDKdBRsIXMNByh1PXSZoa+v6ZoLa8xxtsT/uLQ/NJm0XVAWl/BvId4MlDeXJaeIZQ==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.2.tgz", - "integrity": "sha512-ojlGK1Hsfce93J0+kn3H5R73elidKUaZonirN33GSmgTUMpzI/MIFfSpF3haANe3G1bEBS9/9/QEqwTzwqFsKw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.20.7" - } - }, "node_modules/@types/bcrypt": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz", @@ -4389,12 +4270,6 @@ "@types/express": "*" } }, - "node_modules/@types/cookiejar": { - "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/cookies": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/@types/cookies/-/cookies-0.9.0.tgz", @@ -4491,15 +4366,6 @@ "@types/node": "*" } }, - "node_modules/@types/graceful-fs": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.7.tgz", - "integrity": "sha512-MhzcwU8aUygZroVwL2jeYk6JisJrPl/oov/gsgGCue9mkgl9wjGbzReYQClxiUgFDnib9FuHqTndccKeZKxTRw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/hapi__catbox": { "version": "10.2.6", "resolved": "https://registry.npmjs.org/@types/hapi__catbox/-/hapi__catbox-10.2.6.tgz", @@ -4574,49 +4440,6 @@ "@types/node": "*" } }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", - "dev": true - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-gPQuzaPR5h/djlAv2apEG1HVOyj1IUs7GpfMZixU0/0KXT3pm64ylHuMUI1/Akh+sq/iikxg6Z2j+fcMDXaaTQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.2.tgz", - "integrity": "sha512-kv43F9eb3Lhj+lr/Hn6OcLCs/sSM8bt+fIaP11rCYngfV6NVjzWXJ17owQtDQTL9tQ8WSLUrGsSJ6rJz0F1w1A==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "29.5.12", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", - "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", - "dev": true, - "dependencies": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - } - }, - "node_modules/@types/jest-when": { - "version": "3.5.5", - "resolved": "https://registry.npmjs.org/@types/jest-when/-/jest-when-3.5.5.tgz", - "integrity": "sha512-H9MDPIrz7NOu6IXP9OHExNN9LnJbGYAzRsGIDKxWr7Fth9vovemNV8yFbkUWLSEmuA8PREvAEvt9yK0PPLmFHA==", - "dev": true, - "dependencies": { - "@types/jest": "*" - } - }, "node_modules/@types/js-yaml": { "version": "4.0.9", "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", @@ -4684,12 +4507,6 @@ "@types/node": "*" } }, - "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.3", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.3.tgz", @@ -4727,9 +4544,9 @@ } }, "node_modules/@types/node": { - "version": "20.12.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.4.tgz", - "integrity": "sha512-E+Fa9z3wSQpzgYQdYmme5X3OTuejnnTx88A6p6vkkJosR3KBz+HpE3kqNm98VE6cfLFcISx7zW7MsJkH6KwbTw==", + "version": "20.12.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", + "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==", "dependencies": { "undici-types": "~5.26.4" } @@ -4813,7 +4630,8 @@ "node_modules/@types/picomatch": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/@types/picomatch/-/picomatch-2.3.3.tgz", - "integrity": "sha512-Yll76ZHikRFCyz/pffKGjrCwe/le2CDwOP5F210KQo27kpRE46U2rDnzikNlVn6/ezH3Mhn46bJMTfeVTtcYMg==" + "integrity": "sha512-Yll76ZHikRFCyz/pffKGjrCwe/le2CDwOP5F210KQo27kpRE46U2rDnzikNlVn6/ezH3Mhn46bJMTfeVTtcYMg==", + "dev": true }, "node_modules/@types/qs": { "version": "6.9.8", @@ -4883,33 +4701,6 @@ "@types/node": "*" } }, - "node_modules/@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true - }, - "node_modules/@types/superagent": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.1.tgz", - "integrity": "sha512-YQyEXA4PgCl7EVOoSAS3o0fyPFU6erv5mMixztQYe1bqbWmmn8c+IrqoxjQeZe4MgwXikgcaZPiI/DsbmOVlzA==", - "dev": true, - "dependencies": { - "@types/cookiejar": "^2.1.5", - "@types/methods": "^1.1.4", - "@types/node": "*" - } - }, - "node_modules/@types/supertest": { - "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/methods": "^1.1.4", - "@types/superagent": "^8.1.0" - } - }, "node_modules/@types/tedious": { "version": "4.0.14", "resolved": "https://registry.npmjs.org/@types/tedious/-/tedious-4.0.14.tgz", @@ -4938,38 +4729,23 @@ "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.8.tgz", "integrity": "sha512-c/hzNDBh7eRF+KbCf+OoZxKbnkpaK/cKp9iLQWqB7muXtM+MtL9SUUH8vCFcLn6dH1Qm05jiexK0ofWY7TfOhQ==" }, - "node_modules/@types/yargs": { - "version": "17.0.26", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.26.tgz", - "integrity": "sha512-Y3vDy2X6zw/ZCumcwLpdhM5L7jmyGpmBCTYMHDLqT2IKVMYRRLdv6ZakA+wxhra6Z/3bwhNbNl9bDGXaFU+6rw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.1.tgz", - "integrity": "sha512-axdPBuLuEJt0c4yI5OZssC19K2Mq1uKdrfZBzuxLvaztgqUtFYZUNw7lETExPYJR9jdEoIg4mb7RQKRQzOkeGQ==", - "dev": true - }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.5.0.tgz", - "integrity": "sha512-HpqNTH8Du34nLxbKgVMGljZMG0rJd2O9ecvr2QLYp+7512ty1j42KnsFwspPXg1Vh8an9YImf6CokUBltisZFQ==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.6.0.tgz", + "integrity": "sha512-gKmTNwZnblUdnTIJu3e9kmeRRzV2j1a/LUO27KNNAnIC5zjy1aSvXSRp4rVNlmAoHlQ7HzX42NbKpcSr4jF80A==", "dev": true, "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "7.5.0", - "@typescript-eslint/type-utils": "7.5.0", - "@typescript-eslint/utils": "7.5.0", - "@typescript-eslint/visitor-keys": "7.5.0", + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.6.0", + "@typescript-eslint/type-utils": "7.6.0", + "@typescript-eslint/utils": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0", "debug": "^4.3.4", "graphemer": "^1.4.0", - "ignore": "^5.2.4", + "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -4989,15 +4765,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.5.0.tgz", - "integrity": "sha512-cj+XGhNujfD2/wzR1tabNsidnYRaFfEkcULdcIyVBYcXjBvBKOes+mpMBP7hMpOyk+gBcfXsrg4NBGAStQyxjQ==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.6.0.tgz", + "integrity": "sha512-usPMPHcwX3ZoPWnBnhhorc14NJw9J4HpSXQX4urF2TPKG0au0XhJoZyX62fmvdHONUkmyUe74Hzm1//XA+BoYg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "7.5.0", - "@typescript-eslint/types": "7.5.0", - "@typescript-eslint/typescript-estree": "7.5.0", - "@typescript-eslint/visitor-keys": "7.5.0", + "@typescript-eslint/scope-manager": "7.6.0", + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/typescript-estree": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0", "debug": "^4.3.4" }, "engines": { @@ -5017,13 +4793,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.5.0.tgz", - "integrity": "sha512-Z1r7uJY0MDeUlql9XJ6kRVgk/sP11sr3HKXn268HZyqL7i4cEfrdFuSSY/0tUqT37l5zT0tJOsuDP16kio85iA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.6.0.tgz", + "integrity": "sha512-ngttyfExA5PsHSx0rdFgnADMYQi+Zkeiv4/ZxGYUWd0nLs63Ha0ksmp8VMxAIC0wtCFxMos7Lt3PszJssG/E6w==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.5.0", - "@typescript-eslint/visitor-keys": "7.5.0" + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -5034,15 +4810,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.5.0.tgz", - "integrity": "sha512-A021Rj33+G8mx2Dqh0nMO9GyjjIBK3MqgVgZ2qlKf6CJy51wY/lkkFqq3TqqnH34XyAHUkq27IjlUkWlQRpLHw==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.6.0.tgz", + "integrity": "sha512-NxAfqAPNLG6LTmy7uZgpK8KcuiS2NZD/HlThPXQRGwz6u7MDBWRVliEEl1Gj6U7++kVJTpehkhZzCJLMK66Scw==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "7.5.0", - "@typescript-eslint/utils": "7.5.0", + "@typescript-eslint/typescript-estree": "7.6.0", + "@typescript-eslint/utils": "7.6.0", "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" + "ts-api-utils": "^1.3.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -5061,9 +4837,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.5.0.tgz", - "integrity": "sha512-tv5B4IHeAdhR7uS4+bf8Ov3k793VEVHd45viRRkehIUZxm0WF82VPiLgHzA/Xl4TGPg1ZD49vfxBKFPecD5/mg==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.6.0.tgz", + "integrity": "sha512-h02rYQn8J+MureCvHVVzhl69/GAfQGPQZmOMjG1KfCl7o3HtMSlPaPUAPu6lLctXI5ySRGIYk94clD/AUMCUgQ==", "dev": true, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -5074,19 +4850,19 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.5.0.tgz", - "integrity": "sha512-YklQQfe0Rv2PZEueLTUffiQGKQneiIEKKnfIqPIOxgM9lKSZFCjT5Ad4VqRKj/U4+kQE3fa8YQpskViL7WjdPQ==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.6.0.tgz", + "integrity": "sha512-+7Y/GP9VuYibecrCQWSKgl3GvUM5cILRttpWtnAu8GNL9j11e4tbuGZmZjJ8ejnKYyBRb2ddGQ3rEFCq3QjMJw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.5.0", - "@typescript-eslint/visitor-keys": "7.5.0", + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0", "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" + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -5111,9 +4887,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -5126,18 +4902,18 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.5.0.tgz", - "integrity": "sha512-3vZl9u0R+/FLQcpy2EHyRGNqAS/ofJ3Ji8aebilfJe+fobK8+LbIFmrHciLVDxjDoONmufDcnVSF38KwMEOjzw==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.6.0.tgz", + "integrity": "sha512-x54gaSsRRI+Nwz59TXpCsr6harB98qjXYzsRxGqvA5Ue3kQH+FxS7FYU81g/omn22ML2pZJkisy6Q+ElK8pBCA==", "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": "7.5.0", - "@typescript-eslint/types": "7.5.0", - "@typescript-eslint/typescript-estree": "7.5.0", - "semver": "^7.5.4" + "@types/json-schema": "^7.0.15", + "@types/semver": "^7.5.8", + "@typescript-eslint/scope-manager": "7.6.0", + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/typescript-estree": "7.6.0", + "semver": "^7.6.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -5151,13 +4927,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.5.0.tgz", - "integrity": "sha512-mcuHM/QircmA6O7fy6nn2w/3ditQkj+SgtOc8DW3uQ10Yfj42amm2i+6F2K4YAOPNNTmE6iM1ynM6lrSwdendA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.6.0.tgz", + "integrity": "sha512-4eLB7t+LlNUmXzfOu1VAIAdkjbu5xNSerURS9X/S5TUKWFRpXRQZbmtPqgKmYx8bj3J0irtQXSiWAOY82v+cgw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.5.0", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "7.6.0", + "eslint-visitor-keys": "^3.4.3" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -5173,6 +4949,143 @@ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", "dev": true }, + "node_modules/@vitest/coverage-v8": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.5.0.tgz", + "integrity": "sha512-1igVwlcqw1QUMdfcMlzzY4coikSIBN944pkueGi0pawrX5I5Z+9hxdTR+w3Sg6Q3eZhvdMAs8ZaF9JuTG1uYOQ==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.1", + "@bcoe/v8-coverage": "^0.2.3", + "debug": "^4.3.4", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.4", + "istanbul-reports": "^3.1.6", + "magic-string": "^0.30.5", + "magicast": "^0.3.3", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^2.0.0", + "test-exclude": "^6.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "1.5.0" + } + }, + "node_modules/@vitest/coverage-v8/node_modules/istanbul-lib-source-maps": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.4.tgz", + "integrity": "sha512-wHOoEsNJTVltaJp8eVkm8w+GVkVNHT2YDYo53YdzQEL2gWm1hBX5cGFR9hQJtuGLebidVX7et3+dmDZrmclduw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@vitest/expect": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.5.0.tgz", + "integrity": "sha512-0pzuCI6KYi2SIC3LQezmxujU9RK/vwC1U9R0rLuGlNGcOuDWxqWKu6nUdFsX9tH1WU0SXtAxToOsEjeUn1s3hA==", + "dev": true, + "dependencies": { + "@vitest/spy": "1.5.0", + "@vitest/utils": "1.5.0", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.5.0.tgz", + "integrity": "sha512-7HWwdxXP5yDoe7DTpbif9l6ZmDwCzcSIK38kTSIt6CFEpMjX4EpCgT6wUmS0xTXqMI6E/ONmfgRKmaujpabjZQ==", + "dev": true, + "dependencies": { + "@vitest/utils": "1.5.0", + "p-limit": "^5.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner/node_modules/p-limit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", + "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/runner/node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/snapshot": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.5.0.tgz", + "integrity": "sha512-qpv3fSEuNrhAO3FpH6YYRdaECnnRjg9VxbhdtPwPRnzSfHVXnNzzrpX4cJxqiwgRMo7uRMWDFBlsBq4Cr+rO3A==", + "dev": true, + "dependencies": { + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.5.0.tgz", + "integrity": "sha512-vu6vi6ew5N5MMHJjD5PoakMRKYdmIrNJmyfkhRpQt5d9Ewhw9nZ5Aqynbi3N61bvk9UvZ5UysMT6ayIrZ8GA9w==", + "dev": true, + "dependencies": { + "tinyspy": "^2.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.5.0.tgz", + "integrity": "sha512-BDU0GNL8MWkRkSRdNFvCUCAVOeHaUlVJ9Tx0TYBZyXaaOTmGtUFObzchCivIBrIwKzvZA7A9sCejVhXM2aY98A==", + "dev": true, + "dependencies": { + "diff-sequences": "^29.6.3", + "estree-walker": "^3.0.3", + "loupe": "^2.3.7", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", @@ -5360,9 +5273,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==", "bin": { "acorn": "bin/acorn" }, @@ -5388,9 +5301,9 @@ } }, "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==", "devOptional": true, "engines": { "node": ">=0.4.0" @@ -5676,7 +5589,8 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "devOptional": true + "optional": true, + "peer": true }, "node_modules/argparse": { "version": "2.0.1", @@ -5708,12 +5622,6 @@ "node": ">=8" } }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "dev": true - }, "node_modules/asn1": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", @@ -5723,6 +5631,15 @@ "safer-buffer": "~2.1.0" } }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/async": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", @@ -5733,133 +5650,11 @@ "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.1.tgz", "integrity": "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==" }, - "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 - }, "node_modules/b4a": { "version": "1.6.4", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", "integrity": "sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==" }, - "node_modules/babel-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", - "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", - "dev": true, - "dependencies": { - "@jest/transform": "^29.7.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-istanbul/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", - "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", - "dev": true, - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-preset-jest": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", - "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", - "dev": true, - "dependencies": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -6078,27 +5873,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, - "dependencies": { - "fast-json-stable-stringify": "2.x" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "dependencies": { - "node-int64": "^0.4.0" - } - }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -6239,6 +6013,15 @@ "node": ">= 0.8" } }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/call-bind": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.6.tgz", @@ -6264,15 +6047,6 @@ "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": { - "node": ">=6" - } - }, "node_modules/caniuse-lite": { "version": "1.0.30001581", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001581.tgz", @@ -6293,6 +6067,24 @@ } ] }, + "node_modules/chai": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", + "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.0.8" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -6308,20 +6100,23 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -6362,21 +6157,6 @@ "node": ">=6.0" } }, - "node_modules/ci-info": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", - "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "engines": { - "node": ">=8" - } - }, "node_modules/cjs-module-lexer": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", @@ -6525,22 +6305,6 @@ "node": ">=0.10.0" } }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true, - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", - "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", - "dev": true - }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -6586,18 +6350,6 @@ "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", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -6623,12 +6375,6 @@ "node": ">= 6" } }, - "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 - }, "node_modules/compress-commons": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", @@ -6730,12 +6476,6 @@ "node": ">= 0.6" } }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, "node_modules/cookie": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", @@ -6761,12 +6501,6 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, - "node_modules/cookiejar": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", - "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", - "dev": true - }, "node_modules/core-js-compat": { "version": "3.35.1", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.35.1.tgz", @@ -6898,32 +6632,12 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/create-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", - "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", - "dev": true, - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "prompts": "^2.0.1" - }, - "bin": { - "create-jest": "bin/create-jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "devOptional": true + "optional": true, + "peer": true }, "node_modules/cron": { "version": "3.1.6", @@ -6979,18 +6693,16 @@ } } }, - "node_modules/dedent": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", - "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", "dev": true, - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" + "dependencies": { + "type-detect": "^4.0.0" }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } + "engines": { + "node": ">=6" } }, "node_modules/deep-is": { @@ -7036,15 +6748,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "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, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -7083,25 +6786,6 @@ "node": ">=8" } }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/dezalgo": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", - "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", - "dev": true, - "dependencies": { - "asap": "^2.0.0", - "wrappy": "1" - } - }, "node_modules/diacritics": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/diacritics/-/diacritics-1.3.0.tgz", @@ -7111,7 +6795,8 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "devOptional": true, + "optional": true, + "peer": true, "engines": { "node": ">=0.3.1" } @@ -7265,18 +6950,6 @@ "integrity": "sha512-sYSQhJCJa4aGA1wYol5cMQgekDBlbVfTRavlGZVr3WZpDdOPcp6a6xUnFfrt8TqZhsBYYbDxJZCjGfHuGupCRQ==", "dev": true }, - "node_modules/emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -7362,6 +7035,44 @@ "integrity": "sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==", "dev": true }, + "node_modules/esbuild": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" + } + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -7669,6 +7380,15 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -7707,35 +7427,6 @@ "node": ">=0.8.x" } }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/execa/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==", - "dev": true - }, "node_modules/exiftool-vendored": { "version": "24.6.0", "resolved": "https://registry.npmjs.org/exiftool-vendored/-/exiftool-vendored-24.6.0.tgz", @@ -7775,31 +7466,6 @@ "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz", "integrity": "sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==" }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", - "dev": true, - "dependencies": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/express": { "version": "4.19.2", "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", @@ -7942,15 +7608,6 @@ "reusify": "^1.0.4" } }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "dependencies": { - "bser": "2.1.1" - } - }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -8171,35 +7828,6 @@ "webpack": "^5.11.0" } }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/formidable": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz", - "integrity": "sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==", - "dev": true, - "dependencies": { - "dezalgo": "^1.0.4", - "hexoid": "^1.0.0", - "once": "^1.4.0", - "qs": "^6.11.0" - }, - "funding": { - "url": "https://ko-fi.com/tunnckoCore/commissions" - } - }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -8368,15 +7996,6 @@ "node": ">=14" } }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/geo-tz": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/geo-tz/-/geo-tz-8.0.2.tgz", @@ -8417,6 +8036,15 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/get-intrinsic": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", @@ -8435,15 +8063,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/get-port": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", @@ -8468,18 +8087,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/glob": { "version": "10.3.12", "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", @@ -8710,15 +8317,6 @@ "he": "bin/he" } }, - "node_modules/hexoid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", - "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/highlight.js": { "version": "10.7.3", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", @@ -8766,15 +8364,6 @@ "node": ">= 6" } }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "engines": { - "node": ">=10.17.0" - } - }, "node_modules/i18n-iso-countries": { "version": "7.11.0", "resolved": "https://registry.npmjs.org/i18n-iso-countries/-/i18n-iso-countries-7.11.0.tgz", @@ -8851,25 +8440,6 @@ "module-details-from-path": "^1.0.3" } }, - "node_modules/import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -9025,15 +8595,6 @@ "node": ">=8" } }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -9103,30 +8664,14 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "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" } }, - "node_modules/istanbul-lib-instrument": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.0.tgz", - "integrity": "sha512-x58orMzEVfzPUKqlbLd1hXCnySCxKdDKa6Rjg97CwuLLRI4g3FHTdnExu1OqffVFay6zeMW+T6/DowFLndWnIw==", - "dev": true, - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/istanbul-lib-report": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", @@ -9156,29 +8701,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/istanbul-reports": { "version": "3.1.6", "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", @@ -9217,690 +8739,6 @@ "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", - "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", - "dev": true, - "dependencies": { - "@jest/core": "^29.7.0", - "@jest/types": "^29.6.3", - "import-local": "^3.0.2", - "jest-cli": "^29.7.0" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-changed-files": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", - "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", - "dev": true, - "dependencies": { - "execa": "^5.0.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-circus": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", - "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^1.0.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0", - "pretty-format": "^29.7.0", - "pure-rand": "^6.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-cli": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", - "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", - "dev": true, - "dependencies": { - "@jest/core": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "create-jest": "^29.7.0", - "exit": "^0.1.2", - "import-local": "^3.0.2", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "yargs": "^17.3.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-cli/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/jest-cli/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/jest-cli/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/jest-config": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", - "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-jest": "^29.7.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-config/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, - "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/jest-diff": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", - "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-docblock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", - "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", - "dev": true, - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-each": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", - "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", - "dev": true, - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "jest-util": "^29.7.0", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-environment-node": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", - "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", - "dev": true, - "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/jest-leak-detector": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", - "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", - "dev": true, - "dependencies": { - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", - "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", - "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-mock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", - "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", - "dev": true, - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", - "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", - "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", - "dev": true, - "dependencies": { - "jest-regex-util": "^29.6.3", - "jest-snapshot": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runner": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", - "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", - "dev": true, - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/environment": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-leak-detector": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-resolve": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-util": "^29.7.0", - "jest-watcher": "^29.7.0", - "jest-worker": "^29.7.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runner/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jest-runner/node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/jest-runtime": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", - "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/globals": "^29.7.0", - "@jest/source-map": "^29.6.3", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runtime/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, - "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/jest-snapshot": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", - "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "natural-compare": "^1.4.0", - "pretty-format": "^29.7.0", - "semver": "^7.5.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", - "dev": true, - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-util/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/jest-validate": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", - "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", - "dev": true, - "dependencies": { - "@jest/types": "^29.6.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "leven": "^3.1.0", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-watcher": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", - "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", - "dev": true, - "dependencies": { - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "jest-util": "^29.7.0", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-when": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/jest-when/-/jest-when-3.6.0.tgz", - "integrity": "sha512-+cZWTy0ekAJo7M9Om0Scdor1jm3wDiYJWmXE8U22UVnkH54YCXAuaqz3P+up/FdtOg8g4wHOxV7Thd7nKhT6Dg==", - "dev": true, - "peerDependencies": { - "jest": ">= 25" - } - }, - "node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "dev": true, - "dependencies": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, "node_modules/joi": { "version": "17.12.3", "resolved": "https://registry.npmjs.org/joi/-/joi-17.12.3.tgz", @@ -9937,18 +8775,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/json-bigint": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", @@ -10019,15 +8845,6 @@ "json-buffer": "3.0.1" } }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/lazystream": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", @@ -10066,15 +8883,6 @@ "safe-buffer": "~5.1.0" } }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -10098,6 +8906,15 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, + "node_modules/load-tsconfig": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", + "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -10107,6 +8924,22 @@ "node": ">=6.11.5" } }, + "node_modules/local-pkg": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", + "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==", + "dev": true, + "dependencies": { + "mlly": "^1.4.2", + "pkg-types": "^1.0.3" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -10160,12 +8993,6 @@ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", "dev": true }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -10197,13 +9024,13 @@ "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", "dev": true, "dependencies": { - "yallist": "^3.0.2" + "get-func-name": "^2.0.1" } }, "node_modules/luxon": { @@ -10226,6 +9053,17 @@ "node": ">=12" } }, + "node_modules/magicast": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.4.tgz", + "integrity": "sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.24.4", + "@babel/types": "^7.24.0", + "source-map-js": "^1.2.0" + } + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -10252,16 +9090,8 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "devOptional": true - }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "dependencies": { - "tmpl": "1.0.5" - } + "optional": true, + "peer": true }, "node_modules/media-typer": { "version": "0.3.0", @@ -10452,6 +9282,18 @@ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "dev": true }, + "node_modules/mlly": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.6.1.tgz", + "integrity": "sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==", + "dev": true, + "dependencies": { + "acorn": "^8.11.3", + "pathe": "^1.1.2", + "pkg-types": "^1.0.3", + "ufo": "^1.3.2" + } + }, "node_modules/mnemonist": { "version": "0.39.8", "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.39.8.tgz", @@ -10602,6 +9444,24 @@ "dev": true, "optional": true }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -10685,6 +9545,20 @@ "node": ">=16" } }, + "node_modules/nestjs-cls": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/nestjs-cls/-/nestjs-cls-4.3.0.tgz", + "integrity": "sha512-MVTun6tqCZih8AJXRj8uBuuFyJhQrIA9m9fStiQjbBXUkE3BrlMRvmLzyw8UcneB3xtFFTfwkAh5PYKRulyaOg==", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@nestjs/common": "> 7.0.0 < 11", + "@nestjs/core": "> 7.0.0 < 11", + "reflect-metadata": "*", + "rxjs": ">= 7" + } + }, "node_modules/nestjs-otel": { "version": "5.1.5", "resolved": "https://registry.npmjs.org/nestjs-otel/-/nestjs-otel-5.1.5.tgz", @@ -10743,12 +9617,6 @@ "node-gyp-build-optional-packages-test": "build-test.js" } }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true - }, "node_modules/node-releases": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", @@ -10798,18 +9666,6 @@ "node": ">=0.10.0" } }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/npmlog": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", @@ -11157,6 +10013,21 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/pbf": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.2.1.tgz", @@ -11261,8 +10132,7 @@ "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, "node_modules/picomatch": { "version": "4.0.2", @@ -11275,77 +10145,15 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pirates": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", - "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "node_modules/pkg-types": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", + "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==", "dev": true, "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" + "jsonc-parser": "^3.2.0", + "mlly": "^1.2.0", + "pathe": "^1.1.0" } }, "node_modules/pluralize": { @@ -11357,6 +10165,34 @@ "node": ">=4" } }, + "node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/postgres-array": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", @@ -11491,19 +10327,6 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/proper-lockfile": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", @@ -11608,22 +10431,6 @@ "node": ">=6" } }, - "node_modules/pure-rand": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz", - "integrity": "sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ] - }, "node_modules/qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", @@ -11914,14 +10721,9 @@ } }, "node_modules/reflect-metadata": { - "version": "0.1.14", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.14.tgz", - "integrity": "sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==" - }, - "node_modules/regenerator-runtime": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", - "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==" }, "node_modules/regexp-tree": { "version": "0.1.27", @@ -12008,27 +10810,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-cwd/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -12045,15 +10826,6 @@ "protocol-buffers-schema": "^3.3.1" } }, - "node_modules/resolve.exports": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", - "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/response-time": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/response-time/-/response-time-2.3.2.tgz", @@ -12136,6 +10908,41 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rollup": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.14.3.tgz", + "integrity": "sha512-ag5tTQKYsj1bhrFC9+OEWqb5O6VYgtQDO9hPDBMmIbePwhfSr+ExlcU741t8Dhw5DkPCQf6noz0jb36D6W9/hw==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.14.3", + "@rollup/rollup-android-arm64": "4.14.3", + "@rollup/rollup-darwin-arm64": "4.14.3", + "@rollup/rollup-darwin-x64": "4.14.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.14.3", + "@rollup/rollup-linux-arm-musleabihf": "4.14.3", + "@rollup/rollup-linux-arm64-gnu": "4.14.3", + "@rollup/rollup-linux-arm64-musl": "4.14.3", + "@rollup/rollup-linux-powerpc64le-gnu": "4.14.3", + "@rollup/rollup-linux-riscv64-gnu": "4.14.3", + "@rollup/rollup-linux-s390x-gnu": "4.14.3", + "@rollup/rollup-linux-x64-gnu": "4.14.3", + "@rollup/rollup-linux-x64-musl": "4.14.3", + "@rollup/rollup-win32-arm64-msvc": "4.14.3", + "@rollup/rollup-win32-ia32-msvc": "4.14.3", + "@rollup/rollup-win32-x64-msvc": "4.14.3", + "fsevents": "~2.3.2" + } + }, "node_modules/run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -12522,6 +11329,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -12559,12 +11372,6 @@ "node": ">= 10" } }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -12625,6 +11432,15 @@ "node": ">= 8" } }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", @@ -12690,12 +11506,6 @@ "node": ">= 10.x" } }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, "node_modules/sql-formatter": { "version": "15.3.0", "resolved": "https://registry.npmjs.org/sql-formatter/-/sql-formatter-15.3.0.tgz", @@ -12738,26 +11548,11 @@ "nan": "^2.17.0" } }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "engines": { - "node": ">=8" - } + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true }, "node_modules/standard-as-callback": { "version": "2.1.0", @@ -12772,6 +11567,12 @@ "node": ">= 0.8" } }, + "node_modules/std-env": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", + "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", + "dev": true + }, "node_modules/stream-source": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/stream-source/-/stream-source-0.3.5.tgz", @@ -12802,19 +11603,6 @@ "safe-buffer": "~5.2.0" } }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -12865,24 +11653,6 @@ "node": ">=8" } }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/strip-indent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", @@ -12907,51 +11677,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/superagent": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.1.2.tgz", - "integrity": "sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==", + "node_modules/strip-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.0.tgz", + "integrity": "sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==", "dev": true, "dependencies": { - "component-emitter": "^1.3.0", - "cookiejar": "^2.1.4", - "debug": "^4.3.4", - "fast-safe-stringify": "^2.1.1", - "form-data": "^4.0.0", - "formidable": "^2.1.2", - "methods": "^1.1.2", - "mime": "2.6.0", - "qs": "^6.11.0", - "semver": "^7.3.8" + "js-tokens": "^9.0.0" }, - "engines": { - "node": ">=6.4.0 <13 || >=14" + "funding": { + "url": "https://github.com/sponsors/antfu" } }, - "node_modules/superagent/node_modules/mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "dev": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/supertest": { - "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.1.2" - }, - "engines": { - "node": ">=6.4.0" - } + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.0.tgz", + "integrity": "sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==", + "dev": true }, "node_modules/supports-color": { "version": "7.2.0", @@ -13225,9 +11967,9 @@ } }, "node_modules/testcontainers": { - "version": "10.8.1", - "resolved": "https://registry.npmjs.org/testcontainers/-/testcontainers-10.8.1.tgz", - "integrity": "sha512-2Kzeu3UfnILkhpdz2+YDu1FBFerAusdMCsltErBCJouP5j5xuxrV8BHxhlDt0xsJdM8YnhHgA2B32LmDr5AToA==", + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/testcontainers/-/testcontainers-10.8.2.tgz", + "integrity": "sha512-9Ink7NUyYZwOjQhk0C6R6basWy2WADNly+md3D9YDap0pcDr3C+vrO8Ah1bkYco/9Zg8VoYTHO+blkLeebBYkA==", "dev": true, "dependencies": { "@balena/dockerignore": "^1.0.2", @@ -13465,6 +12207,30 @@ "resolved": "https://registry.npmjs.org/thumbhash/-/thumbhash-0.1.1.tgz", "integrity": "sha512-kH5pKeIIBPQXAOni2AiY/Cu/NKdkFREdpH+TLdM0g6WA7RriCv0kPLgP731ady67MhTAqrVG/4mnEeibVuCJcg==" }, + "node_modules/tinybench": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.7.0.tgz", + "integrity": "sha512-Qgayeb106x2o4hNzNjsZEfFziw8IbKqtbXBjVh7VIZfBxfD5M4gWtpyx5+YTae2gJ6Y6Dz/KLepiv16RFeQWNA==", + "dev": true + }, + "node_modules/tinypool": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", + "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", + "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -13476,12 +12242,6 @@ "node": ">=0.6.0" } }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": 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", @@ -13552,74 +12312,12 @@ "typescript": ">=4.2.0" } }, - "node_modules/ts-jest": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.2.tgz", - "integrity": "sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==", - "dev": true, - "dependencies": { - "bs-logger": "0.x", - "fast-json-stable-stringify": "2.x", - "jest-util": "^29.0.0", - "json5": "^2.2.3", - "lodash.memoize": "4.x", - "make-error": "1.x", - "semver": "^7.5.3", - "yargs-parser": "^21.0.1" - }, - "bin": { - "ts-jest": "cli.js" - }, - "engines": { - "node": "^16.10.0 || ^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/types": "^29.0.0", - "babel-jest": "^29.0.0", - "jest": "^29.0.0", - "typescript": ">=4.3 <6" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@jest/types": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { - "optional": true - } - } - }, - "node_modules/ts-loader": { - "version": "9.5.1", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz", - "integrity": "sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "enhanced-resolve": "^5.0.0", - "micromatch": "^4.0.0", - "semver": "^7.3.4", - "source-map": "^0.7.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "typescript": "*", - "webpack": "^5.0.0" - } - }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "devOptional": true, + "optional": true, + "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -13911,11 +12609,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/typeorm/node_modules/reflect-metadata": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.1.tgz", - "integrity": "sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw==" - }, "node_modules/typeorm/node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -13950,9 +12643,9 @@ } }, "node_modules/typescript": { - "version": "5.4.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.4.tgz", - "integrity": "sha512-dGE2Vv8cpVvw28v8HCPqyb08EzbBURxDpuhJvTrusShUfGnhHBafDsLdS1EhhxyL6BJQE+2cT3dDPAv+MQ6oLw==", + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", "devOptional": true, "bin": { "tsc": "bin/tsc", @@ -13984,6 +12677,12 @@ "node": "*" } }, + "node_modules/ufo": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.3.tgz", + "integrity": "sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==", + "dev": true + }, "node_modules/uglify-js": { "version": "3.17.4", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", @@ -14029,6 +12728,35 @@ "node": ">= 0.8" } }, + "node_modules/unplugin": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.10.1.tgz", + "integrity": "sha512-d6Mhq8RJeGA8UfKCu54Um4lFA0eSaRa3XxdAJg8tIdxbu1ubW0hBCZUL7yI2uGyYCRndvbK8FLHzqy2XKfeMsg==", + "dev": true, + "dependencies": { + "acorn": "^8.11.3", + "chokidar": "^3.6.0", + "webpack-sources": "^3.2.3", + "webpack-virtual-modules": "^0.6.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/unplugin-swc": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/unplugin-swc/-/unplugin-swc-1.4.5.tgz", + "integrity": "sha512-ltkJ70kjL53onJrypaMmKDiOvhghNUCbCxjxT6Ir0eAMIBsOfRhPt6vQtxB8R/6wYk/TfIJ2gCgdx2uKNPJRHA==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.1.0", + "load-tsconfig": "^0.2.5", + "unplugin": "^1.10.1" + }, + "peerDependencies": { + "@swc/core": "^1.2.108" + } + }, "node_modules/update-browserslist-db": { "version": "1.0.13", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", @@ -14122,27 +12850,8 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "devOptional": true - }, - "node_modules/v8-to-istanbul": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", - "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/v8-to-istanbul/node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true + "optional": true, + "peer": true }, "node_modules/validate-npm-package-license": { "version": "3.0.4", @@ -14170,13 +12879,268 @@ "node": ">= 0.8" } }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "node_modules/vite": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.8.tgz", + "integrity": "sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==", "dev": true, "dependencies": { - "makeerror": "1.0.12" + "esbuild": "^0.20.1", + "postcss": "^8.4.38", + "rollup": "^4.13.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.5.0.tgz", + "integrity": "sha512-tV8h6gMj6vPzVCa7l+VGq9lwoJjW8Y79vst8QZZGiuRAfijU+EEWuc0kFpmndQrWhMMhet1jdSF+40KSZUqIIw==", + "dev": true, + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.5.0.tgz", + "integrity": "sha512-d8UKgR0m2kjdxDWX6911uwxout6GHS0XaGH1cksSIVVG8kRlE7G7aBw7myKQCvDI5dT4j7ZMa+l706BIORMDLw==", + "dev": true, + "dependencies": { + "@vitest/expect": "1.5.0", + "@vitest/runner": "1.5.0", + "@vitest/snapshot": "1.5.0", + "@vitest/spy": "1.5.0", + "@vitest/utils": "1.5.0", + "acorn-walk": "^8.3.2", + "chai": "^4.3.10", + "debug": "^4.3.4", + "execa": "^8.0.1", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^2.0.0", + "tinybench": "^2.5.1", + "tinypool": "^0.8.3", + "vite": "^5.0.0", + "vite-node": "1.5.0", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "1.5.0", + "@vitest/ui": "1.5.0", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/vitest/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vitest/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/vitest/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vitest/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vitest/node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vitest/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vitest/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vitest/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/watchpack": { @@ -14270,6 +13234,12 @@ "node": ">=10.13.0" } }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.1.tgz", + "integrity": "sha512-poXpCylU7ExuvZK8z+On3kX+S8o/2dQ/SVYueKA0D4WEMXROXgY8Ez50/bQEUmvoSMMrWcrJqCHuhAbsiwg7Dg==", + "dev": true + }, "node_modules/webpack/node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -14315,6 +13285,22 @@ "node": ">= 8" } }, + "node_modules/why-is-node-running": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", + "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "dev": true, + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", @@ -14363,25 +13349,6 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, - "node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/write-file-atomic/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==", - "dev": true - }, "node_modules/ws": { "version": "8.11.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", @@ -14418,12 +13385,6 @@ "node": ">=10" } }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, "node_modules/yaml": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", @@ -14470,7 +13431,8 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "devOptional": true, + "optional": true, + "peer": true, "engines": { "node": ">=6" } @@ -14677,210 +13639,18 @@ } }, "@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.24.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", + "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", "requires": { - "@babel/highlight": "^7.22.13", - "chalk": "^2.4.2" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "@babel/compat-data": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.20.tgz", - "integrity": "sha512-BQYjKbpXjoXwFW5jGqiizJQQT/aC7pFm9Ok1OWssonuguICi264lbgMzRp2ZMmRSlfkX6DsWDDcsrctK8Rwfiw==", - "dev": true - }, - "@babel/core": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.0.tgz", - "integrity": "sha512-97z/ju/Jy1rZmDxybphrBuI+jtJjFVoz7Mr9yUQVVVi+DNZE333uFQeMOqcCIy1x3WYBIbWftUSLmbNXNT7qFQ==", - "dev": true, - "requires": { - "@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.0", - "@babel/parser": "^7.23.0", - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.0", - "@babel/types": "^7.23.0", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "dependencies": { - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true - } - } - }, - "@babel/generator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.3.tgz", - "integrity": "sha512-keeZWAV4LU3tW0qRi19HRpabC/ilM0HRBBzf9/k8FFiG4KVpiv0FIy4hHfLfFQZNhziCTPTmd59zoyv6DNISzg==", - "dev": true, - "requires": { - "@babel/types": "^7.23.3", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" - } - }, - "@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==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-validator-option": "^7.22.15", - "browserslist": "^4.21.9", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "dependencies": { - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true - } - } - }, - "@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "dev": true - }, - "@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "dev": true, - "requires": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dev": true, - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-module-imports": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", - "dev": true, - "requires": { - "@babel/types": "^7.22.15" - } - }, - "@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==", - "dev": true, - "requires": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.20" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", - "dev": true - }, - "@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", - "dev": true, - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", - "dev": true, - "requires": { - "@babel/types": "^7.22.5" + "@babel/highlight": "^7.24.2", + "picocolors": "^1.0.0" } }, "@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.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", + "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", "dev": true }, "@babel/helper-validator-identifier": { @@ -14888,31 +13658,15 @@ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==" }, - "@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==", - "dev": true - }, - "@babel/helpers": { - "version": "7.23.1", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.1.tgz", - "integrity": "sha512-chNpneuK18yW5Oxsr+t553UZzzAs3aZnFm4bxhebsNTeshrC95yA7l5yl7GBAG+JG1rF0F7zzD2EixK9mWSDoA==", - "dev": true, - "requires": { - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.0", - "@babel/types": "^7.23.0" - } - }, "@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.24.2", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", + "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", "requires": { "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "dependencies": { "ansi-styles": { @@ -14967,189 +13721,18 @@ } }, "@babel/parser": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.3.tgz", - "integrity": "sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz", + "integrity": "sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==", "dev": true }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-jsx": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", - "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-typescript": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", - "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/runtime": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz", - "integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==", - "requires": { - "regenerator-runtime": "^0.14.0" - } - }, - "@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" - } - }, - "@babel/traverse": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.3.tgz", - "integrity": "sha512-+K0yF1/9yR0oHdE0StHuEj3uTPzwwbrLGfNOndVJVV2TqA5+j3oljJUb4nmB954FLGjNem976+B+eDuLIjesiQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.3", - "@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.3", - "@babel/types": "^7.23.3", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "dependencies": { - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - } - } - }, "@babel/types": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.3.tgz", - "integrity": "sha512-OZnvoH2l8PK5eUvEcUyCt/sXgr/h+UWpVuBbOljwcrAgUl6lpchoQ++PHGyQy1AtYnVA6CEq3y5xeEI10brpXw==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", + "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", "dev": true, "requires": { - "@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" } @@ -15177,7 +13760,8 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "devOptional": true, + "optional": true, + "peer": true, "requires": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -15186,7 +13770,8 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "devOptional": true, + "optional": true, + "peer": true, "requires": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -15203,6 +13788,167 @@ "tslib": "^2.4.0" } }, + "@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "dev": true, + "optional": true + }, "@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -15630,244 +14376,12 @@ } } }, - "@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "dependencies": { - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - } - } - }, "@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true }, - "@jest/console": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", - "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0" - } - }, - "@jest/core": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", - "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", - "dev": true, - "requires": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "@jest/environment": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", - "dev": true, - "requires": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0" - } - }, - "@jest/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", - "dev": true, - "requires": { - "expect": "^29.7.0", - "jest-snapshot": "^29.7.0" - } - }, - "@jest/expect-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", - "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", - "dev": true, - "requires": { - "jest-get-type": "^29.6.3" - } - }, - "@jest/fake-timers": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - } - }, - "@jest/globals": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", - "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", - "dev": true, - "requires": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/types": "^29.6.3", - "jest-mock": "^29.7.0" - } - }, - "@jest/reporters": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", - "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", - "dev": true, - "requires": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "dependencies": { - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "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" - } - } - } - }, "@jest/schemas": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", @@ -15877,87 +14391,15 @@ "@sinclair/typebox": "^0.27.8" } }, - "@jest/source-map": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", - "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - } - }, - "@jest/test-result": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", - "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", - "dev": true, - "requires": { - "@jest/console": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - } - }, - "@jest/test-sequencer": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", - "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", - "dev": true, - "requires": { - "@jest/test-result": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "slash": "^3.0.0" - } - }, - "@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - } - }, - "@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "requires": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, "@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, "requires": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" } }, "@jridgewell/resolve-uri": { @@ -15967,9 +14409,9 @@ "devOptional": true }, "@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true }, "@jridgewell/source-map": { @@ -15989,9 +14431,9 @@ "devOptional": true }, "@jridgewell/trace-mapping": { - "version": "0.3.22", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", - "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, "requires": { "@jridgewell/resolve-uri": "^3.1.0", @@ -16215,9 +14657,9 @@ } }, "@nestjs/config": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-3.2.1.tgz", - "integrity": "sha512-tFZyLJKanSAu51ygQ6ZBSpx95pRcwS6qSpJDW6FFgRQzkOaOUXpL8qD8yMNoYoYxuJCxph+waiBaWKgFWxn3sw==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-3.2.2.tgz", + "integrity": "sha512-vGICPOui5vE6kPz1iwQ7oCnp3qWgqxldPmBQ9onkVoKlBtyc83KJCr7CjuVtf4OdovMAVcux1d8Q6jglU2ZphA==", "requires": { "dotenv": "16.4.5", "dotenv-expand": "10.0.0", @@ -17185,6 +15627,143 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, + "@rollup/pluginutils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", + "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", + "dev": true, + "requires": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "dependencies": { + "estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + } + } + }, + "@rollup/rollup-android-arm-eabi": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.3.tgz", + "integrity": "sha512-X9alQ3XM6I9IlSlmC8ddAvMSyG1WuHk5oUnXGw+yUBs3BFoTizmG1La/Gr8fVJvDWAq+zlYTZ9DBgrlKRVY06g==", + "dev": true, + "optional": true + }, + "@rollup/rollup-android-arm64": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.3.tgz", + "integrity": "sha512-eQK5JIi+POhFpzk+LnjKIy4Ks+pwJ+NXmPxOCSvOKSNRPONzKuUvWE+P9JxGZVxrtzm6BAYMaL50FFuPe0oWMQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-darwin-arm64": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.3.tgz", + "integrity": "sha512-Od4vE6f6CTT53yM1jgcLqNfItTsLt5zE46fdPaEmeFHvPs5SjZYlLpHrSiHEKR1+HdRfxuzXHjDOIxQyC3ptBA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-darwin-x64": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.3.tgz", + "integrity": "sha512-0IMAO21axJeNIrvS9lSe/PGthc8ZUS+zC53O0VhF5gMxfmcKAP4ESkKOCwEi6u2asUrt4mQv2rjY8QseIEb1aw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.3.tgz", + "integrity": "sha512-ge2DC7tHRHa3caVEoSbPRJpq7azhG+xYsd6u2MEnJ6XzPSzQsTKyXvh6iWjXRf7Rt9ykIUWHtl0Uz3T6yXPpKw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm-musleabihf": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.14.3.tgz", + "integrity": "sha512-ljcuiDI4V3ySuc7eSk4lQ9wU8J8r8KrOUvB2U+TtK0TiW6OFDmJ+DdIjjwZHIw9CNxzbmXY39wwpzYuFDwNXuw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm64-gnu": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.3.tgz", + "integrity": "sha512-Eci2us9VTHm1eSyn5/eEpaC7eP/mp5n46gTRB3Aar3BgSvDQGJZuicyq6TsH4HngNBgVqC5sDYxOzTExSU+NjA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm64-musl": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.3.tgz", + "integrity": "sha512-UrBoMLCq4E92/LCqlh+blpqMz5h1tJttPIniwUgOFJyjWI1qrtrDhhpHPuFxULlUmjFHfloWdixtDhSxJt5iKw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.3.tgz", + "integrity": "sha512-5aRjvsS8q1nWN8AoRfrq5+9IflC3P1leMoy4r2WjXyFqf3qcqsxRCfxtZIV58tCxd+Yv7WELPcO9mY9aeQyAmw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-riscv64-gnu": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.3.tgz", + "integrity": "sha512-sk/Qh1j2/RJSX7FhEpJn8n0ndxy/uf0kI/9Zc4b1ELhqULVdTfN6HL31CDaTChiBAOgLcsJ1sgVZjWv8XNEsAQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-s390x-gnu": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.3.tgz", + "integrity": "sha512-jOO/PEaDitOmY9TgkxF/TQIjXySQe5KVYB57H/8LRP/ux0ZoO8cSHCX17asMSv3ruwslXW/TLBcxyaUzGRHcqg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-x64-gnu": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.3.tgz", + "integrity": "sha512-8ybV4Xjy59xLMyWo3GCfEGqtKV5M5gCSrZlxkPGvEPCGDLNla7v48S662HSGwRd6/2cSneMQWiv+QzcttLrrOA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-x64-musl": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.3.tgz", + "integrity": "sha512-s+xf1I46trOY10OqAtZ5Rm6lzHre/UiLA1J2uOhCFXWkbZrJRkYBPO6FhvGfHmdtQ3Bx793MNa7LvoWFAm93bg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-arm64-msvc": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.3.tgz", + "integrity": "sha512-+4h2WrGOYsOumDQ5S2sYNyhVfrue+9tc9XcLWLh+Kw3UOxAvrfOrSMFon60KspcDdytkNDh7K2Vs6eMaYImAZg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-ia32-msvc": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.3.tgz", + "integrity": "sha512-T1l7y/bCeL/kUwh9OD4PQT4aM7Bq43vX05htPJJ46RTI4r5KNt6qJRzAfNfM+OYMNEVBWQzR2Gyk+FXLZfogGw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-x64-msvc": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.3.tgz", + "integrity": "sha512-/BypzV0H1y1HzgYpxqRaXGBRqfodgoBBCcsrujT6QRcakDQdfU+Lq9PENPh5jB4I44YWq+0C2eHsHya+nZY1sA==", + "dev": true, + "optional": true + }, "@sideway/address": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", @@ -17209,24 +15788,6 @@ "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true }, - "@sinonjs/commons": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", - "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", - "dev": true, - "requires": { - "@sinonjs/commons": "^3.0.0" - } - }, "@socket.io/component-emitter": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", @@ -17248,38 +15809,147 @@ "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz", "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==" }, + "@swc/core": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.4.14.tgz", + "integrity": "sha512-tHXg6OxboUsqa/L7DpsCcFnxhLkqN/ht5pCwav1HnvfthbiNIJypr86rNx4cUnQDJepETviSqBTIjxa7pSpGDQ==", + "devOptional": true, + "requires": { + "@swc/core-darwin-arm64": "1.4.14", + "@swc/core-darwin-x64": "1.4.14", + "@swc/core-linux-arm-gnueabihf": "1.4.14", + "@swc/core-linux-arm64-gnu": "1.4.14", + "@swc/core-linux-arm64-musl": "1.4.14", + "@swc/core-linux-x64-gnu": "1.4.14", + "@swc/core-linux-x64-musl": "1.4.14", + "@swc/core-win32-arm64-msvc": "1.4.14", + "@swc/core-win32-ia32-msvc": "1.4.14", + "@swc/core-win32-x64-msvc": "1.4.14", + "@swc/counter": "^0.1.2", + "@swc/types": "^0.1.5" + } + }, + "@swc/core-darwin-arm64": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.4.14.tgz", + "integrity": "sha512-8iPfLhYNspBl836YYsfv6ErXwDUqJ7IMieddV3Ey/t/97JAEAdNDUdtTKDtbyP0j/Ebyqyn+fKcqwSq7rAof0g==", + "dev": true, + "optional": true + }, + "@swc/core-darwin-x64": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.4.14.tgz", + "integrity": "sha512-9CqSj8uRZ92cnlgAlVaWMaJJBdxtNvCzJxaGj5KuIseeG6Q0l1g+qk8JcU7h9dAsH9saHTNwNFBVGKQo0W0ujg==", + "dev": true, + "optional": true + }, + "@swc/core-linux-arm-gnueabihf": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.4.14.tgz", + "integrity": "sha512-mfd5JArPITTzMjcezH4DwMw+BdjBV1y25Khp8itEIpdih9ei+fvxOOrDYTN08b466NuE2dF2XuhKtRLA7fXArQ==", + "dev": true, + "optional": true + }, + "@swc/core-linux-arm64-gnu": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.4.14.tgz", + "integrity": "sha512-3Lqlhlmy8MVRS9xTShMaPAp0oyUt0KFhDs4ixJsjdxKecE0NJSV/MInuDmrkij1C8/RQ2wySRlV9np5jK86oWw==", + "dev": true, + "optional": true + }, + "@swc/core-linux-arm64-musl": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.4.14.tgz", + "integrity": "sha512-n0YoCa64TUcJrbcXIHIHDWQjdUPdaXeMHNEu7yyBtOpm01oMGTKP3frsUXIABLBmAVWtKvqit4/W1KVKn5gJzg==", + "dev": true, + "optional": true + }, + "@swc/core-linux-x64-gnu": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.4.14.tgz", + "integrity": "sha512-CGmlwLWbfG1dB4jZBJnp2IWlK5xBMNLjN7AR5kKA3sEpionoccEnChOEvfux1UdVJQjLRKuHNV9yGyqGBTpxfQ==", + "dev": true, + "optional": true + }, + "@swc/core-linux-x64-musl": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.4.14.tgz", + "integrity": "sha512-xq4npk8YKYmNwmr8fbvF2KP3kUVdZYfXZMQnW425gP3/sn+yFQO8Nd0bGH40vOVQn41kEesSe0Z5O/JDor2TgQ==", + "dev": true, + "optional": true + }, + "@swc/core-win32-arm64-msvc": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.4.14.tgz", + "integrity": "sha512-imq0X+gU9uUe6FqzOQot5gpKoaC00aCUiN58NOzwp0QXEupn8CDuZpdBN93HiZswfLruu5jA1tsc15x6v9p0Yg==", + "dev": true, + "optional": true + }, + "@swc/core-win32-ia32-msvc": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.4.14.tgz", + "integrity": "sha512-cH6QpXMw5D3t+lpx6SkErHrxN0yFzmQ0lgNAJxoDRiaAdDbqA6Col8UqUJwUS++Ul6aCWgNhCdiEYehPaoyDPA==", + "dev": true, + "optional": true + }, + "@swc/core-win32-x64-msvc": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.4.14.tgz", + "integrity": "sha512-FmZ4Tby4wW65K/36BKzmuu7mlq7cW5XOxzvufaSNVvQ5PN4OodAlqPjToe029oma4Av+ykJiif64scMttyNAzg==", + "dev": true, + "optional": true + }, + "@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "devOptional": true + }, + "@swc/types": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.6.tgz", + "integrity": "sha512-/JLo/l2JsT/LRd80C3HfbmVpxOAJ11FO2RCEslFrgzLltoP9j8XIbsyDcfCt2WWyX+CM96rBoNM+IToAkFOugg==", + "devOptional": true, + "requires": { + "@swc/counter": "^0.1.3" + } + }, "@testcontainers/postgresql": { - "version": "10.8.1", - "resolved": "https://registry.npmjs.org/@testcontainers/postgresql/-/postgresql-10.8.1.tgz", - "integrity": "sha512-/9zlwfjK6l6iGo1gWQVO/bly4CsikM6Z9UVjkcKlPvzfzkfMr//eeUbDiHTDPu3tB0Idakqany4V5S4LIgz38A==", + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/@testcontainers/postgresql/-/postgresql-10.8.2.tgz", + "integrity": "sha512-UYbcHlK2XnP5DkwIOB8v+atz/ZfSpyEXR//FFhvAKNlLetMTv/8W73Hbhmvv50TCd2J3biFnatyo//uJm9e7ng==", "dev": true, "requires": { - "testcontainers": "^10.8.1" + "testcontainers": "^10.8.2" } }, "@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "devOptional": true + "optional": true, + "peer": true }, "@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "devOptional": true + "optional": true, + "peer": true }, "@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "devOptional": true + "optional": true, + "peer": true }, "@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "devOptional": true + "optional": true, + "peer": true }, "@turf/boolean-point-in-polygon": { "version": "6.5.0", @@ -17331,47 +16001,6 @@ "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.122.tgz", "integrity": "sha512-vBkIh9AY22kVOCEKo5CJlyCgmSWvasC+SWUxL/x/vOwRobMpI/HG1xp/Ae3AqmSiZeLUbOhW0FCD3ZjqqUxmXw==" }, - "@types/babel__core": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.2.tgz", - "integrity": "sha512-pNpr1T1xLUc2l3xJKuPtsEky3ybxN3m4fJkknfIpTCTfIZCDW57oAg+EfCgIIp2rvCe0Wn++/FfodDS4YXxBwA==", - "dev": true, - "requires": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "@types/babel__generator": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.5.tgz", - "integrity": "sha512-h9yIuWbJKdOPLJTbmSpPzkF67e659PbQDba7ifWm5BJ8xTv+sDmS7rFmywkWOvXedGTivCdeGSIIX8WLcRTz8w==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@types/babel__template": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.2.tgz", - "integrity": "sha512-/AVzPICMhMOMYoSx9MoKpGDKdBRsIXMNByh1PXSZoa+v6ZoLa8xxtsT/uLQ/NJm0XVAWl/BvId4MlDeXJaeIZQ==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@types/babel__traverse": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.2.tgz", - "integrity": "sha512-ojlGK1Hsfce93J0+kn3H5R73elidKUaZonirN33GSmgTUMpzI/MIFfSpF3haANe3G1bEBS9/9/QEqwTzwqFsKw==", - "dev": true, - "requires": { - "@babel/types": "^7.20.7" - } - }, "@types/bcrypt": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz", @@ -17425,12 +16054,6 @@ "@types/express": "*" } }, - "@types/cookiejar": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", - "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", - "dev": true - }, "@types/cookies": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/@types/cookies/-/cookies-0.9.0.tgz", @@ -17527,15 +16150,6 @@ "@types/node": "*" } }, - "@types/graceful-fs": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.7.tgz", - "integrity": "sha512-MhzcwU8aUygZroVwL2jeYk6JisJrPl/oov/gsgGCue9mkgl9wjGbzReYQClxiUgFDnib9FuHqTndccKeZKxTRw==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/hapi__catbox": { "version": "10.2.6", "resolved": "https://registry.npmjs.org/@types/hapi__catbox/-/hapi__catbox-10.2.6.tgz", @@ -17609,49 +16223,6 @@ "@types/node": "*" } }, - "@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", - "dev": true - }, - "@types/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-gPQuzaPR5h/djlAv2apEG1HVOyj1IUs7GpfMZixU0/0KXT3pm64ylHuMUI1/Akh+sq/iikxg6Z2j+fcMDXaaTQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.2.tgz", - "integrity": "sha512-kv43F9eb3Lhj+lr/Hn6OcLCs/sSM8bt+fIaP11rCYngfV6NVjzWXJ17owQtDQTL9tQ8WSLUrGsSJ6rJz0F1w1A==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "@types/jest": { - "version": "29.5.12", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", - "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", - "dev": true, - "requires": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - } - }, - "@types/jest-when": { - "version": "3.5.5", - "resolved": "https://registry.npmjs.org/@types/jest-when/-/jest-when-3.5.5.tgz", - "integrity": "sha512-H9MDPIrz7NOu6IXP9OHExNN9LnJbGYAzRsGIDKxWr7Fth9vovemNV8yFbkUWLSEmuA8PREvAEvt9yK0PPLmFHA==", - "dev": true, - "requires": { - "@types/jest": "*" - } - }, "@types/js-yaml": { "version": "4.0.9", "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", @@ -17719,12 +16290,6 @@ "@types/node": "*" } }, - "@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 - }, "@types/mime": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.3.tgz", @@ -17762,9 +16327,9 @@ } }, "@types/node": { - "version": "20.12.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.4.tgz", - "integrity": "sha512-E+Fa9z3wSQpzgYQdYmme5X3OTuejnnTx88A6p6vkkJosR3KBz+HpE3kqNm98VE6cfLFcISx7zW7MsJkH6KwbTw==", + "version": "20.12.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", + "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==", "requires": { "undici-types": "~5.26.4" } @@ -17835,7 +16400,8 @@ "@types/picomatch": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/@types/picomatch/-/picomatch-2.3.3.tgz", - "integrity": "sha512-Yll76ZHikRFCyz/pffKGjrCwe/le2CDwOP5F210KQo27kpRE46U2rDnzikNlVn6/ezH3Mhn46bJMTfeVTtcYMg==" + "integrity": "sha512-Yll76ZHikRFCyz/pffKGjrCwe/le2CDwOP5F210KQo27kpRE46U2rDnzikNlVn6/ezH3Mhn46bJMTfeVTtcYMg==", + "dev": true }, "@types/qs": { "version": "6.9.8", @@ -17905,33 +16471,6 @@ "@types/node": "*" } }, - "@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true - }, - "@types/superagent": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.1.tgz", - "integrity": "sha512-YQyEXA4PgCl7EVOoSAS3o0fyPFU6erv5mMixztQYe1bqbWmmn8c+IrqoxjQeZe4MgwXikgcaZPiI/DsbmOVlzA==", - "dev": true, - "requires": { - "@types/cookiejar": "^2.1.5", - "@types/methods": "^1.1.4", - "@types/node": "*" - } - }, - "@types/supertest": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-6.0.2.tgz", - "integrity": "sha512-137ypx2lk/wTQbW6An6safu9hXmajAifU/s7szAHLN/FeIm5w7yR0Wkl9fdJMRSHwOn4HLAI0DaB2TOORuhPDg==", - "dev": true, - "requires": { - "@types/methods": "^1.1.4", - "@types/superagent": "^8.1.0" - } - }, "@types/tedious": { "version": "4.0.14", "resolved": "https://registry.npmjs.org/@types/tedious/-/tedious-4.0.14.tgz", @@ -17960,95 +16499,80 @@ "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.8.tgz", "integrity": "sha512-c/hzNDBh7eRF+KbCf+OoZxKbnkpaK/cKp9iLQWqB7muXtM+MtL9SUUH8vCFcLn6dH1Qm05jiexK0ofWY7TfOhQ==" }, - "@types/yargs": { - "version": "17.0.26", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.26.tgz", - "integrity": "sha512-Y3vDy2X6zw/ZCumcwLpdhM5L7jmyGpmBCTYMHDLqT2IKVMYRRLdv6ZakA+wxhra6Z/3bwhNbNl9bDGXaFU+6rw==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "@types/yargs-parser": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.1.tgz", - "integrity": "sha512-axdPBuLuEJt0c4yI5OZssC19K2Mq1uKdrfZBzuxLvaztgqUtFYZUNw7lETExPYJR9jdEoIg4mb7RQKRQzOkeGQ==", - "dev": true - }, "@typescript-eslint/eslint-plugin": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.5.0.tgz", - "integrity": "sha512-HpqNTH8Du34nLxbKgVMGljZMG0rJd2O9ecvr2QLYp+7512ty1j42KnsFwspPXg1Vh8an9YImf6CokUBltisZFQ==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.6.0.tgz", + "integrity": "sha512-gKmTNwZnblUdnTIJu3e9kmeRRzV2j1a/LUO27KNNAnIC5zjy1aSvXSRp4rVNlmAoHlQ7HzX42NbKpcSr4jF80A==", "dev": true, "requires": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "7.5.0", - "@typescript-eslint/type-utils": "7.5.0", - "@typescript-eslint/utils": "7.5.0", - "@typescript-eslint/visitor-keys": "7.5.0", + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.6.0", + "@typescript-eslint/type-utils": "7.6.0", + "@typescript-eslint/utils": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0", "debug": "^4.3.4", "graphemer": "^1.4.0", - "ignore": "^5.2.4", + "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" } }, "@typescript-eslint/parser": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.5.0.tgz", - "integrity": "sha512-cj+XGhNujfD2/wzR1tabNsidnYRaFfEkcULdcIyVBYcXjBvBKOes+mpMBP7hMpOyk+gBcfXsrg4NBGAStQyxjQ==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.6.0.tgz", + "integrity": "sha512-usPMPHcwX3ZoPWnBnhhorc14NJw9J4HpSXQX4urF2TPKG0au0XhJoZyX62fmvdHONUkmyUe74Hzm1//XA+BoYg==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "7.5.0", - "@typescript-eslint/types": "7.5.0", - "@typescript-eslint/typescript-estree": "7.5.0", - "@typescript-eslint/visitor-keys": "7.5.0", + "@typescript-eslint/scope-manager": "7.6.0", + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/typescript-estree": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.5.0.tgz", - "integrity": "sha512-Z1r7uJY0MDeUlql9XJ6kRVgk/sP11sr3HKXn268HZyqL7i4cEfrdFuSSY/0tUqT37l5zT0tJOsuDP16kio85iA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.6.0.tgz", + "integrity": "sha512-ngttyfExA5PsHSx0rdFgnADMYQi+Zkeiv4/ZxGYUWd0nLs63Ha0ksmp8VMxAIC0wtCFxMos7Lt3PszJssG/E6w==", "dev": true, "requires": { - "@typescript-eslint/types": "7.5.0", - "@typescript-eslint/visitor-keys": "7.5.0" + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0" } }, "@typescript-eslint/type-utils": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.5.0.tgz", - "integrity": "sha512-A021Rj33+G8mx2Dqh0nMO9GyjjIBK3MqgVgZ2qlKf6CJy51wY/lkkFqq3TqqnH34XyAHUkq27IjlUkWlQRpLHw==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.6.0.tgz", + "integrity": "sha512-NxAfqAPNLG6LTmy7uZgpK8KcuiS2NZD/HlThPXQRGwz6u7MDBWRVliEEl1Gj6U7++kVJTpehkhZzCJLMK66Scw==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "7.5.0", - "@typescript-eslint/utils": "7.5.0", + "@typescript-eslint/typescript-estree": "7.6.0", + "@typescript-eslint/utils": "7.6.0", "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" + "ts-api-utils": "^1.3.0" } }, "@typescript-eslint/types": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.5.0.tgz", - "integrity": "sha512-tv5B4IHeAdhR7uS4+bf8Ov3k793VEVHd45viRRkehIUZxm0WF82VPiLgHzA/Xl4TGPg1ZD49vfxBKFPecD5/mg==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.6.0.tgz", + "integrity": "sha512-h02rYQn8J+MureCvHVVzhl69/GAfQGPQZmOMjG1KfCl7o3HtMSlPaPUAPu6lLctXI5ySRGIYk94clD/AUMCUgQ==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.5.0.tgz", - "integrity": "sha512-YklQQfe0Rv2PZEueLTUffiQGKQneiIEKKnfIqPIOxgM9lKSZFCjT5Ad4VqRKj/U4+kQE3fa8YQpskViL7WjdPQ==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.6.0.tgz", + "integrity": "sha512-+7Y/GP9VuYibecrCQWSKgl3GvUM5cILRttpWtnAu8GNL9j11e4tbuGZmZjJ8ejnKYyBRb2ddGQ3rEFCq3QjMJw==", "dev": true, "requires": { - "@typescript-eslint/types": "7.5.0", - "@typescript-eslint/visitor-keys": "7.5.0", + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0", "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" + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "dependencies": { "brace-expansion": { @@ -18061,9 +16585,9 @@ } }, "minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dev": true, "requires": { "brace-expansion": "^2.0.1" @@ -18072,28 +16596,28 @@ } }, "@typescript-eslint/utils": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.5.0.tgz", - "integrity": "sha512-3vZl9u0R+/FLQcpy2EHyRGNqAS/ofJ3Ji8aebilfJe+fobK8+LbIFmrHciLVDxjDoONmufDcnVSF38KwMEOjzw==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.6.0.tgz", + "integrity": "sha512-x54gaSsRRI+Nwz59TXpCsr6harB98qjXYzsRxGqvA5Ue3kQH+FxS7FYU81g/omn22ML2pZJkisy6Q+ElK8pBCA==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "7.5.0", - "@typescript-eslint/types": "7.5.0", - "@typescript-eslint/typescript-estree": "7.5.0", - "semver": "^7.5.4" + "@types/json-schema": "^7.0.15", + "@types/semver": "^7.5.8", + "@typescript-eslint/scope-manager": "7.6.0", + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/typescript-estree": "7.6.0", + "semver": "^7.6.0" } }, "@typescript-eslint/visitor-keys": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.5.0.tgz", - "integrity": "sha512-mcuHM/QircmA6O7fy6nn2w/3ditQkj+SgtOc8DW3uQ10Yfj42amm2i+6F2K4YAOPNNTmE6iM1ynM6lrSwdendA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.6.0.tgz", + "integrity": "sha512-4eLB7t+LlNUmXzfOu1VAIAdkjbu5xNSerURS9X/S5TUKWFRpXRQZbmtPqgKmYx8bj3J0irtQXSiWAOY82v+cgw==", "dev": true, "requires": { - "@typescript-eslint/types": "7.5.0", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "7.6.0", + "eslint-visitor-keys": "^3.4.3" } }, "@ungap/structured-clone": { @@ -18102,6 +16626,111 @@ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", "dev": true }, + "@vitest/coverage-v8": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.5.0.tgz", + "integrity": "sha512-1igVwlcqw1QUMdfcMlzzY4coikSIBN944pkueGi0pawrX5I5Z+9hxdTR+w3Sg6Q3eZhvdMAs8ZaF9JuTG1uYOQ==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.2.1", + "@bcoe/v8-coverage": "^0.2.3", + "debug": "^4.3.4", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.4", + "istanbul-reports": "^3.1.6", + "magic-string": "^0.30.5", + "magicast": "^0.3.3", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^2.0.0", + "test-exclude": "^6.0.0" + }, + "dependencies": { + "istanbul-lib-source-maps": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.4.tgz", + "integrity": "sha512-wHOoEsNJTVltaJp8eVkm8w+GVkVNHT2YDYo53YdzQEL2gWm1hBX5cGFR9hQJtuGLebidVX7et3+dmDZrmclduw==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + } + } + } + }, + "@vitest/expect": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.5.0.tgz", + "integrity": "sha512-0pzuCI6KYi2SIC3LQezmxujU9RK/vwC1U9R0rLuGlNGcOuDWxqWKu6nUdFsX9tH1WU0SXtAxToOsEjeUn1s3hA==", + "dev": true, + "requires": { + "@vitest/spy": "1.5.0", + "@vitest/utils": "1.5.0", + "chai": "^4.3.10" + } + }, + "@vitest/runner": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.5.0.tgz", + "integrity": "sha512-7HWwdxXP5yDoe7DTpbif9l6ZmDwCzcSIK38kTSIt6CFEpMjX4EpCgT6wUmS0xTXqMI6E/ONmfgRKmaujpabjZQ==", + "dev": true, + "requires": { + "@vitest/utils": "1.5.0", + "p-limit": "^5.0.0", + "pathe": "^1.1.1" + }, + "dependencies": { + "p-limit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", + "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "dev": true, + "requires": { + "yocto-queue": "^1.0.0" + } + }, + "yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true + } + } + }, + "@vitest/snapshot": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.5.0.tgz", + "integrity": "sha512-qpv3fSEuNrhAO3FpH6YYRdaECnnRjg9VxbhdtPwPRnzSfHVXnNzzrpX4cJxqiwgRMo7uRMWDFBlsBq4Cr+rO3A==", + "dev": true, + "requires": { + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "pretty-format": "^29.7.0" + } + }, + "@vitest/spy": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.5.0.tgz", + "integrity": "sha512-vu6vi6ew5N5MMHJjD5PoakMRKYdmIrNJmyfkhRpQt5d9Ewhw9nZ5Aqynbi3N61bvk9UvZ5UysMT6ayIrZ8GA9w==", + "dev": true, + "requires": { + "tinyspy": "^2.2.0" + } + }, + "@vitest/utils": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.5.0.tgz", + "integrity": "sha512-BDU0GNL8MWkRkSRdNFvCUCAVOeHaUlVJ9Tx0TYBZyXaaOTmGtUFObzchCivIBrIwKzvZA7A9sCejVhXM2aY98A==", + "dev": true, + "requires": { + "diff-sequences": "^29.6.3", + "estree-walker": "^3.0.3", + "loupe": "^2.3.7", + "pretty-format": "^29.7.0" + } + }, "@webassemblyjs/ast": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", @@ -18283,9 +16912,9 @@ } }, "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==" }, "acorn-import-assertions": { "version": "1.9.0", @@ -18301,9 +16930,9 @@ "requires": {} }, "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==", "devOptional": true }, "agent-base": { @@ -18497,7 +17126,8 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "devOptional": true + "optional": true, + "peer": true }, "argparse": { "version": "2.0.1", @@ -18526,12 +17156,6 @@ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "dev": true - }, "asn1": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", @@ -18541,6 +17165,12 @@ "safer-buffer": "~2.1.0" } }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, "async": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", @@ -18551,108 +17181,11 @@ "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.1.tgz", "integrity": "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==" }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true - }, "b4a": { "version": "1.6.4", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", "integrity": "sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==" }, - "babel-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", - "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", - "dev": true, - "requires": { - "@jest/transform": "^29.7.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - } - }, - "babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "dependencies": { - "istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "requires": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - } - }, - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true - } - } - }, - "babel-plugin-jest-hoist": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", - "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", - "dev": true, - "requires": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - } - }, - "babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "requires": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - } - }, - "babel-preset-jest": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", - "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", - "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" - } - }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -18818,24 +17351,6 @@ "update-browserslist-db": "^1.0.13" } }, - "bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, - "requires": { - "fast-json-stable-stringify": "2.x" - } - }, - "bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "requires": { - "node-int64": "^0.4.0" - } - }, "buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -18934,6 +17449,12 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" }, + "cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true + }, "call-bind": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.6.tgz", @@ -18950,18 +17471,27 @@ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, "caniuse-lite": { "version": "1.0.30001581", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001581.tgz", "integrity": "sha512-whlTkwhqV2tUmP3oYhtNfaWGYHDdS3JYFQBKXxcUR9qqPWsRhFHhoISO2Xnl/g0xyKzht9mI1LZpiNWfMzHixQ==", "dev": true }, + "chai": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", + "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.0.8" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -18971,17 +17501,20 @@ "supports-color": "^7.1.0" } }, - "char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true - }, "chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" }, + "check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "requires": { + "get-func-name": "^2.0.2" + } + }, "chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -19008,12 +17541,6 @@ "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", "dev": true }, - "ci-info": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", - "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", - "dev": true - }, "cjs-module-lexer": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", @@ -19124,18 +17651,6 @@ "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==" }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true - }, - "collect-v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", - "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", - "dev": true - }, "color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -19172,15 +17687,6 @@ "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" }, - "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, - "requires": { - "delayed-stream": "~1.0.0" - } - }, "commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -19200,12 +17706,6 @@ "repeat-string": "^1.6.1" } }, - "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 - }, "compress-commons": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", @@ -19280,12 +17780,6 @@ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" }, - "convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, "cookie": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", @@ -19305,12 +17799,6 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, - "cookiejar": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", - "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", - "dev": true - }, "core-js-compat": { "version": "3.35.1", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.35.1.tgz", @@ -19393,26 +17881,12 @@ } } }, - "create-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", - "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "prompts": "^2.0.1" - } - }, "create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "devOptional": true + "optional": true, + "peer": true }, "cron": { "version": "3.1.6", @@ -19454,12 +17928,14 @@ "ms": "2.1.2" } }, - "dedent": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", - "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", + "deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", "dev": true, - "requires": {} + "requires": { + "type-detect": "^4.0.0" + } }, "deep-is": { "version": "0.1.4", @@ -19492,12 +17968,6 @@ "has-property-descriptors": "^1.0.1" } }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true - }, "delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -19523,22 +17993,6 @@ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==" }, - "detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true - }, - "dezalgo": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", - "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", - "dev": true, - "requires": { - "asap": "^2.0.0", - "wrappy": "1" - } - }, "diacritics": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/diacritics/-/diacritics-1.3.0.tgz", @@ -19548,7 +18002,8 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "devOptional": true + "optional": true, + "peer": true }, "diff-sequences": { "version": "29.6.3", @@ -19671,12 +18126,6 @@ "integrity": "sha512-sYSQhJCJa4aGA1wYol5cMQgekDBlbVfTRavlGZVr3WZpDdOPcp6a6xUnFfrt8TqZhsBYYbDxJZCjGfHuGupCRQ==", "dev": true }, - "emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", - "dev": true - }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -19747,6 +18196,37 @@ "integrity": "sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==", "dev": true }, + "esbuild": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "dev": true, + "requires": { + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" + } + }, "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -19950,6 +18430,15 @@ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true }, + "estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "requires": { + "@types/estree": "^1.0.0" + } + }, "esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -19976,31 +18465,6 @@ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" }, - "execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "dependencies": { - "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==", - "dev": true - } - } - }, "exiftool-vendored": { "version": "24.6.0", "resolved": "https://registry.npmjs.org/exiftool-vendored/-/exiftool-vendored-24.6.0.tgz", @@ -20034,25 +18498,6 @@ "integrity": "sha512-K8j9NgxRpTFskFuXEl0AGsc692yYyThe4i3SXgx7xc0fu/vwD2c7tRGljkEtvaweYnMmfrF4DhCpuTu0aux6sg==", "optional": true }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true - }, - "expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", - "dev": true, - "requires": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" - } - }, "express": { "version": "4.19.2", "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", @@ -20185,15 +18630,6 @@ "reusify": "^1.0.4" } }, - "fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "requires": { - "bser": "2.1.1" - } - }, "figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -20363,29 +18799,6 @@ "tapable": "^2.2.1" } }, - "form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "formidable": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz", - "integrity": "sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==", - "dev": true, - "requires": { - "dezalgo": "^1.0.4", - "hexoid": "^1.0.0", - "once": "^1.4.0", - "qs": "^6.11.0" - } - }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -20520,12 +18933,6 @@ "json-bigint": "^1.0.0" } }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true - }, "geo-tz": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/geo-tz/-/geo-tz-8.0.2.tgz", @@ -20552,6 +18959,12 @@ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, + "get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true + }, "get-intrinsic": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", @@ -20564,12 +18977,6 @@ "hasown": "^2.0.0" } }, - "get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true - }, "get-port": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", @@ -20582,12 +18989,6 @@ "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", "dev": true }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - }, "glob": { "version": "10.3.12", "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", @@ -20748,12 +19149,6 @@ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" }, - "hexoid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", - "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", - "dev": true - }, "highlight.js": { "version": "10.7.3", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", @@ -20792,12 +19187,6 @@ "debug": "4" } }, - "human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true - }, "i18n-iso-countries": { "version": "7.11.0", "resolved": "https://registry.npmjs.org/i18n-iso-countries/-/i18n-iso-countries-7.11.0.tgz", @@ -20845,16 +19234,6 @@ "module-details-from-path": "^1.0.3" } }, - "import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - } - }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -20970,12 +19349,6 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, - "is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true - }, "is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -21021,24 +19394,11 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "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 }, - "istanbul-lib-instrument": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.0.tgz", - "integrity": "sha512-x58orMzEVfzPUKqlbLd1hXCnySCxKdDKa6Rjg97CwuLLRI4g3FHTdnExu1OqffVFay6zeMW+T6/DowFLndWnIw==", - "dev": true, - "requires": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - } - }, "istanbul-lib-report": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", @@ -21061,25 +19421,6 @@ } } }, - "istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, "istanbul-reports": { "version": "3.1.6", "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", @@ -21104,536 +19445,6 @@ "@pkgjs/parseargs": "^0.11.0" } }, - "jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", - "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", - "dev": true, - "requires": { - "@jest/core": "^29.7.0", - "@jest/types": "^29.6.3", - "import-local": "^3.0.2", - "jest-cli": "^29.7.0" - } - }, - "jest-changed-files": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", - "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", - "dev": true, - "requires": { - "execa": "^5.0.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0" - } - }, - "jest-circus": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", - "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", - "dev": true, - "requires": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^1.0.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0", - "pretty-format": "^29.7.0", - "pure-rand": "^6.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-cli": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", - "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", - "dev": true, - "requires": { - "@jest/core": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "create-jest": "^29.7.0", - "exit": "^0.1.2", - "import-local": "^3.0.2", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "yargs": "^17.3.1" - }, - "dependencies": { - "cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - } - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "requires": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - } - } - } - }, - "jest-config": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", - "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-jest": "^29.7.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "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" - } - } - } - }, - "jest-diff": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", - "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - } - }, - "jest-docblock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", - "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", - "dev": true, - "requires": { - "detect-newline": "^3.0.0" - } - }, - "jest-each": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", - "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "jest-util": "^29.7.0", - "pretty-format": "^29.7.0" - } - }, - "jest-environment-node": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", - "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", - "dev": true, - "requires": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - } - }, - "jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "dev": true - }, - "jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - } - }, - "jest-leak-detector": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", - "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", - "dev": true, - "requires": { - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - } - }, - "jest-matcher-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", - "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - } - }, - "jest-message-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", - "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-mock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", - "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-util": "^29.7.0" - } - }, - "jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "requires": {} - }, - "jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "dev": true - }, - "jest-resolve": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", - "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" - } - }, - "jest-resolve-dependencies": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", - "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", - "dev": true, - "requires": { - "jest-regex-util": "^29.6.3", - "jest-snapshot": "^29.7.0" - } - }, - "jest-runner": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", - "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", - "dev": true, - "requires": { - "@jest/console": "^29.7.0", - "@jest/environment": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-leak-detector": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-resolve": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-util": "^29.7.0", - "jest-watcher": "^29.7.0", - "jest-worker": "^29.7.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - } - } - }, - "jest-runtime": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", - "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", - "dev": true, - "requires": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/globals": "^29.7.0", - "@jest/source-map": "^29.6.3", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "dependencies": { - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "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" - } - } - } - }, - "jest-snapshot": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", - "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "natural-compare": "^1.4.0", - "pretty-format": "^29.7.0", - "semver": "^7.5.3" - } - }, - "jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "dependencies": { - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - } - } - }, - "jest-validate": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", - "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "leven": "^3.1.0", - "pretty-format": "^29.7.0" - }, - "dependencies": { - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true - } - } - }, - "jest-watcher": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", - "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", - "dev": true, - "requires": { - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "jest-util": "^29.7.0", - "string-length": "^4.0.1" - } - }, - "jest-when": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/jest-when/-/jest-when-3.6.0.tgz", - "integrity": "sha512-+cZWTy0ekAJo7M9Om0Scdor1jm3wDiYJWmXE8U22UVnkH54YCXAuaqz3P+up/FdtOg8g4wHOxV7Thd7nKhT6Dg==", - "dev": true, - "requires": {} - }, - "jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "dev": true, - "requires": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "dependencies": { - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, "joi": { "version": "17.12.3", "resolved": "https://registry.npmjs.org/joi/-/joi-17.12.3.tgz", @@ -21664,12 +19475,6 @@ "argparse": "^2.0.1" } }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, "json-bigint": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", @@ -21732,12 +19537,6 @@ "json-buffer": "3.0.1" } }, - "kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true - }, "lazystream": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", @@ -21775,12 +19574,6 @@ } } }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true - }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -21801,12 +19594,28 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, + "load-tsconfig": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", + "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", + "dev": true + }, "loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", "dev": true }, + "local-pkg": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", + "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==", + "dev": true, + "requires": { + "mlly": "^1.4.2", + "pkg-types": "^1.0.3" + } + }, "locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -21854,12 +19663,6 @@ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", "dev": true }, - "lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true - }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -21885,13 +19688,13 @@ "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" }, - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", "dev": true, "requires": { - "yallist": "^3.0.2" + "get-func-name": "^2.0.1" } }, "luxon": { @@ -21908,6 +19711,17 @@ "@jridgewell/sourcemap-codec": "^1.4.15" } }, + "magicast": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.4.tgz", + "integrity": "sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q==", + "dev": true, + "requires": { + "@babel/parser": "^7.24.4", + "@babel/types": "^7.24.0", + "source-map-js": "^1.2.0" + } + }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -21927,16 +19741,8 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "devOptional": true - }, - "makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "requires": { - "tmpl": "1.0.5" - } + "optional": true, + "peer": true }, "media-typer": { "version": "0.3.0", @@ -22074,6 +19880,18 @@ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "dev": true }, + "mlly": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.6.1.tgz", + "integrity": "sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==", + "dev": true, + "requires": { + "acorn": "^8.11.3", + "pathe": "^1.1.2", + "pkg-types": "^1.0.3", + "ufo": "^1.3.2" + } + }, "mnemonist": { "version": "0.39.8", "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.39.8.tgz", @@ -22208,6 +20026,12 @@ "dev": true, "optional": true }, + "nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true + }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -22271,6 +20095,12 @@ } } }, + "nestjs-cls": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/nestjs-cls/-/nestjs-cls-4.3.0.tgz", + "integrity": "sha512-MVTun6tqCZih8AJXRj8uBuuFyJhQrIA9m9fStiQjbBXUkE3BrlMRvmLzyw8UcneB3xtFFTfwkAh5PYKRulyaOg==", + "requires": {} + }, "nestjs-otel": { "version": "5.1.5", "resolved": "https://registry.npmjs.org/nestjs-otel/-/nestjs-otel-5.1.5.tgz", @@ -22309,12 +20139,6 @@ "integrity": "sha512-YlCCc6Wffkx0kHkmam79GKvDQ6x+QZkMjFGrIMxgFNILFvGSbCp2fCBC55pGTT9gVaz8Na5CLmxt/urtzRv36w==", "optional": true }, - "node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true - }, "node-releases": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", @@ -22354,15 +20178,6 @@ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, "npmlog": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", @@ -22623,6 +20438,18 @@ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" }, + "pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true + }, + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true + }, "pbf": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.2.1.tgz", @@ -22700,66 +20527,22 @@ "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, "picomatch": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==" }, - "pirates": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", - "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", - "dev": true - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "pkg-types": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", + "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==", "dev": true, "requires": { - "find-up": "^4.0.0" - }, - "dependencies": { - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - } + "jsonc-parser": "^3.2.0", + "mlly": "^1.2.0", + "pathe": "^1.1.0" } }, "pluralize": { @@ -22768,6 +20551,17 @@ "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", "dev": true }, + "postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "dev": true, + "requires": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + } + }, "postgres-array": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", @@ -22852,16 +20646,6 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, - "prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "requires": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - } - }, "proper-lockfile": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", @@ -22947,12 +20731,6 @@ "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", "dev": true }, - "pure-rand": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz", - "integrity": "sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==", - "dev": true - }, "qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", @@ -23168,14 +20946,9 @@ } }, "reflect-metadata": { - "version": "0.1.14", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.14.tgz", - "integrity": "sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==" - }, - "regenerator-runtime": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", - "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==" }, "regexp-tree": { "version": "0.1.27", @@ -23237,23 +21010,6 @@ "supports-preserve-symlinks-flag": "^1.0.0" } }, - "resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - } - } - }, "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -23267,12 +21023,6 @@ "protocol-buffers-schema": "^3.3.1" } }, - "resolve.exports": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", - "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", - "dev": true - }, "response-time": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/response-time/-/response-time-2.3.2.tgz", @@ -23331,6 +21081,32 @@ "glob": "^10.3.7" } }, + "rollup": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.14.3.tgz", + "integrity": "sha512-ag5tTQKYsj1bhrFC9+OEWqb5O6VYgtQDO9hPDBMmIbePwhfSr+ExlcU741t8Dhw5DkPCQf6noz0jb36D6W9/hw==", + "dev": true, + "requires": { + "@rollup/rollup-android-arm-eabi": "4.14.3", + "@rollup/rollup-android-arm64": "4.14.3", + "@rollup/rollup-darwin-arm64": "4.14.3", + "@rollup/rollup-darwin-x64": "4.14.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.14.3", + "@rollup/rollup-linux-arm-musleabihf": "4.14.3", + "@rollup/rollup-linux-arm64-gnu": "4.14.3", + "@rollup/rollup-linux-arm64-musl": "4.14.3", + "@rollup/rollup-linux-powerpc64le-gnu": "4.14.3", + "@rollup/rollup-linux-riscv64-gnu": "4.14.3", + "@rollup/rollup-linux-s390x-gnu": "4.14.3", + "@rollup/rollup-linux-x64-gnu": "4.14.3", + "@rollup/rollup-linux-x64-musl": "4.14.3", + "@rollup/rollup-win32-arm64-msvc": "4.14.3", + "@rollup/rollup-win32-ia32-msvc": "4.14.3", + "@rollup/rollup-win32-x64-msvc": "4.14.3", + "@types/estree": "1.0.5", + "fsevents": "~2.3.2" + } + }, "run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -23629,6 +21405,12 @@ "object-inspect": "^1.9.0" } }, + "siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, "signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -23659,12 +21441,6 @@ "totalist": "^3.0.0" } }, - "sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -23713,6 +21489,12 @@ "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", "dev": true }, + "source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "dev": true + }, "source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", @@ -23774,12 +21556,6 @@ "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==" }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, "sql-formatter": { "version": "15.3.0", "resolved": "https://registry.npmjs.org/sql-formatter/-/sql-formatter-15.3.0.tgz", @@ -23813,22 +21589,11 @@ "nan": "^2.17.0" } }, - "stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, - "requires": { - "escape-string-regexp": "^2.0.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true - } - } + "stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true }, "standard-as-callback": { "version": "2.1.0", @@ -23840,6 +21605,12 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" }, + "std-env": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", + "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", + "dev": true + }, "stream-source": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/stream-source/-/stream-source-0.3.5.tgz", @@ -23867,16 +21638,6 @@ "safe-buffer": "~5.2.0" } }, - "string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "requires": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - } - }, "string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -23913,18 +21674,6 @@ "ansi-regex": "^5.0.1" } }, - "strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true - }, "strip-indent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", @@ -23940,42 +21689,23 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, - "superagent": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.1.2.tgz", - "integrity": "sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==", + "strip-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.0.tgz", + "integrity": "sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==", "dev": true, "requires": { - "component-emitter": "^1.3.0", - "cookiejar": "^2.1.4", - "debug": "^4.3.4", - "fast-safe-stringify": "^2.1.1", - "form-data": "^4.0.0", - "formidable": "^2.1.2", - "methods": "^1.1.2", - "mime": "2.6.0", - "qs": "^6.11.0", - "semver": "^7.3.8" + "js-tokens": "^9.0.0" }, "dependencies": { - "mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "js-tokens": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.0.tgz", + "integrity": "sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==", "dev": true } } }, - "supertest": { - "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, - "requires": { - "methods": "^1.1.2", - "superagent": "^8.1.2" - } - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -24156,9 +21886,9 @@ } }, "testcontainers": { - "version": "10.8.1", - "resolved": "https://registry.npmjs.org/testcontainers/-/testcontainers-10.8.1.tgz", - "integrity": "sha512-2Kzeu3UfnILkhpdz2+YDu1FBFerAusdMCsltErBCJouP5j5xuxrV8BHxhlDt0xsJdM8YnhHgA2B32LmDr5AToA==", + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/testcontainers/-/testcontainers-10.8.2.tgz", + "integrity": "sha512-9Ink7NUyYZwOjQhk0C6R6basWy2WADNly+md3D9YDap0pcDr3C+vrO8Ah1bkYco/9Zg8VoYTHO+blkLeebBYkA==", "dev": true, "requires": { "@balena/dockerignore": "^1.0.2", @@ -24368,6 +22098,24 @@ "resolved": "https://registry.npmjs.org/thumbhash/-/thumbhash-0.1.1.tgz", "integrity": "sha512-kH5pKeIIBPQXAOni2AiY/Cu/NKdkFREdpH+TLdM0g6WA7RriCv0kPLgP731ady67MhTAqrVG/4mnEeibVuCJcg==" }, + "tinybench": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.7.0.tgz", + "integrity": "sha512-Qgayeb106x2o4hNzNjsZEfFziw8IbKqtbXBjVh7VIZfBxfD5M4gWtpyx5+YTae2gJ6Y6Dz/KLepiv16RFeQWNA==", + "dev": true + }, + "tinypool": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", + "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", + "dev": true + }, + "tinyspy": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", + "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", + "dev": true + }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -24376,12 +22124,6 @@ "os-tmpdir": "~1.0.2" } }, - "tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true - }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -24432,40 +22174,12 @@ "dev": true, "requires": {} }, - "ts-jest": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.2.tgz", - "integrity": "sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==", - "dev": true, - "requires": { - "bs-logger": "0.x", - "fast-json-stable-stringify": "2.x", - "jest-util": "^29.0.0", - "json5": "^2.2.3", - "lodash.memoize": "4.x", - "make-error": "1.x", - "semver": "^7.5.3", - "yargs-parser": "^21.0.1" - } - }, - "ts-loader": { - "version": "9.5.1", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz", - "integrity": "sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==", - "dev": true, - "requires": { - "chalk": "^4.1.0", - "enhanced-resolve": "^5.0.0", - "micromatch": "^4.0.0", - "semver": "^7.3.4", - "source-map": "^0.7.4" - } - }, "ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "devOptional": true, + "optional": true, + "peer": true, "requires": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -24604,11 +22318,6 @@ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz", "integrity": "sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==" }, - "reflect-metadata": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.1.tgz", - "integrity": "sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw==" - }, "wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -24636,9 +22345,9 @@ } }, "typescript": { - "version": "5.4.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.4.tgz", - "integrity": "sha512-dGE2Vv8cpVvw28v8HCPqyb08EzbBURxDpuhJvTrusShUfGnhHBafDsLdS1EhhxyL6BJQE+2cT3dDPAv+MQ6oLw==", + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", "devOptional": true }, "ua-parser-js": { @@ -24646,6 +22355,12 @@ "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==" }, + "ufo": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.3.tgz", + "integrity": "sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==", + "dev": true + }, "uglify-js": { "version": "3.17.4", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", @@ -24676,6 +22391,29 @@ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" }, + "unplugin": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.10.1.tgz", + "integrity": "sha512-d6Mhq8RJeGA8UfKCu54Um4lFA0eSaRa3XxdAJg8tIdxbu1ubW0hBCZUL7yI2uGyYCRndvbK8FLHzqy2XKfeMsg==", + "dev": true, + "requires": { + "acorn": "^8.11.3", + "chokidar": "^3.6.0", + "webpack-sources": "^3.2.3", + "webpack-virtual-modules": "^0.6.1" + } + }, + "unplugin-swc": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/unplugin-swc/-/unplugin-swc-1.4.5.tgz", + "integrity": "sha512-ltkJ70kjL53onJrypaMmKDiOvhghNUCbCxjxT6Ir0eAMIBsOfRhPt6vQtxB8R/6wYk/TfIJ2gCgdx2uKNPJRHA==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^5.1.0", + "load-tsconfig": "^0.2.5", + "unplugin": "^1.10.1" + } + }, "update-browserslist-db": { "version": "1.0.13", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", @@ -24737,26 +22475,8 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "devOptional": true - }, - "v8-to-istanbul": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", - "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" - }, - "dependencies": { - "convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - } - } + "optional": true, + "peer": true }, "validate-npm-package-license": { "version": "3.0.4", @@ -24778,13 +22498,130 @@ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" }, - "walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "vite": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.8.tgz", + "integrity": "sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==", "dev": true, "requires": { - "makeerror": "1.0.12" + "esbuild": "^0.20.1", + "fsevents": "~2.3.3", + "postcss": "^8.4.38", + "rollup": "^4.13.0" + } + }, + "vite-node": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.5.0.tgz", + "integrity": "sha512-tV8h6gMj6vPzVCa7l+VGq9lwoJjW8Y79vst8QZZGiuRAfijU+EEWuc0kFpmndQrWhMMhet1jdSF+40KSZUqIIw==", + "dev": true, + "requires": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^5.0.0" + } + }, + "vitest": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.5.0.tgz", + "integrity": "sha512-d8UKgR0m2kjdxDWX6911uwxout6GHS0XaGH1cksSIVVG8kRlE7G7aBw7myKQCvDI5dT4j7ZMa+l706BIORMDLw==", + "dev": true, + "requires": { + "@vitest/expect": "1.5.0", + "@vitest/runner": "1.5.0", + "@vitest/snapshot": "1.5.0", + "@vitest/spy": "1.5.0", + "@vitest/utils": "1.5.0", + "acorn-walk": "^8.3.2", + "chai": "^4.3.10", + "debug": "^4.3.4", + "execa": "^8.0.1", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^2.0.0", + "tinybench": "^2.5.1", + "tinypool": "^0.8.3", + "vite": "^5.0.0", + "vite-node": "1.5.0", + "why-is-node-running": "^2.2.2" + }, + "dependencies": { + "execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + } + }, + "get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true + }, + "human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true + }, + "is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true + }, + "mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true + }, + "npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "requires": { + "path-key": "^4.0.0" + } + }, + "onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "requires": { + "mimic-fn": "^4.0.0" + } + }, + "path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true + }, + "strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true + } } }, "watchpack": { @@ -24872,6 +22709,12 @@ "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", "dev": true }, + "webpack-virtual-modules": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.1.tgz", + "integrity": "sha512-poXpCylU7ExuvZK8z+On3kX+S8o/2dQ/SVYueKA0D4WEMXROXgY8Ez50/bQEUmvoSMMrWcrJqCHuhAbsiwg7Dg==", + "dev": true + }, "whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -24889,6 +22732,16 @@ "isexe": "^2.0.0" } }, + "why-is-node-running": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", + "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "dev": true, + "requires": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + } + }, "wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", @@ -24927,24 +22780,6 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, - "write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, - "dependencies": { - "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==", - "dev": true - } - } - }, "ws": { "version": "8.11.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", @@ -24961,12 +22796,6 @@ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, "yaml": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", @@ -25003,7 +22832,8 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "devOptional": true + "optional": true, + "peer": true }, "yocto-queue": { "version": "0.1.0", diff --git a/server/package.json b/server/package.json index 58e525aa42..d5828822cd 100644 --- a/server/package.json +++ b/server/package.json @@ -18,11 +18,9 @@ "check": "tsc --noEmit", "check:code": "npm run format && npm run lint && npm run check", "check:all": "npm run check:code && npm run test:cov", - "test": "jest", - "test:watch": "jest --watch", - "test:cov": "jest --coverage", - "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", - "e2e:jobs": "jest --config e2e/jobs/jest-e2e.json --runInBand", + "test": "vitest", + "test:watch": "vitest --watch", + "test:cov": "vitest --coverage", "typeorm": "typeorm", "typeorm:migrations:create": "typeorm migration:create", "typeorm:migrations:generate": "typeorm migration:generate -d ./dist/database.config.js", @@ -33,7 +31,6 @@ "sql:generate": "node ./dist/utils/sql.js" }, "dependencies": { - "@babel/runtime": "^7.22.11", "@nestjs/bullmq": "^10.0.1", "@nestjs/common": "^10.2.2", "@nestjs/config": "^3.0.0", @@ -49,7 +46,6 @@ "@opentelemetry/exporter-prometheus": "^0.50.0", "@opentelemetry/sdk-node": "^0.50.0", "@socket.io/postgres-adapter": "^0.3.1", - "@types/picomatch": "^2.3.3", "archiver": "^7.0.0", "async-lock": "^1.4.0", "bcrypt": "^5.1.1", @@ -72,11 +68,12 @@ "luxon": "^3.4.2", "mnemonist": "^0.39.8", "nest-commander": "^3.11.1", + "nestjs-cls": "^4.3.0", "nestjs-otel": "^5.1.5", "openid-client": "^5.4.3", "pg": "^8.11.3", "picomatch": "^4.0.0", - "reflect-metadata": "^0.1.13", + "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1", "sanitize-filename": "^1.6.3", "sharp": "^0.33.0", @@ -89,6 +86,7 @@ "@nestjs/cli": "^10.1.16", "@nestjs/schematics": "^10.0.2", "@nestjs/testing": "^10.2.2", + "@swc/core": "^1.4.14", "@testcontainers/postgresql": "^10.2.1", "@types/archiver": "^6.0.0", "@types/async-lock": "^1.4.2", @@ -97,76 +95,34 @@ "@types/express": "^4.17.17", "@types/fluent-ffmpeg": "^2.1.21", "@types/imagemin": "^8.0.1", - "@types/jest": "29.5.12", - "@types/jest-when": "^3.5.2", "@types/js-yaml": "^4.0.9", "@types/lodash": "^4.14.197", "@types/mock-fs": "^4.13.1", "@types/multer": "^1.4.7", "@types/node": "^20.5.7", - "@types/supertest": "^6.0.0", + "@types/picomatch": "^2.3.3", "@types/ua-parser-js": "^0.7.36", "@typescript-eslint/eslint-plugin": "^7.0.0", "@typescript-eslint/parser": "^7.0.0", + "@vitest/coverage-v8": "^1.5.0", "dotenv": "^16.3.1", "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-unicorn": "^52.0.0", - "jest": "^29.6.4", - "jest-when": "^3.6.0", "mock-fs": "^5.2.0", "prettier": "^3.0.2", "prettier-plugin-organize-imports": "^3.2.3", "rimraf": "^5.0.1", "source-map-support": "^0.5.21", "sql-formatter": "^15.0.0", - "supertest": "^6.3.3", - "testcontainers": "^10.2.1", - "ts-jest": "^29.1.1", - "ts-loader": "^9.4.4", - "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.3.3", - "utimes": "^5.2.1" - }, - "jest": { - "clearMocks": true, - "moduleFileExtensions": [ - "js", - "json", - "ts" - ], - "rootDir": ".", - "testRegex": ".*\\.spec\\.ts$", - "transform": { - "^.+\\.ts$": "ts-jest" - }, - "collectCoverageFrom": [ - "/src/cores/*.(t|j)s", - "/src/dtos/*.(t|j)s", - "/src/interfaces/*.(t|j)s", - "/src/services/*.(t|j)s", - "/src/utils/*.(t|j)s", - "/src/*.t|j)s" - ], - "coverageDirectory": "./coverage", - "coverageThreshold": { - "./src/": { - "branches": 70, - "functions": 75, - "lines": 80, - "statements": 80 - } - }, - "testEnvironment": "node", - "moduleNameMapper": { - "^test(|/.*)$": "/test/$1", - "^src(|/.*)$": "/src/$1" - }, - "globalSetup": "/test/global-setup.js" + "unplugin-swc": "^1.4.5", + "utimes": "^5.2.1", + "vitest": "^1.5.0" }, "volta": { - "node": "20.12.1" + "node": "20.12.2" } } diff --git a/server/src/app.module.ts b/server/src/app.module.ts index ded08a96ab..40fa95aefc 100644 --- a/server/src/app.module.ts +++ b/server/src/app.module.ts @@ -5,28 +5,29 @@ import { APP_GUARD, APP_INTERCEPTOR, APP_PIPE } from '@nestjs/core'; import { EventEmitterModule } from '@nestjs/event-emitter'; import { ScheduleModule, SchedulerRegistry } from '@nestjs/schedule'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { ClsModule } from 'nestjs-cls'; import { OpenTelemetryModule } from 'nestjs-otel'; import { commands } from 'src/commands'; -import { bullConfig, bullQueues, immichAppConfig } from 'src/config'; +import { bullConfig, bullQueues, clsConfig, immichAppConfig } from 'src/config'; import { controllers } from 'src/controllers'; import { databaseConfig } from 'src/database.config'; import { entities } from 'src/entities'; import { AuthGuard } from 'src/middleware/auth.guard'; import { ErrorInterceptor } from 'src/middleware/error.interceptor'; import { FileUploadInterceptor } from 'src/middleware/file-upload.interceptor'; +import { LoggingInterceptor } from 'src/middleware/logging.interceptor'; import { repositories } from 'src/repositories'; import { services } from 'src/services'; import { ApiService } from 'src/services/api.service'; import { MicroservicesService } from 'src/services/microservices.service'; import { otelConfig } from 'src/utils/instrumentation'; -import { ImmichLogger } from 'src/utils/logger'; -const providers = [ImmichLogger]; -const common = [...services, ...providers, ...repositories]; +const common = [...services, ...repositories]; const middleware = [ FileUploadInterceptor, { provide: APP_PIPE, useValue: new ValidationPipe({ transform: true, whitelist: true }) }, + { provide: APP_INTERCEPTOR, useClass: LoggingInterceptor }, { provide: APP_INTERCEPTOR, useClass: ErrorInterceptor }, { provide: APP_GUARD, useClass: AuthGuard }, ]; @@ -34,6 +35,7 @@ const middleware = [ const imports = [ BullModule.forRoot(bullConfig), BullModule.registerQueue(...bullQueues), + ClsModule.forRoot(clsConfig), ConfigModule.forRoot(immichAppConfig), EventEmitterModule.forRoot(), OpenTelemetryModule.forRoot(otelConfig), diff --git a/server/src/commands/reset-admin-password.command.ts b/server/src/commands/reset-admin-password.command.ts index a186603a3f..f7c0775c8b 100644 --- a/server/src/commands/reset-admin-password.command.ts +++ b/server/src/commands/reset-admin-password.command.ts @@ -9,7 +9,7 @@ import { UserService } from 'src/services/user.service'; export class ResetAdminPasswordCommand extends CommandRunner { constructor( private userService: UserService, - private readonly inquirer: InquirerService, + private inquirer: InquirerService, ) { super(); } diff --git a/server/src/config.ts b/server/src/config.ts index c7d2302c1d..068d0b3a9f 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -1,8 +1,10 @@ import { RegisterQueueOptions } from '@nestjs/bullmq'; import { ConfigModuleOptions } from '@nestjs/config'; import { QueueOptions } from 'bullmq'; +import { Request, Response } from 'express'; import { RedisOptions } from 'ioredis'; import Joi from 'joi'; +import { CLS_ID, ClsModuleOptions } from 'nestjs-cls'; import { LogLevel } from 'src/entities/system-config.entity'; import { QueueName } from 'src/interfaces/job.interface'; @@ -69,3 +71,17 @@ export const bullConfig: QueueOptions = { }; export const bullQueues: RegisterQueueOptions[] = Object.values(QueueName).map((name) => ({ name })); + +export const clsConfig: ClsModuleOptions = { + middleware: { + mount: true, + generateId: true, + setup: (cls, req: Request, res: Response) => { + const headerValues = req.headers['x-immich-cid']; + const headerValue = Array.isArray(headerValues) ? headerValues[0] : headerValues; + const cid = headerValue || cls.get(CLS_ID); + cls.set(CLS_ID, cid); + res.header('x-immich-cid', cid); + }, + }, +}; diff --git a/server/src/controllers/index.ts b/server/src/controllers/index.ts index ce51aa4c01..d136a52b04 100644 --- a/server/src/controllers/index.ts +++ b/server/src/controllers/index.ts @@ -17,6 +17,7 @@ import { PersonController } from 'src/controllers/person.controller'; import { SearchController } from 'src/controllers/search.controller'; import { ServerInfoController } from 'src/controllers/server-info.controller'; import { SharedLinkController } from 'src/controllers/shared-link.controller'; +import { SyncController } from 'src/controllers/sync.controller'; import { SystemConfigController } from 'src/controllers/system-config.controller'; import { TagController } from 'src/controllers/tag.controller'; import { TimelineController } from 'src/controllers/timeline.controller'; @@ -43,6 +44,7 @@ export const controllers = [ SearchController, ServerInfoController, SharedLinkController, + SyncController, SystemConfigController, TagController, TimelineController, diff --git a/server/src/controllers/shared-link.controller.ts b/server/src/controllers/shared-link.controller.ts index 990f4e3225..a7a8e3a1c6 100644 --- a/server/src/controllers/shared-link.controller.ts +++ b/server/src/controllers/shared-link.controller.ts @@ -19,7 +19,7 @@ import { UUIDParamDto } from 'src/validation'; @Controller('shared-link') @Authenticated() export class SharedLinkController { - constructor(private readonly service: SharedLinkService) {} + constructor(private service: SharedLinkService) {} @Get() getAllSharedLinks(@Auth() auth: AuthDto): Promise { diff --git a/server/src/controllers/sync.controller.ts b/server/src/controllers/sync.controller.ts new file mode 100644 index 0000000000..c12d42df23 --- /dev/null +++ b/server/src/controllers/sync.controller.ts @@ -0,0 +1,24 @@ +import { Controller, Get, Query } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; +import { AssetResponseDto } from 'src/dtos/asset-response.dto'; +import { AuthDto } from 'src/dtos/auth.dto'; +import { AssetDeltaSyncDto, AssetDeltaSyncResponseDto, AssetFullSyncDto } from 'src/dtos/sync.dto'; +import { Auth, Authenticated } from 'src/middleware/auth.guard'; +import { SyncService } from 'src/services/sync.service'; + +@ApiTags('Sync') +@Controller('sync') +@Authenticated() +export class SyncController { + constructor(private service: SyncService) {} + + @Get('full-sync') + getAllForUserFullSync(@Auth() auth: AuthDto, @Query() dto: AssetFullSyncDto): Promise { + return this.service.getAllAssetsForUserFullSync(auth, dto); + } + + @Get('delta-sync') + getDeltaSync(@Auth() auth: AuthDto, @Query() dto: AssetDeltaSyncDto): Promise { + return this.service.getChangesForDeltaSync(auth, dto); + } +} diff --git a/server/src/controllers/system-config.controller.ts b/server/src/controllers/system-config.controller.ts index 0b46b82a51..08da743191 100644 --- a/server/src/controllers/system-config.controller.ts +++ b/server/src/controllers/system-config.controller.ts @@ -8,7 +8,7 @@ import { SystemConfigService } from 'src/services/system-config.service'; @Controller('system-config') @Authenticated({ admin: true }) export class SystemConfigController { - constructor(private readonly service: SystemConfigService) {} + constructor(private service: SystemConfigService) {} @Get() getConfig(): Promise { diff --git a/server/src/cores/storage.core.spec.ts b/server/src/cores/storage.core.spec.ts index 16258f095e..6ff6ca61bf 100644 --- a/server/src/cores/storage.core.spec.ts +++ b/server/src/cores/storage.core.spec.ts @@ -1,6 +1,7 @@ import { StorageCore } from 'src/cores/storage.core'; +import { vitest } from 'vitest'; -jest.mock('src/constants', () => ({ +vitest.mock('src/constants', () => ({ APP_MEDIA_LOCATION: '/photos', })); diff --git a/server/src/cores/storage.core.ts b/server/src/cores/storage.core.ts index 035f90c911..f1c16e5698 100644 --- a/server/src/cores/storage.core.ts +++ b/server/src/cores/storage.core.ts @@ -7,11 +7,11 @@ import { PersonEntity } from 'src/entities/person.entity'; import { ImageFormat } from 'src/entities/system-config.entity'; import { IAssetRepository } from 'src/interfaces/asset.interface'; import { ICryptoRepository } from 'src/interfaces/crypto.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IMoveRepository } from 'src/interfaces/move.interface'; import { IPersonRepository } from 'src/interfaces/person.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface'; import { ISystemConfigRepository } from 'src/interfaces/system-config.interface'; -import { ImmichLogger } from 'src/utils/logger'; export enum StorageFolder { ENCODED_VIDEO = 'encoded-video', @@ -41,35 +41,37 @@ export type GeneratedAssetType = GeneratedImageType | AssetPathType.ENCODED_VIDE let instance: StorageCore | null; export class StorageCore { - private logger = new ImmichLogger(StorageCore.name); private configCore; private constructor( private assetRepository: IAssetRepository, + private cryptoRepository: ICryptoRepository, private moveRepository: IMoveRepository, private personRepository: IPersonRepository, - private cryptoRepository: ICryptoRepository, - private repository: IStorageRepository, + private storageRepository: IStorageRepository, systemConfigRepository: ISystemConfigRepository, + private logger: ILoggerRepository, ) { - this.configCore = SystemConfigCore.create(systemConfigRepository); + this.configCore = SystemConfigCore.create(systemConfigRepository, this.logger); } static create( assetRepository: IAssetRepository, + cryptoRepository: ICryptoRepository, moveRepository: IMoveRepository, personRepository: IPersonRepository, - cryptoRepository: ICryptoRepository, - configRepository: ISystemConfigRepository, - repository: IStorageRepository, + storageRepository: IStorageRepository, + systemConfigRepository: ISystemConfigRepository, + logger: ILoggerRepository, ) { if (!instance) { instance = new StorageCore( assetRepository, + cryptoRepository, moveRepository, personRepository, - cryptoRepository, - repository, - configRepository, + storageRepository, + systemConfigRepository, + logger, ); } @@ -170,8 +172,8 @@ export class StorageCore { let move = await this.moveRepository.getByEntity(entityId, pathType); if (move) { this.logger.log(`Attempting to finish incomplete move: ${move.oldPath} => ${move.newPath}`); - const oldPathExists = await this.repository.checkFileExists(move.oldPath); - const newPathExists = await this.repository.checkFileExists(move.newPath); + const oldPathExists = await this.storageRepository.checkFileExists(move.oldPath); + const newPathExists = await this.storageRepository.checkFileExists(move.newPath); const newPathCheck = newPathExists ? move.newPath : null; const actualPath = oldPathExists ? move.oldPath : newPathCheck; if (!actualPath) { @@ -205,7 +207,7 @@ export class StorageCore { if (move.oldPath !== newPath) { try { this.logger.debug(`Attempting to rename file: ${move.oldPath} => ${newPath}`); - await this.repository.rename(move.oldPath, newPath); + await this.storageRepository.rename(move.oldPath, newPath); } catch (error: any) { if (error.code !== 'EXDEV') { this.logger.warn( @@ -214,19 +216,19 @@ export class StorageCore { return; } this.logger.debug(`Unable to rename file. Falling back to copy, verify and delete`); - await this.repository.copyFile(move.oldPath, newPath); + await this.storageRepository.copyFile(move.oldPath, newPath); if (!(await this.verifyNewPathContentsMatchesExpected(move.oldPath, newPath, assetInfo))) { this.logger.warn(`Skipping move due to file size mismatch`); - await this.repository.unlink(newPath); + await this.storageRepository.unlink(newPath); return; } - const { atime, mtime } = await this.repository.stat(move.oldPath); - await this.repository.utimes(newPath, atime, mtime); + const { atime, mtime } = await this.storageRepository.stat(move.oldPath); + await this.storageRepository.utimes(newPath, atime, mtime); try { - await this.repository.unlink(move.oldPath); + await this.storageRepository.unlink(move.oldPath); } catch (error: any) { this.logger.warn(`Unable to delete old file, it will now no longer be tracked by Immich: ${error.message}`); } @@ -242,8 +244,8 @@ export class StorageCore { newPath: string, assetInfo?: { sizeInBytes: number; checksum: Buffer }, ) { - const oldStat = await this.repository.stat(oldPath); - const newStat = await this.repository.stat(newPath); + const oldStat = await this.storageRepository.stat(oldPath); + const newStat = await this.storageRepository.stat(newPath); const oldPathSize = assetInfo ? assetInfo.sizeInBytes : oldStat.size; const newPathSize = newStat.size; this.logger.debug(`File size check: ${newPathSize} === ${oldPathSize}`); @@ -269,11 +271,11 @@ export class StorageCore { } ensureFolders(input: string) { - this.repository.mkdirSync(dirname(input)); + this.storageRepository.mkdirSync(dirname(input)); } removeEmptyDirs(folder: StorageFolder) { - return this.repository.removeEmptyDirs(StorageCore.getBaseFolder(folder)); + return this.storageRepository.removeEmptyDirs(StorageCore.getBaseFolder(folder)); } private savePath(pathType: PathType, id: string, newPath: string) { diff --git a/server/src/cores/system-config.core.ts b/server/src/cores/system-config.core.ts index 3a1ea47bbe..9cbe3b8414 100644 --- a/server/src/cores/system-config.core.ts +++ b/server/src/cores/system-config.core.ts @@ -22,8 +22,8 @@ import { VideoCodec, } from 'src/entities/system-config.entity'; import { QueueName } from 'src/interfaces/job.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { ISystemConfigRepository } from 'src/interfaces/system-config.interface'; -import { ImmichLogger } from 'src/utils/logger'; export type SystemConfigValidator = (config: SystemConfig, newConfig: SystemConfig) => void | Promise; @@ -169,16 +169,18 @@ let instance: SystemConfigCore | null; @Injectable() export class SystemConfigCore { - private logger = new ImmichLogger(SystemConfigCore.name); private configCache: SystemConfigEntity[] | null = null; public config$ = new Subject(); - private constructor(private repository: ISystemConfigRepository) {} + private constructor( + private repository: ISystemConfigRepository, + private logger: ILoggerRepository, + ) {} - static create(repository: ISystemConfigRepository) { + static create(repository: ISystemConfigRepository, logger: ILoggerRepository) { if (!instance) { - instance = new SystemConfigCore(repository); + instance = new SystemConfigCore(repository, logger); } return instance; } diff --git a/server/src/dtos/sync.dto.ts b/server/src/dtos/sync.dto.ts new file mode 100644 index 0000000000..a69062ec2d --- /dev/null +++ b/server/src/dtos/sync.dto.ts @@ -0,0 +1,38 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { IsInt, IsPositive } from 'class-validator'; +import { AssetResponseDto } from 'src/dtos/asset-response.dto'; +import { ValidateDate, ValidateUUID } from 'src/validation'; + +export class AssetFullSyncDto { + @ValidateUUID({ optional: true }) + lastId?: string; + + @ValidateDate({ optional: true }) + lastCreationDate?: Date; + + @ValidateDate() + updatedUntil!: Date; + + @IsInt() + @IsPositive() + @Type(() => Number) + @ApiProperty({ type: 'integer' }) + limit!: number; + + @ValidateUUID({ optional: true }) + userId?: string; +} + +export class AssetDeltaSyncDto { + @ValidateDate() + updatedAfter!: Date; + @ValidateUUID({ each: true }) + userIds!: string[]; +} + +export class AssetDeltaSyncResponseDto { + needsFullSync!: boolean; + upserted!: AssetResponseDto[]; + deleted!: string[]; +} diff --git a/server/src/interfaces/asset.interface.ts b/server/src/interfaces/asset.interface.ts index eab392a586..9c2ebe3e73 100644 --- a/server/src/interfaces/asset.interface.ts +++ b/server/src/interfaces/asset.interface.ts @@ -133,6 +133,20 @@ export interface MetadataSearchOptions { numResults: number; } +export interface AssetFullSyncOptions { + ownerId: string; + lastCreationDate?: Date; + lastId?: string; + updatedUntil: Date; + limit: number; +} + +export interface AssetDeltaSyncOptions { + userIds: string[]; + updatedAfter: Date; + limit: number; +} + export type AssetPathEntity = Pick; export const IAssetRepository = 'IAssetRepository'; @@ -155,7 +169,7 @@ export interface IAssetRepository { getRandom(userId: string, count: number): Promise; getFirstAssetForAlbumId(albumId: string): Promise; getLastUpdatedAssetForAlbumId(albumId: string): Promise; - getLibraryAssetPaths(pagination: PaginationOptions, libraryId: string): Paginated; + getExternalLibraryAssetPaths(pagination: PaginationOptions, libraryId: string): Paginated; getByLibraryIdAndOriginalPath(libraryId: string, originalPath: string): Promise; deleteAll(ownerId: string): Promise; getAll(pagination: PaginationOptions, options?: AssetSearchOptions): Paginated; @@ -175,4 +189,6 @@ export interface IAssetRepository { getAssetIdByCity(userId: string, options: AssetExploreFieldOptions): Promise>; getAssetIdByTag(userId: string, options: AssetExploreFieldOptions): Promise>; searchMetadata(query: string, userIds: string[], options: MetadataSearchOptions): Promise; + getAllForUserFullSync(options: AssetFullSyncOptions): Promise; + getChangedDeltaSync(options: AssetDeltaSyncOptions): Promise; } diff --git a/server/src/interfaces/audit.interface.ts b/server/src/interfaces/audit.interface.ts index 767a4bc2f6..b023d00d56 100644 --- a/server/src/interfaces/audit.interface.ts +++ b/server/src/interfaces/audit.interface.ts @@ -1,14 +1,14 @@ -import { AuditEntity, DatabaseAction, EntityType } from 'src/entities/audit.entity'; +import { DatabaseAction, EntityType } from 'src/entities/audit.entity'; export const IAuditRepository = 'IAuditRepository'; export interface AuditSearch { action?: DatabaseAction; entityType?: EntityType; - ownerId?: string; + userIds: string[]; } export interface IAuditRepository { - getAfter(since: Date, options: AuditSearch): Promise; + getAfter(since: Date, options: AuditSearch): Promise; removeBefore(before: Date): Promise; } diff --git a/server/src/interfaces/logger.interface.ts b/server/src/interfaces/logger.interface.ts new file mode 100644 index 0000000000..d8e9a7d2ab --- /dev/null +++ b/server/src/interfaces/logger.interface.ts @@ -0,0 +1,15 @@ +import { LogLevel } from 'src/entities/system-config.entity'; + +export const ILoggerRepository = 'ILoggerRepository'; + +export interface ILoggerRepository { + setContext(message: string): void; + setLogLevel(level: LogLevel): void; + + verbose(message: any, ...args: any): void; + debug(message: any, ...args: any): void; + log(message: any, ...args: any): void; + warn(message: any, ...args: any): void; + error(message: any, ...args: any): void; + fatal(message: any, ...args: any): void; +} diff --git a/server/src/interfaces/storage.interface.ts b/server/src/interfaces/storage.interface.ts index e78bb0195d..1bd49a3f20 100644 --- a/server/src/interfaces/storage.interface.ts +++ b/server/src/interfaces/storage.interface.ts @@ -31,14 +31,6 @@ export interface WatchEvents { onError(error: Error): void; } -export enum StorageEventType { - READY = 'ready', - ADD = 'add', - CHANGE = 'change', - UNLINK = 'unlink', - ERROR = 'error', -} - export interface IStorageRepository { createZipStream(): ImmichZipStream; createReadStream(filepath: string, mimeType?: string | null): Promise; diff --git a/server/src/main.ts b/server/src/main.ts index 3a93038683..54e6a97acc 100644 --- a/server/src/main.ts +++ b/server/src/main.ts @@ -8,34 +8,38 @@ import sirv from 'sirv'; import { ApiModule, ImmichAdminModule, MicroservicesModule } from 'src/app.module'; import { WEB_ROOT, envName, excludePaths, isDev, serverVersion } from 'src/constants'; import { LogLevel } from 'src/entities/system-config.entity'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { WebSocketAdapter } from 'src/middleware/websocket.adapter'; import { ApiService } from 'src/services/api.service'; import { otelSDK } from 'src/utils/instrumentation'; -import { ImmichLogger } from 'src/utils/logger'; import { useSwagger } from 'src/utils/misc'; async function bootstrapMicroservices() { - const logger = new ImmichLogger('ImmichMicroservice'); - const port = Number(process.env.MICROSERVICES_PORT) || 3002; - otelSDK.start(); + + const host = String(process.env.HOST || '0.0.0.0'); + const port = Number(process.env.MICROSERVICES_PORT) || 3002; const app = await NestFactory.create(MicroservicesModule, { bufferLogs: true }); - app.useLogger(app.get(ImmichLogger)); + const logger = await app.resolve(ILoggerRepository); + logger.setContext('ImmichMicroservice'); + app.useLogger(logger); app.useWebSocketAdapter(new WebSocketAdapter(app)); - await app.listen(port); + await app.listen(port, host); logger.log(`Immich Microservices is listening on ${await app.getUrl()} [v${serverVersion}] [${envName}] `); } async function bootstrapApi() { - const logger = new ImmichLogger('ImmichServer'); - const port = Number(process.env.SERVER_PORT) || 3001; - otelSDK.start(); - const app = await NestFactory.create(ApiModule, { bufferLogs: true }); - app.useLogger(app.get(ImmichLogger)); + const host = String(process.env.HOST || '0.0.0.0'); + const port = Number(process.env.SERVER_PORT) || 3001; + const app = await NestFactory.create(ApiModule, { bufferLogs: true }); + const logger = await app.resolve(ILoggerRepository); + + logger.setContext('ImmichServer'); + app.useLogger(logger); app.set('trust proxy', ['loopback', 'linklocal', 'uniquelocal']); app.set('etag', 'strong'); app.use(cookieParser()); @@ -65,7 +69,7 @@ async function bootstrapApi() { } app.use(app.get(ApiService).ssr(excludePaths)); - const server = await app.listen(port); + const server = await app.listen(port, host); server.requestTimeout = 30 * 60 * 1000; logger.log(`Immich Server is listening on ${await app.getUrl()} [v${serverVersion}] [${envName}] `); diff --git a/server/src/middleware/auth.guard.ts b/server/src/middleware/auth.guard.ts index eaa47d013b..8b3abe6693 100644 --- a/server/src/middleware/auth.guard.ts +++ b/server/src/middleware/auth.guard.ts @@ -1,6 +1,7 @@ import { CanActivate, ExecutionContext, + Inject, Injectable, SetMetadata, applyDecorators, @@ -11,8 +12,8 @@ import { ApiBearerAuth, ApiCookieAuth, ApiOkResponse, ApiQuery, ApiSecurity } fr import { Request } from 'express'; import { IMMICH_API_KEY_NAME } from 'src/constants'; import { AuthDto } from 'src/dtos/auth.dto'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { AuthService, LoginDetails } from 'src/services/auth.service'; -import { ImmichLogger } from 'src/utils/logger'; import { UAParser } from 'ua-parser-js'; export enum Metadata { @@ -79,12 +80,13 @@ export interface AuthRequest extends Request { @Injectable() export class AuthGuard implements CanActivate { - private logger = new ImmichLogger(AuthGuard.name); - constructor( + @Inject(ILoggerRepository) private logger: ILoggerRepository, private reflector: Reflector, private authService: AuthService, - ) {} + ) { + this.logger.setContext(AuthGuard.name); + } async canActivate(context: ExecutionContext): Promise { const targets = [context.getHandler(), context.getClass()]; diff --git a/server/src/middleware/error.interceptor.ts b/server/src/middleware/error.interceptor.ts index 9e2273b976..8d1a25d44c 100644 --- a/server/src/middleware/error.interceptor.ts +++ b/server/src/middleware/error.interceptor.ts @@ -2,17 +2,20 @@ import { CallHandler, ExecutionContext, HttpException, + Inject, Injectable, InternalServerErrorException, NestInterceptor, } from '@nestjs/common'; import { Observable, catchError, throwError } from 'rxjs'; -import { ImmichLogger } from 'src/utils/logger'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { isConnectionAborted, routeToErrorMessage } from 'src/utils/misc'; @Injectable() export class ErrorInterceptor implements NestInterceptor { - private logger = new ImmichLogger(ErrorInterceptor.name); + constructor(@Inject(ILoggerRepository) private logger: ILoggerRepository) { + this.logger.setContext(ErrorInterceptor.name); + } intercept(context: ExecutionContext, next: CallHandler): Observable { return next.handle().pipe( diff --git a/server/src/middleware/file-upload.interceptor.ts b/server/src/middleware/file-upload.interceptor.ts index 53acbefa88..1b8405fe6e 100644 --- a/server/src/middleware/file-upload.interceptor.ts +++ b/server/src/middleware/file-upload.interceptor.ts @@ -1,4 +1,4 @@ -import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'; +import { CallHandler, ExecutionContext, Inject, Injectable, NestInterceptor } from '@nestjs/common'; import { PATH_METADATA } from '@nestjs/common/constants'; import { Reflector } from '@nestjs/core'; import { transformException } from '@nestjs/platform-express/multer/multer/multer.utils'; @@ -7,9 +7,9 @@ import multer, { StorageEngine, diskStorage } from 'multer'; import { createHash, randomUUID } from 'node:crypto'; import { Observable } from 'rxjs'; import { UploadFieldName } from 'src/dtos/asset.dto'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { AuthRequest } from 'src/middleware/auth.guard'; import { AssetService, UploadFile } from 'src/services/asset.service'; -import { ImmichLogger } from 'src/utils/logger'; export enum Route { ASSET = 'asset', @@ -59,8 +59,6 @@ const asRequest = (request: AuthRequest, file: Express.Multer.File) => { @Injectable() export class FileUploadInterceptor implements NestInterceptor { - private logger = new ImmichLogger(FileUploadInterceptor.name); - private handlers: { userProfile: RequestHandler; assetUpload: RequestHandler; @@ -70,7 +68,10 @@ export class FileUploadInterceptor implements NestInterceptor { constructor( private reflect: Reflector, private assetService: AssetService, + @Inject(ILoggerRepository) private logger: ILoggerRepository, ) { + this.logger.setContext(FileUploadInterceptor.name); + this.defaultStorage = diskStorage({ filename: this.filename.bind(this), destination: this.destination.bind(this), diff --git a/server/src/middleware/logging.interceptor.ts b/server/src/middleware/logging.interceptor.ts new file mode 100644 index 0000000000..ee8161d9c7 --- /dev/null +++ b/server/src/middleware/logging.interceptor.ts @@ -0,0 +1,28 @@ +import { CallHandler, ExecutionContext, Inject, Injectable, NestInterceptor } from '@nestjs/common'; +import { Observable, finalize } from 'rxjs'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; + +@Injectable() +export class LoggingInterceptor implements NestInterceptor { + constructor(@Inject(ILoggerRepository) private logger: ILoggerRepository) { + this.logger.setContext(LoggingInterceptor.name); + } + + intercept(context: ExecutionContext, next: CallHandler): Observable { + const handler = context.switchToHttp(); + const req = handler.getRequest(); + const res = handler.getResponse(); + + const { method, ip, path } = req; + + const start = performance.now(); + return next.handle().pipe( + finalize(() => { + const finish = performance.now(); + const duration = (finish - start).toFixed(2); + const { statusCode } = res; + this.logger.verbose(`${method} ${path} ${statusCode} ${duration}ms ${ip}`); + }), + ); + } +} diff --git a/server/src/queries/asset.repository.sql b/server/src/queries/asset.repository.sql index d8edfdb12c..86e9796faa 100644 --- a/server/src/queries/asset.repository.sql +++ b/server/src/queries/asset.repository.sql @@ -253,7 +253,7 @@ DELETE FROM "assets" WHERE "ownerId" = $1 --- AssetRepository.getLibraryAssetPaths +-- AssetRepository.getExternalLibraryAssetPaths SELECT DISTINCT "distinctAlias"."AssetEntity_id" AS "ids_AssetEntity_id" FROM @@ -272,6 +272,7 @@ FROM ( ( ((("AssetEntity__AssetEntity_library"."id" = $1))) + AND ("AssetEntity"."isExternal" = $2) ) ) AND ("AssetEntity"."deletedAt" IS NULL) @@ -767,3 +768,151 @@ ORDER BY "asset"."fileCreatedAt" DESC LIMIT 250 + +-- AssetRepository.getAllForUserFullSync +SELECT + "asset"."id" AS "asset_id", + "asset"."deviceAssetId" AS "asset_deviceAssetId", + "asset"."ownerId" AS "asset_ownerId", + "asset"."libraryId" AS "asset_libraryId", + "asset"."deviceId" AS "asset_deviceId", + "asset"."type" AS "asset_type", + "asset"."originalPath" AS "asset_originalPath", + "asset"."previewPath" AS "asset_previewPath", + "asset"."thumbnailPath" AS "asset_thumbnailPath", + "asset"."thumbhash" AS "asset_thumbhash", + "asset"."encodedVideoPath" AS "asset_encodedVideoPath", + "asset"."createdAt" AS "asset_createdAt", + "asset"."updatedAt" AS "asset_updatedAt", + "asset"."deletedAt" AS "asset_deletedAt", + "asset"."fileCreatedAt" AS "asset_fileCreatedAt", + "asset"."localDateTime" AS "asset_localDateTime", + "asset"."fileModifiedAt" AS "asset_fileModifiedAt", + "asset"."isFavorite" AS "asset_isFavorite", + "asset"."isArchived" AS "asset_isArchived", + "asset"."isExternal" AS "asset_isExternal", + "asset"."isReadOnly" AS "asset_isReadOnly", + "asset"."isOffline" AS "asset_isOffline", + "asset"."checksum" AS "asset_checksum", + "asset"."duration" AS "asset_duration", + "asset"."isVisible" AS "asset_isVisible", + "asset"."livePhotoVideoId" AS "asset_livePhotoVideoId", + "asset"."originalFileName" AS "asset_originalFileName", + "asset"."sidecarPath" AS "asset_sidecarPath", + "asset"."stackId" AS "asset_stackId", + "exifInfo"."assetId" AS "exifInfo_assetId", + "exifInfo"."description" AS "exifInfo_description", + "exifInfo"."exifImageWidth" AS "exifInfo_exifImageWidth", + "exifInfo"."exifImageHeight" AS "exifInfo_exifImageHeight", + "exifInfo"."fileSizeInByte" AS "exifInfo_fileSizeInByte", + "exifInfo"."orientation" AS "exifInfo_orientation", + "exifInfo"."dateTimeOriginal" AS "exifInfo_dateTimeOriginal", + "exifInfo"."modifyDate" AS "exifInfo_modifyDate", + "exifInfo"."timeZone" AS "exifInfo_timeZone", + "exifInfo"."latitude" AS "exifInfo_latitude", + "exifInfo"."longitude" AS "exifInfo_longitude", + "exifInfo"."projectionType" AS "exifInfo_projectionType", + "exifInfo"."city" AS "exifInfo_city", + "exifInfo"."livePhotoCID" AS "exifInfo_livePhotoCID", + "exifInfo"."autoStackId" AS "exifInfo_autoStackId", + "exifInfo"."state" AS "exifInfo_state", + "exifInfo"."country" AS "exifInfo_country", + "exifInfo"."make" AS "exifInfo_make", + "exifInfo"."model" AS "exifInfo_model", + "exifInfo"."lensModel" AS "exifInfo_lensModel", + "exifInfo"."fNumber" AS "exifInfo_fNumber", + "exifInfo"."focalLength" AS "exifInfo_focalLength", + "exifInfo"."iso" AS "exifInfo_iso", + "exifInfo"."exposureTime" AS "exifInfo_exposureTime", + "exifInfo"."profileDescription" AS "exifInfo_profileDescription", + "exifInfo"."colorspace" AS "exifInfo_colorspace", + "exifInfo"."bitsPerSample" AS "exifInfo_bitsPerSample", + "exifInfo"."fps" AS "exifInfo_fps", + "stack"."id" AS "stack_id", + "stack"."primaryAssetId" AS "stack_primaryAssetId" +FROM + "assets" "asset" + LEFT JOIN "exif" "exifInfo" ON "exifInfo"."assetId" = "asset"."id" + LEFT JOIN "asset_stack" "stack" ON "stack"."id" = "asset"."stackId" +WHERE + "asset"."ownerId" = $1 + AND ("asset"."fileCreatedAt", "asset"."id") < ($2, $3) + AND "asset"."updatedAt" <= $4 + AND "asset"."isVisible" = true +ORDER BY + "asset"."fileCreatedAt" DESC, + "asset"."id" DESC +LIMIT + 10 + +-- AssetRepository.getChangedDeltaSync +SELECT + "AssetEntity"."id" AS "AssetEntity_id", + "AssetEntity"."deviceAssetId" AS "AssetEntity_deviceAssetId", + "AssetEntity"."ownerId" AS "AssetEntity_ownerId", + "AssetEntity"."libraryId" AS "AssetEntity_libraryId", + "AssetEntity"."deviceId" AS "AssetEntity_deviceId", + "AssetEntity"."type" AS "AssetEntity_type", + "AssetEntity"."originalPath" AS "AssetEntity_originalPath", + "AssetEntity"."previewPath" AS "AssetEntity_previewPath", + "AssetEntity"."thumbnailPath" AS "AssetEntity_thumbnailPath", + "AssetEntity"."thumbhash" AS "AssetEntity_thumbhash", + "AssetEntity"."encodedVideoPath" AS "AssetEntity_encodedVideoPath", + "AssetEntity"."createdAt" AS "AssetEntity_createdAt", + "AssetEntity"."updatedAt" AS "AssetEntity_updatedAt", + "AssetEntity"."deletedAt" AS "AssetEntity_deletedAt", + "AssetEntity"."fileCreatedAt" AS "AssetEntity_fileCreatedAt", + "AssetEntity"."localDateTime" AS "AssetEntity_localDateTime", + "AssetEntity"."fileModifiedAt" AS "AssetEntity_fileModifiedAt", + "AssetEntity"."isFavorite" AS "AssetEntity_isFavorite", + "AssetEntity"."isArchived" AS "AssetEntity_isArchived", + "AssetEntity"."isExternal" AS "AssetEntity_isExternal", + "AssetEntity"."isReadOnly" AS "AssetEntity_isReadOnly", + "AssetEntity"."isOffline" AS "AssetEntity_isOffline", + "AssetEntity"."checksum" AS "AssetEntity_checksum", + "AssetEntity"."duration" AS "AssetEntity_duration", + "AssetEntity"."isVisible" AS "AssetEntity_isVisible", + "AssetEntity"."livePhotoVideoId" AS "AssetEntity_livePhotoVideoId", + "AssetEntity"."originalFileName" AS "AssetEntity_originalFileName", + "AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath", + "AssetEntity"."stackId" AS "AssetEntity_stackId", + "AssetEntity__AssetEntity_exifInfo"."assetId" AS "AssetEntity__AssetEntity_exifInfo_assetId", + "AssetEntity__AssetEntity_exifInfo"."description" AS "AssetEntity__AssetEntity_exifInfo_description", + "AssetEntity__AssetEntity_exifInfo"."exifImageWidth" AS "AssetEntity__AssetEntity_exifInfo_exifImageWidth", + "AssetEntity__AssetEntity_exifInfo"."exifImageHeight" AS "AssetEntity__AssetEntity_exifInfo_exifImageHeight", + "AssetEntity__AssetEntity_exifInfo"."fileSizeInByte" AS "AssetEntity__AssetEntity_exifInfo_fileSizeInByte", + "AssetEntity__AssetEntity_exifInfo"."orientation" AS "AssetEntity__AssetEntity_exifInfo_orientation", + "AssetEntity__AssetEntity_exifInfo"."dateTimeOriginal" AS "AssetEntity__AssetEntity_exifInfo_dateTimeOriginal", + "AssetEntity__AssetEntity_exifInfo"."modifyDate" AS "AssetEntity__AssetEntity_exifInfo_modifyDate", + "AssetEntity__AssetEntity_exifInfo"."timeZone" AS "AssetEntity__AssetEntity_exifInfo_timeZone", + "AssetEntity__AssetEntity_exifInfo"."latitude" AS "AssetEntity__AssetEntity_exifInfo_latitude", + "AssetEntity__AssetEntity_exifInfo"."longitude" AS "AssetEntity__AssetEntity_exifInfo_longitude", + "AssetEntity__AssetEntity_exifInfo"."projectionType" AS "AssetEntity__AssetEntity_exifInfo_projectionType", + "AssetEntity__AssetEntity_exifInfo"."city" AS "AssetEntity__AssetEntity_exifInfo_city", + "AssetEntity__AssetEntity_exifInfo"."livePhotoCID" AS "AssetEntity__AssetEntity_exifInfo_livePhotoCID", + "AssetEntity__AssetEntity_exifInfo"."autoStackId" AS "AssetEntity__AssetEntity_exifInfo_autoStackId", + "AssetEntity__AssetEntity_exifInfo"."state" AS "AssetEntity__AssetEntity_exifInfo_state", + "AssetEntity__AssetEntity_exifInfo"."country" AS "AssetEntity__AssetEntity_exifInfo_country", + "AssetEntity__AssetEntity_exifInfo"."make" AS "AssetEntity__AssetEntity_exifInfo_make", + "AssetEntity__AssetEntity_exifInfo"."model" AS "AssetEntity__AssetEntity_exifInfo_model", + "AssetEntity__AssetEntity_exifInfo"."lensModel" AS "AssetEntity__AssetEntity_exifInfo_lensModel", + "AssetEntity__AssetEntity_exifInfo"."fNumber" AS "AssetEntity__AssetEntity_exifInfo_fNumber", + "AssetEntity__AssetEntity_exifInfo"."focalLength" AS "AssetEntity__AssetEntity_exifInfo_focalLength", + "AssetEntity__AssetEntity_exifInfo"."iso" AS "AssetEntity__AssetEntity_exifInfo_iso", + "AssetEntity__AssetEntity_exifInfo"."exposureTime" AS "AssetEntity__AssetEntity_exifInfo_exposureTime", + "AssetEntity__AssetEntity_exifInfo"."profileDescription" AS "AssetEntity__AssetEntity_exifInfo_profileDescription", + "AssetEntity__AssetEntity_exifInfo"."colorspace" AS "AssetEntity__AssetEntity_exifInfo_colorspace", + "AssetEntity__AssetEntity_exifInfo"."bitsPerSample" AS "AssetEntity__AssetEntity_exifInfo_bitsPerSample", + "AssetEntity__AssetEntity_exifInfo"."fps" AS "AssetEntity__AssetEntity_exifInfo_fps", + "AssetEntity__AssetEntity_stack"."id" AS "AssetEntity__AssetEntity_stack_id", + "AssetEntity__AssetEntity_stack"."primaryAssetId" AS "AssetEntity__AssetEntity_stack_primaryAssetId" +FROM + "assets" "AssetEntity" + LEFT JOIN "exif" "AssetEntity__AssetEntity_exifInfo" ON "AssetEntity__AssetEntity_exifInfo"."assetId" = "AssetEntity"."id" + LEFT JOIN "asset_stack" "AssetEntity__AssetEntity_stack" ON "AssetEntity__AssetEntity_stack"."id" = "AssetEntity"."stackId" +WHERE + ( + ("AssetEntity"."ownerId" IN ($1)) + AND ("AssetEntity"."isVisible" = $2) + AND ("AssetEntity"."updatedAt" > $3) + ) diff --git a/server/src/queries/user.repository.sql b/server/src/queries/user.repository.sql index b3741bcf75..581ebe2277 100644 --- a/server/src/queries/user.repository.sql +++ b/server/src/queries/user.repository.sql @@ -159,10 +159,12 @@ SET COALESCE(SUM(exif."fileSizeInByte"), 0) FROM "assets" "assets" + LEFT JOIN "libraries" "library" ON "library"."id" = "assets"."libraryId" + AND ("library"."deletedAt" IS NULL) LEFT JOIN "exif" "exif" ON "exif"."assetId" = "assets"."id" WHERE "assets"."ownerId" = users.id - AND NOT "assets"."isExternal" + AND "library"."type" = 'UPLOAD' ), "updatedAt" = CURRENT_TIMESTAMP WHERE diff --git a/server/src/repositories/access.repository.ts b/server/src/repositories/access.repository.ts index c322cb8478..6e1fda8376 100644 --- a/server/src/repositories/access.repository.ts +++ b/server/src/repositories/access.repository.ts @@ -1,3 +1,4 @@ +import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { ChunkedSet, DummyValue, GenerateSql } from 'src/decorators'; import { ActivityEntity } from 'src/entities/activity.entity'; @@ -26,6 +27,7 @@ type IPersonAccess = IAccessRepository['person']; type IPartnerAccess = IAccessRepository['partner']; @Instrumentation() +@Injectable() class ActivityAccess implements IActivityAccess { constructor( private activityRepository: Repository, diff --git a/server/src/repositories/asset.repository.ts b/server/src/repositories/asset.repository.ts index b7ed3e4f00..ddc666edd3 100644 --- a/server/src/repositories/asset.repository.ts +++ b/server/src/repositories/asset.repository.ts @@ -2,15 +2,18 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import path from 'node:path'; import { Chunked, ChunkedArray, DummyValue, GenerateSql } from 'src/decorators'; -import { AssetOrder } from 'src/entities/album.entity'; +import { AlbumEntity, AssetOrder } from 'src/entities/album.entity'; import { AssetJobStatusEntity } from 'src/entities/asset-job-status.entity'; import { AssetEntity, AssetType } from 'src/entities/asset.entity'; import { ExifEntity } from 'src/entities/exif.entity'; +import { PartnerEntity } from 'src/entities/partner.entity'; import { SmartInfoEntity } from 'src/entities/smart-info.entity'; import { AssetBuilderOptions, AssetCreate, + AssetDeltaSyncOptions, AssetExploreFieldOptions, + AssetFullSyncOptions, AssetPathEntity, AssetStats, AssetStatsOptions, @@ -39,6 +42,7 @@ import { FindOptionsWhere, In, IsNull, + MoreThan, Not, Repository, } from 'typeorm'; @@ -61,6 +65,8 @@ export class AssetRepository implements IAssetRepository { @InjectRepository(ExifEntity) private exifRepository: Repository, @InjectRepository(AssetJobStatusEntity) private jobStatusRepository: Repository, @InjectRepository(SmartInfoEntity) private smartInfoRepository: Repository, + @InjectRepository(PartnerEntity) private partnerRepository: Repository, + @InjectRepository(AlbumEntity) private albumRepository: Repository, ) {} async upsertExif(exif: Partial): Promise { @@ -160,10 +166,10 @@ export class AssetRepository implements IAssetRepository { } @GenerateSql({ params: [{ take: 1, skip: 0 }, DummyValue.UUID] }) - getLibraryAssetPaths(pagination: PaginationOptions, libraryId: string): Paginated { + getExternalLibraryAssetPaths(pagination: PaginationOptions, libraryId: string): Paginated { return paginate(this.repository, pagination, { select: { id: true, originalPath: true, isOffline: true }, - where: { library: { id: libraryId } }, + where: { library: { id: libraryId }, isExternal: true }, }); } @@ -781,4 +787,55 @@ export class AssetRepository implements IAssetRepository { }) as AssetEntity, ); } + + @GenerateSql({ + params: [ + { + ownerId: DummyValue.UUID, + lastCreationDate: DummyValue.DATE, + lastId: DummyValue.STRING, + updatedUntil: DummyValue.DATE, + limit: 10, + }, + ], + }) + getAllForUserFullSync(options: AssetFullSyncOptions): Promise { + const { ownerId, lastCreationDate, lastId, updatedUntil, limit } = options; + let builder = this.repository + .createQueryBuilder('asset') + .leftJoinAndSelect('asset.exifInfo', 'exifInfo') + .leftJoinAndSelect('asset.stack', 'stack') + .where('asset.ownerId = :ownerId', { ownerId }); + if (lastCreationDate !== undefined && lastId !== undefined) { + builder = builder.andWhere('(asset.fileCreatedAt, asset.id) < (:lastCreationDate, :lastId)', { + lastCreationDate, + lastId, + }); + } + return builder + .andWhere('asset.updatedAt <= :updatedUntil', { updatedUntil }) + .andWhere('asset.isVisible = true') + .orderBy('asset.fileCreatedAt', 'DESC') + .addOrderBy('asset.id', 'DESC') + .limit(limit) + .withDeleted() + .getMany(); + } + + @GenerateSql({ params: [{ userIds: [DummyValue.UUID], updatedAfter: DummyValue.DATE }] }) + getChangedDeltaSync(options: AssetDeltaSyncOptions): Promise { + return this.repository.find({ + where: { + ownerId: In(options.userIds), + isVisible: true, + updatedAt: MoreThan(options.updatedAfter), + }, + relations: { + exifInfo: true, + stack: true, + }, + take: options.limit, + withDeleted: true, + }); + } } diff --git a/server/src/repositories/audit.repository.ts b/server/src/repositories/audit.repository.ts index 50f5631f3a..6cf5b76e6e 100644 --- a/server/src/repositories/audit.repository.ts +++ b/server/src/repositories/audit.repository.ts @@ -1,25 +1,28 @@ +import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { AuditEntity } from 'src/entities/audit.entity'; import { AuditSearch, IAuditRepository } from 'src/interfaces/audit.interface'; import { Instrumentation } from 'src/utils/instrumentation'; -import { LessThan, MoreThan, Repository } from 'typeorm'; +import { In, LessThan, MoreThan, Repository } from 'typeorm'; @Instrumentation() +@Injectable() export class AuditRepository implements IAuditRepository { constructor(@InjectRepository(AuditEntity) private repository: Repository) {} - getAfter(since: Date, options: AuditSearch): Promise { + getAfter(since: Date, options: AuditSearch): Promise { return this.repository .createQueryBuilder('audit') .where({ createdAt: MoreThan(since), action: options.action, entityType: options.entityType, - ownerId: options.ownerId, + ownerId: In(options.userIds), }) .distinctOn(['audit.entityId', 'audit.entityType']) .orderBy('audit.entityId, audit.entityType, audit.createdAt', 'DESC') - .getMany(); + .select('audit.entityId') + .getRawMany(); } async removeBefore(before: Date): Promise { diff --git a/server/src/repositories/database.repository.ts b/server/src/repositories/database.repository.ts index 4ff24eeaa4..b9a04bffc8 100644 --- a/server/src/repositories/database.repository.ts +++ b/server/src/repositories/database.repository.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import { InjectDataSource } from '@nestjs/typeorm'; import AsyncLock from 'async-lock'; import { vectorExt } from 'src/database.config'; @@ -11,8 +11,8 @@ import { VectorUpdateResult, extName, } from 'src/interfaces/database.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { Instrumentation } from 'src/utils/instrumentation'; -import { ImmichLogger } from 'src/utils/logger'; import { Version, VersionType } from 'src/utils/version'; import { isValidInteger } from 'src/validation'; import { DataSource, EntityManager, QueryRunner } from 'typeorm'; @@ -20,10 +20,14 @@ import { DataSource, EntityManager, QueryRunner } from 'typeorm'; @Instrumentation() @Injectable() export class DatabaseRepository implements IDatabaseRepository { - private logger = new ImmichLogger(DatabaseRepository.name); readonly asyncLock = new AsyncLock(); - constructor(@InjectDataSource() private dataSource: DataSource) {} + constructor( + @InjectDataSource() private dataSource: DataSource, + @Inject(ILoggerRepository) private logger: ILoggerRepository, + ) { + this.logger.setContext(DatabaseRepository.name); + } async getExtensionVersion(extension: DatabaseExtension): Promise { const res = await this.dataSource.query(`SELECT extversion FROM pg_extension WHERE extname = $1`, [extension]); diff --git a/server/src/repositories/event.repository.ts b/server/src/repositories/event.repository.ts index be1de76c2d..33dfbfadc5 100644 --- a/server/src/repositories/event.repository.ts +++ b/server/src/repositories/event.repository.ts @@ -1,3 +1,4 @@ +import { Inject, Injectable } from '@nestjs/common'; import { EventEmitter2 } from '@nestjs/event-emitter'; import { OnGatewayConnection, @@ -14,9 +15,9 @@ import { ServerEvent, ServerEventMap, } from 'src/interfaces/event.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { AuthService } from 'src/services/auth.service'; import { Instrumentation } from 'src/utils/instrumentation'; -import { ImmichLogger } from 'src/utils/logger'; @Instrumentation() @WebSocketGateway({ @@ -24,16 +25,18 @@ import { ImmichLogger } from 'src/utils/logger'; path: '/api/socket.io', transports: ['websocket'], }) +@Injectable() export class EventRepository implements OnGatewayConnection, OnGatewayDisconnect, OnGatewayInit, IEventRepository { - private logger = new ImmichLogger(EventRepository.name); - @WebSocketServer() private server?: Server; constructor( private authService: AuthService, private eventEmitter: EventEmitter2, - ) {} + @Inject(ILoggerRepository) private logger: ILoggerRepository, + ) { + this.logger.setContext(EventRepository.name); + } afterInit(server: Server) { this.logger.log('Initialized websocket server'); diff --git a/server/src/repositories/index.ts b/server/src/repositories/index.ts index a7cf67de96..32b7b338fb 100644 --- a/server/src/repositories/index.ts +++ b/server/src/repositories/index.ts @@ -12,6 +12,7 @@ import { IDatabaseRepository } from 'src/interfaces/database.interface'; import { IEventRepository } from 'src/interfaces/event.interface'; import { IJobRepository } from 'src/interfaces/job.interface'; import { ILibraryRepository } from 'src/interfaces/library.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface'; import { IMediaRepository } from 'src/interfaces/media.interface'; import { IMemoryRepository } from 'src/interfaces/memory.interface'; @@ -43,6 +44,7 @@ import { DatabaseRepository } from 'src/repositories/database.repository'; import { EventRepository } from 'src/repositories/event.repository'; import { JobRepository } from 'src/repositories/job.repository'; import { LibraryRepository } from 'src/repositories/library.repository'; +import { LoggerRepository } from 'src/repositories/logger.repository'; import { MachineLearningRepository } from 'src/repositories/machine-learning.repository'; import { MediaRepository } from 'src/repositories/media.repository'; import { MemoryRepository } from 'src/repositories/memory.repository'; @@ -74,6 +76,7 @@ export const repositories = [ { provide: IDatabaseRepository, useClass: DatabaseRepository }, { provide: IEventRepository, useClass: EventRepository }, { provide: IJobRepository, useClass: JobRepository }, + { provide: ILoggerRepository, useClass: LoggerRepository }, { provide: ILibraryRepository, useClass: LibraryRepository }, { provide: IKeyRepository, useClass: ApiKeyRepository }, { provide: IMachineLearningRepository, useClass: MachineLearningRepository }, diff --git a/server/src/repositories/job.repository.ts b/server/src/repositories/job.repository.ts index a7c99f93cb..858798b88d 100644 --- a/server/src/repositories/job.repository.ts +++ b/server/src/repositories/job.repository.ts @@ -1,5 +1,5 @@ import { getQueueToken } from '@nestjs/bullmq'; -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; import { SchedulerRegistry } from '@nestjs/schedule'; import { Job, JobsOptions, Processor, Queue, Worker, WorkerOptions } from 'bullmq'; @@ -15,8 +15,8 @@ import { QueueName, QueueStatus, } from 'src/interfaces/job.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { Instrumentation } from 'src/utils/instrumentation'; -import { ImmichLogger } from 'src/utils/logger'; export const JOBS_TO_QUEUE: Record = { // misc @@ -83,12 +83,14 @@ export const JOBS_TO_QUEUE: Record = { @Injectable() export class JobRepository implements IJobRepository { private workers: Partial> = {}; - private logger = new ImmichLogger(JobRepository.name); constructor( private moduleReference: ModuleRef, private schedulerReqistry: SchedulerRegistry, - ) {} + @Inject(ILoggerRepository) private logger: ILoggerRepository, + ) { + this.logger.setContext(JobRepository.name); + } addHandler(queueName: QueueName, concurrency: number, handler: (item: JobItem) => Promise) { const workerHandler: Processor = async (job: Job) => handler(job as JobItem); diff --git a/server/src/repositories/logger.repository.ts b/server/src/repositories/logger.repository.ts new file mode 100644 index 0000000000..65ccd8ea47 --- /dev/null +++ b/server/src/repositories/logger.repository.ts @@ -0,0 +1,27 @@ +import { Injectable, Scope } from '@nestjs/common'; +import { ClsService } from 'nestjs-cls'; +import { LogLevel } from 'src/entities/system-config.entity'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; +import { ImmichLogger } from 'src/utils/logger'; + +@Injectable({ scope: Scope.TRANSIENT }) +export class LoggerRepository extends ImmichLogger implements ILoggerRepository { + constructor(private cls: ClsService) { + super(LoggerRepository.name); + } + + protected formatContext(context: string): string { + let formattedContext = super.formatContext(context); + + const correlationId = this.cls?.getId(); + if (correlationId && this.isLevelEnabled(LogLevel.VERBOSE)) { + formattedContext += `[${correlationId}] `; + } + + return formattedContext; + } + + setLogLevel(level: LogLevel): void { + ImmichLogger.setLogLevel(level); + } +} diff --git a/server/src/repositories/media.repository.ts b/server/src/repositories/media.repository.ts index 52a538909f..3936ad7e42 100644 --- a/server/src/repositories/media.repository.ts +++ b/server/src/repositories/media.repository.ts @@ -1,9 +1,11 @@ +import { Inject, Injectable } from '@nestjs/common'; import ffmpeg, { FfprobeData } from 'fluent-ffmpeg'; import fs from 'node:fs/promises'; import { Writable } from 'node:stream'; import { promisify } from 'node:util'; import sharp from 'sharp'; import { Colorspace } from 'src/entities/system-config.entity'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { CropOptions, IMediaRepository, @@ -12,7 +14,6 @@ import { VideoInfo, } from 'src/interfaces/media.interface'; import { Instrumentation } from 'src/utils/instrumentation'; -import { ImmichLogger } from 'src/utils/logger'; import { handlePromiseError } from 'src/utils/misc'; const probe = promisify(ffmpeg.ffprobe); @@ -20,9 +21,11 @@ sharp.concurrency(0); sharp.cache({ files: 0 }); @Instrumentation() +@Injectable() export class MediaRepository implements IMediaRepository { - private logger = new ImmichLogger(MediaRepository.name); - + constructor(@Inject(ILoggerRepository) private logger: ILoggerRepository) { + this.logger.setContext(MediaRepository.name); + } crop(input: string | Buffer, options: CropOptions): Promise { return sharp(input, { failOn: 'none' }) .pipelineColorspace('rgb16') diff --git a/server/src/repositories/metadata.repository.ts b/server/src/repositories/metadata.repository.ts index 511023a8ea..8eeb0064ac 100644 --- a/server/src/repositories/metadata.repository.ts +++ b/server/src/repositories/metadata.repository.ts @@ -1,4 +1,4 @@ -import { Inject } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; import { DefaultReadTaskOptions, Tags, exiftool } from 'exiftool-vendored'; import geotz from 'geo-tz'; @@ -11,24 +11,26 @@ import { DummyValue, GenerateSql } from 'src/decorators'; import { ExifEntity } from 'src/entities/exif.entity'; import { GeodataPlacesEntity } from 'src/entities/geodata-places.entity'; import { SystemMetadataKey } from 'src/entities/system-metadata.entity'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { GeoPoint, IMetadataRepository, ImmichTags, ReverseGeocodeResult } from 'src/interfaces/metadata.interface'; import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; import { Instrumentation } from 'src/utils/instrumentation'; -import { ImmichLogger } from 'src/utils/logger'; import { DataSource, QueryRunner, Repository } from 'typeorm'; import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity.js'; @Instrumentation() +@Injectable() export class MetadataRepository implements IMetadataRepository { constructor( @InjectRepository(ExifEntity) private exifRepository: Repository, - @InjectRepository(GeodataPlacesEntity) private readonly geodataPlacesRepository: Repository, + @InjectRepository(GeodataPlacesEntity) private geodataPlacesRepository: Repository, @Inject(ISystemMetadataRepository) - private readonly systemMetadataRepository: ISystemMetadataRepository, + private systemMetadataRepository: ISystemMetadataRepository, @InjectDataSource() private dataSource: DataSource, - ) {} - - private logger = new ImmichLogger(MetadataRepository.name); + @Inject(ILoggerRepository) private logger: ILoggerRepository, + ) { + this.logger.setContext(MetadataRepository.name); + } async init(): Promise { this.logger.log('Initializing metadata repository'); diff --git a/server/src/repositories/metric.repository.ts b/server/src/repositories/metric.repository.ts index c6eb953acf..5948e92fa6 100644 --- a/server/src/repositories/metric.repository.ts +++ b/server/src/repositories/metric.repository.ts @@ -6,7 +6,7 @@ import { apiMetrics, hostMetrics, jobMetrics, repoMetrics } from 'src/utils/inst class MetricGroupRepository implements IMetricGroupRepository { private enabled = false; - constructor(private readonly metricService: MetricService) {} + constructor(private metricService: MetricService) {} addToCounter(name: string, value: number, options?: MetricOptions): void { if (this.enabled) { diff --git a/server/src/repositories/partner.repository.ts b/server/src/repositories/partner.repository.ts index 8465493b54..e0c8998dbf 100644 --- a/server/src/repositories/partner.repository.ts +++ b/server/src/repositories/partner.repository.ts @@ -8,7 +8,7 @@ import { DeepPartial, Repository } from 'typeorm'; @Instrumentation() @Injectable() export class PartnerRepository implements IPartnerRepository { - constructor(@InjectRepository(PartnerEntity) private readonly repository: Repository) {} + constructor(@InjectRepository(PartnerEntity) private repository: Repository) {} getAll(userId: string): Promise { return this.repository.find({ where: [{ sharedWithId: userId }, { sharedById: userId }] }); diff --git a/server/src/repositories/person.repository.ts b/server/src/repositories/person.repository.ts index 0acbafe699..7ffc6bf2b7 100644 --- a/server/src/repositories/person.repository.ts +++ b/server/src/repositories/person.repository.ts @@ -1,3 +1,4 @@ +import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import _ from 'lodash'; import { ChunkedArray, DummyValue, GenerateSql } from 'src/decorators'; @@ -19,6 +20,7 @@ import { Paginated, PaginationOptions, paginate } from 'src/utils/pagination'; import { FindManyOptions, FindOptionsRelations, FindOptionsSelect, In, Repository } from 'typeorm'; @Instrumentation() +@Injectable() export class PersonRepository implements IPersonRepository { constructor( @InjectRepository(AssetEntity) private assetRepository: Repository, diff --git a/server/src/repositories/search.repository.ts b/server/src/repositories/search.repository.ts index 4530d2295f..6ac49a3190 100644 --- a/server/src/repositories/search.repository.ts +++ b/server/src/repositories/search.repository.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { vectorExt } from 'src/database.config'; import { DummyValue, GenerateSql } from 'src/decorators'; @@ -8,6 +8,7 @@ import { GeodataPlacesEntity } from 'src/entities/geodata-places.entity'; import { SmartInfoEntity } from 'src/entities/smart-info.entity'; import { SmartSearchEntity } from 'src/entities/smart-search.entity'; import { DatabaseExtension } from 'src/interfaces/database.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { AssetSearchOptions, FaceEmbeddingSearch, @@ -18,7 +19,6 @@ import { } from 'src/interfaces/search.interface'; import { asVector, searchAssetBuilder } from 'src/utils/database'; import { Instrumentation } from 'src/utils/instrumentation'; -import { ImmichLogger } from 'src/utils/logger'; import { getCLIPModelInfo } from 'src/utils/misc'; import { Paginated, PaginationMode, PaginationResult, paginatedBuilder } from 'src/utils/pagination'; import { isValidInteger } from 'src/validation'; @@ -27,7 +27,6 @@ import { Repository, SelectQueryBuilder } from 'typeorm'; @Instrumentation() @Injectable() export class SearchRepository implements ISearchRepository { - private logger = new ImmichLogger(SearchRepository.name); private faceColumns: string[]; private assetsByCityQuery: string; @@ -36,8 +35,10 @@ export class SearchRepository implements ISearchRepository { @InjectRepository(AssetEntity) private assetRepository: Repository, @InjectRepository(AssetFaceEntity) private assetFaceRepository: Repository, @InjectRepository(SmartSearchEntity) private smartSearchRepository: Repository, - @InjectRepository(GeodataPlacesEntity) private readonly geodataPlacesRepository: Repository, + @InjectRepository(GeodataPlacesEntity) private geodataPlacesRepository: Repository, + @Inject(ILoggerRepository) private logger: ILoggerRepository, ) { + this.logger.setContext(SearchRepository.name); this.faceColumns = this.assetFaceRepository.manager.connection .getMetadata(AssetFaceEntity) .ownColumns.map((column) => column.propertyName) diff --git a/server/src/repositories/storage.repository.spec.ts b/server/src/repositories/storage.repository.spec.ts index b92a26904f..44c81d76a6 100644 --- a/server/src/repositories/storage.repository.spec.ts +++ b/server/src/repositories/storage.repository.spec.ts @@ -1,6 +1,8 @@ import mockfs from 'mock-fs'; import { CrawlOptionsDto } from 'src/dtos/library.dto'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { StorageRepository } from 'src/repositories/storage.repository'; +import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock'; interface Test { test: string; @@ -181,9 +183,11 @@ const tests: Test[] = [ describe(StorageRepository.name, () => { let sut: StorageRepository; + let logger: ILoggerRepository; beforeEach(() => { - sut = new StorageRepository(); + logger = newLoggerRepositoryMock(); + sut = new StorageRepository(logger); }); afterEach(() => { diff --git a/server/src/repositories/storage.repository.ts b/server/src/repositories/storage.repository.ts index 7e1c8d59e2..ae374ac812 100644 --- a/server/src/repositories/storage.repository.ts +++ b/server/src/repositories/storage.repository.ts @@ -1,3 +1,4 @@ +import { Inject, Injectable } from '@nestjs/common'; import archiver from 'archiver'; import chokidar, { WatchOptions } from 'chokidar'; import { glob, globStream } from 'fast-glob'; @@ -5,21 +6,23 @@ import { constants, createReadStream, existsSync, mkdirSync } from 'node:fs'; import fs from 'node:fs/promises'; import path from 'node:path'; import { CrawlOptionsDto } from 'src/dtos/library.dto'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { DiskUsage, IStorageRepository, ImmichReadStream, ImmichZipStream, - StorageEventType, WatchEvents, } from 'src/interfaces/storage.interface'; import { Instrumentation } from 'src/utils/instrumentation'; -import { ImmichLogger } from 'src/utils/logger'; import { mimeTypes } from 'src/utils/mime-types'; @Instrumentation() +@Injectable() export class StorageRepository implements IStorageRepository { - private logger = new ImmichLogger(StorageRepository.name); + constructor(@Inject(ILoggerRepository) private logger: ILoggerRepository) { + this.logger.setContext(StorageRepository.name); + } readdir(folder: string): Promise { return fs.readdir(folder); @@ -173,11 +176,11 @@ export class StorageRepository implements IStorageRepository { watch(paths: string[], options: WatchOptions, events: Partial) { const watcher = chokidar.watch(paths, options); - watcher.on(StorageEventType.READY, () => events.onReady?.()); - watcher.on(StorageEventType.ADD, (path) => events.onAdd?.(path)); - watcher.on(StorageEventType.CHANGE, (path) => events.onChange?.(path)); - watcher.on(StorageEventType.UNLINK, (path) => events.onUnlink?.(path)); - watcher.on(StorageEventType.ERROR, (error) => events.onError?.(error)); + watcher.on('ready', () => events.onReady?.()); + watcher.on('add', (path) => events.onAdd?.(path)); + watcher.on('change', (path) => events.onChange?.(path)); + watcher.on('unlink', (path) => events.onUnlink?.(path)); + watcher.on('error', (error) => events.onError?.(error)); return () => watcher.close(); } diff --git a/server/src/repositories/system-config.repository.ts b/server/src/repositories/system-config.repository.ts index baa3218b00..3d2dbecbc8 100644 --- a/server/src/repositories/system-config.repository.ts +++ b/server/src/repositories/system-config.repository.ts @@ -1,3 +1,4 @@ +import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { readFile } from 'node:fs/promises'; import { Chunked, DummyValue, GenerateSql } from 'src/decorators'; @@ -7,6 +8,7 @@ import { Instrumentation } from 'src/utils/instrumentation'; import { In, Repository } from 'typeorm'; @Instrumentation() +@Injectable() export class SystemConfigRepository implements ISystemConfigRepository { constructor( @InjectRepository(SystemConfigEntity) diff --git a/server/src/repositories/system-metadata.repository.ts b/server/src/repositories/system-metadata.repository.ts index 80936e46f6..91b887a176 100644 --- a/server/src/repositories/system-metadata.repository.ts +++ b/server/src/repositories/system-metadata.repository.ts @@ -1,3 +1,4 @@ +import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { SystemMetadata, SystemMetadataEntity } from 'src/entities/system-metadata.entity'; import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; @@ -5,6 +6,7 @@ import { Instrumentation } from 'src/utils/instrumentation'; import { Repository } from 'typeorm'; @Instrumentation() +@Injectable() export class SystemMetadataRepository implements ISystemMetadataRepository { constructor( @InjectRepository(SystemMetadataEntity) diff --git a/server/src/repositories/user.repository.ts b/server/src/repositories/user.repository.ts index f0e00d0496..0435d76d19 100644 --- a/server/src/repositories/user.repository.ts +++ b/server/src/repositories/user.repository.ts @@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { DummyValue, GenerateSql } from 'src/decorators'; import { AssetEntity } from 'src/entities/asset.entity'; +import { LibraryType } from 'src/entities/library.entity'; import { UserEntity } from 'src/entities/user.entity'; import { IUserRepository, @@ -117,11 +118,14 @@ export class UserRepository implements IUserRepository { @GenerateSql({ params: [DummyValue.UUID] }) async syncUsage(id?: string) { + // we can't use parameters with getQuery, hence the template string const subQuery = this.assetRepository .createQueryBuilder('assets') .select('COALESCE(SUM(exif."fileSizeInByte"), 0)') + .leftJoin('assets.library', 'library') .leftJoin('assets.exifInfo', 'exif') - .where('assets.ownerId = users.id AND NOT assets.isExternal') + .where('assets.ownerId = users.id') + .andWhere(`library.type = '${LibraryType.UPLOAD}'`) .withDeleted(); const query = this.userRepository diff --git a/server/src/services/activity.service.spec.ts b/server/src/services/activity.service.spec.ts index e7049ea6c9..30720b6c1f 100644 --- a/server/src/services/activity.service.spec.ts +++ b/server/src/services/activity.service.spec.ts @@ -6,11 +6,12 @@ import { activityStub } from 'test/fixtures/activity.stub'; import { authStub } from 'test/fixtures/auth.stub'; import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositories/access.repository.mock'; import { newActivityRepositoryMock } from 'test/repositories/activity.repository.mock'; +import { Mocked } from 'vitest'; describe(ActivityService.name, () => { let sut: ActivityService; let accessMock: IAccessRepositoryMock; - let activityMock: jest.Mocked; + let activityMock: Mocked; beforeEach(() => { accessMock = newAccessRepositoryMock(); diff --git a/server/src/services/album.service.spec.ts b/server/src/services/album.service.spec.ts index 6ac02df557..99ab7bd570 100644 --- a/server/src/services/album.service.spec.ts +++ b/server/src/services/album.service.spec.ts @@ -15,14 +15,15 @@ import { newAlbumUserRepositoryMock } from 'test/repositories/album-user.reposit import { newAlbumRepositoryMock } from 'test/repositories/album.repository.mock'; import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock'; import { newUserRepositoryMock } from 'test/repositories/user.repository.mock'; +import { Mocked } from 'vitest'; describe(AlbumService.name, () => { let sut: AlbumService; let accessMock: IAccessRepositoryMock; - let albumMock: jest.Mocked; - let assetMock: jest.Mocked; - let userMock: jest.Mocked; - let albumUserMock: jest.Mocked; + let albumMock: Mocked; + let assetMock: Mocked; + let userMock: Mocked; + let albumUserMock: Mocked; beforeEach(() => { accessMock = newAccessRepositoryMock(); diff --git a/server/src/services/api-key.service.spec.ts b/server/src/services/api-key.service.spec.ts index 47fd0f5159..2b5efc674f 100644 --- a/server/src/services/api-key.service.spec.ts +++ b/server/src/services/api-key.service.spec.ts @@ -6,11 +6,12 @@ import { keyStub } from 'test/fixtures/api-key.stub'; import { authStub } from 'test/fixtures/auth.stub'; import { newKeyRepositoryMock } from 'test/repositories/api-key.repository.mock'; import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock'; +import { Mocked } from 'vitest'; describe(APIKeyService.name, () => { let sut: APIKeyService; - let keyMock: jest.Mocked; - let cryptoMock: jest.Mocked; + let keyMock: Mocked; + let cryptoMock: Mocked; beforeEach(() => { cryptoMock = newCryptoRepositoryMock(); diff --git a/server/src/services/api.service.ts b/server/src/services/api.service.ts index 87107b23f4..fb9912da95 100644 --- a/server/src/services/api.service.ts +++ b/server/src/services/api.service.ts @@ -1,9 +1,10 @@ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import { Cron, CronExpression, Interval } from '@nestjs/schedule'; import { NextFunction, Request, Response } from 'express'; import { readFileSync } from 'node:fs'; import { join } from 'node:path'; import { ONE_HOUR, WEB_ROOT } from 'src/constants'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { AuthService } from 'src/services/auth.service'; import { DatabaseService } from 'src/services/database.service'; import { JobService } from 'src/services/job.service'; @@ -11,7 +12,6 @@ import { ServerInfoService } from 'src/services/server-info.service'; import { SharedLinkService } from 'src/services/shared-link.service'; import { StorageService } from 'src/services/storage.service'; import { SystemConfigService } from 'src/services/system-config.service'; -import { ImmichLogger } from 'src/utils/logger'; import { OpenGraphTags } from 'src/utils/misc'; const render = (index: string, meta: OpenGraphTags) => { @@ -36,8 +36,6 @@ const render = (index: string, meta: OpenGraphTags) => { @Injectable() export class ApiService { - private logger = new ImmichLogger(ApiService.name); - constructor( private authService: AuthService, private configService: SystemConfigService, @@ -46,7 +44,10 @@ export class ApiService { private sharedLinkService: SharedLinkService, private storageService: StorageService, private databaseService: DatabaseService, - ) {} + @Inject(ILoggerRepository) private logger: ILoggerRepository, + ) { + this.logger.setContext(ApiService.name); + } @Interval(ONE_HOUR.as('milliseconds')) async onVersionCheck() { diff --git a/server/src/services/asset-v1.service.spec.ts b/server/src/services/asset-v1.service.spec.ts index 735ac8322a..bc088cb30b 100644 --- a/server/src/services/asset-v1.service.spec.ts +++ b/server/src/services/asset-v1.service.spec.ts @@ -1,4 +1,3 @@ -import { when } from 'jest-when'; import { AssetRejectReason, AssetUploadAction } from 'src/dtos/asset-v1-response.dto'; import { CreateAssetDto } from 'src/dtos/asset-v1.dto'; import { ASSET_CHECKSUM_CONSTRAINT, AssetEntity, AssetType } from 'src/entities/asset.entity'; @@ -7,6 +6,7 @@ import { IAssetRepositoryV1 } from 'src/interfaces/asset-v1.interface'; import { IAssetRepository } from 'src/interfaces/asset.interface'; import { IJobRepository, JobName } from 'src/interfaces/job.interface'; import { ILibraryRepository } from 'src/interfaces/library.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface'; import { IUserRepository } from 'src/interfaces/user.interface'; import { AssetServiceV1 } from 'src/services/asset-v1.service'; @@ -17,9 +17,11 @@ import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositorie import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock'; import { newJobRepositoryMock } from 'test/repositories/job.repository.mock'; import { newLibraryRepositoryMock } from 'test/repositories/library.repository.mock'; +import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock'; import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock'; import { newUserRepositoryMock } from 'test/repositories/user.repository.mock'; import { QueryFailedError } from 'typeorm'; +import { Mocked, vitest } from 'vitest'; const _getCreateAssetDto = (): CreateAssetDto => { const createAssetDto = new CreateAssetDto(); @@ -62,40 +64,50 @@ const _getAsset_1 = () => { describe('AssetService', () => { let sut: AssetServiceV1; let accessMock: IAccessRepositoryMock; - let assetRepositoryMockV1: jest.Mocked; - let assetMock: jest.Mocked; - let jobMock: jest.Mocked; - let libraryMock: jest.Mocked; - let storageMock: jest.Mocked; - let userMock: jest.Mocked; + let assetRepositoryMockV1: Mocked; + let assetMock: Mocked; + let jobMock: Mocked; + let libraryMock: Mocked; + let loggerMock: Mocked; + let storageMock: Mocked; + let userMock: Mocked; beforeEach(() => { assetRepositoryMockV1 = { - get: jest.fn(), - getAllByUserId: jest.fn(), - getDetectedObjectsByUserId: jest.fn(), - getLocationsByUserId: jest.fn(), - getSearchPropertiesByUserId: jest.fn(), - getAssetsByChecksums: jest.fn(), - getExistingAssets: jest.fn(), - getByOriginalPath: jest.fn(), + get: vitest.fn(), + getAllByUserId: vitest.fn(), + getDetectedObjectsByUserId: vitest.fn(), + getLocationsByUserId: vitest.fn(), + getSearchPropertiesByUserId: vitest.fn(), + getAssetsByChecksums: vitest.fn(), + getExistingAssets: vitest.fn(), + getByOriginalPath: vitest.fn(), }; accessMock = newAccessRepositoryMock(); assetMock = newAssetRepositoryMock(); jobMock = newJobRepositoryMock(); libraryMock = newLibraryRepositoryMock(); + loggerMock = newLoggerRepositoryMock(); storageMock = newStorageRepositoryMock(); userMock = newUserRepositoryMock(); - sut = new AssetServiceV1(accessMock, assetRepositoryMockV1, assetMock, jobMock, libraryMock, storageMock, userMock); + sut = new AssetServiceV1( + accessMock, + assetRepositoryMockV1, + assetMock, + jobMock, + libraryMock, + storageMock, + userMock, + loggerMock, + ); - when(assetRepositoryMockV1.get) - .calledWith(assetStub.livePhotoStillAsset.id) - .mockResolvedValue(assetStub.livePhotoStillAsset); - when(assetRepositoryMockV1.get) - .calledWith(assetStub.livePhotoMotionAsset.id) - .mockResolvedValue(assetStub.livePhotoMotionAsset); + assetRepositoryMockV1.get.mockImplementation((assetId) => + Promise.resolve( + [assetStub.livePhotoMotionAsset, assetStub.livePhotoMotionAsset].find((asset) => asset.id === assetId) ?? null, + ), + ); }); describe('uploadFile', () => { diff --git a/server/src/services/asset-v1.service.ts b/server/src/services/asset-v1.service.ts index 97aa99d91d..61fe5bd80b 100644 --- a/server/src/services/asset-v1.service.ts +++ b/server/src/services/asset-v1.service.ts @@ -33,18 +33,17 @@ import { IAssetRepositoryV1 } from 'src/interfaces/asset-v1.interface'; import { IAssetRepository } from 'src/interfaces/asset.interface'; import { IJobRepository, JobName } from 'src/interfaces/job.interface'; import { ILibraryRepository } from 'src/interfaces/library.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface'; import { IUserRepository } from 'src/interfaces/user.interface'; import { UploadFile } from 'src/services/asset.service'; import { CacheControl, ImmichFileResponse, getLivePhotoMotionFilename } from 'src/utils/file'; -import { ImmichLogger } from 'src/utils/logger'; import { mimeTypes } from 'src/utils/mime-types'; import { QueryFailedError } from 'typeorm'; @Injectable() /** @deprecated */ export class AssetServiceV1 { - readonly logger = new ImmichLogger(AssetServiceV1.name); private access: AccessCore; constructor( @@ -55,8 +54,10 @@ export class AssetServiceV1 { @Inject(ILibraryRepository) private libraryRepository: ILibraryRepository, @Inject(IStorageRepository) private storageRepository: IStorageRepository, @Inject(IUserRepository) private userRepository: IUserRepository, + @Inject(ILoggerRepository) private logger: ILoggerRepository, ) { this.access = AccessCore.create(accessRepository); + this.logger.setContext(AssetServiceV1.name); } public async uploadFile( diff --git a/server/src/services/asset.service.spec.ts b/server/src/services/asset.service.spec.ts index 28841836f2..1516ef8961 100755 --- a/server/src/services/asset.service.spec.ts +++ b/server/src/services/asset.service.spec.ts @@ -1,12 +1,12 @@ import { BadRequestException, UnauthorizedException } from '@nestjs/common'; -import { when } from 'jest-when'; import { mapAsset } from 'src/dtos/asset-response.dto'; import { AssetJobName, AssetStatsResponseDto, UploadFieldName } from 'src/dtos/asset.dto'; import { AssetEntity, AssetType } from 'src/entities/asset.entity'; import { IAssetStackRepository } from 'src/interfaces/asset-stack.interface'; import { AssetStats, IAssetRepository } from 'src/interfaces/asset.interface'; import { ClientEvent, IEventRepository } from 'src/interfaces/event.interface'; -import { IJobRepository, JobItem, JobName } from 'src/interfaces/job.interface'; +import { IJobRepository, JobName } from 'src/interfaces/job.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IPartnerRepository } from 'src/interfaces/partner.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface'; import { ISystemConfigRepository } from 'src/interfaces/system-config.interface'; @@ -22,10 +22,12 @@ import { newAssetStackRepositoryMock } from 'test/repositories/asset-stack.repos import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock'; import { newEventRepositoryMock } from 'test/repositories/event.repository.mock'; import { newJobRepositoryMock } from 'test/repositories/job.repository.mock'; +import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock'; import { newPartnerRepositoryMock } from 'test/repositories/partner.repository.mock'; import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock'; import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; import { newUserRepositoryMock } from 'test/repositories/user.repository.mock'; +import { Mocked, vitest } from 'vitest'; const stats: AssetStats = { [AssetType.IMAGE]: 10, @@ -148,19 +150,26 @@ const uploadTests = [ describe(AssetService.name, () => { let sut: AssetService; let accessMock: IAccessRepositoryMock; - let assetMock: jest.Mocked; - let jobMock: jest.Mocked; - let storageMock: jest.Mocked; - let userMock: jest.Mocked; - let eventMock: jest.Mocked; - let configMock: jest.Mocked; - let partnerMock: jest.Mocked; - let assetStackMock: jest.Mocked; + let assetMock: Mocked; + let jobMock: Mocked; + let storageMock: Mocked; + let userMock: Mocked; + let eventMock: Mocked; + let configMock: Mocked; + let partnerMock: Mocked; + let assetStackMock: Mocked; + let loggerMock: Mocked; it('should work', () => { expect(sut).toBeDefined(); }); + const mockGetById = (assets: AssetEntity[]) => { + assetMock.getById.mockImplementation((assetId) => + Promise.resolve(assets.find((asset) => asset.id === assetId) ?? null), + ); + }; + beforeEach(() => { accessMock = newAccessRepositoryMock(); assetMock = newAssetRepositoryMock(); @@ -171,6 +180,7 @@ describe(AssetService.name, () => { configMock = newSystemConfigRepositoryMock(); partnerMock = newPartnerRepositoryMock(); assetStackMock = newAssetStackRepositoryMock(); + loggerMock = newLoggerRepositoryMock(); sut = new AssetService( accessMock, @@ -182,14 +192,10 @@ describe(AssetService.name, () => { eventMock, partnerMock, assetStackMock, + loggerMock, ); - when(assetMock.getById) - .calledWith(assetStub.livePhotoStillAsset.id) - .mockResolvedValue(assetStub.livePhotoStillAsset as AssetEntity); - when(assetMock.getById) - .calledWith(assetStub.livePhotoMotionAsset.id) - .mockResolvedValue(assetStub.livePhotoMotionAsset as AssetEntity); + mockGetById([assetStub.livePhotoStillAsset, assetStub.livePhotoMotionAsset]); }); describe('canUpload', () => { @@ -299,12 +305,12 @@ describe(AssetService.name, () => { describe('getMemoryLane', () => { beforeAll(() => { - jest.useFakeTimers(); - jest.setSystemTime(new Date('2024-01-15')); + vitest.useFakeTimers(); + vitest.setSystemTime(new Date('2024-01-15')); }); afterAll(() => { - jest.useRealTimers(); + vitest.useRealTimers(); }); it('should group the assets correctly', async () => { @@ -469,9 +475,7 @@ describe(AssetService.name, () => { it('should update parent asset updatedAt when children are added', async () => { accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['parent'])); - when(assetMock.getById) - .calledWith('parent', { stack: { assets: true } }) - .mockResolvedValue(assetStub.image); + mockGetById([{ ...assetStub.image, id: 'parent' }]); await sut.updateAll(authStub.user1, { ids: [], stackParentId: 'parent', @@ -488,9 +492,7 @@ describe(AssetService.name, () => { stack: assetStackStub('stack-1', [{ id: 'parent' } as AssetEntity, { id: 'child-1' } as AssetEntity]), } as AssetEntity, ]); - when(assetStackMock.getById) - .calledWith('stack-1') - .mockResolvedValue(assetStackStub('stack-1', [{ id: 'parent' } as AssetEntity])); + assetStackMock.getById.mockResolvedValue(assetStackStub('stack-1', [{ id: 'parent' } as AssetEntity])); await sut.updateAll(authStub.user1, { ids: ['child-1'], @@ -511,12 +513,10 @@ describe(AssetService.name, () => { { id: 'child-1' } as AssetEntity, { id: 'child-2' } as AssetEntity, ]); - when(assetMock.getById) - .calledWith('parent', { stack: { assets: true } }) - .mockResolvedValue({ - id: 'child-1', - stack, - } as AssetEntity); + assetMock.getById.mockResolvedValue({ + id: 'child-1', + stack, + } as AssetEntity); await sut.updateAll(authStub.user1, { stackParentId: 'parent', @@ -547,9 +547,7 @@ describe(AssetService.name, () => { it('merge stacks if new child has children', async () => { accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['child-1'])); accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['parent'])); - when(assetMock.getById) - .calledWith('parent', { stack: { assets: true } }) - .mockResolvedValue({ ...assetStub.image, id: 'parent' }); + assetMock.getById.mockResolvedValue({ ...assetStub.image, id: 'parent' }); assetMock.getByIds.mockResolvedValue([ { id: 'child-1', @@ -557,9 +555,7 @@ describe(AssetService.name, () => { stack: assetStackStub('stack-1', [{ id: 'child-1' } as AssetEntity, { id: 'child-2' } as AssetEntity]), } as AssetEntity, ]); - when(assetStackMock.getById) - .calledWith('stack-1') - .mockResolvedValue(assetStackStub('stack-1', [{ id: 'parent' } as AssetEntity])); + assetStackMock.getById.mockResolvedValue(assetStackStub('stack-1', [{ id: 'parent' } as AssetEntity])); await sut.updateAll(authStub.user1, { ids: ['child-1'], @@ -579,9 +575,7 @@ describe(AssetService.name, () => { it('should send ws asset update event', async () => { accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['asset-1'])); accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['parent'])); - when(assetMock.getById) - .calledWith('parent', { stack: { assets: true } }) - .mockResolvedValue(assetStub.image); + assetMock.getById.mockResolvedValue(assetStub.image); await sut.updateAll(authStub.user1, { ids: ['asset-1'], @@ -626,32 +620,10 @@ describe(AssetService.name, () => { }); describe('handleAssetDeletion', () => { - beforeEach(() => { - when(jobMock.queue) - .calledWith( - expect.objectContaining({ - name: JobName.ASSET_DELETION, - }), - ) - .mockImplementation(async (item: JobItem) => { - const jobData = (item as { data?: any })?.data || {}; - await sut.handleAssetDeletion(jobData); - }); - }); - it('should remove faces', async () => { const assetWithFace = { ...assetStub.image, faces: [faceStub.face1, faceStub.mergeFace1] }; - when(assetMock.getById) - .calledWith(assetWithFace.id, { - faces: { - person: true, - }, - library: true, - stack: { assets: true }, - exifInfo: true, - }) - .mockResolvedValue(assetWithFace); + assetMock.getById.mockResolvedValue(assetWithFace); await sut.handleAssetDeletion({ id: assetWithFace.id }); @@ -676,16 +648,7 @@ describe(AssetService.name, () => { }); it('should update stack primary asset if deleted asset was primary asset in a stack', async () => { - when(assetMock.getById) - .calledWith(assetStub.primaryImage.id, { - faces: { - person: true, - }, - library: true, - stack: { assets: true }, - exifInfo: true, - }) - .mockResolvedValue(assetStub.primaryImage as AssetEntity); + assetMock.getById.mockResolvedValue(assetStub.primaryImage as AssetEntity); await sut.handleAssetDeletion({ id: assetStub.primaryImage.id }); @@ -696,16 +659,7 @@ describe(AssetService.name, () => { }); it('should only delete generated files for readonly assets', async () => { - when(assetMock.getById) - .calledWith(assetStub.readOnly.id, { - faces: { - person: true, - }, - library: true, - stack: { assets: true }, - exifInfo: true, - }) - .mockResolvedValue(assetStub.readOnly); + assetMock.getById.mockResolvedValue(assetStub.readOnly); await sut.handleAssetDeletion({ id: assetStub.readOnly.id }); @@ -728,7 +682,7 @@ describe(AssetService.name, () => { }); it('should not process assets from external library without fromExternal flag', async () => { - when(assetMock.getById).calledWith(assetStub.external.id).mockResolvedValue(assetStub.external); + assetMock.getById.mockResolvedValue(assetStub.external); await sut.handleAssetDeletion({ id: assetStub.external.id }); @@ -738,16 +692,7 @@ describe(AssetService.name, () => { }); it('should process assets from external library with fromExternal flag', async () => { - when(assetMock.getById) - .calledWith(assetStub.external.id, { - faces: { - person: true, - }, - library: true, - stack: { assets: true }, - exifInfo: true, - }) - .mockResolvedValue(assetStub.external); + assetMock.getById.mockResolvedValue(assetStub.external); await sut.handleAssetDeletion({ id: assetStub.external.id, fromExternal: true }); @@ -769,39 +714,12 @@ describe(AssetService.name, () => { }); it('should delete a live photo', async () => { - when(assetMock.getById) - .calledWith(assetStub.livePhotoStillAsset.id, { - faces: { - person: true, - }, - library: true, - stack: { assets: true }, - exifInfo: true, - }) - .mockResolvedValue(assetStub.livePhotoStillAsset); - when(assetMock.getById) - .calledWith(assetStub.livePhotoMotionAsset.id, { - faces: { - person: true, - }, - library: true, - stack: { assets: true }, - exifInfo: true, - }) - .mockResolvedValue(assetStub.livePhotoMotionAsset); + assetMock.getById.mockResolvedValue(assetStub.livePhotoStillAsset); await sut.handleAssetDeletion({ id: assetStub.livePhotoStillAsset.id }); expect(jobMock.queue.mock.calls).toEqual([ [{ name: JobName.ASSET_DELETION, data: { id: assetStub.livePhotoMotionAsset.id } }], - [ - { - name: JobName.DELETE_FILES, - data: { - files: [undefined, undefined, undefined, undefined, 'fake_path/asset_1.mp4'], - }, - }, - ], [ { name: JobName.DELETE_FILES, @@ -814,18 +732,8 @@ describe(AssetService.name, () => { }); it('should update usage', async () => { - when(assetMock.getById) - .calledWith(assetStub.image.id, { - faces: { - person: true, - }, - library: true, - stack: { assets: true }, - exifInfo: true, - }) - .mockResolvedValue(assetStub.image); + assetMock.getById.mockResolvedValue(assetStub.image); await sut.handleAssetDeletion({ id: assetStub.image.id }); - expect(userMock.updateUsage).toHaveBeenCalledWith(assetStub.image.ownerId, -5000); }); }); @@ -874,18 +782,7 @@ describe(AssetService.name, () => { it('make old parent the child of new parent', async () => { accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set([assetStub.image.id])); accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['new'])); - - when(assetMock.getById) - .calledWith(assetStub.image.id, { - faces: { - person: true, - }, - library: true, - stack: { - assets: true, - }, - }) - .mockResolvedValue({ ...assetStub.image, stackId: 'stack-1' }); + assetMock.getById.mockResolvedValue({ ...assetStub.image, stackId: 'stack-1' }); await sut.updateStackParent(authStub.user1, { oldParentId: assetStub.image.id, diff --git a/server/src/services/asset.service.ts b/server/src/services/asset.service.ts index 606a5f62e2..a26b7f5a73 100644 --- a/server/src/services/asset.service.ts +++ b/server/src/services/asset.service.ts @@ -40,11 +40,11 @@ import { JobName, JobStatus, } from 'src/interfaces/job.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IPartnerRepository } from 'src/interfaces/partner.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface'; import { ISystemConfigRepository } from 'src/interfaces/system-config.interface'; import { IUserRepository } from 'src/interfaces/user.interface'; -import { ImmichLogger } from 'src/utils/logger'; import { mimeTypes } from 'src/utils/mime-types'; import { usePagination } from 'src/utils/pagination'; @@ -63,7 +63,6 @@ export interface UploadFile { } export class AssetService { - private logger = new ImmichLogger(AssetService.name); private access: AccessCore; private configCore: SystemConfigCore; @@ -77,9 +76,11 @@ export class AssetService { @Inject(IEventRepository) private eventRepository: IEventRepository, @Inject(IPartnerRepository) private partnerRepository: IPartnerRepository, @Inject(IAssetStackRepository) private assetStackRepository: IAssetStackRepository, + @Inject(ILoggerRepository) private logger: ILoggerRepository, ) { + this.logger.setContext(AssetService.name); this.access = AccessCore.create(accessRepository); - this.configCore = SystemConfigCore.create(configRepository); + this.configCore = SystemConfigCore.create(configRepository, this.logger); } canUploadFile({ auth, fieldName, file }: UploadRequest): true { @@ -391,12 +392,17 @@ export class AssetService { } await this.assetRepository.remove(asset); - await this.userRepository.updateUsage(asset.ownerId, -(asset.exifInfo?.fileSizeInByte || 0)); + if (asset.library.type === LibraryType.UPLOAD) { + await this.userRepository.updateUsage(asset.ownerId, -(asset.exifInfo?.fileSizeInByte || 0)); + } this.eventRepository.clientSend(ClientEvent.ASSET_DELETE, asset.ownerId, id); // TODO refactor this to use cascades if (asset.livePhotoVideoId) { - await this.jobRepository.queue({ name: JobName.ASSET_DELETION, data: { id: asset.livePhotoVideoId } }); + await this.jobRepository.queue({ + name: JobName.ASSET_DELETION, + data: { id: asset.livePhotoVideoId, fromExternal }, + }); } const files = [asset.thumbnailPath, asset.previewPath, asset.encodedVideoPath]; diff --git a/server/src/services/audit.service.spec.ts b/server/src/services/audit.service.spec.ts index 4af5c1f94d..8557677f92 100644 --- a/server/src/services/audit.service.spec.ts +++ b/server/src/services/audit.service.spec.ts @@ -3,6 +3,7 @@ import { IAssetRepository } from 'src/interfaces/asset.interface'; import { IAuditRepository } from 'src/interfaces/audit.interface'; import { ICryptoRepository } from 'src/interfaces/crypto.interface'; import { JobStatus } from 'src/interfaces/job.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IPersonRepository } from 'src/interfaces/person.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface'; import { IUserRepository } from 'src/interfaces/user.interface'; @@ -13,19 +14,22 @@ import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositorie import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock'; import { newAuditRepositoryMock } from 'test/repositories/audit.repository.mock'; import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock'; +import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock'; import { newPersonRepositoryMock } from 'test/repositories/person.repository.mock'; import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock'; import { newUserRepositoryMock } from 'test/repositories/user.repository.mock'; +import { Mocked } from 'vitest'; describe(AuditService.name, () => { let sut: AuditService; let accessMock: IAccessRepositoryMock; - let assetMock: jest.Mocked; - let auditMock: jest.Mocked; - let cryptoMock: jest.Mocked; - let personMock: jest.Mocked; - let storageMock: jest.Mocked; - let userMock: jest.Mocked; + let assetMock: Mocked; + let auditMock: Mocked; + let cryptoMock: Mocked; + let personMock: Mocked; + let storageMock: Mocked; + let userMock: Mocked; + let loggerMock: Mocked; beforeEach(() => { accessMock = newAccessRepositoryMock(); @@ -35,7 +39,8 @@ describe(AuditService.name, () => { personMock = newPersonRepositoryMock(); storageMock = newStorageRepositoryMock(); userMock = newUserRepositoryMock(); - sut = new AuditService(accessMock, assetMock, cryptoMock, personMock, auditMock, storageMock, userMock); + loggerMock = newLoggerRepositoryMock(); + sut = new AuditService(accessMock, assetMock, cryptoMock, personMock, auditMock, storageMock, userMock, loggerMock); }); it('should work', () => { @@ -61,13 +66,13 @@ describe(AuditService.name, () => { expect(auditMock.getAfter).toHaveBeenCalledWith(date, { action: DatabaseAction.DELETE, - ownerId: authStub.admin.user.id, + userIds: [authStub.admin.user.id], entityType: EntityType.ASSET, }); }); it('should get any new or updated assets and deleted ids', async () => { - auditMock.getAfter.mockResolvedValue([auditStub.delete]); + auditMock.getAfter.mockResolvedValue([auditStub.delete.entityId]); const date = new Date(); await expect(sut.getDeletes(authStub.admin, { after: date, entityType: EntityType.ASSET })).resolves.toEqual({ @@ -77,7 +82,7 @@ describe(AuditService.name, () => { expect(auditMock.getAfter).toHaveBeenCalledWith(date, { action: DatabaseAction.DELETE, - ownerId: authStub.admin.user.id, + userIds: [authStub.admin.user.id], entityType: EntityType.ASSET, }); }); diff --git a/server/src/services/audit.service.ts b/server/src/services/audit.service.ts index d40167429f..bfff09c0bc 100644 --- a/server/src/services/audit.service.ts +++ b/server/src/services/audit.service.ts @@ -20,16 +20,15 @@ import { IAssetRepository } from 'src/interfaces/asset.interface'; import { IAuditRepository } from 'src/interfaces/audit.interface'; import { ICryptoRepository } from 'src/interfaces/crypto.interface'; import { JOBS_ASSET_PAGINATION_SIZE, JobStatus } from 'src/interfaces/job.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IPersonRepository } from 'src/interfaces/person.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface'; import { IUserRepository } from 'src/interfaces/user.interface'; -import { ImmichLogger } from 'src/utils/logger'; import { usePagination } from 'src/utils/pagination'; @Injectable() export class AuditService { private access: AccessCore; - private logger = new ImmichLogger(AuditService.name); constructor( @Inject(IAccessRepository) accessRepository: IAccessRepository, @@ -39,8 +38,10 @@ export class AuditService { @Inject(IAuditRepository) private repository: IAuditRepository, @Inject(IStorageRepository) private storageRepository: IStorageRepository, @Inject(IUserRepository) private userRepository: IUserRepository, + @Inject(ILoggerRepository) private logger: ILoggerRepository, ) { this.access = AccessCore.create(accessRepository); + this.logger.setContext(AuditService.name); } async handleCleanup(): Promise { @@ -53,7 +54,7 @@ export class AuditService { await this.access.requirePermission(auth, Permission.TIMELINE_READ, userId); const audits = await this.repository.getAfter(dto.after, { - ownerId: userId, + userIds: [userId], entityType: dto.entityType, action: DatabaseAction.DELETE, }); @@ -62,7 +63,7 @@ export class AuditService { return { needsFullSync: duration > AUDIT_LOG_MAX_DURATION, - ids: audits.map(({ entityId }) => entityId), + ids: audits, }; } diff --git a/server/src/services/auth.service.spec.ts b/server/src/services/auth.service.spec.ts index 30773f3f16..d53f319661 100644 --- a/server/src/services/auth.service.spec.ts +++ b/server/src/services/auth.service.spec.ts @@ -8,6 +8,7 @@ import { UserEntity } from 'src/entities/user.entity'; import { IKeyRepository } from 'src/interfaces/api-key.interface'; import { ICryptoRepository } from 'src/interfaces/crypto.interface'; import { ILibraryRepository } from 'src/interfaces/library.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface'; import { ISystemConfigRepository } from 'src/interfaces/system-config.interface'; import { IUserTokenRepository } from 'src/interfaces/user-token.interface'; @@ -23,10 +24,12 @@ import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositorie import { newKeyRepositoryMock } from 'test/repositories/api-key.repository.mock'; import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock'; import { newLibraryRepositoryMock } from 'test/repositories/library.repository.mock'; +import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock'; import { newSharedLinkRepositoryMock } from 'test/repositories/shared-link.repository.mock'; import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; import { newUserTokenRepositoryMock } from 'test/repositories/user-token.repository.mock'; import { newUserRepositoryMock } from 'test/repositories/user.repository.mock'; +import { Mock, Mocked, vitest } from 'vitest'; // const token = Buffer.from('my-api-key', 'utf8').toString('base64'); @@ -56,33 +59,34 @@ const oauthUserWithDefaultQuota = { describe('AuthService', () => { let sut: AuthService; - let accessMock: jest.Mocked; - let cryptoMock: jest.Mocked; - let userMock: jest.Mocked; - let libraryMock: jest.Mocked; - let configMock: jest.Mocked; - let userTokenMock: jest.Mocked; - let shareMock: jest.Mocked; - let keyMock: jest.Mocked; + let accessMock: Mocked; + let cryptoMock: Mocked; + let userMock: Mocked; + let libraryMock: Mocked; + let loggerMock: Mocked; + let configMock: Mocked; + let userTokenMock: Mocked; + let shareMock: Mocked; + let keyMock: Mocked; - let callbackMock: jest.Mock; - let userinfoMock: jest.Mock; + let callbackMock: Mock; + let userinfoMock: Mock; beforeEach(() => { - callbackMock = jest.fn().mockReturnValue({ access_token: 'access-token' }); - userinfoMock = jest.fn().mockResolvedValue({ sub, email }); + callbackMock = vitest.fn().mockReturnValue({ access_token: 'access-token' }); + userinfoMock = vitest.fn().mockResolvedValue({ sub, email }); - jest.spyOn(generators, 'state').mockReturnValue('state'); - jest.spyOn(Issuer, 'discover').mockResolvedValue({ + vitest.spyOn(generators, 'state').mockReturnValue('state'); + vitest.spyOn(Issuer, 'discover').mockResolvedValue({ id_token_signing_alg_values_supported: ['RS256'], - Client: jest.fn().mockResolvedValue({ + Client: vitest.fn().mockResolvedValue({ issuer: { metadata: { end_session_endpoint: 'http://end-session-endpoint', }, }, - authorizationUrl: jest.fn().mockReturnValue('http://authorization-url'), - callbackParams: jest.fn().mockReturnValue({ state: 'state' }), + authorizationUrl: vitest.fn().mockReturnValue('http://authorization-url'), + callbackParams: vitest.fn().mockReturnValue({ state: 'state' }), callback: callbackMock, userinfo: userinfoMock, }), @@ -92,12 +96,23 @@ describe('AuthService', () => { cryptoMock = newCryptoRepositoryMock(); userMock = newUserRepositoryMock(); libraryMock = newLibraryRepositoryMock(); + loggerMock = newLoggerRepositoryMock(); configMock = newSystemConfigRepositoryMock(); userTokenMock = newUserTokenRepositoryMock(); shareMock = newSharedLinkRepositoryMock(); keyMock = newKeyRepositoryMock(); - sut = new AuthService(accessMock, cryptoMock, configMock, libraryMock, userMock, userTokenMock, shareMock, keyMock); + sut = new AuthService( + accessMock, + cryptoMock, + configMock, + libraryMock, + loggerMock, + userMock, + userTokenMock, + shareMock, + keyMock, + ); }); it('should be defined', () => { diff --git a/server/src/services/auth.service.ts b/server/src/services/auth.service.ts index 19476667c1..7bebca5989 100644 --- a/server/src/services/auth.service.ts +++ b/server/src/services/auth.service.ts @@ -43,12 +43,12 @@ import { IAccessRepository } from 'src/interfaces/access.interface'; import { IKeyRepository } from 'src/interfaces/api-key.interface'; import { ICryptoRepository } from 'src/interfaces/crypto.interface'; import { ILibraryRepository } from 'src/interfaces/library.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface'; import { ISystemConfigRepository } from 'src/interfaces/system-config.interface'; import { IUserTokenRepository } from 'src/interfaces/user-token.interface'; import { IUserRepository } from 'src/interfaces/user.interface'; import { HumanReadableSize } from 'src/utils/bytes'; -import { ImmichLogger } from 'src/utils/logger'; export interface LoginDetails { isSecure: boolean; @@ -76,7 +76,6 @@ interface ClaimOptions { export class AuthService { private access: AccessCore; private configCore: SystemConfigCore; - private logger = new ImmichLogger(AuthService.name); private userCore: UserCore; constructor( @@ -84,13 +83,15 @@ export class AuthService { @Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository, @Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository, @Inject(ILibraryRepository) libraryRepository: ILibraryRepository, + @Inject(ILoggerRepository) private logger: ILoggerRepository, @Inject(IUserRepository) private userRepository: IUserRepository, @Inject(IUserTokenRepository) private userTokenRepository: IUserTokenRepository, @Inject(ISharedLinkRepository) private sharedLinkRepository: ISharedLinkRepository, @Inject(IKeyRepository) private keyRepository: IKeyRepository, ) { + this.logger.setContext(AuthService.name); this.access = AccessCore.create(accessRepository); - this.configCore = SystemConfigCore.create(configRepository); + this.configCore = SystemConfigCore.create(configRepository, logger); this.userCore = UserCore.create(cryptoRepository, libraryRepository, userRepository); custom.setHttpOptionsDefaults({ timeout: 30_000 }); diff --git a/server/src/services/database.service.spec.ts b/server/src/services/database.service.spec.ts index 6fa5e7fd81..28ed7d7df2 100644 --- a/server/src/services/database.service.spec.ts +++ b/server/src/services/database.service.spec.ts @@ -1,17 +1,20 @@ import { DatabaseExtension, IDatabaseRepository, VectorIndex } from 'src/interfaces/database.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { DatabaseService } from 'src/services/database.service'; -import { ImmichLogger } from 'src/utils/logger'; import { Version, VersionType } from 'src/utils/version'; import { newDatabaseRepositoryMock } from 'test/repositories/database.repository.mock'; +import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock'; +import { Mocked } from 'vitest'; describe(DatabaseService.name, () => { let sut: DatabaseService; - let databaseMock: jest.Mocked; + let databaseMock: Mocked; + let loggerMock: Mocked; beforeEach(() => { databaseMock = newDatabaseRepositoryMock(); - - sut = new DatabaseService(databaseMock); + loggerMock = newLoggerRepositoryMock(); + sut = new DatabaseService(databaseMock, loggerMock); }); it('should work', () => { @@ -22,18 +25,11 @@ describe(DatabaseService.name, () => { [{ vectorExt: DatabaseExtension.VECTORS, extName: 'pgvecto.rs', minVersion: new Version(0, 1, 1) }], [{ vectorExt: DatabaseExtension.VECTOR, extName: 'pgvector', minVersion: new Version(0, 5, 0) }], ] as const)('init', ({ vectorExt, extName, minVersion }) => { - let fatalLog: jest.SpyInstance; - let errorLog: jest.SpyInstance; - let warnLog: jest.SpyInstance; - beforeEach(() => { - fatalLog = jest.spyOn(ImmichLogger.prototype, 'fatal'); - errorLog = jest.spyOn(ImmichLogger.prototype, 'error'); - warnLog = jest.spyOn(ImmichLogger.prototype, 'warn'); databaseMock.getPreferredVectorExtension.mockReturnValue(vectorExt); databaseMock.getExtensionVersion.mockResolvedValue(minVersion); - sut = new DatabaseService(databaseMock); + sut = new DatabaseService(databaseMock, loggerMock); sut.minVectorVersion = minVersion; sut.minVectorsVersion = minVersion; @@ -41,11 +37,6 @@ describe(DatabaseService.name, () => { sut.vectorsVersionPin = VersionType.MINOR; }); - afterEach(() => { - fatalLog.mockRestore(); - warnLog.mockRestore(); - }); - it(`should resolve successfully if minimum supported PostgreSQL and ${extName} version are installed`, async () => { databaseMock.getPostgresVersion.mockResolvedValueOnce(new Version(14, 0, 0)); @@ -56,7 +47,7 @@ describe(DatabaseService.name, () => { expect(databaseMock.createExtension).toHaveBeenCalledTimes(1); expect(databaseMock.getExtensionVersion).toHaveBeenCalled(); expect(databaseMock.runMigrations).toHaveBeenCalledTimes(1); - expect(fatalLog).not.toHaveBeenCalled(); + expect(loggerMock.fatal).not.toHaveBeenCalled(); }); it('should throw an error if PostgreSQL version is below minimum supported version', async () => { @@ -73,7 +64,7 @@ describe(DatabaseService.name, () => { expect(databaseMock.createExtension).toHaveBeenCalledWith(vectorExt); expect(databaseMock.createExtension).toHaveBeenCalledTimes(1); expect(databaseMock.runMigrations).toHaveBeenCalledTimes(1); - expect(fatalLog).not.toHaveBeenCalled(); + expect(loggerMock.fatal).not.toHaveBeenCalled(); }); it(`should throw an error if ${extName} version is not installed even after createVectorExtension`, async () => { @@ -133,7 +124,7 @@ describe(DatabaseService.name, () => { await expect(sut.init()).rejects.toThrow('Failed to create extension'); - expect(fatalLog).toHaveBeenCalledTimes(1); + expect(loggerMock.fatal).toHaveBeenCalledTimes(1); expect(databaseMock.createExtension).toHaveBeenCalledTimes(1); expect(databaseMock.runMigrations).not.toHaveBeenCalled(); }); @@ -147,7 +138,7 @@ describe(DatabaseService.name, () => { expect(databaseMock.updateVectorExtension).toHaveBeenCalledWith(vectorExt, version); expect(databaseMock.updateVectorExtension).toHaveBeenCalledTimes(1); expect(databaseMock.runMigrations).toHaveBeenCalledTimes(1); - expect(fatalLog).not.toHaveBeenCalled(); + expect(loggerMock.fatal).not.toHaveBeenCalled(); }); it(`should not update ${extName} if a newer version is higher than the maximum`, async () => { @@ -158,7 +149,7 @@ describe(DatabaseService.name, () => { expect(databaseMock.updateVectorExtension).not.toHaveBeenCalled(); expect(databaseMock.runMigrations).toHaveBeenCalledTimes(1); - expect(fatalLog).not.toHaveBeenCalled(); + expect(loggerMock.fatal).not.toHaveBeenCalled(); }); it(`should warn if attempted to update ${extName} and failed`, async () => { @@ -168,10 +159,10 @@ describe(DatabaseService.name, () => { await expect(sut.init()).resolves.toBeUndefined(); - expect(warnLog).toHaveBeenCalledTimes(1); - expect(warnLog.mock.calls[0][0]).toContain(extName); - expect(errorLog).toHaveBeenCalledTimes(1); - expect(fatalLog).not.toHaveBeenCalled(); + expect(loggerMock.warn).toHaveBeenCalledTimes(1); + expect(loggerMock.warn.mock.calls[0][0]).toContain(extName); + expect(loggerMock.error).toHaveBeenCalledTimes(1); + expect(loggerMock.fatal).not.toHaveBeenCalled(); expect(databaseMock.updateVectorExtension).toHaveBeenCalledWith(vectorExt, version); expect(databaseMock.runMigrations).toHaveBeenCalledTimes(1); }); @@ -183,11 +174,11 @@ describe(DatabaseService.name, () => { await expect(sut.init()).resolves.toBeUndefined(); - expect(warnLog).toHaveBeenCalledTimes(1); - expect(warnLog.mock.calls[0][0]).toContain(extName); + expect(loggerMock.warn).toHaveBeenCalledTimes(1); + expect(loggerMock.warn.mock.calls[0][0]).toContain(extName); expect(databaseMock.updateVectorExtension).toHaveBeenCalledWith(vectorExt, version); expect(databaseMock.runMigrations).toHaveBeenCalledTimes(1); - expect(fatalLog).not.toHaveBeenCalled(); + expect(loggerMock.fatal).not.toHaveBeenCalled(); }); it.each([{ index: VectorIndex.CLIP }, { index: VectorIndex.FACE }])( @@ -202,7 +193,7 @@ describe(DatabaseService.name, () => { expect(databaseMock.reindex).toHaveBeenCalledWith(index); expect(databaseMock.reindex).toHaveBeenCalledTimes(1); expect(databaseMock.runMigrations).toHaveBeenCalledTimes(1); - expect(fatalLog).not.toHaveBeenCalled(); + expect(loggerMock.fatal).not.toHaveBeenCalled(); }, ); @@ -216,7 +207,7 @@ describe(DatabaseService.name, () => { expect(databaseMock.shouldReindex).toHaveBeenCalledTimes(2); expect(databaseMock.reindex).not.toHaveBeenCalled(); expect(databaseMock.runMigrations).toHaveBeenCalledTimes(1); - expect(fatalLog).not.toHaveBeenCalled(); + expect(loggerMock.fatal).not.toHaveBeenCalled(); }, ); }); diff --git a/server/src/services/database.service.ts b/server/src/services/database.service.ts index a333c0053a..8da48eb152 100644 --- a/server/src/services/database.service.ts +++ b/server/src/services/database.service.ts @@ -7,12 +7,11 @@ import { VectorIndex, extName, } from 'src/interfaces/database.interface'; -import { ImmichLogger } from 'src/utils/logger'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { Version, VersionType } from 'src/utils/version'; @Injectable() export class DatabaseService { - private logger = new ImmichLogger(DatabaseService.name); private vectorExt: VectorExtension; minPostgresVersion = 14; minVectorsVersion = new Version(0, 2, 0); @@ -20,7 +19,11 @@ export class DatabaseService { minVectorVersion = new Version(0, 5, 0); vectorVersionPin = VersionType.MAJOR; - constructor(@Inject(IDatabaseRepository) private databaseRepository: IDatabaseRepository) { + constructor( + @Inject(IDatabaseRepository) private databaseRepository: IDatabaseRepository, + @Inject(ILoggerRepository) private logger: ILoggerRepository, + ) { + this.logger.setContext(DatabaseService.name); this.vectorExt = this.databaseRepository.getPreferredVectorExtension(); } diff --git a/server/src/services/download.service.spec.ts b/server/src/services/download.service.spec.ts index babc21fa8a..331ddcaaa7 100644 --- a/server/src/services/download.service.spec.ts +++ b/server/src/services/download.service.spec.ts @@ -1,6 +1,6 @@ import { BadRequestException } from '@nestjs/common'; -import { when } from 'jest-when'; import { DownloadResponseDto } from 'src/dtos/download.dto'; +import { AssetEntity } from 'src/entities/asset.entity'; import { IAssetRepository } from 'src/interfaces/asset.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface'; import { DownloadService } from 'src/services/download.service'; @@ -11,6 +11,7 @@ import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositorie import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock'; import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock'; import { Readable } from 'typeorm/platform/PlatformTools.js'; +import { Mocked, vitest } from 'vitest'; const downloadResponse: DownloadResponseDto = { totalSize: 105_000, @@ -25,8 +26,8 @@ const downloadResponse: DownloadResponseDto = { describe(DownloadService.name, () => { let sut: DownloadService; let accessMock: IAccessRepositoryMock; - let assetMock: jest.Mocked; - let storageMock: jest.Mocked; + let assetMock: Mocked; + let storageMock: Mocked; it('should work', () => { expect(sut).toBeDefined(); @@ -82,8 +83,8 @@ describe(DownloadService.name, () => { it('should download an archive', async () => { const archiveMock = { - addFile: jest.fn(), - finalize: jest.fn(), + addFile: vitest.fn(), + finalize: vitest.fn(), stream: new Readable(), }; @@ -105,8 +106,8 @@ describe(DownloadService.name, () => { it('should handle duplicate file names', async () => { const archiveMock = { - addFile: jest.fn(), - finalize: jest.fn(), + addFile: vitest.fn(), + finalize: vitest.fn(), stream: new Readable(), }; @@ -128,8 +129,8 @@ describe(DownloadService.name, () => { it('should be deterministic', async () => { const archiveMock = { - addFile: jest.fn(), - finalize: jest.fn(), + addFile: vitest.fn(), + finalize: vitest.fn(), stream: new Readable(), }; @@ -223,14 +224,15 @@ describe(DownloadService.name, () => { it('should include the video portion of a live photo', async () => { const assetIds = [assetStub.livePhotoStillAsset.id]; + const assets = [assetStub.livePhotoStillAsset, assetStub.livePhotoMotionAsset]; accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(assetIds)); - when(assetMock.getByIds) - .calledWith([assetStub.livePhotoStillAsset.id], { exifInfo: true }) - .mockResolvedValue([assetStub.livePhotoStillAsset]); - when(assetMock.getByIds) - .calledWith([assetStub.livePhotoMotionAsset.id], { exifInfo: true }) - .mockResolvedValue([assetStub.livePhotoMotionAsset]); + assetMock.getByIds.mockImplementation( + (ids) => + Promise.resolve( + ids.map((id) => assets.find((asset) => asset.id === id)).filter((asset) => !!asset), + ) as Promise, + ); await expect(sut.getDownloadInfo(authStub.admin, { assetIds })).resolves.toEqual({ totalSize: 125_000, diff --git a/server/src/services/index.ts b/server/src/services/index.ts index 3c903c927d..6c40f8420a 100644 --- a/server/src/services/index.ts +++ b/server/src/services/index.ts @@ -22,6 +22,7 @@ import { SharedLinkService } from 'src/services/shared-link.service'; import { SmartInfoService } from 'src/services/smart-info.service'; import { StorageTemplateService } from 'src/services/storage-template.service'; import { StorageService } from 'src/services/storage.service'; +import { SyncService } from 'src/services/sync.service'; import { SystemConfigService } from 'src/services/system-config.service'; import { TagService } from 'src/services/tag.service'; import { TimelineService } from 'src/services/timeline.service'; @@ -53,6 +54,7 @@ export const services = [ SmartInfoService, StorageService, StorageTemplateService, + SyncService, SystemConfigService, TagService, TimelineService, diff --git a/server/src/services/job.service.spec.ts b/server/src/services/job.service.spec.ts index c141586dbe..ce7d2c00e4 100644 --- a/server/src/services/job.service.spec.ts +++ b/server/src/services/job.service.spec.ts @@ -12,6 +12,7 @@ import { JobStatus, QueueName, } from 'src/interfaces/job.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IMetricRepository } from 'src/interfaces/metric.interface'; import { IPersonRepository } from 'src/interfaces/person.interface'; import { ISystemConfigRepository } from 'src/interfaces/system-config.interface'; @@ -20,12 +21,14 @@ import { assetStub } from 'test/fixtures/asset.stub'; import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock'; import { newEventRepositoryMock } from 'test/repositories/event.repository.mock'; import { newJobRepositoryMock } from 'test/repositories/job.repository.mock'; +import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock'; import { newMetricRepositoryMock } from 'test/repositories/metric.repository.mock'; import { newPersonRepositoryMock } from 'test/repositories/person.repository.mock'; import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; +import { Mocked, vitest } from 'vitest'; const makeMockHandlers = (status: JobStatus) => { - const mock = jest.fn().mockResolvedValue(status); + const mock = vitest.fn().mockResolvedValue(status); return Object.fromEntries(Object.values(JobName).map((jobName) => [jobName, mock])) as unknown as Record< JobName, JobHandler @@ -34,12 +37,13 @@ const makeMockHandlers = (status: JobStatus) => { describe(JobService.name, () => { let sut: JobService; - let assetMock: jest.Mocked; - let configMock: jest.Mocked; - let eventMock: jest.Mocked; - let jobMock: jest.Mocked; - let personMock: jest.Mocked; - let metricMock: jest.Mocked; + let assetMock: Mocked; + let configMock: Mocked; + let eventMock: Mocked; + let jobMock: Mocked; + let personMock: Mocked; + let metricMock: Mocked; + let loggerMock: Mocked; beforeEach(() => { assetMock = newAssetRepositoryMock(); @@ -48,7 +52,8 @@ describe(JobService.name, () => { jobMock = newJobRepositoryMock(); personMock = newPersonRepositoryMock(); metricMock = newMetricRepositoryMock(); - sut = new JobService(assetMock, eventMock, jobMock, configMock, personMock, metricMock); + loggerMock = newLoggerRepositoryMock(); + sut = new JobService(assetMock, eventMock, jobMock, configMock, personMock, metricMock, loggerMock); }); it('should work', () => { @@ -234,7 +239,7 @@ describe(JobService.name, () => { it('should subscribe to config changes', async () => { await sut.init(makeMockHandlers(JobStatus.FAILED)); - SystemConfigCore.create(newSystemConfigRepositoryMock(false)).config$.next({ + SystemConfigCore.create(newSystemConfigRepositoryMock(false), newLoggerRepositoryMock()).config$.next({ job: { [QueueName.BACKGROUND_TASK]: { concurrency: 10 }, [QueueName.SMART_SEARCH]: { concurrency: 10 }, diff --git a/server/src/services/job.service.ts b/server/src/services/job.service.ts index 3f9cd8a221..13d367994b 100644 --- a/server/src/services/job.service.ts +++ b/server/src/services/job.service.ts @@ -17,14 +17,13 @@ import { QueueCleanType, QueueName, } from 'src/interfaces/job.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IMetricRepository } from 'src/interfaces/metric.interface'; import { IPersonRepository } from 'src/interfaces/person.interface'; import { ISystemConfigRepository } from 'src/interfaces/system-config.interface'; -import { ImmichLogger } from 'src/utils/logger'; @Injectable() export class JobService { - private logger = new ImmichLogger(JobService.name); private configCore: SystemConfigCore; constructor( @@ -34,8 +33,10 @@ export class JobService { @Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository, @Inject(IPersonRepository) private personRepository: IPersonRepository, @Inject(IMetricRepository) private metricRepository: IMetricRepository, + @Inject(ILoggerRepository) private logger: ILoggerRepository, ) { - this.configCore = SystemConfigCore.create(configRepository); + this.logger.setContext(JobService.name); + this.configCore = SystemConfigCore.create(configRepository, logger); } async handleCommand(queueName: QueueName, dto: JobCommandDto): Promise { diff --git a/server/src/services/library.service.spec.ts b/server/src/services/library.service.spec.ts index 95e3655cb3..2122b03208 100644 --- a/server/src/services/library.service.spec.ts +++ b/server/src/services/library.service.spec.ts @@ -1,6 +1,4 @@ import { BadRequestException } from '@nestjs/common'; -import { when } from 'jest-when'; -import { R_OK } from 'node:constants'; import { Stats } from 'node:fs'; import { SystemConfigCore } from 'src/cores/system-config.core'; import { mapLibrary } from 'src/dtos/library.dto'; @@ -13,7 +11,8 @@ import { ICryptoRepository } from 'src/interfaces/crypto.interface'; import { IDatabaseRepository } from 'src/interfaces/database.interface'; import { IJobRepository, ILibraryFileJob, ILibraryRefreshJob, JobName, JobStatus } from 'src/interfaces/job.interface'; import { ILibraryRepository } from 'src/interfaces/library.interface'; -import { IStorageRepository, StorageEventType } from 'src/interfaces/storage.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; +import { IStorageRepository } from 'src/interfaces/storage.interface'; import { ISystemConfigRepository } from 'src/interfaces/system-config.interface'; import { LibraryService } from 'src/services/library.service'; import { assetStub } from 'test/fixtures/asset.stub'; @@ -26,19 +25,22 @@ import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.moc import { newDatabaseRepositoryMock } from 'test/repositories/database.repository.mock'; import { newJobRepositoryMock } from 'test/repositories/job.repository.mock'; import { newLibraryRepositoryMock } from 'test/repositories/library.repository.mock'; +import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock'; import { makeMockWatcher, newStorageRepositoryMock } from 'test/repositories/storage.repository.mock'; import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; +import { Mocked, vitest } from 'vitest'; describe(LibraryService.name, () => { let sut: LibraryService; - let assetMock: jest.Mocked; - let configMock: jest.Mocked; - let cryptoMock: jest.Mocked; - let jobMock: jest.Mocked; - let libraryMock: jest.Mocked; - let storageMock: jest.Mocked; - let databaseMock: jest.Mocked; + let assetMock: Mocked; + let configMock: Mocked; + let cryptoMock: Mocked; + let jobMock: Mocked; + let libraryMock: Mocked; + let storageMock: Mocked; + let databaseMock: Mocked; + let loggerMock: Mocked; beforeEach(() => { configMock = newSystemConfigRepositoryMock(); @@ -48,8 +50,18 @@ describe(LibraryService.name, () => { cryptoMock = newCryptoRepositoryMock(); storageMock = newStorageRepositoryMock(); databaseMock = newDatabaseRepositoryMock(); + loggerMock = newLoggerRepositoryMock(); - sut = new LibraryService(assetMock, configMock, cryptoMock, jobMock, libraryMock, storageMock, databaseMock); + sut = new LibraryService( + assetMock, + configMock, + cryptoMock, + jobMock, + libraryMock, + storageMock, + databaseMock, + loggerMock, + ); databaseMock.tryLock.mockResolvedValue(true); }); @@ -69,7 +81,7 @@ describe(LibraryService.name, () => { expect(configMock.load).toHaveBeenCalled(); expect(jobMock.addCronJob).toHaveBeenCalled(); - SystemConfigCore.create(newSystemConfigRepositoryMock(false)).config$.next({ + SystemConfigCore.create(newSystemConfigRepositoryMock(false), newLoggerRepositoryMock()).config$.next({ library: { scan: { enabled: true, @@ -89,15 +101,13 @@ describe(LibraryService.name, () => { ]); configMock.load.mockResolvedValue(systemConfigStub.libraryWatchEnabled); - libraryMock.get.mockResolvedValue(libraryStub.externalLibrary1); - - when(libraryMock.get) - .calledWith(libraryStub.externalLibraryWithImportPaths1.id) - .mockResolvedValue(libraryStub.externalLibraryWithImportPaths1); - - when(libraryMock.get) - .calledWith(libraryStub.externalLibraryWithImportPaths2.id) - .mockResolvedValue(libraryStub.externalLibraryWithImportPaths2); + libraryMock.get.mockImplementation((id) => + Promise.resolve( + [libraryStub.externalLibraryWithImportPaths1, libraryStub.externalLibraryWithImportPaths2].find( + (library) => library.id === id, + ) || null, + ), + ); await sut.init(); @@ -160,7 +170,7 @@ describe(LibraryService.name, () => { storageMock.walk.mockImplementation(async function* generator() { yield '/data/user1/photo.jpg'; }); - assetMock.getLibraryAssetPaths.mockResolvedValue({ items: [], hasNextPage: false }); + assetMock.getExternalLibraryAssetPaths.mockResolvedValue({ items: [], hasNextPage: false }); await sut.handleQueueAssetRefresh(mockLibraryJob); @@ -189,7 +199,7 @@ describe(LibraryService.name, () => { storageMock.walk.mockImplementation(async function* generator() { yield '/data/user1/photo.jpg'; }); - assetMock.getLibraryAssetPaths.mockResolvedValue({ items: [], hasNextPage: false }); + assetMock.getExternalLibraryAssetPaths.mockResolvedValue({ items: [], hasNextPage: false }); await sut.handleQueueAssetRefresh(mockLibraryJob); @@ -238,7 +248,7 @@ describe(LibraryService.name, () => { }; libraryMock.get.mockResolvedValue(libraryStub.externalLibraryWithImportPaths1); - assetMock.getLibraryAssetPaths.mockResolvedValue({ items: [], hasNextPage: false }); + assetMock.getExternalLibraryAssetPaths.mockResolvedValue({ items: [], hasNextPage: false }); await sut.handleQueueAssetRefresh(mockLibraryJob); @@ -256,8 +266,8 @@ describe(LibraryService.name, () => { }; libraryMock.get.mockResolvedValue(libraryStub.externalLibrary1); - assetMock.getLibraryAssetPaths.mockResolvedValue({ - items: [assetStub.image], + assetMock.getExternalLibraryAssetPaths.mockResolvedValue({ + items: [assetStub.external], hasNextPage: false, }); @@ -278,16 +288,16 @@ describe(LibraryService.name, () => { libraryMock.get.mockResolvedValue(libraryStub.externalLibrary1); // eslint-disable-next-line @typescript-eslint/require-await storageMock.walk.mockImplementation(async function* generator() { - yield assetStub.offline.originalPath; + yield assetStub.externalOffline.originalPath; }); - assetMock.getLibraryAssetPaths.mockResolvedValue({ - items: [assetStub.offline], + assetMock.getExternalLibraryAssetPaths.mockResolvedValue({ + items: [assetStub.externalOffline], hasNextPage: false, }); await sut.handleQueueAssetRefresh(mockLibraryJob); - expect(assetMock.updateAll).toHaveBeenCalledWith([assetStub.offline.id], { isOffline: false }); + expect(assetMock.updateAll).toHaveBeenCalledWith([assetStub.externalOffline.id], { isOffline: false }); expect(assetMock.updateAll).not.toHaveBeenCalledWith(expect.anything(), { isOffline: true }); expect(jobMock.queueAll).not.toHaveBeenCalled(); }); @@ -751,7 +761,7 @@ describe(LibraryService.name, () => { configMock.load.mockResolvedValue(systemConfigStub.libraryWatchEnabled); - const mockClose = jest.fn(); + const mockClose = vitest.fn(); storageMock.watch.mockImplementation(makeMockWatcher({ close: mockClose })); await sut.init(); @@ -933,12 +943,6 @@ describe(LibraryService.name, () => { type: LibraryType.EXTERNAL, importPaths: libraryStub.externalLibraryWithImportPaths1.importPaths, }); - - expect(storageMock.watch).toHaveBeenCalledWith( - libraryStub.externalLibraryWithImportPaths1.importPaths, - expect.anything(), - expect.anything(), - ); }); it('should create with exclusion patterns', async () => { @@ -1087,45 +1091,6 @@ describe(LibraryService.name, () => { await expect(sut.update('library-id', {})).resolves.toEqual(mapLibrary(libraryStub.uploadLibrary1)); expect(libraryMock.update).toHaveBeenCalledWith(expect.objectContaining({ id: 'library-id' })); }); - - it('should re-watch library when updating import paths', async () => { - libraryMock.update.mockResolvedValue(libraryStub.externalLibraryWithImportPaths1); - libraryMock.get.mockResolvedValue(libraryStub.externalLibraryWithImportPaths1); - - storageMock.stat.mockResolvedValue({ - isDirectory: () => true, - } as Stats); - - storageMock.checkFileExists.mockResolvedValue(true); - - await expect(sut.update('library-id', { importPaths: ['/data/user1/foo'] })).resolves.toEqual( - mapLibrary(libraryStub.externalLibraryWithImportPaths1), - ); - - expect(libraryMock.update).toHaveBeenCalledWith(expect.objectContaining({ id: 'library-id' })); - expect(storageMock.watch).toHaveBeenCalledWith( - libraryStub.externalLibraryWithImportPaths1.importPaths, - expect.anything(), - expect.anything(), - ); - }); - - it('should re-watch library when updating exclusion patterns', async () => { - libraryMock.update.mockResolvedValue(libraryStub.externalLibraryWithImportPaths1); - configMock.load.mockResolvedValue(systemConfigStub.libraryWatchEnabled); - libraryMock.get.mockResolvedValue(libraryStub.externalLibraryWithImportPaths1); - - await expect(sut.update('library-id', { exclusionPatterns: ['bar'] })).resolves.toEqual( - mapLibrary(libraryStub.externalLibraryWithImportPaths1), - ); - - expect(libraryMock.update).toHaveBeenCalledWith(expect.objectContaining({ id: 'library-id' })); - expect(storageMock.watch).toHaveBeenCalledWith( - expect.arrayContaining([expect.any(String)]), - expect.anything(), - expect.anything(), - ); - }); }); describe('watchAll', () => { @@ -1168,7 +1133,7 @@ describe(LibraryService.name, () => { it('should watch and unwatch library', async () => { libraryMock.getAll.mockResolvedValue([libraryStub.externalLibraryWithImportPaths1]); libraryMock.get.mockResolvedValue(libraryStub.externalLibraryWithImportPaths1); - const mockClose = jest.fn(); + const mockClose = vitest.fn(); storageMock.watch.mockImplementation(makeMockWatcher({ close: mockClose })); await sut.watchAll(); @@ -1198,9 +1163,7 @@ describe(LibraryService.name, () => { it('should handle a new file event', async () => { libraryMock.get.mockResolvedValue(libraryStub.externalLibraryWithImportPaths1); libraryMock.getAll.mockResolvedValue([libraryStub.externalLibraryWithImportPaths1]); - storageMock.watch.mockImplementation( - makeMockWatcher({ items: [{ event: StorageEventType.ADD, value: '/foo/photo.jpg' }] }), - ); + storageMock.watch.mockImplementation(makeMockWatcher({ items: [{ event: 'add', value: '/foo/photo.jpg' }] })); await sut.watchAll(); @@ -1221,7 +1184,7 @@ describe(LibraryService.name, () => { libraryMock.get.mockResolvedValue(libraryStub.externalLibraryWithImportPaths1); libraryMock.getAll.mockResolvedValue([libraryStub.externalLibraryWithImportPaths1]); storageMock.watch.mockImplementation( - makeMockWatcher({ items: [{ event: StorageEventType.CHANGE, value: '/foo/photo.jpg' }] }), + makeMockWatcher({ items: [{ event: 'change', value: '/foo/photo.jpg' }] }), ); await sut.watchAll(); @@ -1244,7 +1207,7 @@ describe(LibraryService.name, () => { libraryMock.getAll.mockResolvedValue([libraryStub.externalLibraryWithImportPaths1]); assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.external); storageMock.watch.mockImplementation( - makeMockWatcher({ items: [{ event: StorageEventType.UNLINK, value: '/foo/photo.jpg' }] }), + makeMockWatcher({ items: [{ event: 'unlink', value: '/foo/photo.jpg' }] }), ); await sut.watchAll(); @@ -1258,19 +1221,17 @@ describe(LibraryService.name, () => { libraryMock.getAll.mockResolvedValue([libraryStub.externalLibraryWithImportPaths1]); storageMock.watch.mockImplementation( makeMockWatcher({ - items: [{ event: StorageEventType.ERROR, value: 'Error!' }], + items: [{ event: 'error', value: 'Error!' }], }), ); - await expect(sut.watchAll()).rejects.toThrow('Error!'); + await expect(sut.watchAll()).resolves.toBeUndefined(); }); it('should ignore unknown extensions', async () => { libraryMock.get.mockResolvedValue(libraryStub.externalLibraryWithImportPaths1); libraryMock.getAll.mockResolvedValue([libraryStub.externalLibraryWithImportPaths1]); - storageMock.watch.mockImplementation( - makeMockWatcher({ items: [{ event: StorageEventType.ADD, value: '/foo/photo.jpg' }] }), - ); + storageMock.watch.mockImplementation(makeMockWatcher({ items: [{ event: 'add', value: '/foo/photo.jpg' }] })); await sut.watchAll(); @@ -1280,9 +1241,7 @@ describe(LibraryService.name, () => { it('should ignore excluded paths', async () => { libraryMock.get.mockResolvedValue(libraryStub.patternPath); libraryMock.getAll.mockResolvedValue([libraryStub.patternPath]); - storageMock.watch.mockImplementation( - makeMockWatcher({ items: [{ event: StorageEventType.ADD, value: '/dir1/photo.txt' }] }), - ); + storageMock.watch.mockImplementation(makeMockWatcher({ items: [{ event: 'add', value: '/dir1/photo.txt' }] })); await sut.watchAll(); @@ -1292,9 +1251,7 @@ describe(LibraryService.name, () => { it('should ignore excluded paths without case sensitivity', async () => { libraryMock.get.mockResolvedValue(libraryStub.patternPath); libraryMock.getAll.mockResolvedValue([libraryStub.patternPath]); - storageMock.watch.mockImplementation( - makeMockWatcher({ items: [{ event: StorageEventType.ADD, value: '/DIR1/photo.txt' }] }), - ); + storageMock.watch.mockImplementation(makeMockWatcher({ items: [{ event: 'add', value: '/DIR1/photo.txt' }] })); await sut.watchAll(); @@ -1313,15 +1270,15 @@ describe(LibraryService.name, () => { configMock.load.mockResolvedValue(systemConfigStub.libraryWatchEnabled); libraryMock.get.mockResolvedValue(libraryStub.externalLibrary1); - when(libraryMock.get) - .calledWith(libraryStub.externalLibraryWithImportPaths1.id) - .mockResolvedValue(libraryStub.externalLibraryWithImportPaths1); + libraryMock.get.mockImplementation((id) => + Promise.resolve( + [libraryStub.externalLibraryWithImportPaths1, libraryStub.externalLibraryWithImportPaths2].find( + (library) => library.id === id, + ) || null, + ), + ); - when(libraryMock.get) - .calledWith(libraryStub.externalLibraryWithImportPaths2.id) - .mockResolvedValue(libraryStub.externalLibraryWithImportPaths2); - - const mockClose = jest.fn(); + const mockClose = vitest.fn(); storageMock.watch.mockImplementation(makeMockWatcher({ close: mockClose })); await sut.init(); @@ -1598,7 +1555,7 @@ describe(LibraryService.name, () => { it('should detect when import path is in immich media folder', async () => { storageMock.stat.mockResolvedValue({ isDirectory: () => true } as Stats); const validImport = libraryStub.hasImmichPaths.importPaths[1]; - when(storageMock.checkFileExists).calledWith(validImport, R_OK).mockResolvedValue(true); + storageMock.checkFileExists.mockImplementation((importPath) => Promise.resolve(importPath === validImport)); await expect( sut.validate('library-id', { importPaths: libraryStub.hasImmichPaths.importPaths }), diff --git a/server/src/services/library.service.ts b/server/src/services/library.service.ts index 63066866fb..00909e9837 100644 --- a/server/src/services/library.service.ts +++ b/server/src/services/library.service.ts @@ -1,7 +1,6 @@ import { BadRequestException, Inject, Injectable } from '@nestjs/common'; import { Trie } from 'mnemonist'; import { R_OK } from 'node:constants'; -import { EventEmitter } from 'node:events'; import { Stats } from 'node:fs'; import path, { basename, parse } from 'node:path'; import picomatch from 'picomatch'; @@ -37,9 +36,9 @@ import { JobStatus, } from 'src/interfaces/job.interface'; import { ILibraryRepository } from 'src/interfaces/library.interface'; -import { IStorageRepository, StorageEventType } from 'src/interfaces/storage.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; +import { IStorageRepository } from 'src/interfaces/storage.interface'; import { ISystemConfigRepository } from 'src/interfaces/system-config.interface'; -import { ImmichLogger } from 'src/utils/logger'; import { mimeTypes } from 'src/utils/mime-types'; import { handlePromiseError } from 'src/utils/misc'; import { usePagination } from 'src/utils/pagination'; @@ -48,8 +47,7 @@ import { validateCronExpression } from 'src/validation'; const LIBRARY_SCAN_BATCH_SIZE = 5000; @Injectable() -export class LibraryService extends EventEmitter { - readonly logger = new ImmichLogger(LibraryService.name); +export class LibraryService { private configCore: SystemConfigCore; private watchLibraries = false; private watchLock = false; @@ -63,9 +61,10 @@ export class LibraryService extends EventEmitter { @Inject(ILibraryRepository) private repository: ILibraryRepository, @Inject(IStorageRepository) private storageRepository: IStorageRepository, @Inject(IDatabaseRepository) private databaseRepository: IDatabaseRepository, + @Inject(ILoggerRepository) private logger: ILoggerRepository, ) { - super(); - this.configCore = SystemConfigCore.create(configRepository); + this.logger.setContext(LibraryService.name); + this.configCore = SystemConfigCore.create(configRepository, this.logger); } async init() { @@ -152,7 +151,6 @@ export class LibraryService extends EventEmitter { if (matcher(path)) { await this.scanAssets(library.id, [path], library.ownerId, false); } - this.emit(StorageEventType.ADD, path); }; return handlePromiseError(handler(), this.logger); }, @@ -163,7 +161,6 @@ export class LibraryService extends EventEmitter { // Note: if the changed file was not previously imported, it will be imported now. await this.scanAssets(library.id, [path], library.ownerId, false); } - this.emit(StorageEventType.CHANGE, path); }; return handlePromiseError(handler(), this.logger); }, @@ -174,13 +171,11 @@ export class LibraryService extends EventEmitter { if (asset && matcher(path)) { await this.assetRepository.update({ id: asset.id, isOffline: true }); } - this.emit(StorageEventType.UNLINK, path); }; return handlePromiseError(handler(), this.logger); }, onError: (error) => { this.logger.error(`Library watcher for library ${library.id} encountered error: ${error}`); - this.emit(StorageEventType.ERROR, error); }, }, ); @@ -281,10 +276,6 @@ export class LibraryService extends EventEmitter { this.logger.log(`Creating ${dto.type} library for ${dto.ownerId}}`); - if (dto.type === LibraryType.EXTERNAL) { - await this.watch(library.id); - } - return mapLibrary(library); } @@ -368,11 +359,6 @@ export class LibraryService extends EventEmitter { } } - if (dto.importPaths || dto.exclusionPatterns) { - // Re-watch library to use new paths and/or exclusion patterns - await this.watch(id); - } - return mapLibrary(library); } @@ -616,7 +602,7 @@ export class LibraryService extends EventEmitter { const assetIdsToMarkOffline = []; const assetIdsToMarkOnline = []; const pagination = usePagination(LIBRARY_SCAN_BATCH_SIZE, (pagination) => - this.assetRepository.getLibraryAssetPaths(pagination, library.id), + this.assetRepository.getExternalLibraryAssetPaths(pagination, library.id), ); this.logger.verbose(`Crawled asset paths paginated`); diff --git a/server/src/services/media.service.spec.ts b/server/src/services/media.service.spec.ts index e281047a50..1b1adcd573 100644 --- a/server/src/services/media.service.spec.ts +++ b/server/src/services/media.service.spec.ts @@ -14,6 +14,7 @@ import { import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface'; import { ICryptoRepository } from 'src/interfaces/crypto.interface'; import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IMediaRepository } from 'src/interfaces/media.interface'; import { IMoveRepository } from 'src/interfaces/move.interface'; import { IPersonRepository } from 'src/interfaces/person.interface'; @@ -27,22 +28,25 @@ import { personStub } from 'test/fixtures/person.stub'; import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock'; import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock'; import { newJobRepositoryMock } from 'test/repositories/job.repository.mock'; +import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock'; import { newMediaRepositoryMock } from 'test/repositories/media.repository.mock'; import { newMoveRepositoryMock } from 'test/repositories/move.repository.mock'; import { newPersonRepositoryMock } from 'test/repositories/person.repository.mock'; import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock'; import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; +import { Mocked } from 'vitest'; describe(MediaService.name, () => { let sut: MediaService; - let assetMock: jest.Mocked; - let configMock: jest.Mocked; - let jobMock: jest.Mocked; - let mediaMock: jest.Mocked; - let moveMock: jest.Mocked; - let personMock: jest.Mocked; - let storageMock: jest.Mocked; - let cryptoMock: jest.Mocked; + let assetMock: Mocked; + let configMock: Mocked; + let jobMock: Mocked; + let mediaMock: Mocked; + let moveMock: Mocked; + let personMock: Mocked; + let storageMock: Mocked; + let cryptoMock: Mocked; + let loggerMock: Mocked; beforeEach(() => { assetMock = newAssetRepositoryMock(); @@ -53,8 +57,19 @@ describe(MediaService.name, () => { personMock = newPersonRepositoryMock(); storageMock = newStorageRepositoryMock(); cryptoMock = newCryptoRepositoryMock(); + loggerMock = newLoggerRepositoryMock(); - sut = new MediaService(assetMock, personMock, jobMock, mediaMock, storageMock, configMock, moveMock, cryptoMock); + sut = new MediaService( + assetMock, + personMock, + jobMock, + mediaMock, + storageMock, + configMock, + moveMock, + cryptoMock, + loggerMock, + ); }); it('should be defined', () => { diff --git a/server/src/services/media.service.ts b/server/src/services/media.service.ts index 3c86c72bd7..47fa31abcc 100644 --- a/server/src/services/media.service.ts +++ b/server/src/services/media.service.ts @@ -25,12 +25,12 @@ import { JobStatus, QueueName, } from 'src/interfaces/job.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { AudioStreamInfo, IMediaRepository, VideoCodecHWConfig, VideoStreamInfo } from 'src/interfaces/media.interface'; import { IMoveRepository } from 'src/interfaces/move.interface'; import { IPersonRepository } from 'src/interfaces/person.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface'; import { ISystemConfigRepository } from 'src/interfaces/system-config.interface'; -import { ImmichLogger } from 'src/utils/logger'; import { AV1Config, H264Config, @@ -46,7 +46,6 @@ import { usePagination } from 'src/utils/pagination'; @Injectable() export class MediaService { - private logger = new ImmichLogger(MediaService.name); private configCore: SystemConfigCore; private storageCore: StorageCore; private hasOpenCL?: boolean = undefined; @@ -60,15 +59,18 @@ export class MediaService { @Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository, @Inject(IMoveRepository) moveRepository: IMoveRepository, @Inject(ICryptoRepository) cryptoRepository: ICryptoRepository, + @Inject(ILoggerRepository) private logger: ILoggerRepository, ) { - this.configCore = SystemConfigCore.create(configRepository); + this.logger.setContext(MediaService.name); + this.configCore = SystemConfigCore.create(configRepository, this.logger); this.storageCore = StorageCore.create( assetRepository, + cryptoRepository, moveRepository, personRepository, - cryptoRepository, - configRepository, storageRepository, + configRepository, + this.logger, ); } diff --git a/server/src/services/memory.service.spec.ts b/server/src/services/memory.service.spec.ts index 5f045ffde8..b4dd4bd2ad 100644 --- a/server/src/services/memory.service.spec.ts +++ b/server/src/services/memory.service.spec.ts @@ -7,10 +7,11 @@ import { memoryStub } from 'test/fixtures/memory.stub'; import { userStub } from 'test/fixtures/user.stub'; import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositories/access.repository.mock'; import { newMemoryRepositoryMock } from 'test/repositories/memory.repository.mock'; +import { Mocked } from 'vitest'; describe(MemoryService.name, () => { let accessMock: IAccessRepositoryMock; - let memoryMock: jest.Mocked; + let memoryMock: Mocked; let sut: MemoryService; beforeEach(() => { diff --git a/server/src/services/metadata.service.spec.ts b/server/src/services/metadata.service.spec.ts index 2150e494c9..5adeb1c774 100644 --- a/server/src/services/metadata.service.spec.ts +++ b/server/src/services/metadata.service.spec.ts @@ -1,5 +1,4 @@ import { BinaryField } from 'exiftool-vendored'; -import { when } from 'jest-when'; import { randomBytes } from 'node:crypto'; import { Stats } from 'node:fs'; import { constants } from 'node:fs/promises'; @@ -12,12 +11,14 @@ import { ICryptoRepository } from 'src/interfaces/crypto.interface'; import { IDatabaseRepository } from 'src/interfaces/database.interface'; import { ClientEvent, IEventRepository } from 'src/interfaces/event.interface'; import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IMediaRepository } from 'src/interfaces/media.interface'; import { IMetadataRepository, ImmichTags } from 'src/interfaces/metadata.interface'; import { IMoveRepository } from 'src/interfaces/move.interface'; import { IPersonRepository } from 'src/interfaces/person.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface'; import { ISystemConfigRepository } from 'src/interfaces/system-config.interface'; +import { IUserRepository } from 'src/interfaces/user.interface'; import { MetadataService, Orientation } from 'src/services/metadata.service'; import { assetStub } from 'test/fixtures/asset.stub'; import { fileStub } from 'test/fixtures/file.stub'; @@ -28,26 +29,31 @@ import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.moc import { newDatabaseRepositoryMock } from 'test/repositories/database.repository.mock'; import { newEventRepositoryMock } from 'test/repositories/event.repository.mock'; import { newJobRepositoryMock } from 'test/repositories/job.repository.mock'; +import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock'; import { newMediaRepositoryMock } from 'test/repositories/media.repository.mock'; import { newMetadataRepositoryMock } from 'test/repositories/metadata.repository.mock'; import { newMoveRepositoryMock } from 'test/repositories/move.repository.mock'; import { newPersonRepositoryMock } from 'test/repositories/person.repository.mock'; import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock'; import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; +import { newUserRepositoryMock } from 'test/repositories/user.repository.mock'; +import { Mocked } from 'vitest'; describe(MetadataService.name, () => { - let albumMock: jest.Mocked; - let assetMock: jest.Mocked; - let configMock: jest.Mocked; - let cryptoRepository: jest.Mocked; - let jobMock: jest.Mocked; - let metadataMock: jest.Mocked; - let moveMock: jest.Mocked; - let mediaMock: jest.Mocked; - let personMock: jest.Mocked; - let storageMock: jest.Mocked; - let eventMock: jest.Mocked; - let databaseMock: jest.Mocked; + let albumMock: Mocked; + let assetMock: Mocked; + let configMock: Mocked; + let cryptoRepository: Mocked; + let jobMock: Mocked; + let metadataMock: Mocked; + let moveMock: Mocked; + let mediaMock: Mocked; + let personMock: Mocked; + let storageMock: Mocked; + let eventMock: Mocked; + let databaseMock: Mocked; + let userMock: Mocked; + let loggerMock: Mocked; let sut: MetadataService; beforeEach(() => { @@ -63,6 +69,8 @@ describe(MetadataService.name, () => { storageMock = newStorageRepositoryMock(); mediaMock = newMediaRepositoryMock(); databaseMock = newDatabaseRepositoryMock(); + userMock = newUserRepositoryMock(); + loggerMock = newLoggerRepositoryMock(); sut = new MetadataService( albumMock, @@ -77,6 +85,8 @@ describe(MetadataService.name, () => { personMock, storageMock, configMock, + userMock, + loggerMock, ); }); @@ -248,14 +258,13 @@ describe(MetadataService.name, () => { const originalDate = new Date('2023-11-21T16:13:17.517Z'); const sidecarDate = new Date('2022-01-01T00:00:00.000Z'); assetMock.getByIds.mockResolvedValue([assetStub.sidecar]); - when(metadataMock.readTags) - .calledWith(assetStub.sidecar.originalPath) - // higher priority tag - .mockResolvedValue({ CreationDate: originalDate.toISOString() }); - when(metadataMock.readTags) - .calledWith(assetStub.sidecar.sidecarPath as string) - // lower priority tag, but in sidecar - .mockResolvedValue({ CreateDate: sidecarDate.toISOString() }); + metadataMock.readTags.mockImplementation((path) => { + const map = { + [assetStub.sidecar.originalPath]: originalDate.toISOString(), + [assetStub.sidecar.sidecarPath as string]: sidecarDate.toISOString(), + }; + return Promise.resolve({ CreationDate: map[path] ?? new Date().toISOString() }); + }); await sut.handleMetadataExtraction({ id: assetStub.image.id }); expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.sidecar.id]); @@ -368,6 +377,7 @@ describe(MetadataService.name, () => { ); expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.livePhotoStillAsset.id]); expect(assetMock.create).toHaveBeenCalled(); // This could have arguments added + expect(userMock.updateUsage).toHaveBeenCalledWith(assetStub.livePhotoMotionAsset.ownerId, 512); expect(storageMock.writeFile).toHaveBeenCalledWith(assetStub.livePhotoMotionAsset.originalPath, video); expect(assetMock.update).toHaveBeenNthCalledWith(1, { id: assetStub.livePhotoStillAsset.id, @@ -396,6 +406,7 @@ describe(MetadataService.name, () => { ); expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.livePhotoStillAsset.id]); expect(assetMock.create).toHaveBeenCalled(); // This could have arguments added + expect(userMock.updateUsage).toHaveBeenCalledWith(assetStub.livePhotoMotionAsset.ownerId, 512); expect(storageMock.writeFile).toHaveBeenCalledWith(assetStub.livePhotoMotionAsset.originalPath, video); expect(assetMock.update).toHaveBeenNthCalledWith(1, { id: assetStub.livePhotoStillAsset.id, @@ -422,6 +433,7 @@ describe(MetadataService.name, () => { expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.livePhotoStillAsset.id]); expect(storageMock.readFile).toHaveBeenCalledWith(assetStub.livePhotoStillAsset.originalPath, expect.any(Object)); expect(assetMock.create).toHaveBeenCalled(); // This could have arguments added + expect(userMock.updateUsage).toHaveBeenCalledWith(assetStub.livePhotoMotionAsset.ownerId, 512); expect(storageMock.writeFile).toHaveBeenCalledWith(assetStub.livePhotoMotionAsset.originalPath, video); expect(assetMock.update).toHaveBeenNthCalledWith(1, { id: assetStub.livePhotoStillAsset.id, @@ -440,6 +452,8 @@ describe(MetadataService.name, () => { cryptoRepository.hashSha1.mockReturnValue(randomBytes(512)); assetMock.getByChecksum.mockResolvedValue(null); assetMock.create.mockImplementation((asset) => Promise.resolve({ ...assetStub.livePhotoMotionAsset, ...asset })); + const video = randomBytes(512); + storageMock.readFile.mockResolvedValue(video); await sut.handleMetadataExtraction({ id: assetStub.livePhotoStillAsset.id }); expect(jobMock.queue).toHaveBeenNthCalledWith(2, { @@ -448,7 +462,7 @@ describe(MetadataService.name, () => { }); }); - it('should not create a new motionphoto video asset if the of the extracted video matches an existing asset', async () => { + it('should not create a new motion photo video asset if the hash of the extracted video matches an existing asset', async () => { assetMock.getByIds.mockResolvedValue([assetStub.livePhotoStillAsset]); metadataMock.readTags.mockResolvedValue({ Directory: 'foo/bar/', @@ -458,6 +472,8 @@ describe(MetadataService.name, () => { }); cryptoRepository.hashSha1.mockReturnValue(randomBytes(512)); assetMock.getByChecksum.mockResolvedValue(assetStub.livePhotoMotionAsset); + const video = randomBytes(512); + storageMock.readFile.mockResolvedValue(video); await sut.handleMetadataExtraction({ id: assetStub.livePhotoStillAsset.id }); expect(assetMock.create).toHaveBeenCalledTimes(0); @@ -491,6 +507,26 @@ describe(MetadataService.name, () => { }); }); + it('should not update storage usage if motion photo is external', async () => { + assetMock.getByIds.mockResolvedValue([ + { ...assetStub.livePhotoStillAsset, livePhotoVideoId: null, isExternal: true }, + ]); + metadataMock.readTags.mockResolvedValue({ + Directory: 'foo/bar/', + MotionPhoto: 1, + MicroVideo: 1, + MicroVideoOffset: 1, + }); + cryptoRepository.hashSha1.mockReturnValue(randomBytes(512)); + assetMock.getByChecksum.mockResolvedValue(null); + assetMock.create.mockResolvedValue(assetStub.livePhotoMotionAsset); + const video = randomBytes(512); + storageMock.readFile.mockResolvedValue(video); + + await sut.handleMetadataExtraction({ id: assetStub.livePhotoStillAsset.id }); + expect(userMock.updateUsage).not.toHaveBeenCalled(); + }); + it('should save all metadata', async () => { const tags: ImmichTags = { BitsPerSample: 1, diff --git a/server/src/services/metadata.service.ts b/server/src/services/metadata.service.ts index 379e034884..e28f1cd474 100644 --- a/server/src/services/metadata.service.ts +++ b/server/src/services/metadata.service.ts @@ -25,13 +25,14 @@ import { JobStatus, QueueName, } from 'src/interfaces/job.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IMediaRepository } from 'src/interfaces/media.interface'; import { IMetadataRepository, ImmichTags } from 'src/interfaces/metadata.interface'; import { IMoveRepository } from 'src/interfaces/move.interface'; import { IPersonRepository } from 'src/interfaces/person.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface'; import { ISystemConfigRepository } from 'src/interfaces/system-config.interface'; -import { ImmichLogger } from 'src/utils/logger'; +import { IUserRepository } from 'src/interfaces/user.interface'; import { handlePromiseError } from 'src/utils/misc'; import { usePagination } from 'src/utils/pagination'; @@ -97,7 +98,6 @@ const validate = (value: T): NonNullable | null => { @Injectable() export class MetadataService { - private logger = new ImmichLogger(MetadataService.name); private storageCore: StorageCore; private configCore: SystemConfigCore; private subscription: Subscription | null = null; @@ -115,15 +115,19 @@ export class MetadataService { @Inject(IPersonRepository) personRepository: IPersonRepository, @Inject(IStorageRepository) private storageRepository: IStorageRepository, @Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository, + @Inject(IUserRepository) private userRepository: IUserRepository, + @Inject(ILoggerRepository) private logger: ILoggerRepository, ) { - this.configCore = SystemConfigCore.create(configRepository); + this.logger.setContext(MetadataService.name); + this.configCore = SystemConfigCore.create(configRepository, this.logger); this.storageCore = StorageCore.create( assetRepository, + cryptoRepository, moveRepository, personRepository, - cryptoRepository, - configRepository, storageRepository, + configRepository, + this.logger, ); } @@ -444,10 +448,14 @@ export class MetadataService { this.storageCore.ensureFolders(motionPath); await this.storageRepository.writeFile(motionAsset.originalPath, video); await this.jobRepository.queue({ name: JobName.METADATA_EXTRACTION, data: { id: motionAsset.id } }); + if (!asset.isExternal) { + await this.userRepository.updateUsage(asset.ownerId, video.byteLength); + } } if (asset.livePhotoVideoId !== motionAsset.id) { await this.assetRepository.update({ id: asset.id, livePhotoVideoId: motionAsset.id }); + // If the asset already had an associated livePhotoVideo, delete it, because // its checksum doesn't match the checksum of the motionAsset we just extracted // (if it did, getByChecksum() would've returned a motionAsset with the same ID as livePhotoVideoId) diff --git a/server/src/services/partner.service.spec.ts b/server/src/services/partner.service.spec.ts index a3c4af7367..70c73b0d94 100644 --- a/server/src/services/partner.service.spec.ts +++ b/server/src/services/partner.service.spec.ts @@ -7,6 +7,7 @@ import { PartnerService } from 'src/services/partner.service'; import { authStub } from 'test/fixtures/auth.stub'; import { partnerStub } from 'test/fixtures/partner.stub'; import { newPartnerRepositoryMock } from 'test/repositories/partner.repository.mock'; +import { Mocked } from 'vitest'; const responseDto = { admin: { @@ -49,8 +50,8 @@ const responseDto = { describe(PartnerService.name, () => { let sut: PartnerService; - let partnerMock: jest.Mocked; - let accessMock: jest.Mocked; + let partnerMock: Mocked; + let accessMock: Mocked; beforeEach(() => { partnerMock = newPartnerRepositoryMock(); diff --git a/server/src/services/person.service.spec.ts b/server/src/services/person.service.spec.ts index 501154c1db..a9d96bbcea 100644 --- a/server/src/services/person.service.spec.ts +++ b/server/src/services/person.service.spec.ts @@ -6,6 +6,7 @@ import { Colorspace, SystemConfigKey } from 'src/entities/system-config.entity'; import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface'; import { ICryptoRepository } from 'src/interfaces/crypto.interface'; import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface'; import { IMediaRepository } from 'src/interfaces/media.interface'; import { IMoveRepository } from 'src/interfaces/move.interface'; @@ -23,6 +24,7 @@ import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositorie import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock'; import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock'; import { newJobRepositoryMock } from 'test/repositories/job.repository.mock'; +import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock'; import { newMachineLearningRepositoryMock } from 'test/repositories/machine-learning.repository.mock'; import { newMediaRepositoryMock } from 'test/repositories/media.repository.mock'; import { newMoveRepositoryMock } from 'test/repositories/move.repository.mock'; @@ -31,6 +33,7 @@ import { newSearchRepositoryMock } from 'test/repositories/search.repository.moc import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock'; import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; import { IsNull } from 'typeorm'; +import { Mocked } from 'vitest'; const responseDto: PersonResponseDto = { id: 'person-1', @@ -61,16 +64,17 @@ const detectFaceMock = { describe(PersonService.name, () => { let accessMock: IAccessRepositoryMock; - let assetMock: jest.Mocked; - let configMock: jest.Mocked; - let jobMock: jest.Mocked; - let machineLearningMock: jest.Mocked; - let mediaMock: jest.Mocked; - let moveMock: jest.Mocked; - let personMock: jest.Mocked; - let storageMock: jest.Mocked; - let searchMock: jest.Mocked; - let cryptoMock: jest.Mocked; + let assetMock: Mocked; + let configMock: Mocked; + let jobMock: Mocked; + let machineLearningMock: Mocked; + let mediaMock: Mocked; + let moveMock: Mocked; + let personMock: Mocked; + let storageMock: Mocked; + let searchMock: Mocked; + let cryptoMock: Mocked; + let loggerMock: Mocked; let sut: PersonService; beforeEach(() => { @@ -85,6 +89,7 @@ describe(PersonService.name, () => { storageMock = newStorageRepositoryMock(); searchMock = newSearchRepositoryMock(); cryptoMock = newCryptoRepositoryMock(); + loggerMock = newLoggerRepositoryMock(); sut = new PersonService( accessMock, assetMock, @@ -97,6 +102,7 @@ describe(PersonService.name, () => { jobMock, searchMock, cryptoMock, + loggerMock, ); mediaMock.crop.mockResolvedValue(croppedFace); diff --git a/server/src/services/person.service.ts b/server/src/services/person.service.ts index d2bc81b0ea..77b7e552cc 100644 --- a/server/src/services/person.service.ts +++ b/server/src/services/person.service.ts @@ -38,6 +38,7 @@ import { JobStatus, QueueName, } from 'src/interfaces/job.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface'; import { CropOptions, IMediaRepository } from 'src/interfaces/media.interface'; import { IMoveRepository } from 'src/interfaces/move.interface'; @@ -46,7 +47,6 @@ import { ISearchRepository } from 'src/interfaces/search.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface'; import { ISystemConfigRepository } from 'src/interfaces/system-config.interface'; import { CacheControl, ImmichFileResponse } from 'src/utils/file'; -import { ImmichLogger } from 'src/utils/logger'; import { mimeTypes } from 'src/utils/mime-types'; import { usePagination } from 'src/utils/pagination'; import { IsNull } from 'typeorm'; @@ -56,7 +56,6 @@ export class PersonService { private access: AccessCore; private configCore: SystemConfigCore; private storageCore: StorageCore; - readonly logger = new ImmichLogger(PersonService.name); constructor( @Inject(IAccessRepository) accessRepository: IAccessRepository, @@ -70,16 +69,19 @@ export class PersonService { @Inject(IJobRepository) private jobRepository: IJobRepository, @Inject(ISearchRepository) private smartInfoRepository: ISearchRepository, @Inject(ICryptoRepository) cryptoRepository: ICryptoRepository, + @Inject(ILoggerRepository) private logger: ILoggerRepository, ) { this.access = AccessCore.create(accessRepository); - this.configCore = SystemConfigCore.create(configRepository); + this.logger.setContext(PersonService.name); + this.configCore = SystemConfigCore.create(configRepository, this.logger); this.storageCore = StorageCore.create( assetRepository, + cryptoRepository, moveRepository, repository, - cryptoRepository, - configRepository, storageRepository, + configRepository, + this.logger, ); } diff --git a/server/src/services/search.service.spec.ts b/server/src/services/search.service.spec.ts index 72b543f2d7..a81ea87973 100644 --- a/server/src/services/search.service.spec.ts +++ b/server/src/services/search.service.spec.ts @@ -2,6 +2,7 @@ import { mapAsset } from 'src/dtos/asset-response.dto'; import { SearchDto } from 'src/dtos/search.dto'; import { SystemConfigKey } from 'src/entities/system-config.entity'; import { IAssetRepository } from 'src/interfaces/asset.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface'; import { IMetadataRepository } from 'src/interfaces/metadata.interface'; import { IPartnerRepository } from 'src/interfaces/partner.interface'; @@ -13,24 +14,27 @@ import { assetStub } from 'test/fixtures/asset.stub'; import { authStub } from 'test/fixtures/auth.stub'; import { personStub } from 'test/fixtures/person.stub'; import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock'; +import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock'; import { newMachineLearningRepositoryMock } from 'test/repositories/machine-learning.repository.mock'; import { newMetadataRepositoryMock } from 'test/repositories/metadata.repository.mock'; import { newPartnerRepositoryMock } from 'test/repositories/partner.repository.mock'; import { newPersonRepositoryMock } from 'test/repositories/person.repository.mock'; import { newSearchRepositoryMock } from 'test/repositories/search.repository.mock'; import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; +import { Mocked, vitest } from 'vitest'; -jest.useFakeTimers(); +vitest.useFakeTimers(); describe(SearchService.name, () => { let sut: SearchService; - let assetMock: jest.Mocked; - let configMock: jest.Mocked; - let machineMock: jest.Mocked; - let personMock: jest.Mocked; - let searchMock: jest.Mocked; - let partnerMock: jest.Mocked; - let metadataMock: jest.Mocked; + let assetMock: Mocked; + let configMock: Mocked; + let machineMock: Mocked; + let personMock: Mocked; + let searchMock: Mocked; + let partnerMock: Mocked; + let metadataMock: Mocked; + let loggerMock: Mocked; beforeEach(() => { assetMock = newAssetRepositoryMock(); @@ -40,8 +44,18 @@ describe(SearchService.name, () => { searchMock = newSearchRepositoryMock(); partnerMock = newPartnerRepositoryMock(); metadataMock = newMetadataRepositoryMock(); + loggerMock = newLoggerRepositoryMock(); - sut = new SearchService(configMock, machineMock, personMock, searchMock, assetMock, partnerMock, metadataMock); + sut = new SearchService( + configMock, + machineMock, + personMock, + searchMock, + assetMock, + partnerMock, + metadataMock, + loggerMock, + ); }); it('should work', () => { diff --git a/server/src/services/search.service.ts b/server/src/services/search.service.ts index 9422dac86b..b8e9f13fa6 100644 --- a/server/src/services/search.service.ts +++ b/server/src/services/search.service.ts @@ -18,6 +18,7 @@ import { import { AssetOrder } from 'src/entities/album.entity'; import { AssetEntity } from 'src/entities/asset.entity'; import { IAssetRepository } from 'src/interfaces/asset.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface'; import { IMetadataRepository } from 'src/interfaces/metadata.interface'; import { IPartnerRepository } from 'src/interfaces/partner.interface'; @@ -37,8 +38,10 @@ export class SearchService { @Inject(IAssetRepository) private assetRepository: IAssetRepository, @Inject(IPartnerRepository) private partnerRepository: IPartnerRepository, @Inject(IMetadataRepository) private metadataRepository: IMetadataRepository, + @Inject(ILoggerRepository) private logger: ILoggerRepository, ) { - this.configCore = SystemConfigCore.create(configRepository); + this.logger.setContext(SearchService.name); + this.configCore = SystemConfigCore.create(configRepository, logger); } async searchPerson(auth: AuthDto, dto: SearchPeopleDto): Promise { diff --git a/server/src/services/server-info.service.spec.ts b/server/src/services/server-info.service.spec.ts index 0348f26d2d..836909b74f 100644 --- a/server/src/services/server-info.service.spec.ts +++ b/server/src/services/server-info.service.spec.ts @@ -1,6 +1,7 @@ import { serverVersion } from 'src/constants'; import { SystemMetadataKey } from 'src/entities/system-metadata.entity'; import { IEventRepository } from 'src/interfaces/event.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IServerInfoRepository } from 'src/interfaces/server-info.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface'; import { ISystemConfigRepository } from 'src/interfaces/system-config.interface'; @@ -8,20 +9,23 @@ import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interf import { IUserRepository } from 'src/interfaces/user.interface'; import { ServerInfoService } from 'src/services/server-info.service'; import { newEventRepositoryMock } from 'test/repositories/event.repository.mock'; +import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock'; import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock'; import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; import { newServerInfoRepositoryMock } from 'test/repositories/system-info.repository.mock'; import { newSystemMetadataRepositoryMock } from 'test/repositories/system-metadata.repository.mock'; import { newUserRepositoryMock } from 'test/repositories/user.repository.mock'; +import { Mocked } from 'vitest'; describe(ServerInfoService.name, () => { let sut: ServerInfoService; - let eventMock: jest.Mocked; - let configMock: jest.Mocked; - let serverInfoMock: jest.Mocked; - let storageMock: jest.Mocked; - let userMock: jest.Mocked; - let systemMetadataMock: jest.Mocked; + let eventMock: Mocked; + let configMock: Mocked; + let serverInfoMock: Mocked; + let storageMock: Mocked; + let userMock: Mocked; + let systemMetadataMock: Mocked; + let loggerMock: Mocked; beforeEach(() => { configMock = newSystemConfigRepositoryMock(); @@ -30,8 +34,17 @@ describe(ServerInfoService.name, () => { storageMock = newStorageRepositoryMock(); userMock = newUserRepositoryMock(); systemMetadataMock = newSystemMetadataRepositoryMock(); + loggerMock = newLoggerRepositoryMock(); - sut = new ServerInfoService(eventMock, configMock, userMock, serverInfoMock, storageMock, systemMetadataMock); + sut = new ServerInfoService( + eventMock, + configMock, + userMock, + serverInfoMock, + storageMock, + systemMetadataMock, + loggerMock, + ); }); it('should work', () => { diff --git a/server/src/services/server-info.service.ts b/server/src/services/server-info.service.ts index 9f0c1e290c..bb092896bf 100644 --- a/server/src/services/server-info.service.ts +++ b/server/src/services/server-info.service.ts @@ -15,19 +15,18 @@ import { } from 'src/dtos/server-info.dto'; import { SystemMetadataKey } from 'src/entities/system-metadata.entity'; import { ClientEvent, IEventRepository, ServerEvent, ServerEventMap } from 'src/interfaces/event.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IServerInfoRepository } from 'src/interfaces/server-info.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface'; import { ISystemConfigRepository } from 'src/interfaces/system-config.interface'; import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; import { IUserRepository, UserStatsQueryResponse } from 'src/interfaces/user.interface'; import { asHumanReadable } from 'src/utils/bytes'; -import { ImmichLogger } from 'src/utils/logger'; import { mimeTypes } from 'src/utils/mime-types'; import { Version } from 'src/utils/version'; @Injectable() export class ServerInfoService { - private logger = new ImmichLogger(ServerInfoService.name); private configCore: SystemConfigCore; private releaseVersion = serverVersion; private releaseVersionCheckedAt: DateTime | null = null; @@ -38,9 +37,11 @@ export class ServerInfoService { @Inject(IUserRepository) private userRepository: IUserRepository, @Inject(IServerInfoRepository) private repository: IServerInfoRepository, @Inject(IStorageRepository) private storageRepository: IStorageRepository, - @Inject(ISystemMetadataRepository) private readonly systemMetadataRepository: ISystemMetadataRepository, + @Inject(ISystemMetadataRepository) private systemMetadataRepository: ISystemMetadataRepository, + @Inject(ILoggerRepository) private logger: ILoggerRepository, ) { - this.configCore = SystemConfigCore.create(configRepository); + this.logger.setContext(ServerInfoService.name); + this.configCore = SystemConfigCore.create(configRepository, this.logger); } onConnect() {} diff --git a/server/src/services/shared-link.service.spec.ts b/server/src/services/shared-link.service.spec.ts index cad52928ca..6baeac858a 100644 --- a/server/src/services/shared-link.service.spec.ts +++ b/server/src/services/shared-link.service.spec.ts @@ -12,12 +12,13 @@ import { sharedLinkResponseStub, sharedLinkStub } from 'test/fixtures/shared-lin import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositories/access.repository.mock'; import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock'; import { newSharedLinkRepositoryMock } from 'test/repositories/shared-link.repository.mock'; +import { Mocked } from 'vitest'; describe(SharedLinkService.name, () => { let sut: SharedLinkService; let accessMock: IAccessRepositoryMock; - let cryptoMock: jest.Mocked; - let shareMock: jest.Mocked; + let cryptoMock: Mocked; + let shareMock: Mocked; beforeEach(() => { accessMock = newAccessRepositoryMock(); diff --git a/server/src/services/smart-info.service.spec.ts b/server/src/services/smart-info.service.spec.ts index 2e1dfbafc0..4d85c00253 100644 --- a/server/src/services/smart-info.service.spec.ts +++ b/server/src/services/smart-info.service.spec.ts @@ -3,6 +3,7 @@ import { SystemConfigKey } from 'src/entities/system-config.entity'; import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface'; import { IDatabaseRepository } from 'src/interfaces/database.interface'; import { IJobRepository, JobName } from 'src/interfaces/job.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface'; import { ISearchRepository } from 'src/interfaces/search.interface'; import { ISystemConfigRepository } from 'src/interfaces/system-config.interface'; @@ -12,9 +13,11 @@ import { assetStub } from 'test/fixtures/asset.stub'; import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock'; import { newDatabaseRepositoryMock } from 'test/repositories/database.repository.mock'; import { newJobRepositoryMock } from 'test/repositories/job.repository.mock'; +import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock'; import { newMachineLearningRepositoryMock } from 'test/repositories/machine-learning.repository.mock'; import { newSearchRepositoryMock } from 'test/repositories/search.repository.mock'; import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; +import { Mocked } from 'vitest'; const asset = { id: 'asset-1', @@ -23,12 +26,13 @@ const asset = { describe(SmartInfoService.name, () => { let sut: SmartInfoService; - let assetMock: jest.Mocked; - let configMock: jest.Mocked; - let jobMock: jest.Mocked; - let searchMock: jest.Mocked; - let machineMock: jest.Mocked; - let databaseMock: jest.Mocked; + let assetMock: Mocked; + let configMock: Mocked; + let jobMock: Mocked; + let searchMock: Mocked; + let machineMock: Mocked; + let databaseMock: Mocked; + let loggerMock: Mocked; beforeEach(() => { assetMock = newAssetRepositoryMock(); @@ -37,7 +41,8 @@ describe(SmartInfoService.name, () => { jobMock = newJobRepositoryMock(); machineMock = newMachineLearningRepositoryMock(); databaseMock = newDatabaseRepositoryMock(); - sut = new SmartInfoService(assetMock, databaseMock, jobMock, machineMock, searchMock, configMock); + loggerMock = newLoggerRepositoryMock(); + sut = new SmartInfoService(assetMock, databaseMock, jobMock, machineMock, searchMock, configMock, loggerMock); assetMock.getByIds.mockResolvedValue([asset]); }); diff --git a/server/src/services/smart-info.service.ts b/server/src/services/smart-info.service.ts index f9d36c238c..9de5edbd88 100644 --- a/server/src/services/smart-info.service.ts +++ b/server/src/services/smart-info.service.ts @@ -11,16 +11,15 @@ import { JobStatus, QueueName, } from 'src/interfaces/job.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface'; import { ISearchRepository } from 'src/interfaces/search.interface'; import { ISystemConfigRepository } from 'src/interfaces/system-config.interface'; -import { ImmichLogger } from 'src/utils/logger'; import { usePagination } from 'src/utils/pagination'; @Injectable() export class SmartInfoService { private configCore: SystemConfigCore; - private logger = new ImmichLogger(SmartInfoService.name); constructor( @Inject(IAssetRepository) private assetRepository: IAssetRepository, @@ -29,8 +28,10 @@ export class SmartInfoService { @Inject(IMachineLearningRepository) private machineLearning: IMachineLearningRepository, @Inject(ISearchRepository) private repository: ISearchRepository, @Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository, + @Inject(ILoggerRepository) private logger: ILoggerRepository, ) { - this.configCore = SystemConfigCore.create(configRepository); + this.logger.setContext(SmartInfoService.name); + this.configCore = SystemConfigCore.create(configRepository, this.logger); } async init() { diff --git a/server/src/services/storage-template.service.spec.ts b/server/src/services/storage-template.service.spec.ts index ba1cb3e59b..952b656546 100644 --- a/server/src/services/storage-template.service.spec.ts +++ b/server/src/services/storage-template.service.spec.ts @@ -1,6 +1,6 @@ -import { when } from 'jest-when'; import { Stats } from 'node:fs'; import { SystemConfigCore, defaults } from 'src/cores/system-config.core'; +import { AssetEntity } from 'src/entities/asset.entity'; import { AssetPathType } from 'src/entities/move.entity'; import { SystemConfig, SystemConfigKey } from 'src/entities/system-config.entity'; import { IAlbumRepository } from 'src/interfaces/album.interface'; @@ -8,6 +8,7 @@ import { IAssetRepository } from 'src/interfaces/asset.interface'; import { ICryptoRepository } from 'src/interfaces/crypto.interface'; import { IDatabaseRepository } from 'src/interfaces/database.interface'; import { JobStatus } from 'src/interfaces/job.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IMoveRepository } from 'src/interfaces/move.interface'; import { IPersonRepository } from 'src/interfaces/person.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface'; @@ -20,23 +21,26 @@ import { newAlbumRepositoryMock } from 'test/repositories/album.repository.mock' import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock'; import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock'; import { newDatabaseRepositoryMock } from 'test/repositories/database.repository.mock'; +import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock'; import { newMoveRepositoryMock } from 'test/repositories/move.repository.mock'; import { newPersonRepositoryMock } from 'test/repositories/person.repository.mock'; import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock'; import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; import { newUserRepositoryMock } from 'test/repositories/user.repository.mock'; +import { Mocked } from 'vitest'; describe(StorageTemplateService.name, () => { let sut: StorageTemplateService; - let albumMock: jest.Mocked; - let assetMock: jest.Mocked; - let configMock: jest.Mocked; - let moveMock: jest.Mocked; - let personMock: jest.Mocked; - let storageMock: jest.Mocked; - let userMock: jest.Mocked; - let cryptoMock: jest.Mocked; - let databaseMock: jest.Mocked; + let albumMock: Mocked; + let assetMock: Mocked; + let configMock: Mocked; + let moveMock: Mocked; + let personMock: Mocked; + let storageMock: Mocked; + let userMock: Mocked; + let cryptoMock: Mocked; + let databaseMock: Mocked; + let loggerMock: Mocked; it('should work', () => { expect(sut).toBeDefined(); @@ -52,6 +56,7 @@ describe(StorageTemplateService.name, () => { userMock = newUserRepositoryMock(); cryptoMock = newCryptoRepositoryMock(); databaseMock = newDatabaseRepositoryMock(); + loggerMock = newLoggerRepositoryMock(); configMock.load.mockResolvedValue([{ key: SystemConfigKey.STORAGE_TEMPLATE_ENABLED, value: true }]); @@ -65,9 +70,10 @@ describe(StorageTemplateService.name, () => { userMock, cryptoMock, databaseMock, + loggerMock, ); - SystemConfigCore.create(configMock).config$.next(defaults); + SystemConfigCore.create(configMock, loggerMock).config$.next(defaults); }); describe('onValidateConfig', () => { @@ -118,43 +124,28 @@ describe(StorageTemplateService.name, () => { const newMotionPicturePath = `upload/library/${userStub.user1.id}/2022/2022-06-19/${assetStub.livePhotoStillAsset.id}.mp4`; const newStillPicturePath = `upload/library/${userStub.user1.id}/2022/2022-06-19/${assetStub.livePhotoStillAsset.id}.jpeg`; - when(assetMock.getByIds) - .calledWith([assetStub.livePhotoStillAsset.id], { exifInfo: true }) - .mockResolvedValue([assetStub.livePhotoStillAsset]); + assetMock.getByIds.mockImplementation((ids) => { + const assets = [assetStub.livePhotoStillAsset, assetStub.livePhotoMotionAsset]; + return Promise.resolve( + ids.map((id) => assets.find((asset) => asset.id === id)).filter((asset) => !!asset), + ) as Promise; + }); - when(assetMock.getByIds) - .calledWith([assetStub.livePhotoMotionAsset.id], { exifInfo: true }) - .mockResolvedValue([assetStub.livePhotoMotionAsset]); + moveMock.create.mockResolvedValueOnce({ + id: '123', + entityId: assetStub.livePhotoStillAsset.id, + pathType: AssetPathType.ORIGINAL, + oldPath: assetStub.livePhotoStillAsset.originalPath, + newPath: newStillPicturePath, + }); - when(moveMock.create) - .calledWith({ - entityId: assetStub.livePhotoStillAsset.id, - pathType: AssetPathType.ORIGINAL, - oldPath: assetStub.livePhotoStillAsset.originalPath, - newPath: newStillPicturePath, - }) - .mockResolvedValue({ - id: '123', - entityId: assetStub.livePhotoStillAsset.id, - pathType: AssetPathType.ORIGINAL, - oldPath: assetStub.livePhotoStillAsset.originalPath, - newPath: newStillPicturePath, - }); - - when(moveMock.create) - .calledWith({ - entityId: assetStub.livePhotoMotionAsset.id, - pathType: AssetPathType.ORIGINAL, - oldPath: assetStub.livePhotoMotionAsset.originalPath, - newPath: newMotionPicturePath, - }) - .mockResolvedValue({ - id: '124', - entityId: assetStub.livePhotoMotionAsset.id, - pathType: AssetPathType.ORIGINAL, - oldPath: assetStub.livePhotoMotionAsset.originalPath, - newPath: newMotionPicturePath, - }); + moveMock.create.mockResolvedValueOnce({ + id: '124', + entityId: assetStub.livePhotoMotionAsset.id, + pathType: AssetPathType.ORIGINAL, + oldPath: assetStub.livePhotoMotionAsset.originalPath, + newPath: newMotionPicturePath, + }); await expect(sut.handleMigrationSingle({ id: assetStub.livePhotoStillAsset.id })).resolves.toBe( JobStatus.SUCCESS, @@ -177,34 +168,22 @@ describe(StorageTemplateService.name, () => { const previousFailedNewPath = `upload/library/${userStub.user1.id}/2023/Feb/${assetStub.image.id}.jpg`; const newPath = `upload/library/${userStub.user1.id}/2023/2023-02-23/${assetStub.image.id}.jpg`; - when(storageMock.checkFileExists).calledWith(assetStub.image.originalPath).mockResolvedValue(true); - when(storageMock.checkFileExists).calledWith(previousFailedNewPath).mockResolvedValue(false); - - when(moveMock.getByEntity).calledWith(assetStub.image.id, AssetPathType.ORIGINAL).mockResolvedValue({ + storageMock.checkFileExists.mockImplementation((path) => Promise.resolve(path === assetStub.image.originalPath)); + moveMock.getByEntity.mockResolvedValue({ id: '123', entityId: assetStub.image.id, pathType: AssetPathType.ORIGINAL, oldPath: assetStub.image.originalPath, newPath: previousFailedNewPath, }); - - when(assetMock.getByIds) - .calledWith([assetStub.image.id], { exifInfo: true }) - .mockResolvedValue([assetStub.image]); - - when(moveMock.update) - .calledWith({ - id: '123', - oldPath: assetStub.image.originalPath, - newPath, - }) - .mockResolvedValue({ - id: '123', - entityId: assetStub.image.id, - pathType: AssetPathType.ORIGINAL, - oldPath: assetStub.image.originalPath, - newPath, - }); + assetMock.getByIds.mockResolvedValue([assetStub.image]); + moveMock.update.mockResolvedValue({ + id: '123', + entityId: assetStub.image.id, + pathType: AssetPathType.ORIGINAL, + oldPath: assetStub.image.originalPath, + newPath, + }); await expect(sut.handleMigrationSingle({ id: assetStub.image.id })).resolves.toBe(JobStatus.SUCCESS); @@ -226,38 +205,24 @@ describe(StorageTemplateService.name, () => { const previousFailedNewPath = `upload/library/${userStub.user1.id}/2023/Feb/${assetStub.image.id}.jpg`; const newPath = `upload/library/${userStub.user1.id}/2023/2023-02-23/${assetStub.image.id}.jpg`; - when(storageMock.checkFileExists).calledWith(assetStub.image.originalPath).mockResolvedValue(false); - when(storageMock.checkFileExists).calledWith(previousFailedNewPath).mockResolvedValue(true); - when(storageMock.stat) - .calledWith(previousFailedNewPath) - .mockResolvedValue({ size: 5000 } as Stats); - when(cryptoMock.hashFile).calledWith(previousFailedNewPath).mockResolvedValue(assetStub.image.checksum); - - when(moveMock.getByEntity).calledWith(assetStub.image.id, AssetPathType.ORIGINAL).mockResolvedValue({ + storageMock.checkFileExists.mockImplementation((path) => Promise.resolve(path === previousFailedNewPath)); + storageMock.stat.mockResolvedValue({ size: 5000 } as Stats); + cryptoMock.hashFile.mockResolvedValue(assetStub.image.checksum); + moveMock.getByEntity.mockResolvedValue({ id: '123', entityId: assetStub.image.id, pathType: AssetPathType.ORIGINAL, oldPath: assetStub.image.originalPath, newPath: previousFailedNewPath, }); - - when(assetMock.getByIds) - .calledWith([assetStub.image.id], { exifInfo: true }) - .mockResolvedValue([assetStub.image]); - - when(moveMock.update) - .calledWith({ - id: '123', - oldPath: previousFailedNewPath, - newPath, - }) - .mockResolvedValue({ - id: '123', - entityId: assetStub.image.id, - pathType: AssetPathType.ORIGINAL, - oldPath: previousFailedNewPath, - newPath, - }); + assetMock.getByIds.mockResolvedValue([assetStub.image]); + moveMock.update.mockResolvedValue({ + id: '123', + entityId: assetStub.image.id, + pathType: AssetPathType.ORIGINAL, + oldPath: previousFailedNewPath, + newPath, + }); await expect(sut.handleMigrationSingle({ id: assetStub.image.id })).resolves.toBe(JobStatus.SUCCESS); @@ -281,30 +246,17 @@ describe(StorageTemplateService.name, () => { userMock.get.mockResolvedValue(userStub.user1); const newPath = `upload/library/${userStub.user1.id}/2023/2023-02-23/${assetStub.image.id}.jpg`; - when(storageMock.rename).calledWith(assetStub.image.originalPath, newPath).mockRejectedValue({ code: 'EXDEV' }); - when(storageMock.stat) - .calledWith(newPath) - .mockResolvedValue({ size: 5000 } as Stats); - when(cryptoMock.hashFile).calledWith(newPath).mockResolvedValue(Buffer.from('different-hash', 'utf8')); - - when(assetMock.getByIds) - .calledWith([assetStub.image.id], { exifInfo: true }) - .mockResolvedValue([assetStub.image]); - - when(moveMock.create) - .calledWith({ - entityId: assetStub.image.id, - pathType: AssetPathType.ORIGINAL, - oldPath: assetStub.image.originalPath, - newPath: newPath, - }) - .mockResolvedValue({ - id: '123', - entityId: assetStub.image.id, - pathType: AssetPathType.ORIGINAL, - oldPath: assetStub.image.originalPath, - newPath, - }); + storageMock.rename.mockRejectedValue({ code: 'EXDEV' }); + storageMock.stat.mockResolvedValue({ size: 5000 } as Stats); + cryptoMock.hashFile.mockResolvedValue(Buffer.from('different-hash', 'utf8')); + assetMock.getByIds.mockResolvedValue([assetStub.image]); + moveMock.create.mockResolvedValue({ + id: '123', + entityId: assetStub.image.id, + pathType: AssetPathType.ORIGINAL, + oldPath: assetStub.image.originalPath, + newPath, + }); await expect(sut.handleMigrationSingle({ id: assetStub.image.id })).resolves.toBe(JobStatus.SUCCESS); @@ -335,38 +287,24 @@ describe(StorageTemplateService.name, () => { const previousFailedNewPath = `upload/library/${userStub.user1.id}/2023/Feb/${assetStub.image.id}.jpg`; const newPath = `upload/library/${userStub.user1.id}/2023/2023-02-23/${assetStub.image.id}.jpg`; - when(storageMock.checkFileExists).calledWith(assetStub.image.originalPath).mockResolvedValue(false); - when(storageMock.checkFileExists).calledWith(previousFailedNewPath).mockResolvedValue(true); - when(storageMock.stat) - .calledWith(previousFailedNewPath) - .mockResolvedValue({ size: failedPathSize } as Stats); - when(cryptoMock.hashFile).calledWith(previousFailedNewPath).mockResolvedValue(failedPathChecksum); - - when(moveMock.getByEntity).calledWith(assetStub.image.id, AssetPathType.ORIGINAL).mockResolvedValue({ + storageMock.checkFileExists.mockImplementation((path) => Promise.resolve(previousFailedNewPath === path)); + storageMock.stat.mockResolvedValue({ size: failedPathSize } as Stats); + cryptoMock.hashFile.mockResolvedValue(failedPathChecksum); + moveMock.getByEntity.mockResolvedValue({ id: '123', entityId: assetStub.image.id, pathType: AssetPathType.ORIGINAL, oldPath: assetStub.image.originalPath, newPath: previousFailedNewPath, }); - - when(assetMock.getByIds) - .calledWith([assetStub.image.id], { exifInfo: true }) - .mockResolvedValue([assetStub.image]); - - when(moveMock.update) - .calledWith({ - id: '123', - oldPath: previousFailedNewPath, - newPath, - }) - .mockResolvedValue({ - id: '123', - entityId: assetStub.image.id, - pathType: AssetPathType.ORIGINAL, - oldPath: previousFailedNewPath, - newPath, - }); + assetMock.getByIds.mockResolvedValue([assetStub.image]); + moveMock.update.mockResolvedValue({ + id: '123', + entityId: assetStub.image.id, + pathType: AssetPathType.ORIGINAL, + oldPath: previousFailedNewPath, + newPath, + }); await expect(sut.handleMigrationSingle({ id: assetStub.image.id })).resolves.toBe(JobStatus.SUCCESS); @@ -408,13 +346,8 @@ describe(StorageTemplateService.name, () => { newPath: 'upload/library/user-id/2023/2023-02-23/asset-id+1.jpg', }); - when(storageMock.checkFileExists) - .calledWith('upload/library/user-id/2023/2023-02-23/asset-id.jpg') - .mockResolvedValue(true); - - when(storageMock.checkFileExists) - .calledWith('upload/library/user-id/2023/2023-02-23/asset-id+1.jpg') - .mockResolvedValue(false); + storageMock.checkFileExists.mockResolvedValueOnce(true); + storageMock.checkFileExists.mockResolvedValueOnce(false); await sut.handleMigration(); @@ -538,18 +471,18 @@ describe(StorageTemplateService.name, () => { oldPath: assetStub.image.originalPath, newPath, }); - when(storageMock.stat) - .calledWith(newPath) - .mockResolvedValue({ - size: 5000, - } as Stats); - when(storageMock.stat) - .calledWith(assetStub.image.originalPath) - .mockResolvedValue({ - atime: new Date(), - mtime: new Date(), - } as Stats); - when(cryptoMock.hashFile).calledWith(newPath).mockResolvedValue(assetStub.image.checksum); + storageMock.stat.mockResolvedValueOnce({ + atime: new Date(), + mtime: new Date(), + } as Stats); + storageMock.stat.mockResolvedValueOnce({ + size: 5000, + } as Stats); + storageMock.stat.mockResolvedValueOnce({ + atime: new Date(), + mtime: new Date(), + } as Stats); + cryptoMock.hashFile.mockResolvedValue(assetStub.image.checksum); await sut.handleMigration(); @@ -581,11 +514,9 @@ describe(StorageTemplateService.name, () => { oldPath: assetStub.image.originalPath, newPath: 'upload/library/user-id/2023/2023-02-23/asset-id.jpg', }); - when(storageMock.stat) - .calledWith('upload/library/user-id/2023/2023-02-23/asset-id.jpg') - .mockResolvedValue({ - size: 100, - } as Stats); + storageMock.stat.mockResolvedValue({ + size: 100, + } as Stats); await sut.handleMigration(); diff --git a/server/src/services/storage-template.service.ts b/server/src/services/storage-template.service.ts index 280c37b95b..835f5ea7d7 100644 --- a/server/src/services/storage-template.service.ts +++ b/server/src/services/storage-template.service.ts @@ -24,13 +24,13 @@ import { ICryptoRepository } from 'src/interfaces/crypto.interface'; import { DatabaseLock, IDatabaseRepository } from 'src/interfaces/database.interface'; import { ServerAsyncEvent, ServerAsyncEventMap } from 'src/interfaces/event.interface'; import { IEntityJob, JOBS_ASSET_PAGINATION_SIZE, JobStatus } from 'src/interfaces/job.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IMoveRepository } from 'src/interfaces/move.interface'; import { IPersonRepository } from 'src/interfaces/person.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface'; import { ISystemConfigRepository } from 'src/interfaces/system-config.interface'; import { IUserRepository } from 'src/interfaces/user.interface'; import { getLivePhotoMotionFilename } from 'src/utils/file'; -import { ImmichLogger } from 'src/utils/logger'; import { usePagination } from 'src/utils/pagination'; export interface MoveAssetMetadata { @@ -47,7 +47,6 @@ interface RenderMetadata { @Injectable() export class StorageTemplateService { - private logger = new ImmichLogger(StorageTemplateService.name); private configCore: SystemConfigCore; private storageCore: StorageCore; private _template: { @@ -73,16 +72,19 @@ export class StorageTemplateService { @Inject(IUserRepository) private userRepository: IUserRepository, @Inject(ICryptoRepository) cryptoRepository: ICryptoRepository, @Inject(IDatabaseRepository) private databaseRepository: IDatabaseRepository, + @Inject(ILoggerRepository) private logger: ILoggerRepository, ) { - this.configCore = SystemConfigCore.create(configRepository); + this.logger.setContext(StorageTemplateService.name); + this.configCore = SystemConfigCore.create(configRepository, this.logger); this.configCore.config$.subscribe((config) => this.onConfig(config)); this.storageCore = StorageCore.create( assetRepository, + cryptoRepository, moveRepository, personRepository, - cryptoRepository, - configRepository, storageRepository, + configRepository, + this.logger, ); } diff --git a/server/src/services/storage.service.spec.ts b/server/src/services/storage.service.spec.ts index 977f632d5e..ee574e0ba6 100644 --- a/server/src/services/storage.service.spec.ts +++ b/server/src/services/storage.service.spec.ts @@ -1,14 +1,19 @@ +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface'; import { StorageService } from 'src/services/storage.service'; +import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock'; import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock'; +import { Mocked } from 'vitest'; describe(StorageService.name, () => { let sut: StorageService; - let storageMock: jest.Mocked; + let storageMock: Mocked; + let loggerMock: Mocked; beforeEach(() => { storageMock = newStorageRepositoryMock(); - sut = new StorageService(storageMock); + loggerMock = newLoggerRepositoryMock(); + sut = new StorageService(storageMock, loggerMock); }); it('should work', () => { diff --git a/server/src/services/storage.service.ts b/server/src/services/storage.service.ts index 81fdb4f415..829888e244 100644 --- a/server/src/services/storage.service.ts +++ b/server/src/services/storage.service.ts @@ -1,14 +1,17 @@ import { Inject, Injectable } from '@nestjs/common'; import { StorageCore, StorageFolder } from 'src/cores/storage.core'; import { IDeleteFilesJob, JobStatus } from 'src/interfaces/job.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface'; -import { ImmichLogger } from 'src/utils/logger'; @Injectable() export class StorageService { - private logger = new ImmichLogger(StorageService.name); - - constructor(@Inject(IStorageRepository) private storageRepository: IStorageRepository) {} + constructor( + @Inject(IStorageRepository) private storageRepository: IStorageRepository, + @Inject(ILoggerRepository) private logger: ILoggerRepository, + ) { + this.logger.setContext(StorageService.name); + } init() { const libraryBase = StorageCore.getBaseFolder(StorageFolder.LIBRARY); diff --git a/server/src/services/sync.service.spec.ts b/server/src/services/sync.service.spec.ts new file mode 100644 index 0000000000..87205c08f1 --- /dev/null +++ b/server/src/services/sync.service.spec.ts @@ -0,0 +1,102 @@ +import { mapAsset } from 'src/dtos/asset-response.dto'; +import { AssetEntity } from 'src/entities/asset.entity'; +import { IAccessRepository } from 'src/interfaces/access.interface'; +import { IAssetRepository } from 'src/interfaces/asset.interface'; +import { IAuditRepository } from 'src/interfaces/audit.interface'; +import { IPartnerRepository } from 'src/interfaces/partner.interface'; +import { SyncService } from 'src/services/sync.service'; +import { assetStub } from 'test/fixtures/asset.stub'; +import { authStub } from 'test/fixtures/auth.stub'; +import { partnerStub } from 'test/fixtures/partner.stub'; +import { newAccessRepositoryMock } from 'test/repositories/access.repository.mock'; +import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock'; +import { newAuditRepositoryMock } from 'test/repositories/audit.repository.mock'; +import { newPartnerRepositoryMock } from 'test/repositories/partner.repository.mock'; +import { Mocked } from 'vitest'; + +const untilDate = new Date(2024); +const mapAssetOpts = { auth: authStub.user1, stripMetadata: false, withStack: true }; + +describe(SyncService.name, () => { + let sut: SyncService; + let accessMock: Mocked; + let assetMock: Mocked; + let partnerMock: Mocked; + let auditMock: Mocked; + + beforeEach(() => { + partnerMock = newPartnerRepositoryMock(); + assetMock = newAssetRepositoryMock(); + accessMock = newAccessRepositoryMock(); + auditMock = newAuditRepositoryMock(); + sut = new SyncService(accessMock, assetMock, partnerMock, auditMock); + }); + + it('should exist', () => { + expect(sut).toBeDefined(); + }); + + describe('getAllAssetsForUserFullSync', () => { + it('should return a list of all assets owned by the user', async () => { + assetMock.getAllForUserFullSync.mockResolvedValue([assetStub.external, assetStub.hasEncodedVideo]); + await expect( + sut.getAllAssetsForUserFullSync(authStub.user1, { limit: 2, updatedUntil: untilDate }), + ).resolves.toEqual([ + mapAsset(assetStub.external, mapAssetOpts), + mapAsset(assetStub.hasEncodedVideo, mapAssetOpts), + ]); + expect(assetMock.getAllForUserFullSync).toHaveBeenCalledWith({ + ownerId: authStub.user1.user.id, + updatedUntil: untilDate, + limit: 2, + }); + }); + }); + + describe('getChangesForDeltaSync', () => { + it('should return a response requiring a full sync when partners are out of sync', async () => { + partnerMock.getAll.mockResolvedValue([partnerStub.adminToUser1]); + await expect( + sut.getChangesForDeltaSync(authStub.user1, { updatedAfter: new Date(), userIds: [authStub.user1.user.id] }), + ).resolves.toEqual({ needsFullSync: true, upserted: [], deleted: [] }); + expect(assetMock.getChangedDeltaSync).toHaveBeenCalledTimes(0); + expect(auditMock.getAfter).toHaveBeenCalledTimes(0); + }); + + it('should return a response requiring a full sync when last sync was too long ago', async () => { + partnerMock.getAll.mockResolvedValue([]); + await expect( + sut.getChangesForDeltaSync(authStub.user1, { updatedAfter: new Date(2000), userIds: [authStub.user1.user.id] }), + ).resolves.toEqual({ needsFullSync: true, upserted: [], deleted: [] }); + expect(assetMock.getChangedDeltaSync).toHaveBeenCalledTimes(0); + expect(auditMock.getAfter).toHaveBeenCalledTimes(0); + }); + + it('should return a response requiring a full sync when there are too many changes', async () => { + partnerMock.getAll.mockResolvedValue([]); + assetMock.getChangedDeltaSync.mockResolvedValue( + Array.from({ length: 10_000 }).fill(assetStub.image), + ); + await expect( + sut.getChangesForDeltaSync(authStub.user1, { updatedAfter: new Date(), userIds: [authStub.user1.user.id] }), + ).resolves.toEqual({ needsFullSync: true, upserted: [], deleted: [] }); + expect(assetMock.getChangedDeltaSync).toHaveBeenCalledTimes(1); + expect(auditMock.getAfter).toHaveBeenCalledTimes(0); + }); + + it('should return a response with changes and deletions', async () => { + partnerMock.getAll.mockResolvedValue([]); + assetMock.getChangedDeltaSync.mockResolvedValue([assetStub.image1]); + auditMock.getAfter.mockResolvedValue([assetStub.external.id]); + await expect( + sut.getChangesForDeltaSync(authStub.user1, { updatedAfter: new Date(), userIds: [authStub.user1.user.id] }), + ).resolves.toEqual({ + needsFullSync: false, + upserted: [mapAsset(assetStub.image1, mapAssetOpts)], + deleted: [assetStub.external.id], + }); + expect(assetMock.getChangedDeltaSync).toHaveBeenCalledTimes(1); + expect(auditMock.getAfter).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/server/src/services/sync.service.ts b/server/src/services/sync.service.ts new file mode 100644 index 0000000000..be11d36fa0 --- /dev/null +++ b/server/src/services/sync.service.ts @@ -0,0 +1,77 @@ +import { Inject } from '@nestjs/common'; +import _ from 'lodash'; +import { DateTime } from 'luxon'; +import { AUDIT_LOG_MAX_DURATION } from 'src/constants'; +import { AccessCore, Permission } from 'src/cores/access.core'; +import { AssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto'; +import { AuthDto } from 'src/dtos/auth.dto'; +import { AssetDeltaSyncDto, AssetDeltaSyncResponseDto, AssetFullSyncDto } from 'src/dtos/sync.dto'; +import { DatabaseAction, EntityType } from 'src/entities/audit.entity'; +import { IAccessRepository } from 'src/interfaces/access.interface'; +import { IAssetRepository } from 'src/interfaces/asset.interface'; +import { IAuditRepository } from 'src/interfaces/audit.interface'; +import { IPartnerRepository } from 'src/interfaces/partner.interface'; + +export class SyncService { + private access: AccessCore; + + constructor( + @Inject(IAccessRepository) accessRepository: IAccessRepository, + @Inject(IAssetRepository) private assetRepository: IAssetRepository, + @Inject(IPartnerRepository) private partnerRepository: IPartnerRepository, + @Inject(IAuditRepository) private auditRepository: IAuditRepository, + ) { + this.access = AccessCore.create(accessRepository); + } + + async getAllAssetsForUserFullSync(auth: AuthDto, dto: AssetFullSyncDto): Promise { + const userId = dto.userId || auth.user.id; + await this.access.requirePermission(auth, Permission.TIMELINE_READ, userId); + const assets = await this.assetRepository.getAllForUserFullSync({ + ownerId: userId, + lastCreationDate: dto.lastCreationDate, + updatedUntil: dto.updatedUntil, + lastId: dto.lastId, + limit: dto.limit, + }); + const options = { auth, stripMetadata: false, withStack: true }; + return assets.map((a) => mapAsset(a, options)); + } + + async getChangesForDeltaSync(auth: AuthDto, dto: AssetDeltaSyncDto): Promise { + await this.access.requirePermission(auth, Permission.TIMELINE_READ, dto.userIds); + const partner = await this.partnerRepository.getAll(auth.user.id); + const userIds = [auth.user.id, ...partner.filter((p) => p.sharedWithId == auth.user.id).map((p) => p.sharedById)]; + userIds.sort(); + dto.userIds.sort(); + const duration = DateTime.now().diff(DateTime.fromJSDate(dto.updatedAfter)); + + if (!_.isEqual(userIds, dto.userIds) || duration > AUDIT_LOG_MAX_DURATION) { + // app does not have the correct partners synced + // or app has not synced in the last 100 days + return { needsFullSync: true, deleted: [], upserted: [] }; + } + + const limit = 10_000; + const upserted = await this.assetRepository.getChangedDeltaSync({ limit, updatedAfter: dto.updatedAfter, userIds }); + + if (upserted.length === limit) { + // too many changes -> do a full sync (paginated) instead + return { needsFullSync: true, deleted: [], upserted: [] }; + } + + const deleted = await this.auditRepository.getAfter(dto.updatedAfter, { + userIds: userIds, + entityType: EntityType.ASSET, + action: DatabaseAction.DELETE, + }); + + const options = { auth, stripMetadata: false, withStack: true }; + const result = { + needsFullSync: false, + upserted: upserted.map((a) => mapAsset(a, options)), + deleted, + }; + return result; + } +} diff --git a/server/src/services/system-config.service.spec.ts b/server/src/services/system-config.service.spec.ts index da37fe0391..49bf8d6544 100644 --- a/server/src/services/system-config.service.spec.ts +++ b/server/src/services/system-config.service.spec.ts @@ -16,12 +16,14 @@ import { } from 'src/entities/system-config.entity'; import { IEventRepository, ServerEvent } from 'src/interfaces/event.interface'; import { QueueName } from 'src/interfaces/job.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { ISearchRepository } from 'src/interfaces/search.interface'; import { ISystemConfigRepository } from 'src/interfaces/system-config.interface'; import { SystemConfigService } from 'src/services/system-config.service'; -import { ImmichLogger } from 'src/utils/logger'; import { newEventRepositoryMock } from 'test/repositories/event.repository.mock'; +import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock'; import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; +import { Mocked } from 'vitest'; const updates: SystemConfigEntity[] = [ { key: SystemConfigKey.FFMPEG_CRF, value: 30 }, @@ -154,15 +156,17 @@ const updatedConfig = Object.freeze({ describe(SystemConfigService.name, () => { let sut: SystemConfigService; - let configMock: jest.Mocked; - let eventMock: jest.Mocked; - let smartInfoMock: jest.Mocked; + let configMock: Mocked; + let eventMock: Mocked; + let loggerMock: Mocked; + let smartInfoMock: Mocked; beforeEach(() => { delete process.env.IMMICH_CONFIG_FILE; configMock = newSystemConfigRepositoryMock(); eventMock = newEventRepositoryMock(); - sut = new SystemConfigService(configMock, eventMock, smartInfoMock); + loggerMock = newLoggerRepositoryMock(); + sut = new SystemConfigService(configMock, eventMock, loggerMock, smartInfoMock); }); it('should work', () => { @@ -179,16 +183,6 @@ describe(SystemConfigService.name, () => { }); describe('getConfig', () => { - let warnLog: jest.SpyInstance; - - beforeEach(() => { - warnLog = jest.spyOn(ImmichLogger.prototype, 'warn'); - }); - - afterEach(() => { - warnLog.mockRestore(); - }); - it('should return the default config', async () => { configMock.load.mockResolvedValue([]); @@ -266,7 +260,7 @@ describe(SystemConfigService.name, () => { configMock.readFile.mockResolvedValue(partialConfig); await sut.getConfig(); - expect(warnLog).toHaveBeenCalled(); + expect(loggerMock.warn).toHaveBeenCalled(); }); const tests = [ @@ -285,7 +279,7 @@ describe(SystemConfigService.name, () => { if (test.warn) { await sut.getConfig(); - expect(warnLog).toHaveBeenCalled(); + expect(loggerMock.warn).toHaveBeenCalled(); } else { await expect(sut.getConfig()).rejects.toBeInstanceOf(Error); } diff --git a/server/src/services/system-config.service.ts b/server/src/services/system-config.service.ts index 6654498682..2203f7a687 100644 --- a/server/src/services/system-config.service.ts +++ b/server/src/services/system-config.service.ts @@ -22,21 +22,22 @@ import { ServerAsyncEventMap, ServerEvent, } from 'src/interfaces/event.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { ISearchRepository } from 'src/interfaces/search.interface'; import { ISystemConfigRepository } from 'src/interfaces/system-config.interface'; -import { ImmichLogger } from 'src/utils/logger'; @Injectable() export class SystemConfigService { - private logger = new ImmichLogger(SystemConfigService.name); private core: SystemConfigCore; constructor( @Inject(ISystemConfigRepository) private repository: ISystemConfigRepository, @Inject(IEventRepository) private eventRepository: IEventRepository, + @Inject(ILoggerRepository) private logger: ILoggerRepository, @Inject(ISearchRepository) private smartInfoRepository: ISearchRepository, ) { - this.core = SystemConfigCore.create(repository); + this.logger.setContext(SystemConfigService.name); + this.core = SystemConfigCore.create(repository, this.logger); this.core.config$.subscribe((config) => this.setLogLevel(config)); } @@ -130,7 +131,7 @@ export class SystemConfigService { const envLevel = this.getEnvLogLevel(); const configLevel = logging.enabled ? logging.level : false; const level = envLevel ?? configLevel; - ImmichLogger.setLogLevel(level); + this.logger.setLogLevel(level); this.logger.log(`LogLevel=${level} ${envLevel ? '(set via LOG_LEVEL)' : '(set via system config)'}`); } diff --git a/server/src/services/tag.service.spec.ts b/server/src/services/tag.service.spec.ts index 2d684616a3..4323c061e1 100644 --- a/server/src/services/tag.service.spec.ts +++ b/server/src/services/tag.service.spec.ts @@ -1,5 +1,4 @@ import { BadRequestException } from '@nestjs/common'; -import { when } from 'jest-when'; import { AssetIdErrorReason } from 'src/dtos/asset-ids.response.dto'; import { TagType } from 'src/entities/tag.entity'; import { ITagRepository } from 'src/interfaces/tag.interface'; @@ -8,10 +7,11 @@ import { assetStub } from 'test/fixtures/asset.stub'; import { authStub } from 'test/fixtures/auth.stub'; import { tagResponseStub, tagStub } from 'test/fixtures/tag.stub'; import { newTagRepositoryMock } from 'test/repositories/tag.repository.mock'; +import { Mocked } from 'vitest'; describe(TagService.name, () => { let sut: TagService; - let tagMock: jest.Mocked; + let tagMock: Mocked; beforeEach(() => { tagMock = newTagRepositoryMock(); @@ -129,9 +129,7 @@ describe(TagService.name, () => { it('should reject duplicate asset ids and accept new ones', async () => { tagMock.getById.mockResolvedValue(tagStub.tag1); - - when(tagMock.hasAsset).calledWith(authStub.admin.user.id, 'tag-1', 'asset-1').mockResolvedValue(true); - when(tagMock.hasAsset).calledWith(authStub.admin.user.id, 'tag-1', 'asset-2').mockResolvedValue(false); + tagMock.hasAsset.mockImplementation((userId, tagId, assetId) => Promise.resolve(assetId === 'asset-1')); await expect( sut.addAssets(authStub.admin, 'tag-1', { @@ -160,9 +158,7 @@ describe(TagService.name, () => { it('should accept accept ids that are tagged and reject the rest', async () => { tagMock.getById.mockResolvedValue(tagStub.tag1); - - when(tagMock.hasAsset).calledWith(authStub.admin.user.id, 'tag-1', 'asset-1').mockResolvedValue(true); - when(tagMock.hasAsset).calledWith(authStub.admin.user.id, 'tag-1', 'asset-2').mockResolvedValue(false); + tagMock.hasAsset.mockImplementation((userId, tagId, assetId) => Promise.resolve(assetId === 'asset-1')); await expect( sut.removeAssets(authStub.admin, 'tag-1', { diff --git a/server/src/services/timeline.service.spec.ts b/server/src/services/timeline.service.spec.ts index c6f058022f..981fc11c3f 100644 --- a/server/src/services/timeline.service.spec.ts +++ b/server/src/services/timeline.service.spec.ts @@ -7,12 +7,13 @@ import { authStub } from 'test/fixtures/auth.stub'; import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositories/access.repository.mock'; import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock'; import { newPartnerRepositoryMock } from 'test/repositories/partner.repository.mock'; +import { Mocked } from 'vitest'; describe(TimelineService.name, () => { let sut: TimelineService; let accessMock: IAccessRepositoryMock; - let assetMock: jest.Mocked; - let partnerMock: jest.Mocked; + let assetMock: Mocked; + let partnerMock: Mocked; beforeEach(() => { accessMock = newAccessRepositoryMock(); assetMock = newAssetRepositoryMock(); diff --git a/server/src/services/trash.service.spec.ts b/server/src/services/trash.service.spec.ts index ecdf577ed3..7974efed62 100644 --- a/server/src/services/trash.service.spec.ts +++ b/server/src/services/trash.service.spec.ts @@ -9,13 +9,14 @@ import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositorie import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock'; import { newEventRepositoryMock } from 'test/repositories/event.repository.mock'; import { newJobRepositoryMock } from 'test/repositories/job.repository.mock'; +import { Mocked } from 'vitest'; describe(TrashService.name, () => { let sut: TrashService; let accessMock: IAccessRepositoryMock; - let assetMock: jest.Mocked; - let jobMock: jest.Mocked; - let eventMock: jest.Mocked; + let assetMock: Mocked; + let jobMock: Mocked; + let eventMock: Mocked; it('should work', () => { expect(sut).toBeDefined(); diff --git a/server/src/services/user.service.spec.ts b/server/src/services/user.service.spec.ts index 973f644d3c..1bf4fc1012 100644 --- a/server/src/services/user.service.spec.ts +++ b/server/src/services/user.service.spec.ts @@ -4,13 +4,13 @@ import { InternalServerErrorException, NotFoundException, } from '@nestjs/common'; -import { when } from 'jest-when'; import { UpdateUserDto, mapUser } from 'src/dtos/user.dto'; import { UserEntity, UserStatus } from 'src/entities/user.entity'; import { IAlbumRepository } from 'src/interfaces/album.interface'; import { ICryptoRepository } from 'src/interfaces/crypto.interface'; import { IJobRepository, JobName } from 'src/interfaces/job.interface'; import { ILibraryRepository } from 'src/interfaces/library.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface'; import { ISystemConfigRepository } from 'src/interfaces/system-config.interface'; import { IUserRepository } from 'src/interfaces/user.interface'; @@ -23,9 +23,11 @@ import { newAlbumRepositoryMock } from 'test/repositories/album.repository.mock' import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock'; import { newJobRepositoryMock } from 'test/repositories/job.repository.mock'; import { newLibraryRepositoryMock } from 'test/repositories/library.repository.mock'; +import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock'; import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock'; import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; import { newUserRepositoryMock } from 'test/repositories/user.repository.mock'; +import { Mocked, vitest } from 'vitest'; const makeDeletedAt = (daysAgo: number) => { const deletedAt = new Date(); @@ -35,14 +37,15 @@ const makeDeletedAt = (daysAgo: number) => { describe(UserService.name, () => { let sut: UserService; - let userMock: jest.Mocked; - let cryptoRepositoryMock: jest.Mocked; + let userMock: Mocked; + let cryptoRepositoryMock: Mocked; - let albumMock: jest.Mocked; - let jobMock: jest.Mocked; - let libraryMock: jest.Mocked; - let storageMock: jest.Mocked; - let configMock: jest.Mocked; + let albumMock: Mocked; + let jobMock: Mocked; + let libraryMock: Mocked; + let storageMock: Mocked; + let configMock: Mocked; + let loggerMock: Mocked; beforeEach(() => { albumMock = newAlbumRepositoryMock(); @@ -52,13 +55,22 @@ describe(UserService.name, () => { libraryMock = newLibraryRepositoryMock(); storageMock = newStorageRepositoryMock(); userMock = newUserRepositoryMock(); + loggerMock = newLoggerRepositoryMock(); - sut = new UserService(albumMock, cryptoRepositoryMock, jobMock, libraryMock, storageMock, configMock, userMock); + sut = new UserService( + albumMock, + cryptoRepositoryMock, + jobMock, + libraryMock, + storageMock, + configMock, + userMock, + loggerMock, + ); - when(userMock.get).calledWith(authStub.admin.user.id, {}).mockResolvedValue(userStub.admin); - when(userMock.get).calledWith(authStub.admin.user.id, { withDeleted: true }).mockResolvedValue(userStub.admin); - when(userMock.get).calledWith(authStub.user1.user.id, {}).mockResolvedValue(userStub.user1); - when(userMock.get).calledWith(authStub.user1.user.id, { withDeleted: true }).mockResolvedValue(userStub.user1); + userMock.get.mockImplementation((userId) => + Promise.resolve([userStub.admin, userStub.user1].find((user) => user.id === userId) ?? null), + ); }); describe('getAll', () => { @@ -136,12 +148,10 @@ describe(UserService.name, () => { }); it('user can only update its information', async () => { - when(userMock.get) - .calledWith('not_immich_auth_user_id', {}) - .mockResolvedValueOnce({ - ...userStub.user1, - id: 'not_immich_auth_user_id', - }); + userMock.get.mockResolvedValueOnce({ + ...userStub.user1, + id: 'not_immich_auth_user_id', + }); const result = sut.update( { user: userStub.user1 }, @@ -195,7 +205,7 @@ describe(UserService.name, () => { shouldChangePassword: true, }; - when(userMock.update).calledWith(userStub.user1.id, update).mockResolvedValueOnce(userStub.user1); + userMock.update.mockResolvedValueOnce(userStub.user1); await sut.update(authStub.admin, update); expect(userMock.update).toHaveBeenCalledWith(userStub.user1.id, { id: 'user-id', @@ -204,7 +214,7 @@ describe(UserService.name, () => { }); it('update user information should throw error if user not found', async () => { - when(userMock.get).calledWith(userStub.user1.id, {}).mockResolvedValueOnce(null); + userMock.get.mockResolvedValueOnce(null); const result = sut.update(authStub.admin, { id: userStub.user1.id, @@ -217,7 +227,7 @@ describe(UserService.name, () => { it('should let the admin update himself', async () => { const dto = { id: userStub.admin.id, shouldChangePassword: true, isAdmin: true }; - when(userMock.update).calledWith(userStub.admin.id, dto).mockResolvedValueOnce(userStub.admin); + userMock.update.mockResolvedValueOnce(userStub.admin); await sut.update(authStub.admin, dto); @@ -227,7 +237,7 @@ describe(UserService.name, () => { it('should not let the another user become an admin', async () => { const dto = { id: userStub.user1.id, shouldChangePassword: true, isAdmin: true }; - when(userMock.get).calledWith(userStub.user1.id, {}).mockResolvedValueOnce(userStub.user1); + userMock.get.mockResolvedValueOnce(userStub.user1); await expect(sut.update(authStub.admin, dto)).rejects.toBeInstanceOf(BadRequestException); }); @@ -235,7 +245,7 @@ describe(UserService.name, () => { describe('restore', () => { it('should throw error if user could not be found', async () => { - when(userMock.get).calledWith(userStub.admin.id, { withDeleted: true }).mockResolvedValue(null); + userMock.get.mockResolvedValue(null); await expect(sut.restore(authStub.admin, userStub.admin.id)).rejects.toThrowError(BadRequestException); expect(userMock.update).not.toHaveBeenCalled(); }); @@ -298,7 +308,7 @@ describe(UserService.name, () => { describe('create', () => { it('should not create a user if there is no local admin account', async () => { - when(userMock.getAdmin).calledWith().mockResolvedValueOnce(null); + userMock.getAdmin.mockResolvedValueOnce(null); await expect( sut.create({ @@ -335,6 +345,7 @@ describe(UserService.name, () => { describe('createProfileImage', () => { it('should throw an error if the user does not exist', async () => { const file = { path: '/profile/path' } as Express.Multer.File; + userMock.get.mockResolvedValue(null); userMock.update.mockResolvedValue({ ...userStub.admin, profileImagePath: file.path }); await expect(sut.createProfileImage(authStub.admin, file)).rejects.toThrowError(BadRequestException); @@ -422,7 +433,7 @@ describe(UserService.name, () => { describe('resetAdminPassword', () => { it('should only work when there is an admin account', async () => { userMock.getAdmin.mockResolvedValue(null); - const ask = jest.fn().mockResolvedValue('new-password'); + const ask = vitest.fn().mockResolvedValue('new-password'); await expect(sut.resetAdminPassword(ask)).rejects.toBeInstanceOf(BadRequestException); @@ -431,7 +442,7 @@ describe(UserService.name, () => { it('should default to a random password', async () => { userMock.getAdmin.mockResolvedValue(userStub.admin); - const ask = jest.fn().mockImplementation(() => {}); + const ask = vitest.fn().mockImplementation(() => {}); const response = await sut.resetAdminPassword(ask); @@ -445,7 +456,7 @@ describe(UserService.name, () => { it('should use the supplied password', async () => { userMock.getAdmin.mockResolvedValue(userStub.admin); - const ask = jest.fn().mockResolvedValue('new-password'); + const ask = vitest.fn().mockResolvedValue('new-password'); const response = await sut.resetAdminPassword(ask); diff --git a/server/src/services/user.service.ts b/server/src/services/user.service.ts index a2bc8c7cfc..cb9012d641 100644 --- a/server/src/services/user.service.ts +++ b/server/src/services/user.service.ts @@ -11,16 +11,15 @@ import { IAlbumRepository } from 'src/interfaces/album.interface'; import { ICryptoRepository } from 'src/interfaces/crypto.interface'; import { IEntityJob, IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface'; import { ILibraryRepository } from 'src/interfaces/library.interface'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface'; import { ISystemConfigRepository } from 'src/interfaces/system-config.interface'; import { IUserRepository, UserFindOptions } from 'src/interfaces/user.interface'; import { CacheControl, ImmichFileResponse } from 'src/utils/file'; -import { ImmichLogger } from 'src/utils/logger'; @Injectable() export class UserService { private configCore: SystemConfigCore; - private logger = new ImmichLogger(UserService.name); private userCore: UserCore; constructor( @@ -31,9 +30,11 @@ export class UserService { @Inject(IStorageRepository) private storageRepository: IStorageRepository, @Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository, @Inject(IUserRepository) private userRepository: IUserRepository, + @Inject(ILoggerRepository) private logger: ILoggerRepository, ) { this.userCore = UserCore.create(cryptoRepository, libraryRepository, userRepository); - this.configCore = SystemConfigCore.create(configRepository); + this.logger.setContext(UserService.name); + this.configCore = SystemConfigCore.create(configRepository, this.logger); } async getAll(auth: AuthDto, isAll: boolean): Promise { diff --git a/server/src/utils/.media.ts.kate-swp b/server/src/utils/.media.ts.kate-swp deleted file mode 100644 index 126d6519e0..0000000000 Binary files a/server/src/utils/.media.ts.kate-swp and /dev/null differ diff --git a/server/src/utils/logger.ts b/server/src/utils/logger.ts index fef13a8fbb..05e8feb498 100644 --- a/server/src/utils/logger.ts +++ b/server/src/utils/logger.ts @@ -4,6 +4,7 @@ import { LogLevel } from 'src/entities/system-config.entity'; const LOG_LEVELS = [LogLevel.VERBOSE, LogLevel.DEBUG, LogLevel.LOG, LogLevel.WARN, LogLevel.ERROR, LogLevel.FATAL]; +// TODO move implementation to logger.repository.ts export class ImmichLogger extends ConsoleLogger { private static logLevels: LogLevel[] = [LogLevel.LOG, LogLevel.WARN, LogLevel.ERROR, LogLevel.FATAL]; diff --git a/server/src/utils/misc.ts b/server/src/utils/misc.ts index 3837c62798..c11c936a1a 100644 --- a/server/src/utils/misc.ts +++ b/server/src/utils/misc.ts @@ -17,12 +17,12 @@ import { IMMICH_API_KEY_NAME, serverVersion, } from 'src/constants'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { Metadata } from 'src/middleware/auth.guard'; -import { ImmichLogger } from 'src/utils/logger'; export const isConnectionAborted = (error: Error | any) => error.code === 'ECONNABORTED'; -export const handlePromiseError = (promise: Promise, logger: ImmichLogger): void => { +export const handlePromiseError = (promise: Promise, logger: ILoggerRepository): void => { promise.catch((error: Error | any) => logger.error(`Promise error: ${error}`, error?.stack)); }; diff --git a/server/src/utils/sql.ts b/server/src/utils/sql.ts index 662c40fcba..36b485b88a 100644 --- a/server/src/utils/sql.ts +++ b/server/src/utils/sql.ts @@ -12,6 +12,7 @@ import { format } from 'sql-formatter'; import { databaseConfig } from 'src/database.config'; import { GENERATE_SQL_KEY, GenerateSqlQueries } from 'src/decorators'; import { entities } from 'src/entities'; +import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { repositories } from 'src/repositories'; import { AccessRepository } from 'src/repositories/access.repository'; import { AuthService } from 'src/services/auth.service'; @@ -58,6 +59,9 @@ class SqlGenerator { try { await this.setup(); for (const repository of repositories) { + if (repository.provide === ILoggerRepository) { + continue; + } await this.process(repository); } await this.write(); diff --git a/server/test/fixtures/asset.stub.ts b/server/test/fixtures/asset.stub.ts index 0b2ff82a3d..7aa49866d0 100644 --- a/server/test/fixtures/asset.stub.ts +++ b/server/test/fixtures/asset.stub.ts @@ -225,7 +225,7 @@ export const assetStub = { deviceId: 'device-id', originalPath: '/data/user1/photo.jpg', previewPath: '/uploads/user-id/thumbs/path.jpg', - checksum: Buffer.from('file hash', 'utf8'), + checksum: Buffer.from('path hash', 'utf8'), type: AssetType.IMAGE, thumbnailPath: '/uploads/user-id/webp/path.ext', thumbhash: Buffer.from('blablabla', 'base64'), @@ -295,6 +295,46 @@ export const assetStub = { deletedAt: null, }), + externalOffline: Object.freeze({ + id: 'asset-id', + deviceAssetId: 'device-asset-id', + fileModifiedAt: new Date('2023-02-23T05:06:29.716Z'), + fileCreatedAt: new Date('2023-02-23T05:06:29.716Z'), + owner: userStub.user1, + ownerId: 'user-id', + deviceId: 'device-id', + originalPath: '/data/user1/photo.jpg', + previewPath: '/uploads/user-id/thumbs/path.jpg', + checksum: Buffer.from('path hash', 'utf8'), + type: AssetType.IMAGE, + thumbnailPath: '/uploads/user-id/webp/path.ext', + thumbhash: Buffer.from('blablabla', 'base64'), + encodedVideoPath: null, + createdAt: new Date('2023-02-23T05:06:29.716Z'), + updatedAt: new Date('2023-02-23T05:06:29.716Z'), + localDateTime: new Date('2023-02-23T05:06:29.716Z'), + isFavorite: true, + isArchived: false, + isReadOnly: false, + isExternal: true, + duration: null, + isVisible: true, + livePhotoVideo: null, + livePhotoVideoId: null, + isOffline: true, + libraryId: 'library-id', + library: libraryStub.externalLibrary1, + tags: [], + sharedLinks: [], + originalFileName: 'asset-id.jpg', + faces: [], + sidecarPath: null, + exifInfo: { + fileSizeInByte: 5000, + } as ExifEntity, + deletedAt: null, + }), + image1: Object.freeze({ id: 'asset-id-1', deviceAssetId: 'device-asset-id', diff --git a/server/test/repositories/access.repository.mock.ts b/server/test/repositories/access.repository.mock.ts index fe7de7c833..21d298599f 100644 --- a/server/test/repositories/access.repository.mock.ts +++ b/server/test/repositories/access.repository.mock.ts @@ -1,16 +1,17 @@ import { AccessCore } from 'src/cores/access.core'; import { IAccessRepository } from 'src/interfaces/access.interface'; +import { Mocked, vitest } from 'vitest'; export interface IAccessRepositoryMock { - activity: jest.Mocked; - asset: jest.Mocked; - album: jest.Mocked; - authDevice: jest.Mocked; - library: jest.Mocked; - timeline: jest.Mocked; - memory: jest.Mocked; - person: jest.Mocked; - partner: jest.Mocked; + activity: Mocked; + asset: Mocked; + album: Mocked; + authDevice: Mocked; + library: Mocked; + timeline: Mocked; + memory: Mocked; + person: Mocked; + partner: Mocked; } export const newAccessRepositoryMock = (reset = true): IAccessRepositoryMock => { @@ -20,47 +21,47 @@ export const newAccessRepositoryMock = (reset = true): IAccessRepositoryMock => return { activity: { - checkOwnerAccess: jest.fn().mockResolvedValue(new Set()), - checkAlbumOwnerAccess: jest.fn().mockResolvedValue(new Set()), - checkCreateAccess: jest.fn().mockResolvedValue(new Set()), + checkOwnerAccess: vitest.fn().mockResolvedValue(new Set()), + checkAlbumOwnerAccess: vitest.fn().mockResolvedValue(new Set()), + checkCreateAccess: vitest.fn().mockResolvedValue(new Set()), }, asset: { - checkOwnerAccess: jest.fn().mockResolvedValue(new Set()), - checkAlbumAccess: jest.fn().mockResolvedValue(new Set()), - checkPartnerAccess: jest.fn().mockResolvedValue(new Set()), - checkSharedLinkAccess: jest.fn().mockResolvedValue(new Set()), + checkOwnerAccess: vitest.fn().mockResolvedValue(new Set()), + checkAlbumAccess: vitest.fn().mockResolvedValue(new Set()), + checkPartnerAccess: vitest.fn().mockResolvedValue(new Set()), + checkSharedLinkAccess: vitest.fn().mockResolvedValue(new Set()), }, album: { - checkOwnerAccess: jest.fn().mockResolvedValue(new Set()), - checkSharedAlbumAccess: jest.fn().mockResolvedValue(new Set()), - checkSharedLinkAccess: jest.fn().mockResolvedValue(new Set()), + checkOwnerAccess: vitest.fn().mockResolvedValue(new Set()), + checkSharedAlbumAccess: vitest.fn().mockResolvedValue(new Set()), + checkSharedLinkAccess: vitest.fn().mockResolvedValue(new Set()), }, authDevice: { - checkOwnerAccess: jest.fn().mockResolvedValue(new Set()), + checkOwnerAccess: vitest.fn().mockResolvedValue(new Set()), }, library: { - checkOwnerAccess: jest.fn().mockResolvedValue(new Set()), + checkOwnerAccess: vitest.fn().mockResolvedValue(new Set()), }, timeline: { - checkPartnerAccess: jest.fn().mockResolvedValue(new Set()), + checkPartnerAccess: vitest.fn().mockResolvedValue(new Set()), }, memory: { - checkOwnerAccess: jest.fn().mockResolvedValue(new Set()), + checkOwnerAccess: vitest.fn().mockResolvedValue(new Set()), }, person: { - checkFaceOwnerAccess: jest.fn().mockResolvedValue(new Set()), - checkOwnerAccess: jest.fn().mockResolvedValue(new Set()), + checkFaceOwnerAccess: vitest.fn().mockResolvedValue(new Set()), + checkOwnerAccess: vitest.fn().mockResolvedValue(new Set()), }, partner: { - checkUpdateAccess: jest.fn().mockResolvedValue(new Set()), + checkUpdateAccess: vitest.fn().mockResolvedValue(new Set()), }, }; }; diff --git a/server/test/repositories/activity.repository.mock.ts b/server/test/repositories/activity.repository.mock.ts index 276b57c6cb..9d29d90ab8 100644 --- a/server/test/repositories/activity.repository.mock.ts +++ b/server/test/repositories/activity.repository.mock.ts @@ -1,10 +1,11 @@ import { IActivityRepository } from 'src/interfaces/activity.interface'; +import { Mocked, vitest } from 'vitest'; -export const newActivityRepositoryMock = (): jest.Mocked => { +export const newActivityRepositoryMock = (): Mocked => { return { - search: jest.fn(), - create: jest.fn(), - delete: jest.fn(), - getStatistics: jest.fn(), + search: vitest.fn(), + create: vitest.fn(), + delete: vitest.fn(), + getStatistics: vitest.fn(), }; }; diff --git a/server/test/repositories/album.repository.mock.ts b/server/test/repositories/album.repository.mock.ts index 38db70e4b8..9b852d7393 100644 --- a/server/test/repositories/album.repository.mock.ts +++ b/server/test/repositories/album.repository.mock.ts @@ -1,27 +1,28 @@ import { IAlbumRepository } from 'src/interfaces/album.interface'; +import { Mocked, vitest } from 'vitest'; -export const newAlbumRepositoryMock = (): jest.Mocked => { +export const newAlbumRepositoryMock = (): Mocked => { return { - getById: jest.fn(), - getByIds: jest.fn(), - getByAssetId: jest.fn(), - getMetadataForIds: jest.fn(), - getInvalidThumbnail: jest.fn(), - getOwned: jest.fn(), - getShared: jest.fn(), - getNotShared: jest.fn(), - restoreAll: jest.fn(), - softDeleteAll: jest.fn(), - deleteAll: jest.fn(), - getAll: jest.fn(), - addAssetIds: jest.fn(), - removeAsset: jest.fn(), - removeAssetIds: jest.fn(), - getAssetIds: jest.fn(), - hasAsset: jest.fn(), - create: jest.fn(), - update: jest.fn(), - delete: jest.fn(), - updateThumbnails: jest.fn(), + getById: vitest.fn(), + getByIds: vitest.fn(), + getByAssetId: vitest.fn(), + getMetadataForIds: vitest.fn(), + getInvalidThumbnail: vitest.fn(), + getOwned: vitest.fn(), + getShared: vitest.fn(), + getNotShared: vitest.fn(), + restoreAll: vitest.fn(), + softDeleteAll: vitest.fn(), + deleteAll: vitest.fn(), + getAll: vitest.fn(), + addAssetIds: vitest.fn(), + removeAsset: vitest.fn(), + removeAssetIds: vitest.fn(), + getAssetIds: vitest.fn(), + hasAsset: vitest.fn(), + create: vitest.fn(), + update: vitest.fn(), + delete: vitest.fn(), + updateThumbnails: vitest.fn(), }; }; diff --git a/server/test/repositories/api-key.repository.mock.ts b/server/test/repositories/api-key.repository.mock.ts index 32b8388a36..a7cfb6369a 100644 --- a/server/test/repositories/api-key.repository.mock.ts +++ b/server/test/repositories/api-key.repository.mock.ts @@ -1,12 +1,13 @@ import { IKeyRepository } from 'src/interfaces/api-key.interface'; +import { Mocked, vitest } from 'vitest'; -export const newKeyRepositoryMock = (): jest.Mocked => { +export const newKeyRepositoryMock = (): Mocked => { return { - create: jest.fn(), - update: jest.fn(), - delete: jest.fn(), - getKey: jest.fn(), - getById: jest.fn(), - getByUserId: jest.fn(), + create: vitest.fn(), + update: vitest.fn(), + delete: vitest.fn(), + getKey: vitest.fn(), + getById: vitest.fn(), + getByUserId: vitest.fn(), }; }; diff --git a/server/test/repositories/asset-stack.repository.mock.ts b/server/test/repositories/asset-stack.repository.mock.ts index 76ada96cdc..6106f8c997 100644 --- a/server/test/repositories/asset-stack.repository.mock.ts +++ b/server/test/repositories/asset-stack.repository.mock.ts @@ -1,10 +1,11 @@ import { IAssetStackRepository } from 'src/interfaces/asset-stack.interface'; +import { Mocked, vitest } from 'vitest'; -export const newAssetStackRepositoryMock = (): jest.Mocked => { +export const newAssetStackRepositoryMock = (): Mocked => { return { - create: jest.fn(), - update: jest.fn(), - delete: jest.fn(), - getById: jest.fn(), + create: vitest.fn(), + update: vitest.fn(), + delete: vitest.fn(), + getById: vitest.fn(), }; }; diff --git a/server/test/repositories/asset.repository.mock.ts b/server/test/repositories/asset.repository.mock.ts index 67770cd93b..694fc87cc2 100644 --- a/server/test/repositories/asset.repository.mock.ts +++ b/server/test/repositories/asset.repository.mock.ts @@ -1,39 +1,42 @@ import { IAssetRepository } from 'src/interfaces/asset.interface'; +import { Mocked, vitest } from 'vitest'; -export const newAssetRepositoryMock = (): jest.Mocked => { +export const newAssetRepositoryMock = (): Mocked => { return { - create: jest.fn(), - upsertExif: jest.fn(), - upsertJobStatus: jest.fn(), - getByDayOfYear: jest.fn(), - getByIds: jest.fn().mockResolvedValue([]), - getByIdsWithAllRelations: jest.fn().mockResolvedValue([]), - getByAlbumId: jest.fn(), - getByUserId: jest.fn(), - getById: jest.fn(), - getWithout: jest.fn(), - getByChecksum: jest.fn(), - getWith: jest.fn(), - getRandom: jest.fn(), - getFirstAssetForAlbumId: jest.fn(), - getLastUpdatedAssetForAlbumId: jest.fn(), - getAll: jest.fn().mockResolvedValue({ items: [], hasNextPage: false }), - getAllByDeviceId: jest.fn(), - updateAll: jest.fn(), - getLibraryAssetPaths: jest.fn(), - getByLibraryIdAndOriginalPath: jest.fn(), - deleteAll: jest.fn(), - update: jest.fn(), - remove: jest.fn(), - findLivePhotoMatch: jest.fn(), - getMapMarkers: jest.fn(), - getStatistics: jest.fn(), - getTimeBucket: jest.fn(), - getTimeBuckets: jest.fn(), - restoreAll: jest.fn(), - softDeleteAll: jest.fn(), - getAssetIdByCity: jest.fn(), - getAssetIdByTag: jest.fn(), - searchMetadata: jest.fn(), + create: vitest.fn(), + upsertExif: vitest.fn(), + upsertJobStatus: vitest.fn(), + getByDayOfYear: vitest.fn(), + getByIds: vitest.fn().mockResolvedValue([]), + getByIdsWithAllRelations: vitest.fn().mockResolvedValue([]), + getByAlbumId: vitest.fn(), + getByUserId: vitest.fn(), + getById: vitest.fn(), + getWithout: vitest.fn(), + getByChecksum: vitest.fn(), + getWith: vitest.fn(), + getRandom: vitest.fn(), + getFirstAssetForAlbumId: vitest.fn(), + getLastUpdatedAssetForAlbumId: vitest.fn(), + getAll: vitest.fn().mockResolvedValue({ items: [], hasNextPage: false }), + getAllByDeviceId: vitest.fn(), + updateAll: vitest.fn(), + getExternalLibraryAssetPaths: vitest.fn(), + getByLibraryIdAndOriginalPath: vitest.fn(), + deleteAll: vitest.fn(), + update: vitest.fn(), + remove: vitest.fn(), + findLivePhotoMatch: vitest.fn(), + getMapMarkers: vitest.fn(), + getStatistics: vitest.fn(), + getTimeBucket: vitest.fn(), + getTimeBuckets: vitest.fn(), + restoreAll: vitest.fn(), + softDeleteAll: vitest.fn(), + getAssetIdByCity: vitest.fn(), + getAssetIdByTag: vitest.fn(), + searchMetadata: vitest.fn(), + getAllForUserFullSync: vitest.fn(), + getChangedDeltaSync: vitest.fn(), }; }; diff --git a/server/test/repositories/audit.repository.mock.ts b/server/test/repositories/audit.repository.mock.ts index 9e4adf5608..13af834ce9 100644 --- a/server/test/repositories/audit.repository.mock.ts +++ b/server/test/repositories/audit.repository.mock.ts @@ -1,8 +1,9 @@ import { IAuditRepository } from 'src/interfaces/audit.interface'; +import { Mocked, vitest } from 'vitest'; -export const newAuditRepositoryMock = (): jest.Mocked => { +export const newAuditRepositoryMock = (): Mocked => { return { - getAfter: jest.fn(), - removeBefore: jest.fn(), + getAfter: vitest.fn(), + removeBefore: vitest.fn(), }; }; diff --git a/server/test/repositories/crypto.repository.mock.ts b/server/test/repositories/crypto.repository.mock.ts index 8d13814db9..98641be8bc 100644 --- a/server/test/repositories/crypto.repository.mock.ts +++ b/server/test/repositories/crypto.repository.mock.ts @@ -1,14 +1,15 @@ import { ICryptoRepository } from 'src/interfaces/crypto.interface'; +import { Mocked, vitest } from 'vitest'; -export const newCryptoRepositoryMock = (): jest.Mocked => { +export const newCryptoRepositoryMock = (): Mocked => { return { - randomUUID: jest.fn().mockReturnValue('random-uuid'), - randomBytes: jest.fn().mockReturnValue(Buffer.from('random-bytes', 'utf8')), - compareBcrypt: jest.fn().mockReturnValue(true), - hashBcrypt: jest.fn().mockImplementation((input) => Promise.resolve(`${input} (hashed)`)), - hashSha256: jest.fn().mockImplementation((input) => `${input} (hashed)`), - hashSha1: jest.fn().mockImplementation((input) => Buffer.from(`${input.toString()} (hashed)`)), - hashFile: jest.fn().mockImplementation((input) => `${input} (file-hashed)`), - newPassword: jest.fn().mockReturnValue(Buffer.from('random-bytes').toString('base64')), + randomUUID: vitest.fn().mockReturnValue('random-uuid'), + randomBytes: vitest.fn().mockReturnValue(Buffer.from('random-bytes', 'utf8')), + compareBcrypt: vitest.fn().mockReturnValue(true), + hashBcrypt: vitest.fn().mockImplementation((input) => Promise.resolve(`${input} (hashed)`)), + hashSha256: vitest.fn().mockImplementation((input) => `${input} (hashed)`), + hashSha1: vitest.fn().mockImplementation((input) => Buffer.from(`${input.toString()} (hashed)`)), + hashFile: vitest.fn().mockImplementation((input) => `${input} (file-hashed)`), + newPassword: vitest.fn().mockReturnValue(Buffer.from('random-bytes').toString('base64')), }; }; diff --git a/server/test/repositories/database.repository.mock.ts b/server/test/repositories/database.repository.mock.ts index 704189571a..b4109a8375 100644 --- a/server/test/repositories/database.repository.mock.ts +++ b/server/test/repositories/database.repository.mock.ts @@ -1,21 +1,22 @@ import { IDatabaseRepository } from 'src/interfaces/database.interface'; import { Version } from 'src/utils/version'; +import { Mocked, vitest } from 'vitest'; -export const newDatabaseRepositoryMock = (): jest.Mocked => { +export const newDatabaseRepositoryMock = (): Mocked => { return { - getExtensionVersion: jest.fn(), - getAvailableExtensionVersion: jest.fn(), - getPreferredVectorExtension: jest.fn(), - getPostgresVersion: jest.fn().mockResolvedValue(new Version(14, 0, 0)), - createExtension: jest.fn().mockImplementation(() => Promise.resolve()), - updateExtension: jest.fn(), - updateVectorExtension: jest.fn(), - reindex: jest.fn(), - shouldReindex: jest.fn(), - runMigrations: jest.fn(), - withLock: jest.fn().mockImplementation((_, function_: () => Promise) => function_()), - tryLock: jest.fn(), - isBusy: jest.fn(), - wait: jest.fn(), + getExtensionVersion: vitest.fn(), + getAvailableExtensionVersion: vitest.fn(), + getPreferredVectorExtension: vitest.fn(), + getPostgresVersion: vitest.fn().mockResolvedValue(new Version(14, 0, 0)), + createExtension: vitest.fn().mockImplementation(() => Promise.resolve()), + updateExtension: vitest.fn(), + updateVectorExtension: vitest.fn(), + reindex: vitest.fn(), + shouldReindex: vitest.fn(), + runMigrations: vitest.fn(), + withLock: vitest.fn().mockImplementation((_, function_: () => Promise) => function_()), + tryLock: vitest.fn(), + isBusy: vitest.fn(), + wait: vitest.fn(), }; }; diff --git a/server/test/repositories/event.repository.mock.ts b/server/test/repositories/event.repository.mock.ts index b21d4a59ec..2277fec83b 100644 --- a/server/test/repositories/event.repository.mock.ts +++ b/server/test/repositories/event.repository.mock.ts @@ -1,10 +1,11 @@ import { IEventRepository } from 'src/interfaces/event.interface'; +import { Mocked, vitest } from 'vitest'; -export const newEventRepositoryMock = (): jest.Mocked => { +export const newEventRepositoryMock = (): Mocked => { return { - clientSend: jest.fn(), - clientBroadcast: jest.fn(), - serverSend: jest.fn(), - serverSendAsync: jest.fn(), + clientSend: vitest.fn(), + clientBroadcast: vitest.fn(), + serverSend: vitest.fn(), + serverSendAsync: vitest.fn(), }; }; diff --git a/server/test/repositories/job.repository.mock.ts b/server/test/repositories/job.repository.mock.ts index 9cd21fe874..6bffe184fd 100644 --- a/server/test/repositories/job.repository.mock.ts +++ b/server/test/repositories/job.repository.mock.ts @@ -1,20 +1,21 @@ import { IJobRepository } from 'src/interfaces/job.interface'; +import { Mocked, vitest } from 'vitest'; -export const newJobRepositoryMock = (): jest.Mocked => { +export const newJobRepositoryMock = (): Mocked => { return { - addHandler: jest.fn(), - addCronJob: jest.fn(), - deleteCronJob: jest.fn(), - updateCronJob: jest.fn(), - setConcurrency: jest.fn(), - empty: jest.fn(), - pause: jest.fn(), - resume: jest.fn(), - queue: jest.fn().mockImplementation(() => Promise.resolve()), - queueAll: jest.fn().mockImplementation(() => Promise.resolve()), - getQueueStatus: jest.fn(), - getJobCounts: jest.fn(), - clear: jest.fn(), - waitForQueueCompletion: jest.fn(), + addHandler: vitest.fn(), + addCronJob: vitest.fn(), + deleteCronJob: vitest.fn(), + updateCronJob: vitest.fn(), + setConcurrency: vitest.fn(), + empty: vitest.fn(), + pause: vitest.fn(), + resume: vitest.fn(), + queue: vitest.fn().mockImplementation(() => Promise.resolve()), + queueAll: vitest.fn().mockImplementation(() => Promise.resolve()), + getQueueStatus: vitest.fn(), + getJobCounts: vitest.fn(), + clear: vitest.fn(), + waitForQueueCompletion: vitest.fn(), }; }; diff --git a/server/test/repositories/library.repository.mock.ts b/server/test/repositories/library.repository.mock.ts index 6cdfb38f41..4280619862 100644 --- a/server/test/repositories/library.repository.mock.ts +++ b/server/test/repositories/library.repository.mock.ts @@ -1,18 +1,19 @@ import { ILibraryRepository } from 'src/interfaces/library.interface'; +import { Mocked, vitest } from 'vitest'; -export const newLibraryRepositoryMock = (): jest.Mocked => { +export const newLibraryRepositoryMock = (): Mocked => { return { - get: jest.fn(), - getCountForUser: jest.fn(), - create: jest.fn(), - delete: jest.fn(), - softDelete: jest.fn(), - update: jest.fn(), - getStatistics: jest.fn(), - getDefaultUploadLibrary: jest.fn(), - getUploadLibraryCount: jest.fn(), - getAssetIds: jest.fn(), - getAllDeleted: jest.fn(), - getAll: jest.fn(), + get: vitest.fn(), + getCountForUser: vitest.fn(), + create: vitest.fn(), + delete: vitest.fn(), + softDelete: vitest.fn(), + update: vitest.fn(), + getStatistics: vitest.fn(), + getDefaultUploadLibrary: vitest.fn(), + getUploadLibraryCount: vitest.fn(), + getAssetIds: vitest.fn(), + getAllDeleted: vitest.fn(), + getAll: vitest.fn(), }; }; diff --git a/server/test/repositories/logger.repository.mock.ts b/server/test/repositories/logger.repository.mock.ts new file mode 100644 index 0000000000..a8537bb2fa --- /dev/null +++ b/server/test/repositories/logger.repository.mock.ts @@ -0,0 +1,16 @@ +import { ILoggerRepository } from 'src/interfaces/logger.interface'; +import { Mocked, vitest } from 'vitest'; + +export const newLoggerRepositoryMock = (): Mocked => { + return { + setLogLevel: vitest.fn(), + setContext: vitest.fn(), + + verbose: vitest.fn(), + debug: vitest.fn(), + log: vitest.fn(), + warn: vitest.fn(), + error: vitest.fn(), + fatal: vitest.fn(), + }; +}; diff --git a/server/test/repositories/machine-learning.repository.mock.ts b/server/test/repositories/machine-learning.repository.mock.ts index bc35b4c855..9dd1bdca29 100644 --- a/server/test/repositories/machine-learning.repository.mock.ts +++ b/server/test/repositories/machine-learning.repository.mock.ts @@ -1,9 +1,10 @@ import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface'; +import { Mocked, vitest } from 'vitest'; -export const newMachineLearningRepositoryMock = (): jest.Mocked => { +export const newMachineLearningRepositoryMock = (): Mocked => { return { - encodeImage: jest.fn(), - encodeText: jest.fn(), - detectFaces: jest.fn(), + encodeImage: vitest.fn(), + encodeText: vitest.fn(), + detectFaces: vitest.fn(), }; }; diff --git a/server/test/repositories/media.repository.mock.ts b/server/test/repositories/media.repository.mock.ts index b904766ea9..2eea47b6ac 100644 --- a/server/test/repositories/media.repository.mock.ts +++ b/server/test/repositories/media.repository.mock.ts @@ -1,11 +1,12 @@ import { IMediaRepository } from 'src/interfaces/media.interface'; +import { Mocked, vitest } from 'vitest'; -export const newMediaRepositoryMock = (): jest.Mocked => { +export const newMediaRepositoryMock = (): Mocked => { return { - generateThumbhash: jest.fn(), - resize: jest.fn(), - crop: jest.fn(), - probe: jest.fn(), - transcode: jest.fn(), + generateThumbhash: vitest.fn(), + resize: vitest.fn(), + crop: vitest.fn(), + probe: vitest.fn(), + transcode: vitest.fn(), }; }; diff --git a/server/test/repositories/memory.repository.mock.ts b/server/test/repositories/memory.repository.mock.ts index 85b17a1985..fc3c968767 100644 --- a/server/test/repositories/memory.repository.mock.ts +++ b/server/test/repositories/memory.repository.mock.ts @@ -1,14 +1,15 @@ import { IMemoryRepository } from 'src/interfaces/memory.interface'; +import { Mocked, vitest } from 'vitest'; -export const newMemoryRepositoryMock = (): jest.Mocked => { +export const newMemoryRepositoryMock = (): Mocked => { return { - search: jest.fn().mockResolvedValue([]), - get: jest.fn(), - create: jest.fn(), - update: jest.fn(), - delete: jest.fn(), - getAssetIds: jest.fn().mockResolvedValue(new Set()), - addAssetIds: jest.fn(), - removeAssetIds: jest.fn(), + search: vitest.fn().mockResolvedValue([]), + get: vitest.fn(), + create: vitest.fn(), + update: vitest.fn(), + delete: vitest.fn(), + getAssetIds: vitest.fn().mockResolvedValue(new Set()), + addAssetIds: vitest.fn(), + removeAssetIds: vitest.fn(), }; }; diff --git a/server/test/repositories/metadata.repository.mock.ts b/server/test/repositories/metadata.repository.mock.ts index ec21ab8c15..80d6bf121c 100644 --- a/server/test/repositories/metadata.repository.mock.ts +++ b/server/test/repositories/metadata.repository.mock.ts @@ -1,17 +1,18 @@ import { IMetadataRepository } from 'src/interfaces/metadata.interface'; +import { Mocked, vitest } from 'vitest'; -export const newMetadataRepositoryMock = (): jest.Mocked => { +export const newMetadataRepositoryMock = (): Mocked => { return { - init: jest.fn(), - teardown: jest.fn(), - reverseGeocode: jest.fn(), - readTags: jest.fn(), - writeTags: jest.fn(), - extractBinaryTag: jest.fn(), - getCameraMakes: jest.fn(), - getCameraModels: jest.fn(), - getCities: jest.fn(), - getCountries: jest.fn(), - getStates: jest.fn(), + init: vitest.fn(), + teardown: vitest.fn(), + reverseGeocode: vitest.fn(), + readTags: vitest.fn(), + writeTags: vitest.fn(), + extractBinaryTag: vitest.fn(), + getCameraMakes: vitest.fn(), + getCameraModels: vitest.fn(), + getCities: vitest.fn(), + getCountries: vitest.fn(), + getStates: vitest.fn(), }; }; diff --git a/server/test/repositories/metric.repository.mock.ts b/server/test/repositories/metric.repository.mock.ts index 383845d345..e2c3e2aac1 100644 --- a/server/test/repositories/metric.repository.mock.ts +++ b/server/test/repositories/metric.repository.mock.ts @@ -1,30 +1,31 @@ import { IMetricRepository } from 'src/interfaces/metric.interface'; +import { Mocked, vitest } from 'vitest'; -export const newMetricRepositoryMock = (): jest.Mocked => { +export const newMetricRepositoryMock = (): Mocked => { return { api: { - addToCounter: jest.fn(), - addToGauge: jest.fn(), - addToHistogram: jest.fn(), - configure: jest.fn(), + addToCounter: vitest.fn(), + addToGauge: vitest.fn(), + addToHistogram: vitest.fn(), + configure: vitest.fn(), }, host: { - addToCounter: jest.fn(), - addToGauge: jest.fn(), - addToHistogram: jest.fn(), - configure: jest.fn(), + addToCounter: vitest.fn(), + addToGauge: vitest.fn(), + addToHistogram: vitest.fn(), + configure: vitest.fn(), }, jobs: { - addToCounter: jest.fn(), - addToGauge: jest.fn(), - addToHistogram: jest.fn(), - configure: jest.fn(), + addToCounter: vitest.fn(), + addToGauge: vitest.fn(), + addToHistogram: vitest.fn(), + configure: vitest.fn(), }, repo: { - addToCounter: jest.fn(), - addToGauge: jest.fn(), - addToHistogram: jest.fn(), - configure: jest.fn(), + addToCounter: vitest.fn(), + addToGauge: vitest.fn(), + addToHistogram: vitest.fn(), + configure: vitest.fn(), }, }; }; diff --git a/server/test/repositories/move.repository.mock.ts b/server/test/repositories/move.repository.mock.ts index b7adec2a7b..1f982a048d 100644 --- a/server/test/repositories/move.repository.mock.ts +++ b/server/test/repositories/move.repository.mock.ts @@ -1,10 +1,11 @@ import { IMoveRepository } from 'src/interfaces/move.interface'; +import { Mocked, vitest } from 'vitest'; -export const newMoveRepositoryMock = (): jest.Mocked => { +export const newMoveRepositoryMock = (): Mocked => { return { - create: jest.fn(), - getByEntity: jest.fn(), - update: jest.fn(), - delete: jest.fn(), + create: vitest.fn(), + getByEntity: vitest.fn(), + update: vitest.fn(), + delete: vitest.fn(), }; }; diff --git a/server/test/repositories/partner.repository.mock.ts b/server/test/repositories/partner.repository.mock.ts index 04370730b1..e16bb6ffdc 100644 --- a/server/test/repositories/partner.repository.mock.ts +++ b/server/test/repositories/partner.repository.mock.ts @@ -1,11 +1,12 @@ import { IPartnerRepository } from 'src/interfaces/partner.interface'; +import { Mocked, vitest } from 'vitest'; -export const newPartnerRepositoryMock = (): jest.Mocked => { +export const newPartnerRepositoryMock = (): Mocked => { return { - create: jest.fn(), - remove: jest.fn(), - getAll: jest.fn(), - get: jest.fn(), - update: jest.fn(), + create: vitest.fn(), + remove: vitest.fn(), + getAll: vitest.fn(), + get: vitest.fn(), + update: vitest.fn(), }; }; diff --git a/server/test/repositories/person.repository.mock.ts b/server/test/repositories/person.repository.mock.ts index 5b94fbc3d2..5909e3c967 100644 --- a/server/test/repositories/person.repository.mock.ts +++ b/server/test/repositories/person.repository.mock.ts @@ -1,32 +1,33 @@ import { IPersonRepository } from 'src/interfaces/person.interface'; +import { Mocked, vitest } from 'vitest'; -export const newPersonRepositoryMock = (): jest.Mocked => { +export const newPersonRepositoryMock = (): Mocked => { return { - getById: jest.fn(), - getAll: jest.fn(), - getAllForUser: jest.fn(), - getAssets: jest.fn(), - getAllWithoutFaces: jest.fn(), + getById: vitest.fn(), + getAll: vitest.fn(), + getAllForUser: vitest.fn(), + getAssets: vitest.fn(), + getAllWithoutFaces: vitest.fn(), - getByName: jest.fn(), + getByName: vitest.fn(), - create: jest.fn(), - update: jest.fn(), - deleteAll: jest.fn(), - delete: jest.fn(), - deleteAllFaces: jest.fn(), + create: vitest.fn(), + update: vitest.fn(), + deleteAll: vitest.fn(), + delete: vitest.fn(), + deleteAllFaces: vitest.fn(), - getStatistics: jest.fn(), - getAllFaces: jest.fn(), - getFacesByIds: jest.fn(), - getRandomFace: jest.fn(), + getStatistics: vitest.fn(), + getAllFaces: vitest.fn(), + getFacesByIds: vitest.fn(), + getRandomFace: vitest.fn(), - reassignFaces: jest.fn(), - createFaces: jest.fn(), - getFaces: jest.fn(), - reassignFace: jest.fn(), - getFaceById: jest.fn(), - getFaceByIdWithAssets: jest.fn(), - getNumberOfPeople: jest.fn(), + reassignFaces: vitest.fn(), + createFaces: vitest.fn(), + getFaces: vitest.fn(), + reassignFace: vitest.fn(), + getFaceById: vitest.fn(), + getFaceByIdWithAssets: vitest.fn(), + getNumberOfPeople: vitest.fn(), }; }; diff --git a/server/test/repositories/search.repository.mock.ts b/server/test/repositories/search.repository.mock.ts index 24e648ee2b..d43b2b9ce9 100644 --- a/server/test/repositories/search.repository.mock.ts +++ b/server/test/repositories/search.repository.mock.ts @@ -1,14 +1,15 @@ import { ISearchRepository } from 'src/interfaces/search.interface'; +import { Mocked, vitest } from 'vitest'; -export const newSearchRepositoryMock = (): jest.Mocked => { +export const newSearchRepositoryMock = (): Mocked => { return { - init: jest.fn(), - searchMetadata: jest.fn(), - searchSmart: jest.fn(), - searchFaces: jest.fn(), - upsert: jest.fn(), - searchPlaces: jest.fn(), - getAssetsByCity: jest.fn(), - deleteAllSearchEmbeddings: jest.fn(), + init: vitest.fn(), + searchMetadata: vitest.fn(), + searchSmart: vitest.fn(), + searchFaces: vitest.fn(), + upsert: vitest.fn(), + searchPlaces: vitest.fn(), + getAssetsByCity: vitest.fn(), + deleteAllSearchEmbeddings: vitest.fn(), }; }; diff --git a/server/test/repositories/shared-link.repository.mock.ts b/server/test/repositories/shared-link.repository.mock.ts index 2fcaf7aee4..251b38d5d7 100644 --- a/server/test/repositories/shared-link.repository.mock.ts +++ b/server/test/repositories/shared-link.repository.mock.ts @@ -1,12 +1,13 @@ import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface'; +import { Mocked, vitest } from 'vitest'; -export const newSharedLinkRepositoryMock = (): jest.Mocked => { +export const newSharedLinkRepositoryMock = (): Mocked => { return { - getAll: jest.fn(), - get: jest.fn(), - getByKey: jest.fn(), - create: jest.fn(), - remove: jest.fn(), - update: jest.fn(), + getAll: vitest.fn(), + get: vitest.fn(), + getByKey: vitest.fn(), + create: vitest.fn(), + remove: vitest.fn(), + update: vitest.fn(), }; }; diff --git a/server/test/repositories/storage.repository.mock.ts b/server/test/repositories/storage.repository.mock.ts index d5049999c7..615fd5d8c9 100644 --- a/server/test/repositories/storage.repository.mock.ts +++ b/server/test/repositories/storage.repository.mock.ts @@ -1,6 +1,7 @@ import { WatchOptions } from 'chokidar'; import { StorageCore } from 'src/cores/storage.core'; -import { IStorageRepository, StorageEventType, WatchEvents } from 'src/interfaces/storage.interface'; +import { IStorageRepository, WatchEvents } from 'src/interfaces/storage.interface'; +import { Mocked, vitest } from 'vitest'; interface MockWatcherOptions { items?: Array<{ event: 'change' | 'add' | 'unlink' | 'error'; value: string }>; @@ -13,19 +14,19 @@ export const makeMockWatcher = events.onReady?.(); for (const item of items || []) { switch (item.event) { - case StorageEventType.ADD: { + case 'add': { events.onAdd?.(item.value); break; } - case StorageEventType.CHANGE: { + case 'change': { events.onChange?.(item.value); break; } - case StorageEventType.UNLINK: { + case 'unlink': { events.onUnlink?.(item.value); break; } - case StorageEventType.ERROR: { + case 'error': { events.onError?.(new Error(item.value)); } } @@ -38,29 +39,29 @@ export const makeMockWatcher = return () => Promise.resolve(); }; -export const newStorageRepositoryMock = (reset = true): jest.Mocked => { +export const newStorageRepositoryMock = (reset = true): Mocked => { if (reset) { StorageCore.reset(); } return { - createZipStream: jest.fn(), - createReadStream: jest.fn(), - readFile: jest.fn(), - writeFile: jest.fn(), - unlink: jest.fn(), - unlinkDir: jest.fn().mockResolvedValue(true), - removeEmptyDirs: jest.fn(), - checkFileExists: jest.fn(), - mkdirSync: jest.fn(), - checkDiskUsage: jest.fn(), - readdir: jest.fn(), - stat: jest.fn(), - crawl: jest.fn(), - walk: jest.fn().mockImplementation(async function* () {}), - rename: jest.fn(), - copyFile: jest.fn(), - utimes: jest.fn(), - watch: jest.fn().mockImplementation(makeMockWatcher({})), + createZipStream: vitest.fn(), + createReadStream: vitest.fn(), + readFile: vitest.fn(), + writeFile: vitest.fn(), + unlink: vitest.fn(), + unlinkDir: vitest.fn().mockResolvedValue(true), + removeEmptyDirs: vitest.fn(), + checkFileExists: vitest.fn(), + mkdirSync: vitest.fn(), + checkDiskUsage: vitest.fn(), + readdir: vitest.fn(), + stat: vitest.fn(), + crawl: vitest.fn(), + walk: vitest.fn().mockImplementation(async function* () {}), + rename: vitest.fn(), + copyFile: vitest.fn(), + utimes: vitest.fn(), + watch: vitest.fn().mockImplementation(makeMockWatcher({})), }; }; diff --git a/server/test/repositories/system-config.repository.mock.ts b/server/test/repositories/system-config.repository.mock.ts index 0ef11ce18f..41135b7d74 100644 --- a/server/test/repositories/system-config.repository.mock.ts +++ b/server/test/repositories/system-config.repository.mock.ts @@ -1,16 +1,17 @@ import { SystemConfigCore } from 'src/cores/system-config.core'; import { ISystemConfigRepository } from 'src/interfaces/system-config.interface'; +import { Mocked, vitest } from 'vitest'; -export const newSystemConfigRepositoryMock = (reset = true): jest.Mocked => { +export const newSystemConfigRepositoryMock = (reset = true): Mocked => { if (reset) { SystemConfigCore.reset(); } return { - fetchStyle: jest.fn(), - load: jest.fn().mockResolvedValue([]), - readFile: jest.fn(), - saveAll: jest.fn().mockResolvedValue([]), - deleteKeys: jest.fn(), + fetchStyle: vitest.fn(), + load: vitest.fn().mockResolvedValue([]), + readFile: vitest.fn(), + saveAll: vitest.fn().mockResolvedValue([]), + deleteKeys: vitest.fn(), }; }; diff --git a/server/test/repositories/system-info.repository.mock.ts b/server/test/repositories/system-info.repository.mock.ts index bdc11f9d63..977d5dca2d 100644 --- a/server/test/repositories/system-info.repository.mock.ts +++ b/server/test/repositories/system-info.repository.mock.ts @@ -1,7 +1,8 @@ import { IServerInfoRepository } from 'src/interfaces/server-info.interface'; +import { Mocked, vitest } from 'vitest'; -export const newServerInfoRepositoryMock = (): jest.Mocked => { +export const newServerInfoRepositoryMock = (): Mocked => { return { - getGitHubRelease: jest.fn(), + getGitHubRelease: vitest.fn(), }; }; diff --git a/server/test/repositories/system-metadata.repository.mock.ts b/server/test/repositories/system-metadata.repository.mock.ts index 5ffc5dd895..1044076ea8 100644 --- a/server/test/repositories/system-metadata.repository.mock.ts +++ b/server/test/repositories/system-metadata.repository.mock.ts @@ -1,8 +1,9 @@ import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; +import { Mocked, vitest } from 'vitest'; -export const newSystemMetadataRepositoryMock = (): jest.Mocked => { +export const newSystemMetadataRepositoryMock = (): Mocked => { return { - get: jest.fn(), - set: jest.fn(), + get: vitest.fn() as any, + set: vitest.fn(), }; }; diff --git a/server/test/repositories/tag.repository.mock.ts b/server/test/repositories/tag.repository.mock.ts index 0c31c546c2..a5123e0f36 100644 --- a/server/test/repositories/tag.repository.mock.ts +++ b/server/test/repositories/tag.repository.mock.ts @@ -1,16 +1,17 @@ import { ITagRepository } from 'src/interfaces/tag.interface'; +import { Mocked, vitest } from 'vitest'; -export const newTagRepositoryMock = (): jest.Mocked => { +export const newTagRepositoryMock = (): Mocked => { return { - getAll: jest.fn(), - getById: jest.fn(), - create: jest.fn(), - update: jest.fn(), - remove: jest.fn(), - hasAsset: jest.fn(), - hasName: jest.fn(), - getAssets: jest.fn(), - addAssets: jest.fn(), - removeAssets: jest.fn(), + getAll: vitest.fn(), + getById: vitest.fn(), + create: vitest.fn(), + update: vitest.fn(), + remove: vitest.fn(), + hasAsset: vitest.fn(), + hasName: vitest.fn(), + getAssets: vitest.fn(), + addAssets: vitest.fn(), + removeAssets: vitest.fn(), }; }; diff --git a/server/test/repositories/user-token.repository.mock.ts b/server/test/repositories/user-token.repository.mock.ts index b3fa7e73f1..f34e65b7f3 100644 --- a/server/test/repositories/user-token.repository.mock.ts +++ b/server/test/repositories/user-token.repository.mock.ts @@ -1,11 +1,12 @@ import { IUserTokenRepository } from 'src/interfaces/user-token.interface'; +import { Mocked, vitest } from 'vitest'; -export const newUserTokenRepositoryMock = (): jest.Mocked => { +export const newUserTokenRepositoryMock = (): Mocked => { return { - create: jest.fn(), - save: jest.fn(), - delete: jest.fn(), - getByToken: jest.fn(), - getAll: jest.fn(), + create: vitest.fn(), + save: vitest.fn(), + delete: vitest.fn(), + getByToken: vitest.fn(), + getAll: vitest.fn(), }; }; diff --git a/server/test/repositories/user.repository.mock.ts b/server/test/repositories/user.repository.mock.ts index 80d9a4cfd0..5f2e2f083e 100644 --- a/server/test/repositories/user.repository.mock.ts +++ b/server/test/repositories/user.repository.mock.ts @@ -1,25 +1,26 @@ import { UserCore } from 'src/cores/user.core'; import { IUserRepository } from 'src/interfaces/user.interface'; +import { Mocked, vitest } from 'vitest'; -export const newUserRepositoryMock = (reset = true): jest.Mocked => { +export const newUserRepositoryMock = (reset = true): Mocked => { if (reset) { UserCore.reset(); } return { - get: jest.fn(), - getAdmin: jest.fn(), - getByEmail: jest.fn(), - getByStorageLabel: jest.fn(), - getByOAuthId: jest.fn(), - getUserStats: jest.fn(), - getList: jest.fn(), - create: jest.fn(), - update: jest.fn(), - delete: jest.fn(), - getDeletedUsers: jest.fn(), - hasAdmin: jest.fn(), - updateUsage: jest.fn(), - syncUsage: jest.fn(), + get: vitest.fn(), + getAdmin: vitest.fn(), + getByEmail: vitest.fn(), + getByStorageLabel: vitest.fn(), + getByOAuthId: vitest.fn(), + getUserStats: vitest.fn(), + getList: vitest.fn(), + create: vitest.fn(), + update: vitest.fn(), + delete: vitest.fn(), + getDeletedUsers: vitest.fn(), + hasAdmin: vitest.fn(), + updateUsage: vitest.fn(), + syncUsage: vitest.fn(), }; }; diff --git a/server/test/utils.ts b/server/test/utils.ts deleted file mode 100644 index c7732eabc1..0000000000 --- a/server/test/utils.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { INestApplication } from '@nestjs/common'; -import { Test } from '@nestjs/testing'; -import { DateTime } from 'luxon'; -import fs from 'node:fs'; -import { tmpdir } from 'node:os'; -import { join } from 'node:path'; -import { EventEmitter } from 'node:stream'; -import { AppTestModule } from 'src/app.module'; -import { dataSource } from 'src/database.config'; -import { IJobRepository, JobItem, JobItemHandler, QueueName } from 'src/interfaces/job.interface'; -import { IMediaRepository } from 'src/interfaces/media.interface'; -import { StorageEventType } from 'src/interfaces/storage.interface'; -import { MediaRepository } from 'src/repositories/media.repository'; -import { ApiService } from 'src/services/api.service'; -import { MicroservicesService } from 'src/services/microservices.service'; -import { EntityTarget, ObjectLiteral } from 'typeorm'; - -export const IMMICH_TEST_ASSET_PATH = process.env.IMMICH_TEST_ASSET_PATH as string; -export const IMMICH_TEST_ASSET_TEMP_PATH = join(tmpdir(), 'immich'); - -export const today = DateTime.fromObject({ year: 2023, month: 11, day: 3 }); -export const yesterday = today.minus({ days: 1 }); - -export interface ResetOptions { - entities?: EntityTarget[]; -} -export const db = { - reset: async (options?: ResetOptions) => { - if (!dataSource.isInitialized) { - await dataSource.initialize(); - } - await dataSource.transaction(async (em) => { - const entities = options?.entities || []; - const tableNames = - entities.length > 0 - ? entities.map((entity) => em.getRepository(entity).metadata.tableName) - : dataSource.entityMetadatas - .map((entity) => entity.tableName) - .filter((tableName) => !tableName.startsWith('geodata')); - - let deleteUsers = false; - for (const tableName of tableNames) { - if (tableName === 'users') { - deleteUsers = true; - continue; - } - await em.query(`DELETE FROM ${tableName} CASCADE;`); - } - if (deleteUsers) { - await em.query(`DELETE FROM "users" CASCADE;`); - } - - // Release all locks - await em.query('SELECT pg_advisory_unlock_all()'); - }); - }, - disconnect: async () => { - if (dataSource.isInitialized) { - await dataSource.destroy(); - } - }, -}; - -class JobMock implements IJobRepository { - private _handler: JobItemHandler = () => Promise.resolve(); - addHandler(_queueName: QueueName, _concurrency: number, handler: JobItemHandler) { - this._handler = handler; - } - addCronJob() {} - updateCronJob() {} - deleteCronJob() {} - validateCronExpression() {} - queue(item: JobItem) { - return this._handler(item); - } - queueAll(items: JobItem[]) { - return Promise.all(items.map((arg) => this._handler(arg))).then(() => {}); - } - async resume() {} - async empty() {} - async setConcurrency() {} - getQueueStatus() { - return Promise.resolve(null) as any; - } - getJobCounts() { - return Promise.resolve(null) as any; - } - async pause() {} - clear() { - return Promise.resolve([]); - } - async waitForQueueCompletion() {} -} - -class MediaMockRepository extends MediaRepository { - generateThumbhash() { - return Promise.resolve(Buffer.from('mock-thumbhash')); - } -} - -let app: INestApplication; - -export const testApp = { - create: async (): Promise => { - const moduleFixture = await Test.createTestingModule({ imports: [AppTestModule] }) - .overrideProvider(IJobRepository) - .useClass(JobMock) - .overrideProvider(IMediaRepository) - .useClass(MediaMockRepository) - .compile(); - - app = await moduleFixture.createNestApplication().init(); - await app.get(ApiService).init(); - await db.reset(); - await app.get(ApiService).init(); - await app.get(MicroservicesService).init(); - - return app; - }, - reset: async (options?: ResetOptions) => { - await db.reset(options); - }, - get: (member: any) => app.get(member), - teardown: async () => { - if (app) { - await app.get(MicroservicesService).teardown(); - await app.close(); - } - await db.disconnect(); - }, -}; - -export function waitForEvent(emitter: EventEmitter, event: string, times = 1): Promise { - const promises: Promise[] = []; - - for (let i = 1; i <= times; i++) { - promises.push( - new Promise((resolve, reject) => { - const success = (value: any) => { - emitter.off(StorageEventType.ERROR, fail); - resolve(value); - }; - const fail = (error: Error) => { - emitter.off(event, success); - reject(error); - }; - emitter.once(event, success); - emitter.once(StorageEventType.ERROR, fail); - }), - ); - } - return Promise.all(promises); -} - -const directoryExists = async (dirPath: string) => - await fs.promises - .access(dirPath) - .then(() => true) - .catch(() => false); - -export async function restoreTempFolder(): Promise { - if (await directoryExists(`${IMMICH_TEST_ASSET_TEMP_PATH}`)) { - // Temp directory exists, delete all files inside it - await fs.promises.rm(IMMICH_TEST_ASSET_TEMP_PATH, { recursive: true }); - } - // Create temp folder - await fs.promises.mkdir(IMMICH_TEST_ASSET_TEMP_PATH); -} diff --git a/server/tsconfig.json b/server/tsconfig.json index ce3edda395..2b7b09545b 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -17,10 +17,7 @@ "esModuleInterop": true, "preserveWatchOutput": true, "baseUrl": "./", + "types": ["vitest/globals"] }, - "exclude": [ - "dist", - "node_modules", - "upload" - ], -} \ No newline at end of file + "exclude": ["dist", "node_modules", "upload"] +} diff --git a/server/vitest.config.mjs b/server/vitest.config.mjs new file mode 100644 index 0000000000..192f2b8df8 --- /dev/null +++ b/server/vitest.config.mjs @@ -0,0 +1,15 @@ +import swc from 'unplugin-swc'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + root: './', + globals: true, + server: { + deps: { + fallbackCJS: true, + }, + }, + }, + plugins: [swc.vite()], +}); diff --git a/web/package-lock.json b/web/package-lock.json index 0abde5959c..b5e3a6c2f9 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -69,8 +69,8 @@ "@oazapfts/runtime": "^1.0.2" }, "devDependencies": { - "@types/node": "^20.12.4", - "typescript": "^5.4.4" + "@types/node": "^20.11.0", + "typescript": "^5.3.3" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -1800,9 +1800,9 @@ "dev": true }, "node_modules/@socket.io/component-emitter": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", - "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.1.tgz", + "integrity": "sha512-dzJtaDAAoXx4GCOJpbB2eG/Qj8VDpdwkLsWGzGm+0L7E8/434RyMbAHmk9ubXWVAb9nXmc44jUf8GKqVDiKezg==" }, "node_modules/@sveltejs/adapter-static": { "version": "3.0.1", @@ -1857,17 +1857,17 @@ } }, "node_modules/@sveltejs/vite-plugin-svelte": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-3.0.2.tgz", - "integrity": "sha512-MpmF/cju2HqUls50WyTHQBZUV3ovV/Uk8k66AN2gwHogNAG8wnW8xtZDhzNBsFJJuvmq1qnzA5kE7YfMJNFv2Q==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-3.1.0.tgz", + "integrity": "sha512-sY6ncCvg+O3njnzbZexcVtUqOBE3iYmQPJ9y+yXSkOwG576QI/xJrBnQSRXFLGwJNBa0T78JEKg5cIR0WOAuUw==", "dev": true, "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^2.0.0", "debug": "^4.3.4", "deepmerge": "^4.3.1", "kleur": "^4.1.5", - "magic-string": "^0.30.5", - "svelte-hmr": "^0.15.3", + "magic-string": "^0.30.9", + "svelte-hmr": "^0.16.0", "vitefu": "^0.2.5" }, "engines": { @@ -2162,12 +2162,6 @@ "@types/geojson": "*" } }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true - }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -2264,22 +2258,22 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.5.0.tgz", - "integrity": "sha512-HpqNTH8Du34nLxbKgVMGljZMG0rJd2O9ecvr2QLYp+7512ty1j42KnsFwspPXg1Vh8an9YImf6CokUBltisZFQ==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.6.0.tgz", + "integrity": "sha512-gKmTNwZnblUdnTIJu3e9kmeRRzV2j1a/LUO27KNNAnIC5zjy1aSvXSRp4rVNlmAoHlQ7HzX42NbKpcSr4jF80A==", "dev": true, "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "7.5.0", - "@typescript-eslint/type-utils": "7.5.0", - "@typescript-eslint/utils": "7.5.0", - "@typescript-eslint/visitor-keys": "7.5.0", + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.6.0", + "@typescript-eslint/type-utils": "7.6.0", + "@typescript-eslint/utils": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0", "debug": "^4.3.4", "graphemer": "^1.4.0", - "ignore": "^5.2.4", + "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -2332,15 +2326,15 @@ "dev": true }, "node_modules/@typescript-eslint/parser": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.5.0.tgz", - "integrity": "sha512-cj+XGhNujfD2/wzR1tabNsidnYRaFfEkcULdcIyVBYcXjBvBKOes+mpMBP7hMpOyk+gBcfXsrg4NBGAStQyxjQ==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.6.0.tgz", + "integrity": "sha512-usPMPHcwX3ZoPWnBnhhorc14NJw9J4HpSXQX4urF2TPKG0au0XhJoZyX62fmvdHONUkmyUe74Hzm1//XA+BoYg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "7.5.0", - "@typescript-eslint/types": "7.5.0", - "@typescript-eslint/typescript-estree": "7.5.0", - "@typescript-eslint/visitor-keys": "7.5.0", + "@typescript-eslint/scope-manager": "7.6.0", + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/typescript-estree": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0", "debug": "^4.3.4" }, "engines": { @@ -2360,13 +2354,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.5.0.tgz", - "integrity": "sha512-Z1r7uJY0MDeUlql9XJ6kRVgk/sP11sr3HKXn268HZyqL7i4cEfrdFuSSY/0tUqT37l5zT0tJOsuDP16kio85iA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.6.0.tgz", + "integrity": "sha512-ngttyfExA5PsHSx0rdFgnADMYQi+Zkeiv4/ZxGYUWd0nLs63Ha0ksmp8VMxAIC0wtCFxMos7Lt3PszJssG/E6w==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.5.0", - "@typescript-eslint/visitor-keys": "7.5.0" + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -2377,15 +2371,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.5.0.tgz", - "integrity": "sha512-A021Rj33+G8mx2Dqh0nMO9GyjjIBK3MqgVgZ2qlKf6CJy51wY/lkkFqq3TqqnH34XyAHUkq27IjlUkWlQRpLHw==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.6.0.tgz", + "integrity": "sha512-NxAfqAPNLG6LTmy7uZgpK8KcuiS2NZD/HlThPXQRGwz6u7MDBWRVliEEl1Gj6U7++kVJTpehkhZzCJLMK66Scw==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "7.5.0", - "@typescript-eslint/utils": "7.5.0", + "@typescript-eslint/typescript-estree": "7.6.0", + "@typescript-eslint/utils": "7.6.0", "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" + "ts-api-utils": "^1.3.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -2404,9 +2398,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.5.0.tgz", - "integrity": "sha512-tv5B4IHeAdhR7uS4+bf8Ov3k793VEVHd45viRRkehIUZxm0WF82VPiLgHzA/Xl4TGPg1ZD49vfxBKFPecD5/mg==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.6.0.tgz", + "integrity": "sha512-h02rYQn8J+MureCvHVVzhl69/GAfQGPQZmOMjG1KfCl7o3HtMSlPaPUAPu6lLctXI5ySRGIYk94clD/AUMCUgQ==", "dev": true, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -2417,19 +2411,19 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.5.0.tgz", - "integrity": "sha512-YklQQfe0Rv2PZEueLTUffiQGKQneiIEKKnfIqPIOxgM9lKSZFCjT5Ad4VqRKj/U4+kQE3fa8YQpskViL7WjdPQ==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.6.0.tgz", + "integrity": "sha512-+7Y/GP9VuYibecrCQWSKgl3GvUM5cILRttpWtnAu8GNL9j11e4tbuGZmZjJ8ejnKYyBRb2ddGQ3rEFCq3QjMJw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.5.0", - "@typescript-eslint/visitor-keys": "7.5.0", + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0", "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" + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -2466,9 +2460,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -2502,18 +2496,18 @@ "dev": true }, "node_modules/@typescript-eslint/utils": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.5.0.tgz", - "integrity": "sha512-3vZl9u0R+/FLQcpy2EHyRGNqAS/ofJ3Ji8aebilfJe+fobK8+LbIFmrHciLVDxjDoONmufDcnVSF38KwMEOjzw==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.6.0.tgz", + "integrity": "sha512-x54gaSsRRI+Nwz59TXpCsr6harB98qjXYzsRxGqvA5Ue3kQH+FxS7FYU81g/omn22ML2pZJkisy6Q+ElK8pBCA==", "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": "7.5.0", - "@typescript-eslint/types": "7.5.0", - "@typescript-eslint/typescript-estree": "7.5.0", - "semver": "^7.5.4" + "@types/json-schema": "^7.0.15", + "@types/semver": "^7.5.8", + "@typescript-eslint/scope-manager": "7.6.0", + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/typescript-estree": "7.6.0", + "semver": "^7.6.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -2560,13 +2554,13 @@ "dev": true }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.5.0.tgz", - "integrity": "sha512-mcuHM/QircmA6O7fy6nn2w/3ditQkj+SgtOc8DW3uQ10Yfj42amm2i+6F2K4YAOPNNTmE6iM1ynM6lrSwdendA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.6.0.tgz", + "integrity": "sha512-4eLB7t+LlNUmXzfOu1VAIAdkjbu5xNSerURS9X/S5TUKWFRpXRQZbmtPqgKmYx8bj3J0irtQXSiWAOY82v+cgw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.5.0", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "7.6.0", + "eslint-visitor-keys": "^3.4.3" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -2583,14 +2577,14 @@ "dev": true }, "node_modules/@vitest/browser": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-1.4.0.tgz", - "integrity": "sha512-kC44DzuqPZZrqe2P7SX2a3zHDAt919WtpkUMAxzv9eP5uPfVXtpk2Ipms2NXJGY5190aJc1uY+ambfJ3rwDJRA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-1.5.0.tgz", + "integrity": "sha512-tJLV8j8sufAT2a3eONnfgIclaqx4RqC8sA3xH8uXwxFw7CNwWCPeJ0czVkUzCE/15bHRoGxwsHC+Ycch6zDb1Q==", "dev": true, "optional": true, "peer": true, "dependencies": { - "@vitest/utils": "1.4.0", + "@vitest/utils": "1.5.0", "magic-string": "^0.30.5", "sirv": "^2.0.4" }, @@ -2599,7 +2593,7 @@ }, "peerDependencies": { "playwright": "*", - "vitest": "1.4.0", + "vitest": "1.5.0", "webdriverio": "*" }, "peerDependenciesMeta": { @@ -2615,9 +2609,9 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.4.0.tgz", - "integrity": "sha512-4hDGyH1SvKpgZnIByr9LhGgCEuF9DKM34IBLCC/fVfy24Z3+PZ+Ii9hsVBsHvY1umM1aGPEjceRkzxCfcQ10wg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.5.0.tgz", + "integrity": "sha512-1igVwlcqw1QUMdfcMlzzY4coikSIBN944pkueGi0pawrX5I5Z+9hxdTR+w3Sg6Q3eZhvdMAs8ZaF9JuTG1uYOQ==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.1", @@ -2632,24 +2626,23 @@ "picocolors": "^1.0.0", "std-env": "^3.5.0", "strip-literal": "^2.0.0", - "test-exclude": "^6.0.0", - "v8-to-istanbul": "^9.2.0" + "test-exclude": "^6.0.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "1.4.0" + "vitest": "1.5.0" } }, "node_modules/@vitest/expect": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.4.0.tgz", - "integrity": "sha512-Jths0sWCJZ8BxjKe+p+eKsoqev1/T8lYcrjavEaz8auEJ4jAVY0GwW3JKmdVU4mmNPLPHixh4GNXP7GFtAiDHA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.5.0.tgz", + "integrity": "sha512-0pzuCI6KYi2SIC3LQezmxujU9RK/vwC1U9R0rLuGlNGcOuDWxqWKu6nUdFsX9tH1WU0SXtAxToOsEjeUn1s3hA==", "dev": true, "dependencies": { - "@vitest/spy": "1.4.0", - "@vitest/utils": "1.4.0", + "@vitest/spy": "1.5.0", + "@vitest/utils": "1.5.0", "chai": "^4.3.10" }, "funding": { @@ -2657,12 +2650,12 @@ } }, "node_modules/@vitest/runner": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.4.0.tgz", - "integrity": "sha512-EDYVSmesqlQ4RD2VvWo3hQgTJ7ZrFQ2VSJdfiJiArkCerDAGeyF1i6dHkmySqk573jLp6d/cfqCN+7wUB5tLgg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.5.0.tgz", + "integrity": "sha512-7HWwdxXP5yDoe7DTpbif9l6ZmDwCzcSIK38kTSIt6CFEpMjX4EpCgT6wUmS0xTXqMI6E/ONmfgRKmaujpabjZQ==", "dev": true, "dependencies": { - "@vitest/utils": "1.4.0", + "@vitest/utils": "1.5.0", "p-limit": "^5.0.0", "pathe": "^1.1.1" }, @@ -2698,9 +2691,9 @@ } }, "node_modules/@vitest/snapshot": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.4.0.tgz", - "integrity": "sha512-saAFnt5pPIA5qDGxOHxJ/XxhMFKkUSBJmVt5VgDsAqPTX6JP326r5C/c9UuCMPoXNzuudTPsYDZCoJ5ilpqG2A==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.5.0.tgz", + "integrity": "sha512-qpv3fSEuNrhAO3FpH6YYRdaECnnRjg9VxbhdtPwPRnzSfHVXnNzzrpX4cJxqiwgRMo7uRMWDFBlsBq4Cr+rO3A==", "dev": true, "dependencies": { "magic-string": "^0.30.5", @@ -2744,9 +2737,9 @@ "dev": true }, "node_modules/@vitest/spy": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.4.0.tgz", - "integrity": "sha512-Ywau/Qs1DzM/8Uc+yA77CwSegizMlcgTJuYGAi0jujOteJOUf1ujunHThYo243KG9nAyWT3L9ifPYZ5+As/+6Q==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.5.0.tgz", + "integrity": "sha512-vu6vi6ew5N5MMHJjD5PoakMRKYdmIrNJmyfkhRpQt5d9Ewhw9nZ5Aqynbi3N61bvk9UvZ5UysMT6ayIrZ8GA9w==", "dev": true, "dependencies": { "tinyspy": "^2.2.0" @@ -2756,9 +2749,9 @@ } }, "node_modules/@vitest/utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.4.0.tgz", - "integrity": "sha512-mx3Yd1/6e2Vt/PUC98DcqTirtfxUyAZ32uK82r8rZzbtBeBo+nqgnjx/LvqQdWsrvNtm14VmurNgcf4nqY5gJg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.5.0.tgz", + "integrity": "sha512-BDU0GNL8MWkRkSRdNFvCUCAVOeHaUlVJ9Tx0TYBZyXaaOTmGtUFObzchCivIBrIwKzvZA7A9sCejVhXM2aY98A==", "dev": true, "dependencies": { "diff-sequences": "^29.6.3", @@ -3514,7 +3507,9 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/cookie": { "version": "0.6.0", @@ -4075,10 +4070,13 @@ } }, "node_modules/eslint-compat-utils": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.1.2.tgz", - "integrity": "sha512-Jia4JDldWnFNIru1Ehx1H5s9/yxiRHY/TimCuUc0jNexew3cF1gI6CYZil1ociakfWO3rRqFjl1mskBblB3RYg==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.0.tgz", + "integrity": "sha512-dc6Y8tzEcSYZMHa+CMPLi/hyo1FzNeonbhJL7Ol0ccuKQkwopJcJBA9YL/xmMTLU1eKigXo9vj9nALElWYSowg==", "dev": true, + "dependencies": { + "semver": "^7.5.4" + }, "engines": { "node": ">=12" }, @@ -4086,6 +4084,39 @@ "eslint": ">=6.0.0" } }, + "node_modules/eslint-compat-utils/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==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-compat-utils/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-compat-utils/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/eslint-config-prettier": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", @@ -4099,23 +4130,23 @@ } }, "node_modules/eslint-plugin-svelte": { - "version": "2.35.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.35.1.tgz", - "integrity": "sha512-IF8TpLnROSGy98Z3NrsKXWDSCbNY2ReHDcrYTuXZMbfX7VmESISR78TWgO9zdg4Dht1X8coub5jKwHzP0ExRug==", + "version": "2.37.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.37.0.tgz", + "integrity": "sha512-H/2Gz7agYHEMEEzRuLYuCmAIdjuBnbhFG9hOK0yCdSBvvJGJMkjo+lR6j67OIvLOavgp4L7zA5LnDKi8WqdPhQ==", "dev": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@jridgewell/sourcemap-codec": "^1.4.14", - "debug": "^4.3.1", - "eslint-compat-utils": "^0.1.2", + "@eslint-community/eslint-utils": "^4.4.0", + "@jridgewell/sourcemap-codec": "^1.4.15", + "debug": "^4.3.4", + "eslint-compat-utils": "^0.5.0", "esutils": "^2.0.3", - "known-css-properties": "^0.29.0", - "postcss": "^8.4.5", + "known-css-properties": "^0.30.0", + "postcss": "^8.4.38", "postcss-load-config": "^3.1.4", "postcss-safe-parser": "^6.0.0", - "postcss-selector-parser": "^6.0.11", - "semver": "^7.5.3", - "svelte-eslint-parser": ">=0.33.0 <1.0.0" + "postcss-selector-parser": "^6.0.16", + "semver": "^7.6.0", + "svelte-eslint-parser": ">=0.34.0 <1.0.0" }, "engines": { "node": "^14.17.0 || >=16.0.0" @@ -4124,8 +4155,8 @@ "url": "https://github.com/sponsors/ota-meshi" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0-0", - "svelte": "^3.37.0 || ^4.0.0" + "eslint": "^7.0.0 || ^8.0.0-0 || ^9.0.0-0", + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0-next.95" }, "peerDependenciesMeta": { "svelte": { @@ -4146,9 +4177,9 @@ } }, "node_modules/eslint-plugin-svelte/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -5855,9 +5886,9 @@ } }, "node_modules/known-css-properties": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.29.0.tgz", - "integrity": "sha512-Ne7wqW7/9Cz54PDt4I3tcV+hAyat8ypyOGzYRJQfdxnnjeWsTxt1cy8pjvvKeI5kfXuyvULyeeAvwvvtAX3ayQ==", + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.30.0.tgz", + "integrity": "sha512-VSWXYUnsPu9+WYKkfmJyLKtIvaRJi1kXUqVmBACORXZQxT5oZDsoZ2vQP+bQFDnWtpI/4eq3MLoRMjI2fnLzTQ==", "dev": true }, "node_modules/levn": { @@ -5979,9 +6010,9 @@ } }, "node_modules/magic-string": { - "version": "0.30.5", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", - "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", + "version": "0.30.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.9.tgz", + "integrity": "sha512-S1+hd+dIrC8EZqKyT9DstTH/0Z+f76kmmvZnkfQVmOpDEF9iVgdYif3Q/pIWHmCoo59bQVGW0kVL3e2nl+9+Sw==", "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" }, @@ -6869,9 +6900,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.0.13", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", - "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "version": "6.0.16", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", + "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", "dev": true, "dependencies": { "cssesc": "^3.0.0", @@ -8015,9 +8046,9 @@ } }, "node_modules/svelte": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.12.tgz", - "integrity": "sha512-d8+wsh5TfPwqVzbm4/HCXC783/KPHV60NvwitJnyTA5lWn1elhXMNWhXGCJ7PwPa8qFUnyJNIyuIRt2mT0WMug==", + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.13.tgz", + "integrity": "sha512-jtVt2KXLbQnsWN93Zd7EVboNh8Tqexes4rZfXNP7nYRjd9+JjubTD8BXloUmU1OUYpc6pdd1aKBhCV+b2ZKoMg==", "dependencies": { "@ampproject/remapping": "^2.2.1", "@jridgewell/sourcemap-codec": "^1.4.15", @@ -8061,16 +8092,16 @@ } }, "node_modules/svelte-eslint-parser": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.33.0.tgz", - "integrity": "sha512-5awZ6Bs+Tb/zQwa41PSdcLynAVQTwW0HGyCBjtbAQ59taLZqDgQSMzRlDmapjZdDtzERm0oXDZNE0E+PKJ6ryg==", + "version": "0.34.1", + "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.34.1.tgz", + "integrity": "sha512-9+uLA1pqI9AZioKVGJzYYmlOZWxfoCXSbAM9iaNm7H01XlYlzRTtJfZgl9o3StQGN41PfGJIbkKkfk3e/pHFfA==", "dev": true, "dependencies": { - "eslint-scope": "^7.0.0", - "eslint-visitor-keys": "^3.0.0", - "espree": "^9.0.0", - "postcss": "^8.4.28", - "postcss-scss": "^4.0.7" + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "postcss": "^8.4.38", + "postcss-scss": "^4.0.9" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -8079,7 +8110,7 @@ "url": "https://github.com/sponsors/ota-meshi" }, "peerDependencies": { - "svelte": "^3.37.0 || ^4.0.0" + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0-next.94" }, "peerDependenciesMeta": { "svelte": { @@ -8088,9 +8119,9 @@ } }, "node_modules/svelte-hmr": { - "version": "0.15.3", - "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.15.3.tgz", - "integrity": "sha512-41snaPswvSf8TJUhlkoJBekRrABDXDMdpNpT2tfHIv4JuhgvHqLMhEPGtaQn0BmbNSTkuz2Ed20DF2eHw0SmBQ==", + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.16.0.tgz", + "integrity": "sha512-Gyc7cOS3VJzLlfj7wKS0ZnzDVdv3Pn2IuVeJPk9m2skfhcu5bq3wtIZyQGggr7/Iim5rH5cncyQft/kRLupcnA==", "dev": true, "engines": { "node": "^12.20 || ^14.13.1 || >= 16" @@ -8111,9 +8142,9 @@ } }, "node_modules/svelte-maplibre": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/svelte-maplibre/-/svelte-maplibre-0.8.2.tgz", - "integrity": "sha512-l4FW7VE/1/uUUyk639gtvYyK0QbNw3roRq9ouxPDS+DAW8gwpxlnDwYTtjEO+Yq7TK6xOjimJtKOG3KES8e7Uw==", + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/svelte-maplibre/-/svelte-maplibre-0.8.3.tgz", + "integrity": "sha512-aC18gtP83Xa2f95Wi/Ku1sJ5Lhhg4L05AOq/IQQ5Gdq8KxglWou7CMHlJ6dJGx2nJpQxVtFQ+3dYPwGewYuOJw==", "dependencies": { "d3-geo": "^3.1.0", "just-compare": "^2.3.0", @@ -8386,9 +8417,9 @@ "dev": true }, "node_modules/tinypool": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.2.tgz", - "integrity": "sha512-SUszKYe5wgsxnNOVlBYO6IC+8VGWdVGZWAqUxp3UErNBtptZvWbwyUOyzNL59zigz2rCA92QiL3wvG+JDSdJdQ==", + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", + "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", "dev": true, "engines": { "node": ">=14.0.0" @@ -8527,9 +8558,9 @@ } }, "node_modules/typescript": { - "version": "5.4.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.4.tgz", - "integrity": "sha512-dGE2Vv8cpVvw28v8HCPqyb08EzbBURxDpuhJvTrusShUfGnhHBafDsLdS1EhhxyL6BJQE+2cT3dDPAv+MQ6oLw==", + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -8652,20 +8683,6 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true }, - "node_modules/v8-to-istanbul": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", - "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -8745,9 +8762,9 @@ } }, "node_modules/vite-node": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.4.0.tgz", - "integrity": "sha512-VZDAseqjrHgNd4Kh8icYHWzTKSCZMhia7GyHfhtzLW33fZlG9SwsB6CEhgyVOWkJfJ2pFLrp/Gj1FSfAiqH9Lw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.5.0.tgz", + "integrity": "sha512-tV8h6gMj6vPzVCa7l+VGq9lwoJjW8Y79vst8QZZGiuRAfijU+EEWuc0kFpmndQrWhMMhet1jdSF+40KSZUqIIw==", "dev": true, "dependencies": { "cac": "^6.7.14", @@ -8781,16 +8798,16 @@ } }, "node_modules/vitest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.4.0.tgz", - "integrity": "sha512-gujzn0g7fmwf83/WzrDTnncZt2UiXP41mHuFYFrdwaLRVQ6JYQEiME2IfEjU3vcFL3VKa75XhI3lFgn+hfVsQw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.5.0.tgz", + "integrity": "sha512-d8UKgR0m2kjdxDWX6911uwxout6GHS0XaGH1cksSIVVG8kRlE7G7aBw7myKQCvDI5dT4j7ZMa+l706BIORMDLw==", "dev": true, "dependencies": { - "@vitest/expect": "1.4.0", - "@vitest/runner": "1.4.0", - "@vitest/snapshot": "1.4.0", - "@vitest/spy": "1.4.0", - "@vitest/utils": "1.4.0", + "@vitest/expect": "1.5.0", + "@vitest/runner": "1.5.0", + "@vitest/snapshot": "1.5.0", + "@vitest/spy": "1.5.0", + "@vitest/utils": "1.5.0", "acorn-walk": "^8.3.2", "chai": "^4.3.10", "debug": "^4.3.4", @@ -8802,9 +8819,9 @@ "std-env": "^3.5.0", "strip-literal": "^2.0.0", "tinybench": "^2.5.1", - "tinypool": "^0.8.2", + "tinypool": "^0.8.3", "vite": "^5.0.0", - "vite-node": "1.4.0", + "vite-node": "1.5.0", "why-is-node-running": "^2.2.2" }, "bin": { @@ -8819,8 +8836,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "1.4.0", - "@vitest/ui": "1.4.0", + "@vitest/browser": "1.5.0", + "@vitest/ui": "1.5.0", "happy-dom": "*", "jsdom": "*" }, diff --git a/web/src/lib/components/album-page/album-description.svelte b/web/src/lib/components/album-page/album-description.svelte index 505e934144..28e726593d 100644 --- a/web/src/lib/components/album-page/album-description.svelte +++ b/web/src/lib/components/album-page/album-description.svelte @@ -32,7 +32,7 @@ {#if isOwned}