diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index c3269a712..1d455b251 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -78,6 +78,7 @@
"isomorphic-dompurify": "2.13.0",
"js-yaml": "^4.1.0",
"minimist": "^1.2.8",
+ "mongodb-memory-server": "^11.0.1",
"playwright": "^1.55.1",
"prettier": "^3.5.3",
"prettier-plugin-svelte": "^3.2.6",
@@ -111,6 +112,7 @@
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
+ "dev": true,
"license": "Apache-2.0",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
@@ -1033,31 +1035,6 @@
"node": ">=18.0.0"
}
},
- "node_modules/@babel/code-frame": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
- "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@babel/helper-validator-identifier": "^7.27.1",
- "js-tokens": "^4.0.0",
- "picocolors": "^1.1.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-validator-identifier": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
- "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
- "license": "MIT",
- "peer": true,
- "engines": {
- "node": ">=6.9.0"
- }
- },
"node_modules/@babel/runtime": {
"version": "7.27.6",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz",
@@ -1199,6 +1176,7 @@
"cpu": [
"ppc64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1215,6 +1193,7 @@
"cpu": [
"arm"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1231,6 +1210,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1247,6 +1227,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1263,6 +1244,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1279,6 +1261,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1295,6 +1278,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1311,6 +1295,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1327,6 +1312,7 @@
"cpu": [
"arm"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1343,6 +1329,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1359,6 +1346,7 @@
"cpu": [
"ia32"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1375,6 +1363,7 @@
"cpu": [
"loong64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1391,6 +1380,7 @@
"cpu": [
"mips64el"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1407,6 +1397,7 @@
"cpu": [
"ppc64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1423,6 +1414,7 @@
"cpu": [
"riscv64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1439,6 +1431,7 @@
"cpu": [
"s390x"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1455,6 +1448,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1471,6 +1465,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1487,6 +1482,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1503,6 +1499,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1519,6 +1516,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1535,6 +1533,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1551,6 +1550,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1567,6 +1567,7 @@
"cpu": [
"ia32"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1583,6 +1584,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2349,16 +2351,6 @@
"url": "https://opencollective.com/libvips"
}
},
- "node_modules/@internationalized/date": {
- "version": "3.10.0",
- "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.10.0.tgz",
- "integrity": "sha512-oxDR/NTEJ1k+UFVQElaNIk65E/Z83HK1z1WI3lQyhTtnNg4R5oVXaPzK3jcpKG8UHKDVuDQHzn+wsxSz8RP3aw==",
- "license": "Apache-2.0",
- "peer": true,
- "dependencies": {
- "@swc/helpers": "^0.5.0"
- }
- },
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
@@ -2565,6 +2557,16 @@
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"license": "MIT"
},
+ "node_modules/@mongodb-js/saslprep": {
+ "version": "1.4.6",
+ "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.6.tgz",
+ "integrity": "sha512-y+x3H1xBZd38n10NZF/rEBlvDOOMQ6LKUTHqr8R9VkJ+mmQOYtJFxIlkkK8fZrtOiL6VixbOBWMbZGBdal3Z1g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "sparse-bitfield": "^3.0.3"
+ }
+ },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -2623,6 +2625,7 @@
"version": "1.0.0-next.29",
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz",
"integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==",
+ "dev": true,
"license": "MIT"
},
"node_modules/@resvg/resvg-js": {
@@ -2943,6 +2946,7 @@
"cpu": [
"arm"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2956,6 +2960,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2969,6 +2974,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2982,6 +2988,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2995,6 +3002,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3008,6 +3016,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3021,6 +3030,7 @@
"cpu": [
"arm"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3034,6 +3044,7 @@
"cpu": [
"arm"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3047,6 +3058,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3060,6 +3072,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3073,6 +3086,7 @@
"cpu": [
"loong64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3086,6 +3100,7 @@
"cpu": [
"ppc64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3099,6 +3114,7 @@
"cpu": [
"riscv64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3112,6 +3128,7 @@
"cpu": [
"riscv64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3125,6 +3142,7 @@
"cpu": [
"s390x"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3138,6 +3156,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3151,6 +3170,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3164,6 +3184,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3177,6 +3198,7 @@
"cpu": [
"ia32"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3190,6 +3212,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -3802,13 +3825,14 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
"integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
- "devOptional": true,
+ "dev": true,
"license": "MIT"
},
"node_modules/@sveltejs/acorn-typescript": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.5.tgz",
"integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==",
+ "dev": true,
"license": "MIT",
"peerDependencies": {
"acorn": "^8.9.0"
@@ -3834,7 +3858,7 @@
"version": "2.50.2",
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.50.2.tgz",
"integrity": "sha512-875hTUkEbz+MyJIxWbQjfMaekqdmEKUUfR7JyKcpfMRZqcGyrO9Gd+iS1D/Dx8LpE5FEtutWGOtlAh4ReSAiOA==",
- "devOptional": true,
+ "dev": true,
"license": "MIT",
"dependencies": {
"@standard-schema/spec": "^1.0.0",
@@ -3877,7 +3901,7 @@
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-5.1.0.tgz",
"integrity": "sha512-wojIS/7GYnJDYIg1higWj2ROA6sSRWvcR1PO/bqEyFr/5UZah26c8Cz4u0NaqjPeVltzsVpt2Tm8d2io0V+4Tw==",
- "devOptional": true,
+ "dev": true,
"license": "MIT",
"dependencies": {
"@sveltejs/vite-plugin-svelte-inspector": "^4.0.1",
@@ -3899,7 +3923,7 @@
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-4.0.1.tgz",
"integrity": "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==",
- "devOptional": true,
+ "dev": true,
"license": "MIT",
"dependencies": {
"debug": "^4.3.7"
@@ -3913,16 +3937,6 @@
"vite": "^6.0.0"
}
},
- "node_modules/@swc/helpers": {
- "version": "0.5.17",
- "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz",
- "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==",
- "license": "Apache-2.0",
- "peer": true,
- "dependencies": {
- "tslib": "^2.8.0"
- }
- },
"node_modules/@tailwindcss/typography": {
"version": "0.5.16",
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.16.tgz",
@@ -3939,40 +3953,6 @@
"tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
}
},
- "node_modules/@testing-library/dom": {
- "version": "10.4.0",
- "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz",
- "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@babel/code-frame": "^7.10.4",
- "@babel/runtime": "^7.12.5",
- "@types/aria-query": "^5.0.1",
- "aria-query": "5.3.0",
- "chalk": "^4.1.0",
- "dom-accessibility-api": "^0.5.9",
- "lz-string": "^1.5.0",
- "pretty-format": "^27.0.2"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@testing-library/user-event": {
- "version": "14.6.1",
- "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz",
- "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==",
- "license": "MIT",
- "peer": true,
- "engines": {
- "node": ">=12",
- "npm": ">=6"
- },
- "peerDependencies": {
- "@testing-library/dom": ">=7.21.4"
- }
- },
"node_modules/@tokenizer/inflate": {
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz",
@@ -4006,17 +3986,11 @@
"node": ">= 10"
}
},
- "node_modules/@types/aria-query": {
- "version": "5.0.4",
- "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
- "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
- "license": "MIT",
- "peer": true
- },
"node_modules/@types/chai": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz",
"integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@types/deep-eql": "*"
@@ -4026,13 +4000,14 @@
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
- "devOptional": true,
+ "dev": true,
"license": "MIT"
},
"node_modules/@types/deep-eql": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
"integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
+ "dev": true,
"license": "MIT"
},
"node_modules/@types/dompurify": {
@@ -4049,6 +4024,7 @@
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
"license": "MIT"
},
"node_modules/@types/js-yaml": {
@@ -4172,6 +4148,23 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/webidl-conversions": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
+ "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/whatwg-url": {
+ "version": "13.0.0",
+ "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-13.0.0.tgz",
+ "integrity": "sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/webidl-conversions": "*"
+ }
+ },
"node_modules/@types/yazl": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@types/yazl/-/yazl-3.3.0.tgz",
@@ -4387,46 +4380,11 @@
"dev": true,
"license": "ISC"
},
- "node_modules/@vitest/browser": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-3.2.2.tgz",
- "integrity": "sha512-LJk8ZhCGhgG6G6jFFJ9LX83ibRY8FszLuu9zPaYFDrcHbBwNXwt1v06HRs/vHVYxwjw3/BGzSIgn9Et2P6rCiA==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@testing-library/dom": "^10.4.0",
- "@testing-library/user-event": "^14.6.1",
- "@vitest/mocker": "3.2.2",
- "@vitest/utils": "3.2.2",
- "magic-string": "^0.30.17",
- "sirv": "^3.0.1",
- "tinyrainbow": "^2.0.0",
- "ws": "^8.18.2"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- },
- "peerDependencies": {
- "playwright": "*",
- "vitest": "3.2.2",
- "webdriverio": "^7.0.0 || ^8.0.0 || ^9.0.0"
- },
- "peerDependenciesMeta": {
- "playwright": {
- "optional": true
- },
- "safaridriver": {
- "optional": true
- },
- "webdriverio": {
- "optional": true
- }
- }
- },
"node_modules/@vitest/expect": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.2.tgz",
"integrity": "sha512-ipHw0z669vEMjzz3xQE8nJX1s0rQIb7oEl4jjl35qWTwm/KIHERIg/p/zORrjAaZKXfsv7IybcNGHwhOOAPMwQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@types/chai": "^5.2.2",
@@ -4443,6 +4401,7 @@
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.2.tgz",
"integrity": "sha512-jKojcaRyIYpDEf+s7/dD3LJt53c0dPfp5zCPXz9H/kcGrSlovU/t1yEaNzM9oFME3dcd4ULwRI/x0Po1Zf+LTw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@vitest/spy": "3.2.2",
@@ -4469,6 +4428,7 @@
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@types/estree": "^1.0.0"
@@ -4478,6 +4438,7 @@
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.2.tgz",
"integrity": "sha512-FY4o4U1UDhO9KMd2Wee5vumwcaHw7Vg4V7yR4Oq6uK34nhEJOmdRYrk3ClburPRUA09lXD/oXWZ8y/Sdma0aUQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"tinyrainbow": "^2.0.0"
@@ -4490,6 +4451,7 @@
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.2.tgz",
"integrity": "sha512-GYcHcaS3ejGRZYed2GAkvsjBeXIEerDKdX3orQrBJqLRiea4NSS9qvn9Nxmuy1IwIB+EjFOaxXnX79l8HFaBwg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@vitest/utils": "3.2.2",
@@ -4503,12 +4465,14 @@
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true,
"license": "MIT"
},
"node_modules/@vitest/snapshot": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.2.tgz",
"integrity": "sha512-aMEI2XFlR1aNECbBs5C5IZopfi5Lb8QJZGGpzS8ZUHML5La5wCbrbhLOVSME68qwpT05ROEEOAZPRXFpxZV2wA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@vitest/pretty-format": "3.2.2",
@@ -4523,12 +4487,14 @@
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true,
"license": "MIT"
},
"node_modules/@vitest/spy": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.2.tgz",
"integrity": "sha512-6Utxlx3o7pcTxvp0u8kUiXtRFScMrUg28KjB3R2hon7w4YqOFAEA9QwzPVVS1QNL3smo4xRNOpNZClRVfpMcYg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"tinyspy": "^4.0.3"
@@ -4541,6 +4507,7 @@
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.2.tgz",
"integrity": "sha512-qJYMllrWpF/OYfWHP32T31QCaLa3BAzT/n/8mNGhPdVcjY+JYazQFO1nsJvXU12Kp1xMpNY4AGuljPTNjQve6A==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@vitest/pretty-format": "3.2.2",
@@ -4612,6 +4579,7 @@
"version": "8.14.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
+ "dev": true,
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
@@ -4791,16 +4759,6 @@
"dev": true,
"license": "Python-2.0"
},
- "node_modules/aria-query": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
- "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
- "license": "Apache-2.0",
- "peer": true,
- "dependencies": {
- "dequal": "^2.0.3"
- }
- },
"node_modules/array-union": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
@@ -4815,11 +4773,22 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
"integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
}
},
+ "node_modules/async-mutex": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz",
+ "integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -4882,17 +4851,48 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
"integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
+ "dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">= 0.4"
}
},
+ "node_modules/b4a": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz",
+ "integrity": "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "react-native-b4a": "*"
+ },
+ "peerDependenciesMeta": {
+ "react-native-b4a": {
+ "optional": true
+ }
+ }
+ },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
+ "node_modules/bare-events": {
+ "version": "2.8.2",
+ "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz",
+ "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "bare-abort-controller": "*"
+ },
+ "peerDependenciesMeta": {
+ "bare-abort-controller": {
+ "optional": true
+ }
+ }
+ },
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@@ -5070,6 +5070,16 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
+ "node_modules/bson": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/bson/-/bson-7.2.0.tgz",
+ "integrity": "sha512-YCEo7KjMlbNlyHhz7zAZNDpIpQbd+wOEHJYezv0nMYTn4x31eIUM2yomNNubclAt63dObUzKHWsBLJ9QcZNSnQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=20.19.0"
+ }
+ },
"node_modules/bson-objectid": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/bson-objectid/-/bson-objectid-2.0.4.tgz",
@@ -5101,6 +5111,16 @@
"ieee754": "^1.2.1"
}
},
+ "node_modules/buffer-crc32": {
+ "version": "0.2.13",
+ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
+ "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@@ -5114,6 +5134,7 @@
"version": "6.7.14",
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
"integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -5158,6 +5179,19 @@
"node": ">=6"
}
},
+ "node_modules/camelcase": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/camelcase-css": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
@@ -5200,6 +5234,7 @@
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz",
"integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"assertion-error": "^2.0.1",
@@ -5216,6 +5251,7 @@
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.1.0",
@@ -5232,6 +5268,7 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz",
"integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">= 16"
@@ -5408,7 +5445,7 @@
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
- "devOptional": true,
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.6"
@@ -5588,6 +5625,7 @@
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
"integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@@ -5604,7 +5642,7 @@
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
- "devOptional": true,
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -5650,7 +5688,7 @@
"version": "5.6.2",
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.2.tgz",
"integrity": "sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==",
- "devOptional": true,
+ "dev": true,
"license": "MIT"
},
"node_modules/didyoumean": {
@@ -5691,13 +5729,6 @@
"node": ">=6.0.0"
}
},
- "node_modules/dom-accessibility-api": {
- "version": "0.5.16",
- "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
- "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
- "license": "MIT",
- "peer": true
- },
"node_modules/dom-serializer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
@@ -5902,6 +5933,7 @@
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
"integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
+ "dev": true,
"license": "MIT"
},
"node_modules/es-object-atoms": {
@@ -5935,6 +5967,7 @@
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz",
"integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==",
+ "dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
@@ -6229,6 +6262,7 @@
"version": "1.4.7",
"resolved": "https://registry.npmjs.org/esrap/-/esrap-1.4.7.tgz",
"integrity": "sha512-0ZxW6guTF/AeKeKi7he93lmgv7Hx7giD1tBrOeVqkqsZGQJd2/kfnL7LdIsr9FT/AtkBK9XeDTov+gxprBqdEg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15"
@@ -6307,6 +6341,16 @@
"node": ">=0.8.x"
}
},
+ "node_modules/events-universal": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz",
+ "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "bare-events": "^2.7.0"
+ }
+ },
"node_modules/eventsource": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz",
@@ -6367,6 +6411,7 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.1.tgz",
"integrity": "sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==",
+ "dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=12.0.0"
@@ -6495,6 +6540,13 @@
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"license": "MIT"
},
+ "node_modules/fast-fifo": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
+ "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/fast-glob": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
@@ -6599,6 +6651,7 @@
"version": "6.4.5",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz",
"integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==",
+ "dev": true,
"license": "MIT",
"peerDependencies": {
"picomatch": "^3 || ^4"
@@ -6679,6 +6732,24 @@
"url": "https://opencollective.com/express"
}
},
+ "node_modules/find-cache-dir": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz",
+ "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "commondir": "^1.0.1",
+ "make-dir": "^3.0.2",
+ "pkg-dir": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/avajs/find-cache-dir?sponsor=1"
+ }
+ },
"node_modules/find-up": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
@@ -6718,6 +6789,27 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/follow-redirects": {
+ "version": "1.15.11",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
+ "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
"node_modules/foreground-child": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
@@ -7030,6 +7122,7 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -7741,13 +7834,6 @@
"node": ">=10"
}
},
- "node_modules/js-tokens": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
- "license": "MIT",
- "peer": true
- },
"node_modules/js-yaml": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
@@ -7903,7 +7989,7 @@
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
"integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
- "devOptional": true,
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@@ -8056,6 +8142,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
+ "dev": true,
"license": "MIT"
},
"node_modules/locate-path": {
@@ -8188,6 +8275,7 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz",
"integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==",
+ "dev": true,
"license": "MIT"
},
"node_modules/lru-cache": {
@@ -8226,11 +8314,38 @@
"version": "0.30.17",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
"integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0"
}
},
+ "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,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
"node_modules/marked": {
"version": "12.0.2",
"resolved": "https://registry.npmjs.org/marked/-/marked-12.0.2.tgz",
@@ -8261,6 +8376,13 @@
"node": ">= 0.8"
}
},
+ "node_modules/memory-pager": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
+ "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/merge-descriptors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
@@ -8412,11 +8534,138 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/mongodb": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.1.0.tgz",
+ "integrity": "sha512-kMfnKunbolQYwCIyrkxNJFB4Ypy91pYqua5NargS/f8ODNSJxT03ZU3n1JqL4mCzbSih8tvmMEMLpKTT7x5gCg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@mongodb-js/saslprep": "^1.3.0",
+ "bson": "^7.1.1",
+ "mongodb-connection-string-url": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=20.19.0"
+ },
+ "peerDependencies": {
+ "@aws-sdk/credential-providers": "^3.806.0",
+ "@mongodb-js/zstd": "^7.0.0",
+ "gcp-metadata": "^7.0.1",
+ "kerberos": "^7.0.0",
+ "mongodb-client-encryption": ">=7.0.0 <7.1.0",
+ "snappy": "^7.3.2",
+ "socks": "^2.8.6"
+ },
+ "peerDependenciesMeta": {
+ "@aws-sdk/credential-providers": {
+ "optional": true
+ },
+ "@mongodb-js/zstd": {
+ "optional": true
+ },
+ "gcp-metadata": {
+ "optional": true
+ },
+ "kerberos": {
+ "optional": true
+ },
+ "mongodb-client-encryption": {
+ "optional": true
+ },
+ "snappy": {
+ "optional": true
+ },
+ "socks": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/mongodb-connection-string-url": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-7.0.1.tgz",
+ "integrity": "sha512-h0AZ9A7IDVwwHyMxmdMXKy+9oNlF0zFoahHiX3vQ8e3KFcSP3VmsmfvtRSuLPxmyv2vjIDxqty8smTgie/SNRQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/whatwg-url": "^13.0.0",
+ "whatwg-url": "^14.1.0"
+ },
+ "engines": {
+ "node": ">=20.19.0"
+ }
+ },
+ "node_modules/mongodb-connection-string-url/node_modules/tr46": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
+ "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "punycode": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/mongodb-connection-string-url/node_modules/whatwg-url": {
+ "version": "14.2.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
+ "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tr46": "^5.1.0",
+ "webidl-conversions": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/mongodb-memory-server": {
+ "version": "11.0.1",
+ "resolved": "https://registry.npmjs.org/mongodb-memory-server/-/mongodb-memory-server-11.0.1.tgz",
+ "integrity": "sha512-nUlKovSJZBh7q5hPsewFRam9H66D08Ne18nyknkNalfXMPtK1Og3kOcuqQhcX88x/pghSZPIJHrLbxNFW3OWiw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "mongodb-memory-server-core": "11.0.1",
+ "tslib": "^2.8.1"
+ },
+ "engines": {
+ "node": ">=20.19.0"
+ }
+ },
+ "node_modules/mongodb-memory-server-core": {
+ "version": "11.0.1",
+ "resolved": "https://registry.npmjs.org/mongodb-memory-server-core/-/mongodb-memory-server-core-11.0.1.tgz",
+ "integrity": "sha512-IcIb2S9Xf7Lmz43Z1ZujMqNg7PU5Q7yn+4wOnu7l6pfeGPkEmlqzV1hIbroVx8s4vXhPB1oMGC1u8clW7aj3Xw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "async-mutex": "^0.5.0",
+ "camelcase": "^6.3.0",
+ "debug": "^4.4.3",
+ "find-cache-dir": "^3.3.2",
+ "follow-redirects": "^1.15.11",
+ "https-proxy-agent": "^7.0.6",
+ "mongodb": "^7.0.0",
+ "new-find-package-json": "^2.0.0",
+ "semver": "^7.7.3",
+ "tar-stream": "^3.1.7",
+ "tslib": "^2.8.1",
+ "yauzl": "^3.2.0"
+ },
+ "engines": {
+ "node": ">=20.19.0"
+ }
+ },
"node_modules/mri": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
"integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
- "devOptional": true,
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
@@ -8426,6 +8675,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",
"integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
@@ -8488,6 +8738,19 @@
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"license": "MIT"
},
+ "node_modules/new-find-package-json": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/new-find-package-json/-/new-find-package-json-2.0.0.tgz",
+ "integrity": "sha512-lDcBsjBSMlj3LXH2v/FW3txlh2pYTjmbOXPYJD93HI5EwuLzI11tdHSIpUMmfq/IOsldj4Ps8M8flhm+pCK4Ew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": ">=12.22.0"
+ }
+ },
"node_modules/node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
@@ -8824,6 +9087,16 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/package-json-from-dist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
@@ -8998,6 +9271,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz",
"integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">= 14.16"
@@ -9016,6 +9290,13 @@
"url": "https://github.com/sponsors/Borewit"
}
},
+ "node_modules/pend": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
+ "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -9026,6 +9307,7 @@
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
@@ -9135,6 +9417,75 @@
"node": ">=16.20.0"
}
},
+ "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==",
+ "dev": true,
+ "license": "MIT",
+ "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,
+ "license": "MIT",
+ "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,
+ "license": "MIT",
+ "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,
+ "license": "MIT",
+ "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,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/pkg-types": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz",
@@ -9158,7 +9509,7 @@
"version": "1.55.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.1.tgz",
"integrity": "sha512-cJW4Xd/G3v5ovXtJJ52MAOclqeac9S/aGGgRzLabuF8TnIb6xHvMzKIa6JmrRzUkeXJgfL1MhukP0NK6l39h3A==",
- "devOptional": true,
+ "dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.55.1"
@@ -9177,7 +9528,7 @@
"version": "1.55.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.1.tgz",
"integrity": "sha512-Z6Mh9mkwX+zxSlHqdr5AOcJnfp+xUWLCt9uKV18fhzA8eyxUd8NUWzAjxUh55RZKSYwDGX0cfaySdhZJGMoJ+w==",
- "devOptional": true,
+ "dev": true,
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
@@ -9536,34 +9887,6 @@
}
}
},
- "node_modules/pretty-format": {
- "version": "27.5.1",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
- "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "ansi-regex": "^5.0.1",
- "ansi-styles": "^5.0.0",
- "react-is": "^17.0.1"
- },
- "engines": {
- "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
- }
- },
- "node_modules/pretty-format/node_modules/ansi-styles": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
- "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
- "license": "MIT",
- "peer": true,
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
"node_modules/process": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
@@ -9761,13 +10084,6 @@
"url": "https://opencollective.com/express"
}
},
- "node_modules/react-is": {
- "version": "17.0.2",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
- "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
- "license": "MIT",
- "peer": true
- },
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -9929,6 +10245,7 @@
"version": "4.41.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.1.tgz",
"integrity": "sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@types/estree": "1.0.7"
@@ -9968,6 +10285,7 @@
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
"integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
+ "dev": true,
"license": "MIT"
},
"node_modules/router": {
@@ -10043,7 +10361,7 @@
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
"integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==",
- "devOptional": true,
+ "dev": true,
"license": "MIT",
"dependencies": {
"mri": "^1.1.0"
@@ -10136,9 +10454,9 @@
"license": "BSD-3-Clause"
},
"node_modules/semver": {
- "version": "7.7.2",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
- "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@@ -10221,7 +10539,7 @@
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.0.1.tgz",
"integrity": "sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q==",
- "devOptional": true,
+ "dev": true,
"license": "MIT"
},
"node_modules/setprototypeof": {
@@ -10366,6 +10684,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
"integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true,
"license": "ISC"
},
"node_modules/signal-exit": {
@@ -10393,6 +10712,7 @@
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.1.tgz",
"integrity": "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@polka/url": "^1.0.0-next.24",
@@ -10474,6 +10794,16 @@
"node": ">=0.10.0"
}
},
+ "node_modules/sparse-bitfield": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
+ "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "memory-pager": "^1.0.2"
+ }
+ },
"node_modules/split2": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
@@ -10493,6 +10823,7 @@
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
"integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true,
"license": "MIT"
},
"node_modules/statuses": {
@@ -10508,8 +10839,21 @@
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz",
"integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==",
+ "dev": true,
"license": "MIT"
},
+ "node_modules/streamx": {
+ "version": "2.23.0",
+ "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz",
+ "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "events-universal": "^1.0.0",
+ "fast-fifo": "^1.3.2",
+ "text-decoder": "^1.1.0"
+ }
+ },
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
@@ -10788,6 +11132,7 @@
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
@@ -10812,6 +11157,7 @@
"version": "5.33.14",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.33.14.tgz",
"integrity": "sha512-kRlbhIlMTijbFmVDQFDeKXPLlX1/ovXwV0I162wRqQhRcygaqDIcu1d/Ese3H2uI+yt3uT8E7ndgDthQv5v5BA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "^2.3.0",
@@ -10909,6 +11255,7 @@
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
"integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
+ "dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">= 0.4"
@@ -10918,6 +11265,7 @@
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
"integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@types/estree": "^1.0.6"
@@ -11101,6 +11449,18 @@
"node": ">=8.10.0"
}
},
+ "node_modules/tar-stream": {
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz",
+ "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "b4a": "^1.6.4",
+ "fast-fifo": "^1.2.0",
+ "streamx": "^2.15.0"
+ }
+ },
"node_modules/tdigest": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.2.tgz",
@@ -11110,6 +11470,16 @@
"bintrees": "1.0.2"
}
},
+ "node_modules/text-decoder": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz",
+ "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "b4a": "^1.6.4"
+ }
+ },
"node_modules/text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@@ -11171,18 +11541,21 @@
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
"integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+ "dev": true,
"license": "MIT"
},
"node_modules/tinyexec": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
"integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
+ "dev": true,
"license": "MIT"
},
"node_modules/tinyglobby": {
"version": "0.2.14",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
"integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.4.4",
@@ -11199,6 +11572,7 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.0.tgz",
"integrity": "sha512-7CotroY9a8DKsKprEy/a14aCCm8jYVmR7aFy4fpkZM8sdpNJbKkixuNjgM50yCmip2ezc8z4N7k3oe2+rfRJCQ==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": "^18.0.0 || >=20.0.0"
@@ -11208,6 +11582,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz",
"integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=14.0.0"
@@ -11217,6 +11592,7 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.3.tgz",
"integrity": "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=14.0.0"
@@ -11264,6 +11640,7 @@
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
"integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@@ -11625,6 +12002,7 @@
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "^0.25.0",
@@ -11699,6 +12077,7 @@
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.2.tgz",
"integrity": "sha512-Xj/jovjZvDXOq2FgLXu8NsY4uHUMWtzVmMC2LkCu9HWdr9Qu1Is5sanX3Z4jOFKdohfaWDnEJWp9pRP0vVpAcA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"cac": "^6.7.14",
@@ -11721,12 +12100,14 @@
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true,
"license": "MIT"
},
"node_modules/vite/node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
@@ -11741,7 +12122,7 @@
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.0.6.tgz",
"integrity": "sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA==",
- "devOptional": true,
+ "dev": true,
"license": "MIT",
"workspaces": [
"tests/deps/*",
@@ -11760,6 +12141,7 @@
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.2.tgz",
"integrity": "sha512-fyNn/Rp016Bt5qvY0OQvIUCwW2vnaEBLxP42PmKbNIoasSYjML+8xyeADOPvBe+Xfl/ubIw4og7Lt9jflRsCNw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@types/chai": "^5.2.2",
@@ -11849,6 +12231,7 @@
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true,
"license": "MIT"
},
"node_modules/w3c-xmlserializer": {
@@ -11941,6 +12324,7 @@
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
"integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"siginfo": "^2.0.0",
@@ -12132,6 +12516,20 @@
"node": ">= 14.6"
}
},
+ "node_modules/yauzl": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.2.0.tgz",
+ "integrity": "sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "buffer-crc32": "~0.2.3",
+ "pend": "~1.2.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/yazl": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/yazl/-/yazl-3.3.1.tgz",
@@ -12175,6 +12573,7 @@
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz",
"integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==",
+ "dev": true,
"license": "MIT"
},
"node_modules/zod": {
diff --git a/frontend/package.json b/frontend/package.json
index d8a45fa47..c2549c78b 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -46,6 +46,7 @@
"isomorphic-dompurify": "2.13.0",
"js-yaml": "^4.1.0",
"minimist": "^1.2.8",
+ "mongodb-memory-server": "^11.0.1",
"playwright": "^1.55.1",
"prettier": "^3.5.3",
"prettier-plugin-svelte": "^3.2.6",
diff --git a/frontend/src/hooks.server.ts b/frontend/src/hooks.server.ts
index b16cd0426..d54dd3c3c 100644
--- a/frontend/src/hooks.server.ts
+++ b/frontend/src/hooks.server.ts
@@ -177,7 +177,29 @@ export const handle: Handle = async ({ event, resolve }) => {
) {
// To get the same CSRF token after callback
refreshSessionCookie(event.cookies, auth.secretSessionId);
- return await triggerOauthFlow(event);
+ const oauthResponse = await triggerOauthFlow(event);
+ if (oauthResponse.status === 502) {
+ const accept = event.request.headers.get("accept") || "";
+ const wantsHtml = accept.includes("text/html");
+
+ if (wantsHtml) {
+ try {
+ const body = await oauthResponse.clone().json();
+ if (typeof body?.code === "string") {
+ return new Response(null, {
+ status: 302,
+ headers: {
+ Location: `${base}/?error=${encodeURIComponent(body.code)}`,
+ "Cache-Control": "no-store",
+ },
+ });
+ }
+ } catch {
+ // fall through and return original structured 502 response
+ }
+ }
+ }
+ return oauthResponse;
}
} else {
// Redirect to OAuth flow unless on the authorized pages (home, shared conversation, login, healthcheck, model thumbnails)
@@ -193,7 +215,29 @@ export const handle: Handle = async ({ event, resolve }) => {
!event.url.pathname.startsWith(`${base}/api`)
) {
refreshSessionCookie(event.cookies, auth.secretSessionId);
- return triggerOauthFlow(event);
+ const oauthResponse = await triggerOauthFlow(event);
+ if (oauthResponse.status === 502) {
+ const accept = event.request.headers.get("accept") || "";
+ const wantsHtml = accept.includes("text/html");
+
+ if (wantsHtml) {
+ try {
+ const body = await oauthResponse.clone().json();
+ if (typeof body?.code === "string") {
+ return new Response(null, {
+ status: 302,
+ headers: {
+ Location: `${base}/?error=${encodeURIComponent(body.code)}`,
+ "Cache-Control": "no-store",
+ },
+ });
+ }
+ } catch {
+ // fall through and return original structured 502 response
+ }
+ }
+ }
+ return oauthResponse;
}
}
}
diff --git a/frontend/src/lib/components/SignInButton.svelte b/frontend/src/lib/components/SignInButton.svelte
new file mode 100644
index 000000000..9bf7a0114
--- /dev/null
+++ b/frontend/src/lib/components/SignInButton.svelte
@@ -0,0 +1,36 @@
+
+
+
diff --git a/frontend/src/lib/server/auth.spec.ts b/frontend/src/lib/server/auth.spec.ts
new file mode 100644
index 000000000..5bc9aad66
--- /dev/null
+++ b/frontend/src/lib/server/auth.spec.ts
@@ -0,0 +1,234 @@
+/**
+ * Unit tests for OAuth provider error handling in auth.ts
+ *
+ * Runs in the "server" Vitest workspace (environment: node).
+ * Uses vi.hoisted() so that mock variables are accessible inside vi.mock() factories.
+ */
+import { describe, it, expect, vi, beforeEach } from "vitest";
+
+// ---------------------------------------------------------------------------
+// Hoist mock references so they can be used in vi.mock() factories
+// ---------------------------------------------------------------------------
+const { mockIssuerDiscover } = vi.hoisted(() => ({
+ mockIssuerDiscover: vi.fn(),
+}));
+
+// ---------------------------------------------------------------------------
+// All vi.mock() calls – must appear before any named imports
+// ---------------------------------------------------------------------------
+vi.mock("$app/paths", () => ({ base: "" }));
+vi.mock("$app/environment", () => ({ dev: false }));
+vi.mock("$lib/migrations/lock", () => ({
+ acquireLock: vi.fn(),
+ releaseLock: vi.fn(),
+ isDBLocked: vi.fn(),
+}));
+vi.mock("$lib/utils/sha256", () => ({
+ sha256: vi.fn(async (v: string) => v + "-hashed"),
+}));
+vi.mock("bson-objectid", () => ({ default: class ObjectId { } }));
+vi.mock("$lib/types/Semaphore", () => ({ Semaphores: { OAUTH_TOKEN_REFRESH: "oauth_refresh" } }));
+vi.mock("$lib/server/database", () => ({ collections: {} }));
+vi.mock("./adminToken", () => ({
+ adminTokenManager: { isAdmin: vi.fn(() => false) },
+}));
+vi.mock("$lib/server/logger", () => ({
+ logger: { error: vi.fn(), info: vi.fn(), warn: vi.fn() },
+}));
+vi.mock("$lib/server/config", () => ({
+ config: {
+ OPENID_CLIENT_ID: "test-client-id",
+ OPENID_CLIENT_SECRET: "test-client-secret",
+ OPENID_PROVIDER_URL: "https://provider.example.com",
+ OPENID_SCOPES: "openid profile",
+ OPENID_NAME_CLAIM: "name",
+ OPENID_TOLERANCE: "",
+ OPENID_RESOURCE: "",
+ OPENID_CONFIG: "{}",
+ COOKIE_NAME: "hf-chat",
+ COOKIE_SAMESITE: "",
+ COOKIE_SECURE: "",
+ ALLOW_INSECURE_COOKIES: "true",
+ PUBLIC_ORIGIN: "http://localhost:5173",
+ COUPLE_SESSION_WITH_COOKIE_NAME: "",
+ TRUSTED_EMAIL_HEADER: "",
+ ALTERNATIVE_REDIRECT_URLS: [],
+ },
+}));
+vi.mock("openid-client", () => ({
+ Issuer: { discover: mockIssuerDiscover },
+ custom: { clock_tolerance: Symbol("clock_tolerance") },
+ generators: {
+ codeVerifier: vi.fn(() => "test-code-verifier"),
+ codeChallenge: vi.fn(() => "test-code-challenge"),
+ },
+}));
+
+// ---------------------------------------------------------------------------
+// Imports (after all vi.mock() calls)
+// ---------------------------------------------------------------------------
+import { OAuthProviderError } from "./auth";
+
+// ---------------------------------------------------------------------------
+// OAuthProviderError class tests
+// ---------------------------------------------------------------------------
+describe("OAuthProviderError", () => {
+ it("defaults code to OAUTH_PROVIDER_ERROR", () => {
+ const err = new OAuthProviderError("Something went wrong");
+ expect(err.code).toBe("OAUTH_PROVIDER_ERROR");
+ expect(err.name).toBe("OAuthProviderError");
+ expect(err.message).toBe("Something went wrong");
+ });
+
+ it("accepts OAUTH_PROVIDER_UNAVAILABLE code", () => {
+ const err = new OAuthProviderError("Provider down", "OAUTH_PROVIDER_UNAVAILABLE");
+ expect(err.code).toBe("OAUTH_PROVIDER_UNAVAILABLE");
+ });
+
+ it("is an instance of Error", () => {
+ const err = new OAuthProviderError("test");
+ expect(err instanceof Error).toBe(true);
+ });
+});
+
+// ---------------------------------------------------------------------------
+// triggerOauthFlow error handling tests
+// ---------------------------------------------------------------------------
+describe("triggerOauthFlow – provider failures", () => {
+ const makeEvent = () => ({
+ url: new URL("http://localhost:5173/login"),
+ locals: { sessionId: "test-session-id" },
+ cookies: {
+ set: vi.fn(),
+ get: vi.fn(),
+ getAll: vi.fn(() => []),
+ delete: vi.fn(),
+ serialize: vi.fn(),
+ },
+ request: new Request("http://localhost:5173/login"),
+ });
+
+ beforeEach(() => {
+ // Reset module cache to clear auth.ts's module-level lastIssuer cache.
+ vi.resetModules();
+ vi.clearAllMocks();
+ mockIssuerDiscover.mockReset();
+ });
+
+ it("returns 502 + OAUTH_PROVIDER_UNAVAILABLE on TypeError (fetch failed)", async () => {
+ mockIssuerDiscover.mockRejectedValue(new TypeError("fetch failed"));
+ const { triggerOauthFlow } = await import("./auth");
+ const response = await triggerOauthFlow(makeEvent() as never);
+
+ expect(response.status).toBe(502);
+ const body = await response.json();
+ expect(body.code).toBe("OAUTH_PROVIDER_UNAVAILABLE");
+ expect(typeof body.message).toBe("string");
+ });
+
+ it("returns 502 + OAUTH_PROVIDER_UNAVAILABLE on ECONNREFUSED", async () => {
+ mockIssuerDiscover.mockRejectedValue(new Error("connect ECONNREFUSED 127.0.0.1:443"));
+ const { triggerOauthFlow } = await import("./auth");
+ const response = await triggerOauthFlow(makeEvent() as never);
+
+ expect(response.status).toBe(502);
+ const body = await response.json();
+ expect(body.code).toBe("OAUTH_PROVIDER_UNAVAILABLE");
+ });
+
+ it("returns 502 + OAUTH_PROVIDER_UNAVAILABLE on ENOTFOUND (DNS failure)", async () => {
+ mockIssuerDiscover.mockRejectedValue(
+ new Error("getaddrinfo ENOTFOUND provider.example.com")
+ );
+ const { triggerOauthFlow } = await import("./auth");
+ const response = await triggerOauthFlow(makeEvent() as never);
+
+ expect(response.status).toBe(502);
+ const body = await response.json();
+ expect(body.code).toBe("OAUTH_PROVIDER_UNAVAILABLE");
+ });
+
+ it("returns 502 + OAUTH_PROVIDER_ERROR on non-network provider error", async () => {
+ mockIssuerDiscover.mockRejectedValue(new Error("Invalid discovery document"));
+ const { triggerOauthFlow } = await import("./auth");
+ const response = await triggerOauthFlow(makeEvent() as never);
+
+ expect(response.status).toBe(502);
+ const body = await response.json();
+ expect(body.code).toBe("OAUTH_PROVIDER_ERROR");
+ });
+
+ it("has Content-Type application/json on error response", async () => {
+ mockIssuerDiscover.mockRejectedValue(new Error("provider error"));
+ const { triggerOauthFlow } = await import("./auth");
+ const response = await triggerOauthFlow(makeEvent() as never);
+
+ expect(response.headers.get("Content-Type")).toContain("application/json");
+ });
+
+ it("lets SvelteKit redirect pass through when provider is healthy", async () => {
+ mockIssuerDiscover.mockResolvedValue({
+ metadata: { id_token_signing_alg_values_supported: ["RS256"] },
+ Client: class {
+ authorizationUrl() {
+ return "https://provider.example.com/auth?code_challenge=xyz";
+ }
+ },
+ });
+
+ const { triggerOauthFlow } = await import("./auth");
+ await expect(triggerOauthFlow(makeEvent() as never)).rejects.toMatchObject({ status: 302 });
+ });
+});
+
+// ---------------------------------------------------------------------------
+// getOIDCUserData error handling tests
+// ---------------------------------------------------------------------------
+describe("getOIDCUserData – provider failures", () => {
+ beforeEach(() => {
+ // Reset module cache to clear auth.ts's module-level lastIssuer cache.
+ vi.resetModules();
+ vi.clearAllMocks();
+ mockIssuerDiscover.mockReset();
+ });
+
+ it("throws OAuthProviderError OAUTH_PROVIDER_UNAVAILABLE on TypeError fetch failed", async () => {
+ mockIssuerDiscover.mockRejectedValue(new TypeError("fetch failed"));
+ const { getOIDCUserData } = await import("./auth");
+
+ await expect(
+ getOIDCUserData(
+ { redirectURI: "http://localhost:5173/login/callback" },
+ "test-code",
+ "test-verifier",
+ undefined,
+ new URL("http://localhost:5173")
+ )
+ ).rejects.toSatisfy(
+ (e: any) => e.name === "OAuthProviderError" && e.code === "OAUTH_PROVIDER_UNAVAILABLE"
+ );
+ });
+
+ it("throws OAuthProviderError OAUTH_PROVIDER_ERROR on token exchange error", async () => {
+ mockIssuerDiscover.mockResolvedValue({
+ metadata: { id_token_signing_alg_values_supported: ["RS256"] },
+ Client: class {
+ callback = vi.fn().mockRejectedValue(new Error("invalid_grant"));
+ userinfo = vi.fn();
+ },
+ });
+ const { getOIDCUserData } = await import("./auth");
+
+ await expect(
+ getOIDCUserData(
+ { redirectURI: "http://localhost:5173/login/callback" },
+ "test-code",
+ "test-verifier",
+ undefined,
+ new URL("http://localhost:5173")
+ )
+ ).rejects.toSatisfy(
+ (e: any) => e.name === "OAuthProviderError" && e.code === "OAUTH_PROVIDER_ERROR"
+ );
+ });
+});
diff --git a/frontend/src/lib/server/auth.ts b/frontend/src/lib/server/auth.ts
index 5f9f299af..c3e3a35a3 100644
--- a/frontend/src/lib/server/auth.ts
+++ b/frontend/src/lib/server/auth.ts
@@ -33,6 +33,20 @@ export interface OIDCUserInfo {
userData: UserinfoResponse;
}
+/** Structured error codes returned when an OAuth provider is unavailable or misbehaving. */
+export type OAuthErrorCode = "OAUTH_PROVIDER_UNAVAILABLE" | "OAUTH_PROVIDER_ERROR";
+
+/** Thrown (and caught) to distinguish OAuth provider failures from other errors. */
+export class OAuthProviderError extends Error {
+ public readonly code: OAuthErrorCode;
+
+ constructor(message: string, code: OAuthErrorCode = "OAUTH_PROVIDER_ERROR") {
+ super(message);
+ this.name = "OAuthProviderError";
+ this.code = code;
+ }
+}
+
const stringWithDefault = (value: string) =>
z
.string()
@@ -319,18 +333,33 @@ export async function getOIDCUserData(
iss: string | undefined,
url: URL
): Promise {
- const client = await getOIDCClient(settings, url);
- const token = await client.callback(
- settings.redirectURI,
- {
- code,
- iss,
- },
- { code_verifier: codeVerifier }
- );
- const userData = await client.userinfo(token);
-
- return { token, userData };
+ try {
+ const client = await getOIDCClient(settings, url);
+ const token = await client.callback(
+ settings.redirectURI,
+ {
+ code,
+ iss,
+ },
+ { code_verifier: codeVerifier }
+ );
+ const userData = await client.userinfo(token);
+ return { token, userData };
+ } catch (err) {
+ // Re-wrap as a typed error so callers (e.g. callback +server.ts) can distinguish
+ // provider unavailability from other failures without logging sensitive token data.
+ const isNetworkError =
+ err instanceof TypeError ||
+ (err instanceof Error && (err.message.includes("ECONNREFUSED") || err.message.includes("ENOTFOUND") || err.message.includes("fetch failed")));
+ const code: OAuthErrorCode = isNetworkError
+ ? "OAUTH_PROVIDER_UNAVAILABLE"
+ : "OAUTH_PROVIDER_ERROR";
+ logger.error({ code, msg: err instanceof Error ? err.message : String(err) }, "OAuth provider error in getOIDCUserData");
+ throw new OAuthProviderError(
+ isNetworkError ? "OAuth provider is unreachable" : "OAuth provider returned an error",
+ code
+ );
+ }
}
/**
@@ -522,8 +551,6 @@ export async function triggerOauthFlow({ url, locals, cookies }: RequestEvent):
// let redirectURI = `${(referer ? new URL(referer) : url).origin}${base}/login/callback`;
let redirectURI = `${url.origin}${base}/login/callback`;
- // TODO: Handle errors if provider is not responding
-
if (url.searchParams.has("callback")) {
const callback = url.searchParams.get("callback") || redirectURI;
if (config.ALTERNATIVE_REDIRECT_URLS.includes(callback)) {
@@ -545,10 +572,47 @@ export async function triggerOauthFlow({ url, locals, cookies }: RequestEvent):
next = sanitizeReturnPath(`${base}/`) ?? "/";
}
- const authorizationUrl = await getOIDCAuthorizationUrl(
- { redirectURI },
- { sessionId: locals.sessionId, next, url, cookies }
- );
+ try {
+ const authorizationUrl = await getOIDCAuthorizationUrl(
+ { redirectURI },
+ { sessionId: locals.sessionId, next, url, cookies }
+ );
+ throw redirect(302, authorizationUrl);
+ } catch (err) {
+ // Let SvelteKit redirect responses pass through unchanged.
+ if (err instanceof Response || (err && typeof err === "object" && "status" in err && "location" in err)) {
+ throw err;
+ }
- throw redirect(302, authorizationUrl);
+ // Determine structured error code based on error type.
+ const isNetworkError =
+ err instanceof TypeError ||
+ (err instanceof Error &&
+ (err.message.includes("ECONNREFUSED") ||
+ err.message.includes("ENOTFOUND") ||
+ err.message.includes("fetch failed")));
+ const errorCode: OAuthErrorCode = isNetworkError
+ ? "OAUTH_PROVIDER_UNAVAILABLE"
+ : "OAUTH_PROVIDER_ERROR";
+
+ // Log the error without sensitive data (no tokens, no secrets).
+ logger.error(
+ { code: errorCode, msg: err instanceof Error ? err.message : String(err) },
+ "OAuth provider error during authorization URL generation"
+ );
+
+ return new Response(
+ JSON.stringify({
+ code: errorCode,
+ message:
+ errorCode === "OAUTH_PROVIDER_UNAVAILABLE"
+ ? "The sign-in service is temporarily unavailable. Please try again later."
+ : "An error occurred during sign-in. Please try again.",
+ }),
+ {
+ status: 502,
+ headers: { "Content-Type": "application/json" },
+ }
+ );
+ }
}
diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte
index 06e9c69b4..7dc919ff4 100644
--- a/frontend/src/routes/+layout.svelte
+++ b/frontend/src/routes/+layout.svelte
@@ -135,6 +135,13 @@
let errorToastTimeout: ReturnType;
let currentError: string | undefined = $state();
+ let lastOAuthErrorCode: string | null = $state(null);
+
+ const OAUTH_ERROR_MESSAGES: Record = {
+ OAUTH_PROVIDER_UNAVAILABLE:
+ "Sign-in service is temporarily unavailable. Please try again later.",
+ OAUTH_PROVIDER_ERROR: "An error occurred during sign-in. Please try again.",
+ };
async function onError() {
// If a new different error comes, wait for the current error to hide first
@@ -203,6 +210,16 @@
if ($error) onError();
});
+ $effect(() => {
+ const oauthErrorCode = page.url.searchParams.get("error");
+ if (!oauthErrorCode || oauthErrorCode === lastOAuthErrorCode) {
+ return;
+ }
+
+ lastOAuthErrorCode = oauthErrorCode;
+ $error = OAUTH_ERROR_MESSAGES[oauthErrorCode] ?? ERROR_MESSAGES.default;
+ });
+
$effect(() => {
if ($titleUpdate) {
const convIdx = conversations.findIndex(({ id }) => id === $titleUpdate?.convId);
diff --git a/frontend/src/routes/login/callback/+server.ts b/frontend/src/routes/login/callback/+server.ts
index 9e04ae8a3..838c7a51b 100644
--- a/frontend/src/routes/login/callback/+server.ts
+++ b/frontend/src/routes/login/callback/+server.ts
@@ -1,10 +1,11 @@
import { error, redirect } from "@sveltejs/kit";
-import { getOIDCUserData, validateAndParseCsrfToken } from "$lib/server/auth";
+import { getOIDCUserData, validateAndParseCsrfToken, OAuthProviderError } from "$lib/server/auth";
import { z } from "zod";
import { base } from "$app/paths";
import { config } from "$lib/server/config";
import JSON5 from "json5";
import { updateUser } from "./updateUser.js";
+import { logger } from "$lib/server/logger";
const sanitizeJSONEnv = (val: string, fallback: string) => {
const raw = (val ?? "").trim();
@@ -57,13 +58,38 @@ export async function GET({ url, locals, cookies, request, getClientAddress }) {
throw error(403, "Code verifier cookie not found");
}
- const { userData, token } = await getOIDCUserData(
- { redirectURI: validatedToken.redirectUrl },
- code,
- codeVerifier,
- iss,
- url
- );
+ let userData: Awaited>["userData"];
+ let token: Awaited>["token"];
+ try {
+ ({ userData, token } = await getOIDCUserData(
+ { redirectURI: validatedToken.redirectUrl },
+ code,
+ codeVerifier,
+ iss,
+ url
+ ));
+ } catch (err) {
+ if (err instanceof OAuthProviderError) {
+ const message =
+ err.code === "OAUTH_PROVIDER_UNAVAILABLE"
+ ? "The sign-in service is temporarily unavailable. Please try again later."
+ : "An error occurred during sign-in. Please try again.";
+
+ logger.error(
+ { code: err.code, msg: err.message },
+ "OAuth provider error during callback token exchange"
+ );
+
+ return new Response(
+ JSON.stringify({ code: err.code, message }),
+ {
+ status: 502,
+ headers: { "Content-Type": "application/json" },
+ }
+ );
+ }
+ throw err;
+ }
// Filter by allowed user emails or domains
if (allowedUserEmails.length > 0 || allowedUserDomains.length > 0) {
diff --git a/frontend/src/routes/login/callback/auth-provider-failure.spec.ts b/frontend/src/routes/login/callback/auth-provider-failure.spec.ts
new file mode 100644
index 000000000..309eb70ca
--- /dev/null
+++ b/frontend/src/routes/login/callback/auth-provider-failure.spec.ts
@@ -0,0 +1,170 @@
+/**
+ * Integration tests for OAuth provider failure in the login callback flow.
+ *
+ * Runs in the "server" Vitest workspace (environment: node).
+ * Uses vi.hoisted() so that mock variables are accessible inside vi.mock() factories.
+ */
+import { describe, it, expect, vi, beforeEach } from "vitest";
+
+// ---------------------------------------------------------------------------
+// Hoist mock references so they can be used in vi.mock() factories
+// ---------------------------------------------------------------------------
+const { mockIssuerDiscover } = vi.hoisted(() => ({
+ mockIssuerDiscover: vi.fn(),
+}));
+
+// ---------------------------------------------------------------------------
+// All vi.mock() calls – must appear before any named imports
+// ---------------------------------------------------------------------------
+vi.mock("$app/paths", () => ({ base: "" }));
+vi.mock("$app/environment", () => ({ dev: false }));
+vi.mock("$lib/server/database", () => ({ collections: {} }));
+vi.mock("$lib/migrations/lock", () => ({
+ acquireLock: vi.fn(),
+ releaseLock: vi.fn(),
+ isDBLocked: vi.fn(),
+}));
+vi.mock("$lib/utils/sha256", () => ({
+ sha256: vi.fn(async (v: string) => v + "-hashed"),
+}));
+vi.mock("bson-objectid", () => ({ default: class ObjectId { } }));
+vi.mock("$lib/types/Semaphore", () => ({ Semaphores: { OAUTH_TOKEN_REFRESH: "oauth_refresh" } }));
+vi.mock("$lib/server/logger", () => ({
+ logger: { error: vi.fn(), info: vi.fn(), warn: vi.fn() },
+}));
+vi.mock("./adminToken", () => ({
+ adminTokenManager: { isAdmin: vi.fn(() => false) },
+}));
+vi.mock("$lib/server/config", () => ({
+ config: {
+ OPENID_CLIENT_ID: "test-client-id",
+ OPENID_CLIENT_SECRET: "test-client-secret",
+ OPENID_PROVIDER_URL: "https://provider.example.com",
+ OPENID_SCOPES: "openid profile",
+ OPENID_NAME_CLAIM: "name",
+ OPENID_TOLERANCE: "",
+ OPENID_RESOURCE: "",
+ OPENID_CONFIG: "{}",
+ COOKIE_NAME: "hf-chat",
+ COOKIE_SAMESITE: "",
+ COOKIE_SECURE: "",
+ ALLOW_INSECURE_COOKIES: "true",
+ PUBLIC_ORIGIN: "http://localhost:5173",
+ COUPLE_SESSION_WITH_COOKIE_NAME: "",
+ TRUSTED_EMAIL_HEADER: "",
+ ALTERNATIVE_REDIRECT_URLS: [],
+ },
+}));
+vi.mock("openid-client", () => ({
+ Issuer: { discover: mockIssuerDiscover },
+ custom: { clock_tolerance: Symbol("clock_tolerance") },
+ generators: {
+ codeVerifier: vi.fn(() => "test-code-verifier"),
+ codeChallenge: vi.fn(() => "test-code-challenge"),
+ },
+}));
+
+// ---------------------------------------------------------------------------
+// Imports (after all vi.mock() calls)
+// ---------------------------------------------------------------------------
+import { OAuthProviderError } from "$lib/server/auth";
+
+// ---------------------------------------------------------------------------
+// Helper to call triggerOauthFlow with a mocked-down provider
+// ---------------------------------------------------------------------------
+async function simulateProviderDown(
+ providerError: Error
+): Promise<{ status: number; body: { code: string; message: string } }> {
+ mockIssuerDiscover.mockRejectedValue(providerError);
+ const { triggerOauthFlow } = await import("$lib/server/auth");
+
+ const event = {
+ url: new URL("http://localhost:5173/login"),
+ locals: { sessionId: "e2e-session-id" },
+ cookies: {
+ set: vi.fn(),
+ get: vi.fn(),
+ getAll: vi.fn(() => []),
+ delete: vi.fn(),
+ serialize: vi.fn(),
+ },
+ request: new Request("http://localhost:5173/login"),
+ };
+
+ const response = await triggerOauthFlow(event as never);
+ const body = await response.json();
+ return { status: response.status, body };
+}
+
+// ---------------------------------------------------------------------------
+// E2E: provider unavailable scenarios
+// ---------------------------------------------------------------------------
+describe("E2E: OAuth provider unavailability", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ mockIssuerDiscover.mockReset();
+ });
+
+ it("returns 502 + OAUTH_PROVIDER_UNAVAILABLE when TypeError (fetch failed)", async () => {
+ const { status, body } = await simulateProviderDown(new TypeError("fetch failed"));
+ expect(status).toBe(502);
+ expect(body.code).toBe("OAUTH_PROVIDER_UNAVAILABLE");
+ expect(body.message).toMatch(/temporarily unavailable/i);
+ });
+
+ it("returns 502 + OAUTH_PROVIDER_UNAVAILABLE when ECONNREFUSED", async () => {
+ const { status, body } = await simulateProviderDown(
+ new Error("connect ECONNREFUSED 127.0.0.1:443")
+ );
+ expect(status).toBe(502);
+ expect(body.code).toBe("OAUTH_PROVIDER_UNAVAILABLE");
+ });
+
+ it("returns 502 + OAUTH_PROVIDER_UNAVAILABLE when ENOTFOUND (DNS failure)", async () => {
+ const { status, body } = await simulateProviderDown(
+ new Error("getaddrinfo ENOTFOUND provider.example.com")
+ );
+ expect(status).toBe(502);
+ expect(body.code).toBe("OAUTH_PROVIDER_UNAVAILABLE");
+ });
+
+ it("returns 502 + OAUTH_PROVIDER_ERROR on non-network provider error", async () => {
+ const { status, body } = await simulateProviderDown(
+ new Error("Internal server error from OIDC provider")
+ );
+ expect(status).toBe(502);
+ expect(body.code).toBe("OAUTH_PROVIDER_ERROR");
+ expect(body.message).toMatch(/error occurred/i);
+ });
+});
+
+// ---------------------------------------------------------------------------
+// E2E: OAuthProviderError propagation
+// ---------------------------------------------------------------------------
+describe("E2E: OAuthProviderError propagation", () => {
+ it("carries OAUTH_PROVIDER_UNAVAILABLE code", () => {
+ const err = new OAuthProviderError("Provider unreachable", "OAUTH_PROVIDER_UNAVAILABLE");
+ expect(err.name).toBe("OAuthProviderError");
+ expect(err.code).toBe("OAUTH_PROVIDER_UNAVAILABLE");
+ });
+
+ it("defaults to OAUTH_PROVIDER_ERROR code", () => {
+ const err = new OAuthProviderError("Unknown error");
+ expect(err.code).toBe("OAUTH_PROVIDER_ERROR");
+ });
+
+ it("non-OAuthProviderError errors are re-thrown unchanged", () => {
+ const originalErr = new Error("Some unrelated error");
+ let rethrown: Error | null = null;
+ try {
+ throw originalErr;
+ } catch (e: any) {
+ if (e.name === "OAuthProviderError") {
+ rethrown = new Error("wrongly caught");
+ } else {
+ rethrown = e as Error;
+ }
+ }
+ expect(rethrown).toBe(originalErr);
+ });
+});
diff --git a/frontend/src/routes/login/callback/provider-error-response.spec.ts b/frontend/src/routes/login/callback/provider-error-response.spec.ts
new file mode 100644
index 000000000..6076455b0
--- /dev/null
+++ b/frontend/src/routes/login/callback/provider-error-response.spec.ts
@@ -0,0 +1,100 @@
+import { beforeEach, describe, expect, it, vi } from "vitest";
+
+const { mockGetOIDCUserData, mockValidateAndParseCsrfToken, mockUpdateUser, MockOAuthProviderError } =
+ vi.hoisted(() => {
+ class MockOAuthProviderError extends Error {
+ public readonly code: "OAUTH_PROVIDER_UNAVAILABLE" | "OAUTH_PROVIDER_ERROR";
+ constructor(
+ message: string,
+ code: "OAUTH_PROVIDER_UNAVAILABLE" | "OAUTH_PROVIDER_ERROR" = "OAUTH_PROVIDER_ERROR"
+ ) {
+ super(message);
+ this.name = "OAuthProviderError";
+ this.code = code;
+ }
+ }
+
+ return {
+ mockGetOIDCUserData: vi.fn(),
+ mockValidateAndParseCsrfToken: vi.fn(),
+ mockUpdateUser: vi.fn(),
+ MockOAuthProviderError,
+ };
+ });
+
+vi.mock("$app/paths", () => ({ base: "" }));
+vi.mock("$lib/server/logger", () => ({
+ logger: { error: vi.fn(), info: vi.fn(), warn: vi.fn() },
+}));
+vi.mock("$lib/server/config", () => ({
+ config: {
+ ALLOWED_USER_EMAILS: "[]",
+ ALLOWED_USER_DOMAINS: "[]",
+ },
+}));
+vi.mock("$lib/server/auth", () => ({
+ getOIDCUserData: mockGetOIDCUserData,
+ validateAndParseCsrfToken: mockValidateAndParseCsrfToken,
+ OAuthProviderError: MockOAuthProviderError,
+}));
+vi.mock("./updateUser.js", () => ({
+ updateUser: mockUpdateUser,
+}));
+
+const makeEvent = () => {
+ const state = Buffer.from("csrf-token").toString("base64");
+ return {
+ url: new URL(`http://localhost:5173/login/callback?code=test-code&state=${encodeURIComponent(state)}`),
+ locals: { sessionId: "session-123" },
+ cookies: {
+ get: vi.fn(() => "code-verifier"),
+ set: vi.fn(),
+ delete: vi.fn(),
+ getAll: vi.fn(() => []),
+ serialize: vi.fn(),
+ },
+ request: new Request("http://localhost:5173/login/callback"),
+ getClientAddress: vi.fn(() => "127.0.0.1"),
+ };
+};
+
+describe("login callback structured OAuth provider failures", () => {
+ beforeEach(() => {
+ vi.resetModules();
+ vi.clearAllMocks();
+ mockValidateAndParseCsrfToken.mockResolvedValue({
+ redirectUrl: "http://localhost:5173/login/callback",
+ });
+ });
+
+ it("returns 502 with OAUTH_PROVIDER_UNAVAILABLE JSON payload", async () => {
+ mockGetOIDCUserData.mockRejectedValue(
+ new MockOAuthProviderError("Provider unreachable", "OAUTH_PROVIDER_UNAVAILABLE")
+ );
+ const { GET } = await import("./+server");
+
+ const response = await GET(makeEvent() as never);
+ const body = await response.json();
+
+ expect(response.status).toBe(502);
+ expect(response.headers.get("Content-Type")).toContain("application/json");
+ expect(body.code).toBe("OAUTH_PROVIDER_UNAVAILABLE");
+ expect(body.message).toMatch(/temporarily unavailable/i);
+ expect(mockUpdateUser).not.toHaveBeenCalled();
+ });
+
+ it("returns 502 with OAUTH_PROVIDER_ERROR JSON payload", async () => {
+ mockGetOIDCUserData.mockRejectedValue(
+ new MockOAuthProviderError("Provider internal error", "OAUTH_PROVIDER_ERROR")
+ );
+ const { GET } = await import("./+server");
+
+ const response = await GET(makeEvent() as never);
+ const body = await response.json();
+
+ expect(response.status).toBe(502);
+ expect(body.code).toBe("OAUTH_PROVIDER_ERROR");
+ expect(body.message).toMatch(/error occurred during sign-in/i);
+ expect(mockUpdateUser).not.toHaveBeenCalled();
+ });
+});