Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
049ed4b956 | |||
3fe9fc7142 |
393
bun.lock
Normal file
393
bun.lock
Normal file
@ -0,0 +1,393 @@
|
|||||||
|
{
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"workspaces": {
|
||||||
|
"": {
|
||||||
|
"name": "nixi-api",
|
||||||
|
"dependencies": {
|
||||||
|
"@hono/node-server": "^1.19.0",
|
||||||
|
"@hono/zod-validator": "^0.7.2",
|
||||||
|
"bcrypt": "^6.0.0",
|
||||||
|
"hono": "^4.9.2",
|
||||||
|
"pg": "^8.16.3",
|
||||||
|
"zod": "^4.0.17",
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@prettier/plugin-oxc": "^0.0.4",
|
||||||
|
"@types/bcrypt": "^6.0.0",
|
||||||
|
"@types/node": "^24.2.1",
|
||||||
|
"@types/pg": "^8.15.5",
|
||||||
|
"oxlint": "^1.12.0",
|
||||||
|
"prettier": "3.6.2",
|
||||||
|
"rolldown": "^1.0.0-beta.33",
|
||||||
|
"tsx": "^4.20.4",
|
||||||
|
"typescript": "^5.9.2",
|
||||||
|
"vitest": "^3.2.4",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"packages": {
|
||||||
|
"@emnapi/core": ["@emnapi/core@1.4.5", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.4", "tslib": "^2.4.0" } }, "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q=="],
|
||||||
|
|
||||||
|
"@emnapi/runtime": ["@emnapi/runtime@1.4.5", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg=="],
|
||||||
|
|
||||||
|
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.0.4", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g=="],
|
||||||
|
|
||||||
|
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.9", "", { "os": "aix", "cpu": "ppc64" }, "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA=="],
|
||||||
|
|
||||||
|
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.9", "", { "os": "android", "cpu": "arm" }, "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ=="],
|
||||||
|
|
||||||
|
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.9", "", { "os": "android", "cpu": "arm64" }, "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg=="],
|
||||||
|
|
||||||
|
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.9", "", { "os": "android", "cpu": "x64" }, "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw=="],
|
||||||
|
|
||||||
|
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg=="],
|
||||||
|
|
||||||
|
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ=="],
|
||||||
|
|
||||||
|
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.9", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q=="],
|
||||||
|
|
||||||
|
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.9", "", { "os": "freebsd", "cpu": "x64" }, "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.9", "", { "os": "linux", "cpu": "arm" }, "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.9", "", { "os": "linux", "cpu": "ia32" }, "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.9", "", { "os": "linux", "cpu": "ppc64" }, "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.9", "", { "os": "linux", "cpu": "s390x" }, "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.9", "", { "os": "linux", "cpu": "x64" }, "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg=="],
|
||||||
|
|
||||||
|
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.9", "", { "os": "none", "cpu": "arm64" }, "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q=="],
|
||||||
|
|
||||||
|
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.9", "", { "os": "none", "cpu": "x64" }, "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g=="],
|
||||||
|
|
||||||
|
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.9", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ=="],
|
||||||
|
|
||||||
|
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.9", "", { "os": "openbsd", "cpu": "x64" }, "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA=="],
|
||||||
|
|
||||||
|
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.9", "", { "os": "none", "cpu": "arm64" }, "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg=="],
|
||||||
|
|
||||||
|
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.9", "", { "os": "sunos", "cpu": "x64" }, "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw=="],
|
||||||
|
|
||||||
|
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ=="],
|
||||||
|
|
||||||
|
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.9", "", { "os": "win32", "cpu": "ia32" }, "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww=="],
|
||||||
|
|
||||||
|
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.9", "", { "os": "win32", "cpu": "x64" }, "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ=="],
|
||||||
|
|
||||||
|
"@hono/node-server": ["@hono/node-server@1.19.0", "", { "peerDependencies": { "hono": "^4" } }, "sha512-1k8/8OHf5VIymJEcJyVksFpT+AQ5euY0VA5hUkCnlKpD4mr8FSbvXaHblxeTTEr90OaqWzAkQaqD80qHZQKxBA=="],
|
||||||
|
|
||||||
|
"@hono/zod-validator": ["@hono/zod-validator@0.7.2", "", { "peerDependencies": { "hono": ">=3.9.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-ub5eL/NeZ4eLZawu78JpW/J+dugDAYhwqUIdp9KYScI6PZECij4Hx4UsrthlEUutqDDhPwRI0MscUfNkvn/mqQ=="],
|
||||||
|
|
||||||
|
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
|
||||||
|
|
||||||
|
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="],
|
||||||
|
|
||||||
|
"@oxc-parser/binding-android-arm64": ["@oxc-parser/binding-android-arm64@0.74.0", "", { "os": "android", "cpu": "arm64" }, "sha512-lgq8TJq22eyfojfa2jBFy2m66ckAo7iNRYDdyn9reXYA3I6Wx7tgGWVx1JAp1lO+aUiqdqP/uPlDaETL9tqRcg=="],
|
||||||
|
|
||||||
|
"@oxc-parser/binding-darwin-arm64": ["@oxc-parser/binding-darwin-arm64@0.74.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-xbY/io/hkARggbpYEMFX6CwFzb7f4iS6WuBoBeZtdqRWfIEi7sm/uYWXfyVeB8uqOATvJ07WRFC2upI8PSI83g=="],
|
||||||
|
|
||||||
|
"@oxc-parser/binding-darwin-x64": ["@oxc-parser/binding-darwin-x64@0.74.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-FIj2gAGtFaW0Zk+TnGyenMUoRu1ju+kJ/h71D77xc1owOItbFZFGa+4WSVck1H8rTtceeJlK+kux+vCjGFCl9Q=="],
|
||||||
|
|
||||||
|
"@oxc-parser/binding-freebsd-x64": ["@oxc-parser/binding-freebsd-x64@0.74.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-W1I+g5TJg0TRRMHgEWNWsTIfe782V3QuaPgZxnfPNmDMywYdtlzllzclBgaDq6qzvZCCQc/UhvNb37KWTCTj8A=="],
|
||||||
|
|
||||||
|
"@oxc-parser/binding-linux-arm-gnueabihf": ["@oxc-parser/binding-linux-arm-gnueabihf@0.74.0", "", { "os": "linux", "cpu": "arm" }, "sha512-gxqkyRGApeVI8dgvJ19SYe59XASW3uVxF1YUgkE7peW/XIg5QRAOVTFKyTjI9acYuK1MF6OJHqx30cmxmZLtiQ=="],
|
||||||
|
|
||||||
|
"@oxc-parser/binding-linux-arm-musleabihf": ["@oxc-parser/binding-linux-arm-musleabihf@0.74.0", "", { "os": "linux", "cpu": "arm" }, "sha512-jpnAUP4Fa93VdPPDzxxBguJmldj/Gpz7wTXKFzpAueqBMfZsy9KNC+0qT2uZ9HGUDMzNuKw0Se3bPCpL/gfD2Q=="],
|
||||||
|
|
||||||
|
"@oxc-parser/binding-linux-arm64-gnu": ["@oxc-parser/binding-linux-arm64-gnu@0.74.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-fcWyM7BNfCkHqIf3kll8fJctbR/PseL4RnS2isD9Y3FFBhp4efGAzhDaxIUK5GK7kIcFh1P+puIRig8WJ6IMVQ=="],
|
||||||
|
|
||||||
|
"@oxc-parser/binding-linux-arm64-musl": ["@oxc-parser/binding-linux-arm64-musl@0.74.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-AMY30z/C77HgiRRJX7YtVUaelKq1ex0aaj28XoJu4SCezdS8i0IftUNTtGS1UzGjGZB8zQz5SFwVy4dRu4GLwg=="],
|
||||||
|
|
||||||
|
"@oxc-parser/binding-linux-riscv64-gnu": ["@oxc-parser/binding-linux-riscv64-gnu@0.74.0", "", { "os": "linux", "cpu": "none" }, "sha512-/RZAP24TgZo4vV/01TBlzRqs0R7E6xvatww4LnmZEBBulQBU/SkypDywfriFqWuFoa61WFXPV7sLcTjJGjim/w=="],
|
||||||
|
|
||||||
|
"@oxc-parser/binding-linux-s390x-gnu": ["@oxc-parser/binding-linux-s390x-gnu@0.74.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-620J1beNAlGSPBD+Msb3ptvrwxu04B8iULCH03zlf0JSLy/5sqlD6qBs0XUVkUJv1vbakUw1gfVnUQqv0UTuEg=="],
|
||||||
|
|
||||||
|
"@oxc-parser/binding-linux-x64-gnu": ["@oxc-parser/binding-linux-x64-gnu@0.74.0", "", { "os": "linux", "cpu": "x64" }, "sha512-WBFgQmGtFnPNzHyLKbC1wkYGaRIBxXGofO0+hz1xrrkPgbxbJS1Ukva1EB8sPaVBBQ52Bdc2GjLSp721NWRvww=="],
|
||||||
|
|
||||||
|
"@oxc-parser/binding-linux-x64-musl": ["@oxc-parser/binding-linux-x64-musl@0.74.0", "", { "os": "linux", "cpu": "x64" }, "sha512-y4mapxi0RGqlp3t6Sm+knJlAEqdKDYrEue2LlXOka/F2i4sRN0XhEMPiSOB3ppHmvK4I2zY2XBYTsX1Fel0fAg=="],
|
||||||
|
|
||||||
|
"@oxc-parser/binding-wasm32-wasi": ["@oxc-parser/binding-wasm32-wasi@0.74.0", "", { "dependencies": { "@napi-rs/wasm-runtime": "^0.2.11" }, "cpu": "none" }, "sha512-yDS9bRDh5ymobiS2xBmjlrGdUuU61IZoJBaJC5fELdYT5LJNBXlbr3Yc6m2PWfRJwkH6Aq5fRvxAZ4wCbkGa8w=="],
|
||||||
|
|
||||||
|
"@oxc-parser/binding-win32-arm64-msvc": ["@oxc-parser/binding-win32-arm64-msvc@0.74.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-XFWY52Rfb4N5wEbMCTSBMxRkDLGbAI9CBSL24BIDywwDJMl31gHEVlmHdCDRoXAmanCI6gwbXYTrWe0HvXJ7Aw=="],
|
||||||
|
|
||||||
|
"@oxc-parser/binding-win32-x64-msvc": ["@oxc-parser/binding-win32-x64-msvc@0.74.0", "", { "os": "win32", "cpu": "x64" }, "sha512-1D3x6iU2apLyfTQHygbdaNbX3nZaHu4yaXpD7ilYpoLo7f0MX0tUuoDrqJyJrVGqvyXgc0uz4yXz9tH9ZZhvvg=="],
|
||||||
|
|
||||||
|
"@oxc-project/runtime": ["@oxc-project/runtime@0.71.0", "", {}, "sha512-QwoF5WUXIGFQ+hSxWEib4U/aeLoiDN9JlP18MnBgx9LLPRDfn1iICtcow7Jgey6HLH4XFceWXQD5WBJ39dyJcw=="],
|
||||||
|
|
||||||
|
"@oxc-project/types": ["@oxc-project/types@0.71.0", "", {}, "sha512-5CwQ4MI+P4MQbjLWXgNurA+igGwu/opNetIE13LBs9+V93R64MLvDKOOLZIXSzEfovU3Zef3q3GjPnMTgJTn2w=="],
|
||||||
|
|
||||||
|
"@oxlint-tsgolint/darwin-arm64": ["@oxlint-tsgolint/darwin-arm64@0.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-qL0zqIYdYrXl6ghTIHnhJkvyYy1eKz0P8YIEp59MjY3/zNiyk/gtyp8LkwZdqb9ezbcX9UDQhSuSO1wURJsq8g=="],
|
||||||
|
|
||||||
|
"@oxlint-tsgolint/darwin-x64": ["@oxlint-tsgolint/darwin-x64@0.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-c3nSjqmDSKzemChAEUv/zy2e9cwgkkO/7rz4Y447+8pSbeZNHi3RrNpVHdrKL/Qep4pt6nFZE+6PoczZxHNQjg=="],
|
||||||
|
|
||||||
|
"@oxlint-tsgolint/linux-arm64": ["@oxlint-tsgolint/linux-arm64@0.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-P2BA54c/Ej5AGkChH1/7zMd6PwZfa+jnw8juB/JWops+BX+lbhbbBHz0cYduDBgWYjRo4e3OVJOTskqcpuMfNw=="],
|
||||||
|
|
||||||
|
"@oxlint-tsgolint/linux-x64": ["@oxlint-tsgolint/linux-x64@0.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-hbgLpnDNicPrbHOAQ9nNfLOSrUrdWANP/umR7P/cwCc1sv66eEs7bm4G3mrhRU8aXFBJmbhdNqiDSUkYYvHWJQ=="],
|
||||||
|
|
||||||
|
"@oxlint-tsgolint/win32-arm64": ["@oxlint-tsgolint/win32-arm64@0.0.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-ozKEppmwZhC5LMedClBEat6cXgBGUvxGOgsKK2ZZNE6zSScX7QbvJAOt3nWMGs8GQshHy/6ndMB33+uRloglQA=="],
|
||||||
|
|
||||||
|
"@oxlint-tsgolint/win32-x64": ["@oxlint-tsgolint/win32-x64@0.0.4", "", { "os": "win32", "cpu": "x64" }, "sha512-gLfx+qogW21QcaRKFg6ARgra7tSPqyn+Ems3FgTUyxV4OpJYn7KsQroygxOWElqv6JUobtvHBrxdB6YhlvERbQ=="],
|
||||||
|
|
||||||
|
"@oxlint/darwin-arm64": ["@oxlint/darwin-arm64@1.12.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Pv+Ho1uq2ny8g2P6JgQpaIUF1FHPL32DfOlZhKqmzDT3PydtFvZp/7zNyJE3BIXeTOOOG1Eg12hjZHMLsWxyNw=="],
|
||||||
|
|
||||||
|
"@oxlint/darwin-x64": ["@oxlint/darwin-x64@1.12.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-kNXPH/7jXjX4pawrEWXQHOasOdOsrYKhskA1qYwLYcv/COVSoxOSElkQtQa+KxN5zzt3F02kBdWDndLpgJLbLQ=="],
|
||||||
|
|
||||||
|
"@oxlint/linux-arm64-gnu": ["@oxlint/linux-arm64-gnu@1.12.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-U7NETs02K55ZyDlgdhx4lWeFYbkUKcL+YcG+Ak70EyEt/BKIIVt4B84VdV1JzC71FErUipDYAwPJmxMREXr4Sg=="],
|
||||||
|
|
||||||
|
"@oxlint/linux-arm64-musl": ["@oxlint/linux-arm64-musl@1.12.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-e4Pb2eZu3V2BsiX4t4gyv9iJ8+KRT6bkoWM5uC9BLX7edsVchwLwL6LB2vPYusYdPPrxdjlFCg6ni+9wlw7FbQ=="],
|
||||||
|
|
||||||
|
"@oxlint/linux-x64-gnu": ["@oxlint/linux-x64-gnu@1.12.0", "", { "os": "linux", "cpu": "x64" }, "sha512-qJK98Dj/z7Nbm0xoz0nCCMFGy0W/kLewPzOK5QENxuUoQQ6ymt7/75rXOuTwAZJ6JFTarqfSuMAA0pka6Tmytw=="],
|
||||||
|
|
||||||
|
"@oxlint/linux-x64-musl": ["@oxlint/linux-x64-musl@1.12.0", "", { "os": "linux", "cpu": "x64" }, "sha512-jNeltpHc1eonSev/bWKipJ7FI6+Rc7EXh6Y7E0pm8e95sc1klFA29FFVs3FjMA6CCa+SRT0u0nnNTTAtf2QOiQ=="],
|
||||||
|
|
||||||
|
"@oxlint/win32-arm64": ["@oxlint/win32-arm64@1.12.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-T3fpNZJ3Q9YGgJTKc1YyvGoomSXnrV5mREz0QACE06zUzfS8EWyaYc/GN17FhHvQ4uQk/1xLgnM6FPsuLMeRhw=="],
|
||||||
|
|
||||||
|
"@oxlint/win32-x64": ["@oxlint/win32-x64@1.12.0", "", { "os": "win32", "cpu": "x64" }, "sha512-2eC4XQ1SMM2z7bCDG+Ifrn5GrvP6fkL0FGi4ZwDCrx6fwb1byFrXgSUNIPiqiiqBBrFRMKlXzU9zD6IjuFlUOg=="],
|
||||||
|
|
||||||
|
"@prettier/plugin-oxc": ["@prettier/plugin-oxc@0.0.4", "", { "dependencies": { "oxc-parser": "0.74.0" } }, "sha512-UGXe+g/rSRbglL0FOJiar+a+nUrst7KaFmsg05wYbKiInGWP6eAj/f8A2Uobgo5KxEtb2X10zeflNH6RK2xeIQ=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-beta.9-commit.d91dfb5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Mp0/gqiPdepHjjVm7e0yL1acWvI0rJVVFQEADSezvAjon9sjQ7CEg9JnXICD4B1YrPmN9qV/e7cQZCp87tTV4w=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-beta.9-commit.d91dfb5", "", { "os": "darwin", "cpu": "x64" }, "sha512-40re4rMNrsi57oavRzIOpRGmg3QRlW6Ea8Q3znaqgOuJuKVrrm2bIQInTfkZJG7a4/5YMX7T951d0+toGLTdCA=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-beta.9-commit.d91dfb5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-8BDM939bbMariZupiHp3OmP5N+LXPT4mULA0hZjDaq970PCxv4krZOSMG+HkWUUwmuQROtV+/00xw39EO0P+8g=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.9-commit.d91dfb5", "", { "os": "linux", "cpu": "arm" }, "sha512-sntsPaPgrECpBB/+2xrQzVUt0r493TMPI+4kWRMhvMsmrxOqH1Ep5lM0Wua/ZdbfZNwm1aVa5pcESQfNfM4Fhw=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-beta.9-commit.d91dfb5", "", { "os": "linux", "cpu": "arm64" }, "sha512-5clBW/I+er9F2uM1OFjJFWX86y7Lcy0M+NqsN4s3o07W+8467Zk8oQa4B45vdaXoNUF/yqIAgKkA/OEdQDxZqA=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-beta.9-commit.d91dfb5", "", { "os": "linux", "cpu": "arm64" }, "sha512-wv+rnAfQDk9p/CheX8/Kmqk2o1WaFa4xhWI9gOyDMk/ljvOX0u0ubeM8nI1Qfox7Tnh71eV5AjzSePXUhFOyOg=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-beta.9-commit.d91dfb5", "", { "os": "linux", "cpu": "x64" }, "sha512-gxD0/xhU4Py47IH3bKZbWtvB99tMkUPGPJFRfSc5UB9Osoje0l0j1PPbxpUtXIELurYCqwLBKXIMTQGifox1BQ=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-beta.9-commit.d91dfb5", "", { "os": "linux", "cpu": "x64" }, "sha512-HotuVe3XUjDwqqEMbm3o3IRkP9gdm8raY/btd/6KE3JGLF/cv4+3ff1l6nOhAZI8wulWDPEXPtE7v+HQEaTXnA=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-beta.9-commit.d91dfb5", "", { "dependencies": { "@napi-rs/wasm-runtime": "^0.2.4" }, "cpu": "none" }, "sha512-8Cx+ucbd8n2dIr21FqBh6rUvTVL0uTgEtKR7l+MUZ5BgY4dFh1e4mPVX8oqmoYwOxBiXrsD2JIOCz4AyKLKxWA=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-beta.9-commit.d91dfb5", "", { "os": "win32", "cpu": "arm64" }, "sha512-Vhq5vikrVDxAa75fxsyqj0c0Y/uti/TwshXI71Xb8IeUQJOBnmLUsn5dgYf5ljpYYkNa0z9BPAvUDIDMmyDi+w=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-win32-ia32-msvc": ["@rolldown/binding-win32-ia32-msvc@1.0.0-beta.9-commit.d91dfb5", "", { "os": "win32", "cpu": "ia32" }, "sha512-lN7RIg9Iugn08zP2aZN9y/MIdG8iOOCE93M1UrFlrxMTqPf8X+fDzmR/OKhTSd1A2pYNipZHjyTcb5H8kyQSow=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-beta.9-commit.d91dfb5", "", { "os": "win32", "cpu": "x64" }, "sha512-7/7cLIn48Y+EpQ4CePvf8reFl63F15yPUlg4ZAhl+RXJIfydkdak1WD8Ir3AwAO+bJBXzrfNL+XQbxm0mcQZmw=="],
|
||||||
|
|
||||||
|
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.9-commit.d91dfb5", "", {}, "sha512-8sExkWRK+zVybw3+2/kBkYBFeLnEUWz1fT7BLHplpzmtqkOfTbAQ9gkt4pzwGIIZmg4Qn5US5ACjUBenrhezwQ=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.48.1", "", { "os": "android", "cpu": "arm" }, "sha512-rGmb8qoG/zdmKoYELCBwu7vt+9HxZ7Koos3pD0+sH5fR3u3Wb/jGcpnqxcnWsPEKDUyzeLSqksN8LJtgXjqBYw=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.48.1", "", { "os": "android", "cpu": "arm64" }, "sha512-4e9WtTxrk3gu1DFE+imNJr4WsL13nWbD/Y6wQcyku5qadlKHY3OQ3LJ/INrrjngv2BJIHnIzbqMk1GTAC2P8yQ=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.48.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-+XjmyChHfc4TSs6WUQGmVf7Hkg8ferMAE2aNYYWjiLzAS/T62uOsdfnqv+GHRjq7rKRnYh4mwWb4Hz7h/alp8A=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.48.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-upGEY7Ftw8M6BAJyGwnwMw91rSqXTcOKZnnveKrVWsMTF8/k5mleKSuh7D4v4IV1pLxKAk3Tbs0Lo9qYmii5mQ=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.48.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-P9ViWakdoynYFUOZhqq97vBrhuvRLAbN/p2tAVJvhLb8SvN7rbBnJQcBu8e/rQts42pXGLVhfsAP0k9KXWa3nQ=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.48.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-VLKIwIpnBya5/saccM8JshpbxfyJt0Dsli0PjXozHwbSVaHTvWXJH1bbCwPXxnMzU4zVEfgD1HpW3VQHomi2AQ=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.48.1", "", { "os": "linux", "cpu": "arm" }, "sha512-3zEuZsXfKaw8n/yF7t8N6NNdhyFw3s8xJTqjbTDXlipwrEHo4GtIKcMJr5Ed29leLpB9AugtAQpAHW0jvtKKaQ=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.48.1", "", { "os": "linux", "cpu": "arm" }, "sha512-leo9tOIlKrcBmmEypzunV/2w946JeLbTdDlwEZ7OnnsUyelZ72NMnT4B2vsikSgwQifjnJUbdXzuW4ToN1wV+Q=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.48.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Vy/WS4z4jEyvnJm+CnPfExIv5sSKqZrUr98h03hpAMbE2aI0aD2wvK6GiSe8Gx2wGp3eD81cYDpLLBqNb2ydwQ=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.48.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-x5Kzn7XTwIssU9UYqWDB9VpLpfHYuXw5c6bJr4Mzv9kIv242vmJHbI5PJJEnmBYitUIfoMCODDhR7KoZLot2VQ=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.48.1", "", { "os": "linux", "cpu": "none" }, "sha512-yzCaBbwkkWt/EcgJOKDUdUpMHjhiZT/eDktOPWvSRpqrVE04p0Nd6EGV4/g7MARXXeOqstflqsKuXVM3H9wOIQ=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.48.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-UK0WzWUjMAJccHIeOpPhPcKBqax7QFg47hwZTp6kiMhQHeOYJeaMwzeRZe1q5IiTKsaLnHu9s6toSYVUlZ2QtQ=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.48.1", "", { "os": "linux", "cpu": "none" }, "sha512-3NADEIlt+aCdCbWVZ7D3tBjBX1lHpXxcvrLt/kdXTiBrOds8APTdtk2yRL2GgmnSVeX4YS1JIf0imFujg78vpw=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.48.1", "", { "os": "linux", "cpu": "none" }, "sha512-euuwm/QTXAMOcyiFCcrx0/S2jGvFlKJ2Iro8rsmYL53dlblp3LkUQVFzEidHhvIPPvcIsxDhl2wkBE+I6YVGzA=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.48.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-w8mULUjmPdWLJgmTYJx/W6Qhln1a+yqvgwmGXcQl2vFBkWsKGUBRbtLRuKJUln8Uaimf07zgJNxOhHOvjSQmBQ=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.48.1", "", { "os": "linux", "cpu": "x64" }, "sha512-90taWXCWxTbClWuMZD0DKYohY1EovA+W5iytpE89oUPmT5O1HFdf8cuuVIylE6vCbrGdIGv85lVRzTcpTRZ+kA=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.48.1", "", { "os": "linux", "cpu": "x64" }, "sha512-2Gu29SkFh1FfTRuN1GR1afMuND2GKzlORQUP3mNMJbqdndOg7gNsa81JnORctazHRokiDzQ5+MLE5XYmZW5VWg=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.48.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-6kQFR1WuAO50bxkIlAVeIYsz3RUx+xymwhTo9j94dJ+kmHe9ly7muH23sdfWduD0BA8pD9/yhonUvAjxGh34jQ=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.48.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-RUyZZ/mga88lMI3RlXFs4WQ7n3VyU07sPXmMG7/C1NOi8qisUg57Y7LRarqoGoAiopmGmChUhSwfpvQ3H5iGSQ=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.48.1", "", { "os": "win32", "cpu": "x64" }, "sha512-8a/caCUN4vkTChxkaIJcMtwIVcBhi4X2PQRoT+yCK3qRYaZ7cURrmJFL5Ux9H9RaMIXj9RuihckdmkBX3zZsgg=="],
|
||||||
|
|
||||||
|
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ=="],
|
||||||
|
|
||||||
|
"@types/bcrypt": ["@types/bcrypt@6.0.0", "", { "dependencies": { "@types/node": "*" } }, "sha512-/oJGukuH3D2+D+3H4JWLaAsJ/ji86dhRidzZ/Od7H/i8g+aCmvkeCc6Ni/f9uxGLSQVCRZkX2/lqEFG2BvWtlQ=="],
|
||||||
|
|
||||||
|
"@types/chai": ["@types/chai@5.2.2", "", { "dependencies": { "@types/deep-eql": "*" } }, "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg=="],
|
||||||
|
|
||||||
|
"@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
|
||||||
|
|
||||||
|
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||||
|
|
||||||
|
"@types/node": ["@types/node@24.3.0", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow=="],
|
||||||
|
|
||||||
|
"@types/pg": ["@types/pg@8.15.5", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-LF7lF6zWEKxuT3/OR8wAZGzkg4ENGXFNyiV/JeOt9z5B+0ZVwbql9McqX5c/WStFq1GaGso7H1AzP/qSzmlCKQ=="],
|
||||||
|
|
||||||
|
"@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="],
|
||||||
|
|
||||||
|
"@vitest/mocker": ["@vitest/mocker@3.2.4", "", { "dependencies": { "@vitest/spy": "3.2.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ=="],
|
||||||
|
|
||||||
|
"@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="],
|
||||||
|
|
||||||
|
"@vitest/runner": ["@vitest/runner@3.2.4", "", { "dependencies": { "@vitest/utils": "3.2.4", "pathe": "^2.0.3", "strip-literal": "^3.0.0" } }, "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ=="],
|
||||||
|
|
||||||
|
"@vitest/snapshot": ["@vitest/snapshot@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "magic-string": "^0.30.17", "pathe": "^2.0.3" } }, "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ=="],
|
||||||
|
|
||||||
|
"@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="],
|
||||||
|
|
||||||
|
"@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="],
|
||||||
|
|
||||||
|
"ansis": ["ansis@4.1.0", "", {}, "sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w=="],
|
||||||
|
|
||||||
|
"assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
|
||||||
|
|
||||||
|
"bcrypt": ["bcrypt@6.0.0", "", { "dependencies": { "node-addon-api": "^8.3.0", "node-gyp-build": "^4.8.4" } }, "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg=="],
|
||||||
|
|
||||||
|
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
|
||||||
|
|
||||||
|
"chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="],
|
||||||
|
|
||||||
|
"check-error": ["check-error@2.1.1", "", {}, "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw=="],
|
||||||
|
|
||||||
|
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
|
||||||
|
|
||||||
|
"deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="],
|
||||||
|
|
||||||
|
"es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="],
|
||||||
|
|
||||||
|
"esbuild": ["esbuild@0.25.9", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.9", "@esbuild/android-arm": "0.25.9", "@esbuild/android-arm64": "0.25.9", "@esbuild/android-x64": "0.25.9", "@esbuild/darwin-arm64": "0.25.9", "@esbuild/darwin-x64": "0.25.9", "@esbuild/freebsd-arm64": "0.25.9", "@esbuild/freebsd-x64": "0.25.9", "@esbuild/linux-arm": "0.25.9", "@esbuild/linux-arm64": "0.25.9", "@esbuild/linux-ia32": "0.25.9", "@esbuild/linux-loong64": "0.25.9", "@esbuild/linux-mips64el": "0.25.9", "@esbuild/linux-ppc64": "0.25.9", "@esbuild/linux-riscv64": "0.25.9", "@esbuild/linux-s390x": "0.25.9", "@esbuild/linux-x64": "0.25.9", "@esbuild/netbsd-arm64": "0.25.9", "@esbuild/netbsd-x64": "0.25.9", "@esbuild/openbsd-arm64": "0.25.9", "@esbuild/openbsd-x64": "0.25.9", "@esbuild/openharmony-arm64": "0.25.9", "@esbuild/sunos-x64": "0.25.9", "@esbuild/win32-arm64": "0.25.9", "@esbuild/win32-ia32": "0.25.9", "@esbuild/win32-x64": "0.25.9" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g=="],
|
||||||
|
|
||||||
|
"estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
|
||||||
|
|
||||||
|
"expect-type": ["expect-type@1.2.2", "", {}, "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA=="],
|
||||||
|
|
||||||
|
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||||
|
|
||||||
|
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||||
|
|
||||||
|
"get-tsconfig": ["get-tsconfig@4.10.1", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ=="],
|
||||||
|
|
||||||
|
"hono": ["hono@4.9.4", "", {}, "sha512-61hl6MF6ojTl/8QSRu5ran6GXt+6zsngIUN95KzF5v5UjiX/xnrLR358BNRawwIRO49JwUqJqQe3Rb2v559R8Q=="],
|
||||||
|
|
||||||
|
"js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="],
|
||||||
|
|
||||||
|
"loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="],
|
||||||
|
|
||||||
|
"magic-string": ["magic-string@0.30.18", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ=="],
|
||||||
|
|
||||||
|
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||||
|
|
||||||
|
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
||||||
|
|
||||||
|
"node-addon-api": ["node-addon-api@8.5.0", "", {}, "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A=="],
|
||||||
|
|
||||||
|
"node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="],
|
||||||
|
|
||||||
|
"oxc-parser": ["oxc-parser@0.74.0", "", { "dependencies": { "@oxc-project/types": "^0.74.0" }, "optionalDependencies": { "@oxc-parser/binding-android-arm64": "0.74.0", "@oxc-parser/binding-darwin-arm64": "0.74.0", "@oxc-parser/binding-darwin-x64": "0.74.0", "@oxc-parser/binding-freebsd-x64": "0.74.0", "@oxc-parser/binding-linux-arm-gnueabihf": "0.74.0", "@oxc-parser/binding-linux-arm-musleabihf": "0.74.0", "@oxc-parser/binding-linux-arm64-gnu": "0.74.0", "@oxc-parser/binding-linux-arm64-musl": "0.74.0", "@oxc-parser/binding-linux-riscv64-gnu": "0.74.0", "@oxc-parser/binding-linux-s390x-gnu": "0.74.0", "@oxc-parser/binding-linux-x64-gnu": "0.74.0", "@oxc-parser/binding-linux-x64-musl": "0.74.0", "@oxc-parser/binding-wasm32-wasi": "0.74.0", "@oxc-parser/binding-win32-arm64-msvc": "0.74.0", "@oxc-parser/binding-win32-x64-msvc": "0.74.0" } }, "sha512-2tDN/ttU8WE6oFh8EzKNam7KE7ZXSG5uXmvX85iNzxdJfMssDWcj3gpYzZi1E04XuE7m3v1dVWl/8BE886vPGw=="],
|
||||||
|
|
||||||
|
"oxlint": ["oxlint@1.12.0", "", { "optionalDependencies": { "@oxlint/darwin-arm64": "1.12.0", "@oxlint/darwin-x64": "1.12.0", "@oxlint/linux-arm64-gnu": "1.12.0", "@oxlint/linux-arm64-musl": "1.12.0", "@oxlint/linux-x64-gnu": "1.12.0", "@oxlint/linux-x64-musl": "1.12.0", "@oxlint/win32-arm64": "1.12.0", "@oxlint/win32-x64": "1.12.0", "oxlint-tsgolint": ">=0.0.1" }, "bin": { "oxlint": "bin/oxlint", "oxc_language_server": "bin/oxc_language_server" } }, "sha512-tBQ9aB00aYLlGXE21WJHnKQAI8xoi2V6Eiz/WvGV7FwU9YLYuNOurEEVbfoS5u0ODX8GLvGWj1fdHh5Rb74Kkw=="],
|
||||||
|
|
||||||
|
"oxlint-tsgolint": ["oxlint-tsgolint@0.0.4", "", { "optionalDependencies": { "@oxlint-tsgolint/darwin-arm64": "0.0.4", "@oxlint-tsgolint/darwin-x64": "0.0.4", "@oxlint-tsgolint/linux-arm64": "0.0.4", "@oxlint-tsgolint/linux-x64": "0.0.4", "@oxlint-tsgolint/win32-arm64": "0.0.4", "@oxlint-tsgolint/win32-x64": "0.0.4" }, "bin": { "tsgolint": "bin/tsgolint.js" } }, "sha512-KFWVP+VU3ymgK/Dtuf6iRkqjo+aN42lS1YThY6JWlNi1GQqm7wtio/kAwssqDhm8kP+CVXbgZAtu1wgsK4XeTg=="],
|
||||||
|
|
||||||
|
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||||
|
|
||||||
|
"pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="],
|
||||||
|
|
||||||
|
"pg": ["pg@8.16.3", "", { "dependencies": { "pg-connection-string": "^2.9.1", "pg-pool": "^3.10.1", "pg-protocol": "^1.10.3", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.2.7" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw=="],
|
||||||
|
|
||||||
|
"pg-cloudflare": ["pg-cloudflare@1.2.7", "", {}, "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg=="],
|
||||||
|
|
||||||
|
"pg-connection-string": ["pg-connection-string@2.9.1", "", {}, "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w=="],
|
||||||
|
|
||||||
|
"pg-int8": ["pg-int8@1.0.1", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="],
|
||||||
|
|
||||||
|
"pg-pool": ["pg-pool@3.10.1", "", { "peerDependencies": { "pg": ">=8.0" } }, "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg=="],
|
||||||
|
|
||||||
|
"pg-protocol": ["pg-protocol@1.10.3", "", {}, "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ=="],
|
||||||
|
|
||||||
|
"pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="],
|
||||||
|
|
||||||
|
"pgpass": ["pgpass@1.0.5", "", { "dependencies": { "split2": "^4.1.0" } }, "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug=="],
|
||||||
|
|
||||||
|
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||||
|
|
||||||
|
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||||
|
|
||||||
|
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
||||||
|
|
||||||
|
"postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="],
|
||||||
|
|
||||||
|
"postgres-bytea": ["postgres-bytea@1.0.0", "", {}, "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w=="],
|
||||||
|
|
||||||
|
"postgres-date": ["postgres-date@1.0.7", "", {}, "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="],
|
||||||
|
|
||||||
|
"postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="],
|
||||||
|
|
||||||
|
"prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="],
|
||||||
|
|
||||||
|
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
|
||||||
|
|
||||||
|
"rolldown": ["rolldown@1.0.0-beta.9-commit.d91dfb5", "", { "dependencies": { "@oxc-project/runtime": "0.71.0", "@oxc-project/types": "0.71.0", "@rolldown/pluginutils": "1.0.0-beta.9-commit.d91dfb5", "ansis": "^4.0.0" }, "optionalDependencies": { "@rolldown/binding-darwin-arm64": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-darwin-x64": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-freebsd-x64": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-linux-x64-musl": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-wasm32-wasi": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.9-commit.d91dfb5" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-FHkj6gGEiEgmAXQchglofvUUdwj2Oiw603Rs+zgFAnn9Cb7T7z3fiaEc0DbN3ja4wYkW6sF2rzMEtC1V4BGx/g=="],
|
||||||
|
|
||||||
|
"rollup": ["rollup@4.48.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.48.1", "@rollup/rollup-android-arm64": "4.48.1", "@rollup/rollup-darwin-arm64": "4.48.1", "@rollup/rollup-darwin-x64": "4.48.1", "@rollup/rollup-freebsd-arm64": "4.48.1", "@rollup/rollup-freebsd-x64": "4.48.1", "@rollup/rollup-linux-arm-gnueabihf": "4.48.1", "@rollup/rollup-linux-arm-musleabihf": "4.48.1", "@rollup/rollup-linux-arm64-gnu": "4.48.1", "@rollup/rollup-linux-arm64-musl": "4.48.1", "@rollup/rollup-linux-loongarch64-gnu": "4.48.1", "@rollup/rollup-linux-ppc64-gnu": "4.48.1", "@rollup/rollup-linux-riscv64-gnu": "4.48.1", "@rollup/rollup-linux-riscv64-musl": "4.48.1", "@rollup/rollup-linux-s390x-gnu": "4.48.1", "@rollup/rollup-linux-x64-gnu": "4.48.1", "@rollup/rollup-linux-x64-musl": "4.48.1", "@rollup/rollup-win32-arm64-msvc": "4.48.1", "@rollup/rollup-win32-ia32-msvc": "4.48.1", "@rollup/rollup-win32-x64-msvc": "4.48.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-jVG20NvbhTYDkGAty2/Yh7HK6/q3DGSRH4o8ALKGArmMuaauM9kLfoMZ+WliPwA5+JHr2lTn3g557FxBV87ifg=="],
|
||||||
|
|
||||||
|
"siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="],
|
||||||
|
|
||||||
|
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||||
|
|
||||||
|
"split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
|
||||||
|
|
||||||
|
"stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
|
||||||
|
|
||||||
|
"std-env": ["std-env@3.9.0", "", {}, "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw=="],
|
||||||
|
|
||||||
|
"strip-literal": ["strip-literal@3.0.0", "", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA=="],
|
||||||
|
|
||||||
|
"tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
|
||||||
|
|
||||||
|
"tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="],
|
||||||
|
|
||||||
|
"tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="],
|
||||||
|
|
||||||
|
"tinypool": ["tinypool@1.1.1", "", {}, "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg=="],
|
||||||
|
|
||||||
|
"tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="],
|
||||||
|
|
||||||
|
"tinyspy": ["tinyspy@4.0.3", "", {}, "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A=="],
|
||||||
|
|
||||||
|
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||||
|
|
||||||
|
"tsx": ["tsx@4.20.5", "", { "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-+wKjMNU9w/EaQayHXb7WA7ZaHY6hN8WgfvHNQ3t1PnU91/7O8TcTnIhCDYTZwnt8JsO9IBqZ30Ln1r7pPF52Aw=="],
|
||||||
|
|
||||||
|
"typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="],
|
||||||
|
|
||||||
|
"undici-types": ["undici-types@7.10.0", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="],
|
||||||
|
|
||||||
|
"vite": ["vite@7.1.3", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.14" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw=="],
|
||||||
|
|
||||||
|
"vite-node": ["vite-node@3.2.4", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="],
|
||||||
|
|
||||||
|
"vitest": ["vitest@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", "@vitest/mocker": "3.2.4", "@vitest/pretty-format": "^3.2.4", "@vitest/runner": "3.2.4", "@vitest/snapshot": "3.2.4", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "debug": "^4.4.1", "expect-type": "^1.2.1", "magic-string": "^0.30.17", "pathe": "^2.0.3", "picomatch": "^4.0.2", "std-env": "^3.9.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.14", "tinypool": "^1.1.1", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", "vite-node": "3.2.4", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@vitest/browser": "3.2.4", "@vitest/ui": "3.2.4", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A=="],
|
||||||
|
|
||||||
|
"why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="],
|
||||||
|
|
||||||
|
"xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="],
|
||||||
|
|
||||||
|
"zod": ["zod@4.1.1", "", {}, "sha512-SgMZK/h8Tigt9nnKkfJMvB/mKjiJXaX26xegP4sa+0wHIFVFWVlsQGdhklDmuargBD3Hsi3rsQRIzwJIhTPJHA=="],
|
||||||
|
|
||||||
|
"oxc-parser/@oxc-project/types": ["@oxc-project/types@0.74.0", "", {}, "sha512-KOw/RZrVlHGhCXh1RufBFF7Nuo7HdY5w1lRJukM/igIl6x9qtz8QycDvZdzb4qnHO7znrPyo2sJrFJK2eKHgfQ=="],
|
||||||
|
}
|
||||||
|
}
|
1692
package-lock.json
generated
1692
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
19
package.json
19
package.json
@ -14,21 +14,26 @@
|
|||||||
"dev": "tsx watch src/app.ts",
|
"dev": "tsx watch src/app.ts",
|
||||||
"lint": "oxlint .",
|
"lint": "oxlint .",
|
||||||
"type": "tsc --noEmit",
|
"type": "tsc --noEmit",
|
||||||
"build": "rolldown src/app.ts --file dist/app.js --platform node --format esm --minify"
|
"build": "rolldown src/app.ts --file dist/app.js --platform node --format esm --minify",
|
||||||
|
"tests": "vitest run"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@prettier/plugin-oxc": "^0.0.4",
|
"@prettier/plugin-oxc": "^0.0.4",
|
||||||
|
"@types/bcrypt": "^6.0.0",
|
||||||
"@types/node": "^24.2.1",
|
"@types/node": "^24.2.1",
|
||||||
"@types/pg": "^8.15.5",
|
"@types/pg": "^8.15.5",
|
||||||
"oxlint": "^1.11.1",
|
"oxlint": "^1.12.0",
|
||||||
"prettier": "3.6.2",
|
"prettier": "3.6.2",
|
||||||
"rolldown": "^1.0.0-beta.32",
|
"rolldown": "^1.0.0-beta.33",
|
||||||
"tsx": "^4.20.3",
|
"tsx": "^4.20.4",
|
||||||
"typescript": "^5.9.2"
|
"typescript": "^5.9.2",
|
||||||
|
"vitest": "^3.2.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hono/node-server": "^1.18.1",
|
"@hono/node-server": "^1.19.0",
|
||||||
"hono": "^4.9.0",
|
"@hono/zod-validator": "^0.7.2",
|
||||||
|
"bcrypt": "^6.0.0",
|
||||||
|
"hono": "^4.9.2",
|
||||||
"pg": "^8.16.3",
|
"pg": "^8.16.3",
|
||||||
"zod": "^4.0.17"
|
"zod": "^4.0.17"
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import loadConfiguration from "./config";
|
|||||||
import { DatabaseInterface } from "./database/DatabaseInterface";
|
import { DatabaseInterface } from "./database/DatabaseInterface";
|
||||||
import PgDatabase from "./database/PgDatabase";
|
import PgDatabase from "./database/PgDatabase";
|
||||||
import { setup as setupAccounts } from "./domain/account/setup";
|
import { setup as setupAccounts } from "./domain/account/setup";
|
||||||
|
import { HTTPError } from "./errors";
|
||||||
|
|
||||||
const config = loadConfiguration();
|
const config = loadConfiguration();
|
||||||
const database: DatabaseInterface = new PgDatabase(config.database);
|
const database: DatabaseInterface = new PgDatabase(config.database);
|
||||||
@ -11,6 +12,14 @@ const app = new Hono();
|
|||||||
|
|
||||||
app.route("/accounts", setupAccounts(database));
|
app.route("/accounts", setupAccounts(database));
|
||||||
|
|
||||||
|
app.onError((err, c) => {
|
||||||
|
if (err instanceof HTTPError) {
|
||||||
|
return c.json({ error: err.message }, err.statusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.json({ error: "Internal server error" }, 500);
|
||||||
|
});
|
||||||
|
|
||||||
serve({
|
serve({
|
||||||
port: config.port,
|
port: config.port,
|
||||||
fetch: app.fetch,
|
fetch: app.fetch,
|
||||||
|
@ -1,15 +1,40 @@
|
|||||||
import { Hono } from "hono";
|
import { Hono } from "hono";
|
||||||
|
import { validator } from "hono/validator";
|
||||||
import { AccountServiceInterface } from "../service/AccountServiceInterface";
|
import { AccountServiceInterface } from "../service/AccountServiceInterface";
|
||||||
|
import { loginSchema, registerSchema } from "../validation/AccountValidation";
|
||||||
|
import { BadSchemaError } from "../../../errors";
|
||||||
|
import { createRateLimit } from "../../../middleware/rateLimiter";
|
||||||
|
|
||||||
|
const loginRateLimit = createRateLimit({
|
||||||
|
windowMs: 15 * 60 * 1000,
|
||||||
|
maxAttempts: 5,
|
||||||
|
keyGenerator: (c) => {
|
||||||
|
const ip =
|
||||||
|
c.req.header("x-forwarded-for") ||
|
||||||
|
c.req.header("x-real-ip") ||
|
||||||
|
"127.0.0.1";
|
||||||
|
return `login:${ip}`;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export default function toRoutes(
|
export default function toRoutes(
|
||||||
accountService: AccountServiceInterface,
|
accountService: AccountServiceInterface,
|
||||||
): Hono {
|
): Hono {
|
||||||
const app = new Hono();
|
const app = new Hono();
|
||||||
|
|
||||||
app.post("/login", async (ctx) => {
|
app.post(
|
||||||
try {
|
"/login",
|
||||||
const { email, password } = await ctx.req.json();
|
loginRateLimit,
|
||||||
|
validator("json", (value) => {
|
||||||
|
const parsed = loginSchema.safeParse(value);
|
||||||
|
if (!parsed.success) {
|
||||||
|
throw new BadSchemaError(parsed.error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed.data;
|
||||||
|
}),
|
||||||
|
async (ctx) => {
|
||||||
|
const { email, password } = ctx.req.valid("json");
|
||||||
const account = await accountService.login(email, password);
|
const account = await accountService.login(email, password);
|
||||||
|
|
||||||
return ctx.json({
|
return ctx.json({
|
||||||
@ -17,38 +42,30 @@ export default function toRoutes(
|
|||||||
accountId: account.id,
|
accountId: account.id,
|
||||||
email: account.email,
|
email: account.email,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
},
|
||||||
return ctx.json(
|
);
|
||||||
{
|
|
||||||
success: false,
|
|
||||||
error: error instanceof Error ? error.message : "Erreur inconnue",
|
|
||||||
},
|
|
||||||
400,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
app.post("/register", async (ctx) => {
|
app.post(
|
||||||
try {
|
"/register",
|
||||||
const { email, password } = await ctx.req.json();
|
validator("json", (value) => {
|
||||||
|
const parsed = registerSchema.safeParse(value);
|
||||||
|
if (!parsed.success) {
|
||||||
|
throw new BadSchemaError(parsed.error.message);
|
||||||
|
}
|
||||||
|
|
||||||
const account = await accountService.createAccount(email, password);
|
return parsed.data;
|
||||||
|
}),
|
||||||
|
async (ctx) => {
|
||||||
|
const { email, password } = ctx.req.valid("json");
|
||||||
|
const account = await accountService.register(email, password);
|
||||||
|
|
||||||
return ctx.json({
|
return ctx.json({
|
||||||
success: true,
|
success: true,
|
||||||
accountId: account.id,
|
accountId: account.id,
|
||||||
email: account.email,
|
email: account.email,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
},
|
||||||
return ctx.json(
|
);
|
||||||
{
|
|
||||||
success: false,
|
|
||||||
error: error instanceof Error ? error.message : "Erreur inconnue",
|
|
||||||
},
|
|
||||||
400,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,13 @@
|
|||||||
|
import bcrypt from "bcrypt";
|
||||||
|
import {
|
||||||
|
InvalidEmailFormatError,
|
||||||
|
WeakPasswordError,
|
||||||
|
} from "../errors/AccountErrors";
|
||||||
|
import {
|
||||||
|
EMAIL_REGEX,
|
||||||
|
MIN_PASSWORD_LENGTH,
|
||||||
|
} from "../validation/AccountValidation";
|
||||||
|
|
||||||
export class AccountEntity {
|
export class AccountEntity {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly id: string,
|
public readonly id: string,
|
||||||
@ -8,34 +18,36 @@ export class AccountEntity {
|
|||||||
public readonly updatedAt: Date,
|
public readonly updatedAt: Date,
|
||||||
) {
|
) {
|
||||||
this.validateEmail(email);
|
this.validateEmail(email);
|
||||||
this.validatePassword(password);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logique métier : validation de l'email
|
|
||||||
private validateEmail(email: string): void {
|
private validateEmail(email: string): void {
|
||||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
if (!EMAIL_REGEX.test(email)) {
|
||||||
if (!emailRegex.test(email)) {
|
throw new InvalidEmailFormatError(email);
|
||||||
throw new Error("Format d'email invalide");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private validatePassword(password: string): void {
|
private static validatePassword(password: string): void {
|
||||||
if (password.length < 8) {
|
if (password.length < MIN_PASSWORD_LENGTH) {
|
||||||
throw new Error("Mot de passe trop court");
|
throw new WeakPasswordError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logique métier : vérification du mot de passe
|
|
||||||
public verifyPassword(plainPassword: string): boolean {
|
public verifyPassword(plainPassword: string): boolean {
|
||||||
// Dans un vrai projet, on utiliserait bcrypt
|
return bcrypt.compareSync(plainPassword, this.password);
|
||||||
return this.password === plainPassword;
|
}
|
||||||
|
|
||||||
|
public get hashedPassword(): string {
|
||||||
|
return this.password;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Factory method pour créer un nouveau compte
|
|
||||||
static create(email: string, password: string): AccountEntity {
|
static create(email: string, password: string): AccountEntity {
|
||||||
|
AccountEntity.validatePassword(password);
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const id = crypto.randomUUID();
|
const id = crypto.randomUUID();
|
||||||
return new AccountEntity(id, email, password, 1, now, now);
|
const hashedPassword = bcrypt.hashSync(password, 10);
|
||||||
|
|
||||||
|
return new AccountEntity(id, email, hashedPassword, 1, now, now);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
44
src/domain/account/errors/AccountErrors.ts
Normal file
44
src/domain/account/errors/AccountErrors.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { HTTPError } from "../../../errors";
|
||||||
|
import { MIN_PASSWORD_LENGTH } from "../validation/AccountValidation";
|
||||||
|
|
||||||
|
export class AccountNotFoundError extends HTTPError {
|
||||||
|
constructor(identifier: string) {
|
||||||
|
super(404, `Compte non trouvé : ${identifier}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AccountAlreadyExistsError extends HTTPError {
|
||||||
|
constructor(email: string) {
|
||||||
|
super(409, `Un compte avec cet email existe déjà : ${email}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BadPasswordError extends HTTPError {
|
||||||
|
constructor() {
|
||||||
|
super(401, "Mot de passe incorrect");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class InvalidEmailFormatError extends HTTPError {
|
||||||
|
constructor(email: string) {
|
||||||
|
super(400, `Format d'email invalide : ${email}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class WeakPasswordError extends HTTPError {
|
||||||
|
constructor() {
|
||||||
|
super(
|
||||||
|
400,
|
||||||
|
`Le mot de passe doit contenir au moins ${MIN_PASSWORD_LENGTH} caractères`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TooManyAttemptsError extends HTTPError {
|
||||||
|
constructor(retryAfter: number) {
|
||||||
|
super(
|
||||||
|
429,
|
||||||
|
`Trop de tentatives de connexion. Réessayez dans ${retryAfter} secondes`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,6 @@ import { AccountEntity } from "../entity/AccountEntity";
|
|||||||
|
|
||||||
export interface AccountRepositoryInterface {
|
export interface AccountRepositoryInterface {
|
||||||
findByEmail(email: string): Promise<AccountEntity | null>;
|
findByEmail(email: string): Promise<AccountEntity | null>;
|
||||||
save(account: AccountEntity): Promise<number>;
|
save(account: AccountEntity): Promise<string>;
|
||||||
findById(id: string): Promise<AccountEntity | null>;
|
findById(id: string): Promise<AccountEntity | null>;
|
||||||
}
|
}
|
||||||
|
@ -6,21 +6,86 @@ export default class AccountRepository implements AccountRepositoryInterface {
|
|||||||
constructor(private readonly database: DatabaseInterface) {}
|
constructor(private readonly database: DatabaseInterface) {}
|
||||||
|
|
||||||
async findByEmail(email: string): Promise<AccountEntity | null> {
|
async findByEmail(email: string): Promise<AccountEntity | null> {
|
||||||
// Implémentation simple pour l'exemple
|
const sql = `
|
||||||
// Dans un vrai projet, on ferait une requête à la base de données
|
SELECT id, email, password, role_id, created_at, updated_at
|
||||||
console.log(`Recherche du compte avec email: ${email}`);
|
FROM accounts
|
||||||
return null;
|
WHERE email = $1
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = await this.database.fetchOne<{
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
role_id: number;
|
||||||
|
created_at: Date;
|
||||||
|
updated_at: Date;
|
||||||
|
}>(sql, [email]);
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AccountEntity(
|
||||||
|
result.id,
|
||||||
|
result.email,
|
||||||
|
result.password,
|
||||||
|
result.role_id,
|
||||||
|
result.created_at,
|
||||||
|
result.updated_at,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async save(account: AccountEntity): Promise<number> {
|
async save(account: AccountEntity): Promise<string> {
|
||||||
// Implémentation simple pour l'exemple
|
const sql = `
|
||||||
console.log(`Sauvegarde du compte: ${account.id}`);
|
INSERT INTO accounts (id, email, password, role_id, created_at, updated_at)
|
||||||
return 1;
|
VALUES ($1, $2, $3, $4, $5, $6)
|
||||||
|
ON CONFLICT (id) DO UPDATE SET
|
||||||
|
email = EXCLUDED.email,
|
||||||
|
password = EXCLUDED.password,
|
||||||
|
role_id = EXCLUDED.role_id,
|
||||||
|
updated_at = EXCLUDED.updated_at
|
||||||
|
RETURNING id
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = await this.database.fetchOne<{ id: string }>(sql, [
|
||||||
|
account.id,
|
||||||
|
account.email,
|
||||||
|
account.hashedPassword,
|
||||||
|
account.roleId,
|
||||||
|
account.createdAt,
|
||||||
|
account.updatedAt,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return result!.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
async findById(id: string): Promise<AccountEntity | null> {
|
async findById(id: string): Promise<AccountEntity | null> {
|
||||||
// Implémentation simple pour l'exemple
|
const sql = `
|
||||||
console.log(`Recherche du compte avec ID: ${id}`);
|
SELECT id, email, password, role_id, created_at, updated_at
|
||||||
return null;
|
FROM accounts
|
||||||
|
WHERE id = $1
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = await this.database.fetchOne<{
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
role_id: number;
|
||||||
|
created_at: Date;
|
||||||
|
updated_at: Date;
|
||||||
|
}>(sql, [id]);
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AccountEntity(
|
||||||
|
result.id,
|
||||||
|
result.email,
|
||||||
|
result.password,
|
||||||
|
result.role_id,
|
||||||
|
result.created_at,
|
||||||
|
result.updated_at,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,36 +1,37 @@
|
|||||||
import { AccountEntity } from "../entity/AccountEntity";
|
import { AccountEntity } from "../entity/AccountEntity";
|
||||||
import { AccountRepositoryInterface } from "../repository/AccountRepositoryInterface";
|
import { AccountRepositoryInterface } from "../repository/AccountRepositoryInterface";
|
||||||
import { AccountServiceInterface } from "./AccountServiceInterface";
|
import { AccountServiceInterface } from "./AccountServiceInterface";
|
||||||
|
import {
|
||||||
|
AccountNotFoundError,
|
||||||
|
AccountAlreadyExistsError,
|
||||||
|
BadPasswordError,
|
||||||
|
} from "../errors/AccountErrors";
|
||||||
|
|
||||||
export default class AccountService implements AccountServiceInterface {
|
export default class AccountService implements AccountServiceInterface {
|
||||||
constructor(private readonly accountRepository: AccountRepositoryInterface) {}
|
constructor(private readonly accountRepository: AccountRepositoryInterface) {}
|
||||||
|
|
||||||
async login(email: string, password: string): Promise<AccountEntity> {
|
async login(email: string, password: string): Promise<AccountEntity> {
|
||||||
// Logique métier DDD : authentification
|
|
||||||
const account = await this.accountRepository.findByEmail(email);
|
const account = await this.accountRepository.findByEmail(email);
|
||||||
|
|
||||||
if (!account) {
|
if (!account) {
|
||||||
throw new Error("Compte non trouvé");
|
throw new AccountNotFoundError(email);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!account.verifyPassword(password)) {
|
if (!account.verifyPassword(password)) {
|
||||||
throw new Error("Mot de passe incorrect");
|
throw new BadPasswordError();
|
||||||
}
|
}
|
||||||
|
|
||||||
return account;
|
return account;
|
||||||
}
|
}
|
||||||
|
|
||||||
async createAccount(email: string, password: string): Promise<AccountEntity> {
|
async register(email: string, password: string): Promise<AccountEntity> {
|
||||||
// Logique métier DDD : vérifier que l'email n'existe pas déjà
|
|
||||||
const existingAccount = await this.accountRepository.findByEmail(email);
|
const existingAccount = await this.accountRepository.findByEmail(email);
|
||||||
|
|
||||||
if (existingAccount) {
|
if (existingAccount) {
|
||||||
throw new Error("Un compte avec cet email existe déjà");
|
throw new AccountAlreadyExistsError(email);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Utilisation de la factory method de l'entité
|
|
||||||
const newAccount = AccountEntity.create(email, password);
|
const newAccount = AccountEntity.create(email, password);
|
||||||
|
|
||||||
await this.accountRepository.save(newAccount);
|
await this.accountRepository.save(newAccount);
|
||||||
|
|
||||||
return newAccount;
|
return newAccount;
|
||||||
|
@ -2,5 +2,5 @@ import { AccountEntity } from "../entity/AccountEntity";
|
|||||||
|
|
||||||
export interface AccountServiceInterface {
|
export interface AccountServiceInterface {
|
||||||
login(email: string, password: string): Promise<AccountEntity>;
|
login(email: string, password: string): Promise<AccountEntity>;
|
||||||
createAccount(email: string, password: string): Promise<AccountEntity>;
|
register(email: string, password: string): Promise<AccountEntity>;
|
||||||
}
|
}
|
||||||
|
25
src/domain/account/validation/AccountValidation.ts
Normal file
25
src/domain/account/validation/AccountValidation.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
|
export const MIN_PASSWORD_LENGTH = 8;
|
||||||
|
|
||||||
|
export const emailSchema = z
|
||||||
|
.string()
|
||||||
|
.regex(EMAIL_REGEX, "Format d'email invalide");
|
||||||
|
|
||||||
|
export const passwordSchema = z
|
||||||
|
.string()
|
||||||
|
.min(
|
||||||
|
MIN_PASSWORD_LENGTH,
|
||||||
|
`Le mot de passe doit contenir au moins ${MIN_PASSWORD_LENGTH} caractères`,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const loginSchema = z.object({
|
||||||
|
email: emailSchema,
|
||||||
|
password: passwordSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const registerSchema = z.object({
|
||||||
|
email: emailSchema,
|
||||||
|
password: passwordSchema,
|
||||||
|
});
|
17
src/errors.ts
Normal file
17
src/errors.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
|
||||||
|
export class HTTPError extends Error {
|
||||||
|
constructor(
|
||||||
|
public readonly statusCode: ContentfulStatusCode,
|
||||||
|
message: string,
|
||||||
|
parent?: Error,
|
||||||
|
) {
|
||||||
|
super(message, { cause: parent });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BadSchemaError extends HTTPError {
|
||||||
|
constructor(message: string, parent?: Error) {
|
||||||
|
super(400, message, parent);
|
||||||
|
}
|
||||||
|
}
|
57
src/middleware/rateLimiter.ts
Normal file
57
src/middleware/rateLimiter.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import { Context, Next } from 'hono';
|
||||||
|
import { TooManyAttemptsError } from '../domain/account/errors/AccountErrors';
|
||||||
|
|
||||||
|
interface RateLimitConfig {
|
||||||
|
windowMs: number;
|
||||||
|
maxAttempts: number;
|
||||||
|
keyGenerator?: (c: Context) => string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AttemptRecord {
|
||||||
|
count: number;
|
||||||
|
resetTime: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const attempts = new Map<string, AttemptRecord>();
|
||||||
|
|
||||||
|
function cleanupExpiredEntries(): void {
|
||||||
|
const now = Date.now();
|
||||||
|
for (const [key, record] of attempts.entries()) {
|
||||||
|
if (now > record.resetTime) {
|
||||||
|
attempts.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createRateLimit(config: RateLimitConfig) {
|
||||||
|
const { windowMs, maxAttempts, keyGenerator = (c) => c.req.header('x-forwarded-for') || '127.0.0.1' } = config;
|
||||||
|
|
||||||
|
return async (c: Context, next: Next) => {
|
||||||
|
const key = keyGenerator(c);
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
cleanupExpiredEntries();
|
||||||
|
|
||||||
|
const record = attempts.get(key);
|
||||||
|
|
||||||
|
if (!record) {
|
||||||
|
attempts.set(key, { count: 1, resetTime: now + windowMs });
|
||||||
|
await next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (now > record.resetTime) {
|
||||||
|
attempts.set(key, { count: 1, resetTime: now + windowMs });
|
||||||
|
await next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (record.count >= maxAttempts) {
|
||||||
|
const retryAfter = Math.ceil((record.resetTime - now) / 1000);
|
||||||
|
throw new TooManyAttemptsError(retryAfter);
|
||||||
|
}
|
||||||
|
|
||||||
|
record.count++;
|
||||||
|
await next();
|
||||||
|
};
|
||||||
|
}
|
56
tests/AccountEntity.test.ts
Normal file
56
tests/AccountEntity.test.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import { AccountEntity } from "../src/domain/account/entity/AccountEntity";
|
||||||
|
import {
|
||||||
|
InvalidEmailFormatError,
|
||||||
|
WeakPasswordError,
|
||||||
|
} from "../src/domain/account/errors/AccountErrors";
|
||||||
|
import { MIN_PASSWORD_LENGTH } from "../src/domain/account/validation/AccountValidation";
|
||||||
|
|
||||||
|
describe("AccountEntity", () => {
|
||||||
|
describe("create", () => {
|
||||||
|
it("should create an account with valid email and password", () => {
|
||||||
|
const email = "test@example.com";
|
||||||
|
const password = "a".repeat(MIN_PASSWORD_LENGTH); // Use minimum length
|
||||||
|
|
||||||
|
const account = AccountEntity.create(email, password);
|
||||||
|
|
||||||
|
expect(account.email).toBe(email);
|
||||||
|
expect(account.roleId).toBe(1);
|
||||||
|
expect(account.id).toBeDefined();
|
||||||
|
expect(account.createdAt).toBeInstanceOf(Date);
|
||||||
|
expect(account.updatedAt).toBeInstanceOf(Date);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw InvalidEmailFormatError for invalid email", () => {
|
||||||
|
const invalidEmail = "invalid-email";
|
||||||
|
const password = "password123";
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
AccountEntity.create(invalidEmail, password);
|
||||||
|
}).toThrow(InvalidEmailFormatError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw WeakPasswordError for short password", () => {
|
||||||
|
const email = "test@example.com";
|
||||||
|
const shortPassword = "a".repeat(MIN_PASSWORD_LENGTH - 1); // One less than minimum
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
AccountEntity.create(email, shortPassword);
|
||||||
|
}).toThrow(WeakPasswordError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("verifyPassword", () => {
|
||||||
|
it("should return true for correct password", () => {
|
||||||
|
const account = AccountEntity.create("test@example.com", "password123");
|
||||||
|
|
||||||
|
expect(account.verifyPassword("password123")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false for incorrect password", () => {
|
||||||
|
const account = AccountEntity.create("test@example.com", "password123");
|
||||||
|
|
||||||
|
expect(account.verifyPassword("wrongpassword")).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
116
tests/AccountRepository.test.ts
Normal file
116
tests/AccountRepository.test.ts
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||||
|
import AccountRepository from "../src/domain/account/repository/AccoutRepository";
|
||||||
|
import { DatabaseInterface } from "../src/database/DatabaseInterface";
|
||||||
|
import { AccountEntity } from "../src/domain/account/entity/AccountEntity";
|
||||||
|
|
||||||
|
describe("AccountRepository", () => {
|
||||||
|
let accountRepository: AccountRepository;
|
||||||
|
let mockDatabase: DatabaseInterface;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockDatabase = {
|
||||||
|
ping: vi.fn(),
|
||||||
|
fetchAll: vi.fn(),
|
||||||
|
fetchOne: vi.fn(),
|
||||||
|
execute: vi.fn(),
|
||||||
|
};
|
||||||
|
accountRepository = new AccountRepository(mockDatabase);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("findByEmail", () => {
|
||||||
|
it("should return account when found", async () => {
|
||||||
|
const email = "test@example.com";
|
||||||
|
const mockResult = {
|
||||||
|
id: "123",
|
||||||
|
email: email,
|
||||||
|
password: "hashedPassword",
|
||||||
|
role_id: 1,
|
||||||
|
created_at: new Date(),
|
||||||
|
updated_at: new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
vi.mocked(mockDatabase.fetchOne).mockResolvedValue(mockResult);
|
||||||
|
|
||||||
|
const result = await accountRepository.findByEmail(email);
|
||||||
|
|
||||||
|
expect(result).toBeInstanceOf(AccountEntity);
|
||||||
|
expect(result?.email).toBe(email);
|
||||||
|
expect(mockDatabase.fetchOne).toHaveBeenCalledWith(
|
||||||
|
expect.stringContaining(
|
||||||
|
"SELECT id, email, password, role_id, created_at, updated_at",
|
||||||
|
),
|
||||||
|
[email],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return null when account not found", async () => {
|
||||||
|
const email = "nonexistent@example.com";
|
||||||
|
|
||||||
|
vi.mocked(mockDatabase.fetchOne).mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
const result = await accountRepository.findByEmail(email);
|
||||||
|
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("findById", () => {
|
||||||
|
it("should return account when found", async () => {
|
||||||
|
const id = "123";
|
||||||
|
const mockResult = {
|
||||||
|
id: id,
|
||||||
|
email: "test@example.com",
|
||||||
|
password: "hashedPassword",
|
||||||
|
role_id: 1,
|
||||||
|
created_at: new Date(),
|
||||||
|
updated_at: new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
vi.mocked(mockDatabase.fetchOne).mockResolvedValue(mockResult);
|
||||||
|
|
||||||
|
const result = await accountRepository.findById(id);
|
||||||
|
|
||||||
|
expect(result).toBeInstanceOf(AccountEntity);
|
||||||
|
expect(result?.id).toBe(id);
|
||||||
|
expect(mockDatabase.fetchOne).toHaveBeenCalledWith(
|
||||||
|
expect.stringContaining(
|
||||||
|
"SELECT id, email, password, role_id, created_at, updated_at",
|
||||||
|
),
|
||||||
|
[id],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return null when account not found", async () => {
|
||||||
|
const id = "nonexistent";
|
||||||
|
|
||||||
|
vi.mocked(mockDatabase.fetchOne).mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
const result = await accountRepository.findById(id);
|
||||||
|
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("save", () => {
|
||||||
|
it("should save account successfully", async () => {
|
||||||
|
const account = AccountEntity.create("test@example.com", "password123");
|
||||||
|
|
||||||
|
vi.mocked(mockDatabase.fetchOne).mockResolvedValue({ id: account.id });
|
||||||
|
|
||||||
|
const result = await accountRepository.save(account);
|
||||||
|
|
||||||
|
expect(result).toBe(account.id);
|
||||||
|
expect(mockDatabase.fetchOne).toHaveBeenCalledWith(
|
||||||
|
expect.stringContaining("INSERT INTO accounts"),
|
||||||
|
expect.arrayContaining([
|
||||||
|
account.id,
|
||||||
|
account.email,
|
||||||
|
expect.any(String),
|
||||||
|
account.roleId,
|
||||||
|
account.createdAt,
|
||||||
|
account.updatedAt,
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
95
tests/AccountService.test.ts
Normal file
95
tests/AccountService.test.ts
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||||
|
import AccountService from "../src/domain/account/service/AccountService";
|
||||||
|
import { AccountRepositoryInterface } from "../src/domain/account/repository/AccountRepositoryInterface";
|
||||||
|
import { AccountEntity } from "../src/domain/account/entity/AccountEntity";
|
||||||
|
import {
|
||||||
|
AccountNotFoundError,
|
||||||
|
AccountAlreadyExistsError,
|
||||||
|
BadPasswordError,
|
||||||
|
} from "../src/domain/account/errors/AccountErrors";
|
||||||
|
|
||||||
|
describe("AccountService", () => {
|
||||||
|
let accountService: AccountService;
|
||||||
|
let mockAccountRepository: AccountRepositoryInterface;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockAccountRepository = {
|
||||||
|
findByEmail: vi.fn(),
|
||||||
|
save: vi.fn(),
|
||||||
|
findById: vi.fn(),
|
||||||
|
};
|
||||||
|
accountService = new AccountService(mockAccountRepository);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("createAccount", () => {
|
||||||
|
it("should create a new account successfully", async () => {
|
||||||
|
const email = "test@example.com";
|
||||||
|
const password = "password123";
|
||||||
|
|
||||||
|
vi.mocked(mockAccountRepository.findByEmail).mockResolvedValue(null);
|
||||||
|
vi.mocked(mockAccountRepository.save).mockResolvedValue("123");
|
||||||
|
|
||||||
|
const result = await accountService.register(email, password);
|
||||||
|
|
||||||
|
expect(result).toBeInstanceOf(AccountEntity);
|
||||||
|
expect(result.email).toBe(email);
|
||||||
|
expect(mockAccountRepository.findByEmail).toHaveBeenCalledWith(email);
|
||||||
|
expect(mockAccountRepository.save).toHaveBeenCalledWith(
|
||||||
|
expect.any(AccountEntity),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw error if account already exists", async () => {
|
||||||
|
const email = "test@example.com";
|
||||||
|
const password = "password123";
|
||||||
|
const existingAccount = AccountEntity.create(email, password);
|
||||||
|
|
||||||
|
vi.mocked(mockAccountRepository.findByEmail).mockResolvedValue(
|
||||||
|
existingAccount,
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(accountService.register(email, password)).rejects.toThrow(
|
||||||
|
AccountAlreadyExistsError,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("login", () => {
|
||||||
|
it("should login successfully with correct credentials", async () => {
|
||||||
|
const email = "test@example.com";
|
||||||
|
const password = "password123";
|
||||||
|
const account = AccountEntity.create(email, password);
|
||||||
|
|
||||||
|
vi.mocked(mockAccountRepository.findByEmail).mockResolvedValue(account);
|
||||||
|
|
||||||
|
const result = await accountService.login(email, password);
|
||||||
|
|
||||||
|
expect(result).toBe(account);
|
||||||
|
expect(mockAccountRepository.findByEmail).toHaveBeenCalledWith(email);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw error if account not found", async () => {
|
||||||
|
const email = "test@example.com";
|
||||||
|
const password = "password123";
|
||||||
|
|
||||||
|
vi.mocked(mockAccountRepository.findByEmail).mockResolvedValue(null);
|
||||||
|
|
||||||
|
await expect(accountService.login(email, password)).rejects.toThrow(
|
||||||
|
AccountNotFoundError,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw error if password is incorrect", async () => {
|
||||||
|
const email = "test@example.com";
|
||||||
|
const password = "password123";
|
||||||
|
const wrongPassword = "wrongpassword";
|
||||||
|
const account = AccountEntity.create(email, password);
|
||||||
|
|
||||||
|
vi.mocked(mockAccountRepository.findByEmail).mockResolvedValue(account);
|
||||||
|
|
||||||
|
await expect(accountService.login(email, wrongPassword)).rejects.toThrow(
|
||||||
|
BadPasswordError,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
132
tests/AccountValidation.test.ts
Normal file
132
tests/AccountValidation.test.ts
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import {
|
||||||
|
emailSchema,
|
||||||
|
passwordSchema,
|
||||||
|
loginSchema,
|
||||||
|
registerSchema,
|
||||||
|
EMAIL_REGEX,
|
||||||
|
MIN_PASSWORD_LENGTH,
|
||||||
|
} from "../src/domain/account/validation/AccountValidation";
|
||||||
|
import { AccountEntity } from "../src/domain/account/entity/AccountEntity";
|
||||||
|
import {
|
||||||
|
InvalidEmailFormatError,
|
||||||
|
WeakPasswordError,
|
||||||
|
} from "../src/domain/account/errors/AccountErrors";
|
||||||
|
|
||||||
|
describe("AccountValidation", () => {
|
||||||
|
describe("Email validation consistency", () => {
|
||||||
|
const validEmails = [
|
||||||
|
"test@example.com",
|
||||||
|
"user.name@domain.co.uk",
|
||||||
|
"firstname+lastname@example.org",
|
||||||
|
];
|
||||||
|
|
||||||
|
const invalidEmails = [
|
||||||
|
"invalid-email",
|
||||||
|
"@example.com",
|
||||||
|
"test@",
|
||||||
|
"test",
|
||||||
|
"test@domain",
|
||||||
|
"",
|
||||||
|
];
|
||||||
|
|
||||||
|
it("should validate same emails in Zod and Entity", () => {
|
||||||
|
validEmails.forEach((email) => {
|
||||||
|
expect(emailSchema.safeParse(email).success).toBe(true);
|
||||||
|
expect(() => AccountEntity.create(email, "password123")).not.toThrow(
|
||||||
|
InvalidEmailFormatError,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
invalidEmails.forEach((email) => {
|
||||||
|
expect(emailSchema.safeParse(email).success).toBe(false);
|
||||||
|
expect(() => AccountEntity.create(email, "password123")).toThrow(
|
||||||
|
InvalidEmailFormatError,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should use same regex pattern", () => {
|
||||||
|
validEmails.forEach((email) => {
|
||||||
|
expect(EMAIL_REGEX.test(email)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
invalidEmails.forEach((email) => {
|
||||||
|
expect(EMAIL_REGEX.test(email)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Password validation consistency", () => {
|
||||||
|
const validPasswords = ["password123", "abcdefgh", "12345678", "P@ssw0rd!"];
|
||||||
|
|
||||||
|
const invalidPasswords = ["1234567", "short", "", "abc"];
|
||||||
|
|
||||||
|
it("should validate same passwords in Zod and Entity", () => {
|
||||||
|
validPasswords.forEach((password) => {
|
||||||
|
expect(passwordSchema.safeParse(password).success).toBe(true);
|
||||||
|
expect(() =>
|
||||||
|
AccountEntity.create("test@example.com", password),
|
||||||
|
).not.toThrow(WeakPasswordError);
|
||||||
|
});
|
||||||
|
|
||||||
|
invalidPasswords.forEach((password) => {
|
||||||
|
expect(passwordSchema.safeParse(password).success).toBe(false);
|
||||||
|
expect(() =>
|
||||||
|
AccountEntity.create("test@example.com", password),
|
||||||
|
).toThrow(WeakPasswordError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should use same minimum length", () => {
|
||||||
|
validPasswords.forEach((password) => {
|
||||||
|
expect(password.length >= MIN_PASSWORD_LENGTH).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
invalidPasswords.forEach((password) => {
|
||||||
|
expect(password.length >= MIN_PASSWORD_LENGTH).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Complete schemas", () => {
|
||||||
|
it("should validate login schema correctly", () => {
|
||||||
|
const validLogin = { email: "test@example.com", password: "password123" };
|
||||||
|
const invalidLogin = { email: "invalid", password: "123" };
|
||||||
|
|
||||||
|
expect(loginSchema.safeParse(validLogin).success).toBe(true);
|
||||||
|
expect(loginSchema.safeParse(invalidLogin).success).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should validate register schema correctly", () => {
|
||||||
|
const validRegister = {
|
||||||
|
email: "test@example.com",
|
||||||
|
password: "password123",
|
||||||
|
};
|
||||||
|
const invalidRegister = { email: "invalid", password: "123" };
|
||||||
|
|
||||||
|
expect(registerSchema.safeParse(validRegister).success).toBe(true);
|
||||||
|
expect(registerSchema.safeParse(invalidRegister).success).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Error messages consistency", () => {
|
||||||
|
it("should return consistent password error message", () => {
|
||||||
|
const result = passwordSchema.safeParse("123");
|
||||||
|
expect(result.success).toBe(false);
|
||||||
|
if (!result.success) {
|
||||||
|
expect(result.error.issues[0]?.message).toContain(
|
||||||
|
`${MIN_PASSWORD_LENGTH} caractères`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return consistent email error message", () => {
|
||||||
|
const result = emailSchema.safeParse("invalid-email");
|
||||||
|
expect(result.success).toBe(false);
|
||||||
|
if (!result.success) {
|
||||||
|
expect(result.error.issues[0]?.message).toBe("Format d'email invalide");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
9
vite.config.ts
Normal file
9
vite.config.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { defineConfig } from "vitest/config";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
globals: true,
|
||||||
|
environment: "node",
|
||||||
|
include: ["tests/**/*.test.ts"],
|
||||||
|
},
|
||||||
|
});
|
Reference in New Issue
Block a user