From 650cb623f5c8c9e8687d51d002a7c6f1bcbed462 Mon Sep 17 00:00:00 2001 From: Quan Nguyen Date: Wed, 22 Apr 2026 01:48:06 +0700 Subject: [PATCH] feature: solve problem4 & 5 & 6 --- .gitignore | 1 + readme.md | 2 +- src/problem4/.keep | 0 src/problem4/README.md | 20 + src/problem4/index.test.ts | 22 + src/problem4/index.ts | 33 + src/problem5/.env.example | 3 + src/problem5/.keep | 0 src/problem5/99prob5.postman_collection.json | 1079 ++++++ src/problem5/README.md | 152 + src/problem5/docker-compose.yml | 13 + src/problem5/jest.config.js | 12 + src/problem5/package.json | 33 + src/problem5/src/common/error-codes.ts | 33 + src/problem5/src/env.ts | 9 + src/problem5/src/index.ts | 42 + .../__tests__/auth.middleware.spec.ts | 86 + .../errors-handler.middleware.spec.ts | 94 + .../src/middlewares/auth.middleware.ts | 21 + .../middlewares/errors-handler.middleware.ts | 17 + src/problem5/src/mocks/seeds.ts | 177 + .../books/__tests__/book.controller.spec.ts | 176 + .../books/__tests__/book.service.spec.ts | 233 ++ .../books/__tests__/book.validator.spec.ts | 167 + .../src/modules/books/book.constant.ts | 12 + .../src/modules/books/book.controller.ts | 69 + src/problem5/src/modules/books/book.schema.ts | 18 + .../src/modules/books/book.service.ts | 180 + .../src/modules/books/book.validator.ts | 62 + src/problem5/src/modules/books/index.ts | 7 + src/problem5/src/route/book.route.ts | 18 + src/problem5/src/route/index.ts | 1 + src/problem5/test/app.e2e.test.ts | 261 ++ src/problem5/tsconfig.json | 16 + src/problem5/yarn.lock | 3397 +++++++++++++++++ src/problem6/README.md | 389 ++ 36 files changed, 6854 insertions(+), 1 deletion(-) create mode 100644 .gitignore delete mode 100644 src/problem4/.keep create mode 100644 src/problem4/README.md create mode 100644 src/problem4/index.test.ts create mode 100644 src/problem4/index.ts create mode 100644 src/problem5/.env.example delete mode 100644 src/problem5/.keep create mode 100644 src/problem5/99prob5.postman_collection.json create mode 100644 src/problem5/README.md create mode 100644 src/problem5/docker-compose.yml create mode 100644 src/problem5/jest.config.js create mode 100644 src/problem5/package.json create mode 100644 src/problem5/src/common/error-codes.ts create mode 100644 src/problem5/src/env.ts create mode 100644 src/problem5/src/index.ts create mode 100644 src/problem5/src/middlewares/__tests__/auth.middleware.spec.ts create mode 100644 src/problem5/src/middlewares/__tests__/errors-handler.middleware.spec.ts create mode 100644 src/problem5/src/middlewares/auth.middleware.ts create mode 100644 src/problem5/src/middlewares/errors-handler.middleware.ts create mode 100644 src/problem5/src/mocks/seeds.ts create mode 100644 src/problem5/src/modules/books/__tests__/book.controller.spec.ts create mode 100644 src/problem5/src/modules/books/__tests__/book.service.spec.ts create mode 100644 src/problem5/src/modules/books/__tests__/book.validator.spec.ts create mode 100644 src/problem5/src/modules/books/book.constant.ts create mode 100644 src/problem5/src/modules/books/book.controller.ts create mode 100644 src/problem5/src/modules/books/book.schema.ts create mode 100644 src/problem5/src/modules/books/book.service.ts create mode 100644 src/problem5/src/modules/books/book.validator.ts create mode 100644 src/problem5/src/modules/books/index.ts create mode 100644 src/problem5/src/route/book.route.ts create mode 100644 src/problem5/src/route/index.ts create mode 100644 src/problem5/test/app.e2e.test.ts create mode 100644 src/problem5/tsconfig.json create mode 100644 src/problem5/yarn.lock create mode 100644 src/problem6/README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..40b878db5b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ \ No newline at end of file diff --git a/readme.md b/readme.md index 1ff4bc95b4..b70b20e892 100644 --- a/readme.md +++ b/readme.md @@ -5,6 +5,6 @@ Please submit your application along with the solutions attached or linked. It is important that you minimally attempt the problems, even if you do not arrive at a working solution. -## Submission ## +## Submission You can either provide a link to an online repository, attach the solution in your application, or whichever method you prefer. We're cool as long as we can view your solution without any pain. diff --git a/src/problem4/.keep b/src/problem4/.keep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/problem4/README.md b/src/problem4/README.md new file mode 100644 index 0000000000..23c1201554 --- /dev/null +++ b/src/problem4/README.md @@ -0,0 +1,20 @@ +# Problem 4: Sum to N + +Three ways to calculate the sum of integers from 1 to n. + +## Solutions + +**A. Math formula** — O(1) time, O(1) space +Uses the formula `n * (n + 1) / 2` + +**B. Iterative** — O(n) time, O(1) space +Loops from 1 to n, accumulating the sum + +**C. Recursive** — O(n) time, O(n) space +Calls itself with `n-1` until reaching the base case + +## Test + +```bash +npx tsx src/problem4/index.test.ts +``` diff --git a/src/problem4/index.test.ts b/src/problem4/index.test.ts new file mode 100644 index 0000000000..b019027a04 --- /dev/null +++ b/src/problem4/index.test.ts @@ -0,0 +1,22 @@ +import { sum_to_n_a, sum_to_n_b, sum_to_n_c } from "./index"; + +function assertEquals(actual: number, expected: number, name: string) { + if (actual !== expected) { + throw new Error(`${name}: expected ${expected}, got ${actual}`); + } + console.log(`${name}: PASS`); +} + +assertEquals(sum_to_n_a(5), 15, "sum_to_n_a(5)"); +assertEquals(sum_to_n_a(10), 55, "sum_to_n_a(10)"); +assertEquals(sum_to_n_a(0), 0, "sum_to_n_a(0)"); + +assertEquals(sum_to_n_b(5), 15, "sum_to_n_b(5)"); +assertEquals(sum_to_n_b(10), 55, "sum_to_n_b(10)"); +assertEquals(sum_to_n_b(0), 0, "sum_to_n_b(0)"); + +assertEquals(sum_to_n_c(5), 15, "sum_to_n_c(5)"); +assertEquals(sum_to_n_c(10), 55, "sum_to_n_c(10)"); +assertEquals(sum_to_n_c(0), 0, "sum_to_n_c(0)"); + +console.log("All tests passed!"); diff --git a/src/problem4/index.ts b/src/problem4/index.ts new file mode 100644 index 0000000000..fe619d2a16 --- /dev/null +++ b/src/problem4/index.ts @@ -0,0 +1,33 @@ +/** + * 1st solution + * O(1) time, O(1) space + */ +export const sum_to_n_a = (n: number): number => { + return (n * (n + 1)) / 2; +}; + +/** + * 2nd solution: Iterative + * O(n) time, O(1) space + */ +export const sum_to_n_b = (n: number): number => { + let sum = 0; + + for (let i = 1; i <= n; i++) { + sum += i; + } + + return sum; +}; + +/** + * 3rd solution: Recursive + * O(n) time, O(n) space + */ +export const sum_to_n_c = (n: number): number => { + if (n <= 1) { + return n; + } + + return n + sum_to_n_c(n - 1); +}; diff --git a/src/problem5/.env.example b/src/problem5/.env.example new file mode 100644 index 0000000000..302c2794af --- /dev/null +++ b/src/problem5/.env.example @@ -0,0 +1,3 @@ +MONGODB_URI=mongodb://localhost:27017/99problem5 +PORT=3000 +X_API_KEY=your-secret-api-key diff --git a/src/problem5/.keep b/src/problem5/.keep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/problem5/99prob5.postman_collection.json b/src/problem5/99prob5.postman_collection.json new file mode 100644 index 0000000000..a9ae467c75 --- /dev/null +++ b/src/problem5/99prob5.postman_collection.json @@ -0,0 +1,1079 @@ +{ + "info": { + "_postman_id": "1b6a0f00-b0ae-4b6d-a99c-12d873365b8d", + "name": "99prob5", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "16604169" + }, + "item": [ + { + "name": "list", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:3000/api/books/", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3000", + "path": [ + "api", + "books", + "" + ] + } + }, + "response": [ + { + "name": "200 - successful", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:3000/api/books/", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3000", + "path": [ + "api", + "books", + "" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": null, + "header": [ + { + "key": "X-Powered-By", + "value": "Express" + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8" + }, + { + "key": "Content-Length", + "value": "2729" + }, + { + "key": "ETag", + "value": "W/\"aa9-jUySqSw1KHjcx57Y8Yy/2E9EgwI\"" + }, + { + "key": "Date", + "value": "Tue, 21 Apr 2026 13:06:11 GMT" + }, + { + "key": "Connection", + "value": "keep-alive" + }, + { + "key": "Keep-Alive", + "value": "timeout=5" + } + ], + "cookie": [], + "body": "{\n \"message\": \"List books successfully!\",\n \"data\": {\n \"total\": 20,\n \"page\": 1,\n \"limit\": 10,\n \"data\": [\n {\n \"_id\": \"507f1f77bcf86cd799439001\",\n \"title\": \"Clean Code\",\n \"author\": \"Robert C. Martin\",\n \"publisher\": \"Prentice Hall\",\n \"publishedDate\": \"2008-08-01T00:00:00.000Z\",\n \"country\": \"US\",\n \"__v\": 0,\n \"createdAt\": \"2026-04-21T13:06:10.557Z\",\n \"updatedAt\": \"2026-04-21T13:06:10.557Z\"\n },\n {\n \"_id\": \"507f1f77bcf86cd799439002\",\n \"title\": \"The Pragmatic Programmer\",\n \"author\": \"Andrew Hunt\",\n \"publisher\": \"Addison-Wesley\",\n \"publishedDate\": \"1999-10-20T00:00:00.000Z\",\n \"country\": \"US\",\n \"__v\": 0,\n \"createdAt\": \"2026-04-21T13:06:10.558Z\",\n \"updatedAt\": \"2026-04-21T13:06:10.558Z\"\n },\n {\n \"_id\": \"507f1f77bcf86cd799439003\",\n \"title\": \"Design Patterns\",\n \"author\": \"Erich Gamma\",\n \"publisher\": \"Addison-Wesley\",\n \"publishedDate\": \"1994-11-10T00:00:00.000Z\",\n \"country\": \"US\",\n \"__v\": 0,\n \"createdAt\": \"2026-04-21T13:06:10.558Z\",\n \"updatedAt\": \"2026-04-21T13:06:10.558Z\"\n },\n {\n \"_id\": \"507f1f77bcf86cd799439004\",\n \"title\": \"The Mythical Man-Month\",\n \"author\": \"Frederick P. Brooks Jr.\",\n \"publisher\": \"Addison-Wesley\",\n \"publishedDate\": \"1975-01-01T00:00:00.000Z\",\n \"country\": \"US\",\n \"__v\": 0,\n \"createdAt\": \"2026-04-21T13:06:10.558Z\",\n \"updatedAt\": \"2026-04-21T13:06:10.558Z\"\n },\n {\n \"_id\": \"507f1f77bcf86cd799439005\",\n \"title\": \"Code Complete\",\n \"author\": \"Steve McConnell\",\n \"publisher\": \"Microsoft Press\",\n \"publishedDate\": \"2004-06-09T00:00:00.000Z\",\n \"country\": \"US\",\n \"__v\": 0,\n \"createdAt\": \"2026-04-21T13:06:10.558Z\",\n \"updatedAt\": \"2026-04-21T13:06:10.558Z\"\n },\n {\n \"_id\": \"507f1f77bcf86cd799439006\",\n \"title\": \"Refactoring\",\n \"author\": \"Martin Fowler\",\n \"publisher\": \"Addison-Wesley\",\n \"publishedDate\": \"1999-07-08T00:00:00.000Z\",\n \"country\": \"US\",\n \"__v\": 0,\n \"createdAt\": \"2026-04-21T13:06:10.558Z\",\n \"updatedAt\": \"2026-04-21T13:06:10.558Z\"\n },\n {\n \"_id\": \"507f1f77bcf86cd799439007\",\n \"title\": \"Introduction to Algorithms\",\n \"author\": \"Thomas H. Cormen\",\n \"publisher\": \"MIT Press\",\n \"publishedDate\": \"1990-07-12T00:00:00.000Z\",\n \"country\": \"US\",\n \"__v\": 0,\n \"createdAt\": \"2026-04-21T13:06:10.558Z\",\n \"updatedAt\": \"2026-04-21T13:06:10.558Z\"\n },\n {\n \"_id\": \"507f1f77bcf86cd799439008\",\n \"title\": \"The Clean Coder\",\n \"author\": \"Robert C. Martin\",\n \"publisher\": \"Prentice Hall\",\n \"publishedDate\": \"2011-05-13T00:00:00.000Z\",\n \"country\": \"US\",\n \"__v\": 0,\n \"createdAt\": \"2026-04-21T13:06:10.558Z\",\n \"updatedAt\": \"2026-04-21T13:06:10.558Z\"\n },\n {\n \"_id\": \"507f1f77bcf86cd799439009\",\n \"title\": \"Working Effectively with Legacy Code\",\n \"author\": \"Michael Feathers\",\n \"publisher\": \"Prentice Hall\",\n \"publishedDate\": \"2004-10-02T00:00:00.000Z\",\n \"country\": \"US\",\n \"__v\": 0,\n \"createdAt\": \"2026-04-21T13:06:10.558Z\",\n \"updatedAt\": \"2026-04-21T13:06:10.558Z\"\n },\n {\n \"_id\": \"507f1f77bcf86cd799439010\",\n \"title\": \"Domain-Driven Design\",\n \"author\": \"Eric Evans\",\n \"publisher\": \"Addison-Wesley\",\n \"publishedDate\": \"2003-08-30T00:00:00.000Z\",\n \"country\": \"US\",\n \"__v\": 0,\n \"createdAt\": \"2026-04-21T13:06:10.558Z\",\n \"updatedAt\": \"2026-04-21T13:06:10.558Z\"\n }\n ]\n }\n}" + } + ] + }, + { + "name": "create", + "request": { + "method": "POST", + "header": [ + { + "key": "x-api-key", + "value": "random-api-key", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"title\": \"To Kill a Mockingbird\",\n \"author\": \"Harper Lee\",\n \"publisher\": \"J. B. Lippincott & Co.\",\n \"publishedDate\": \"1960-07-11\",\n \"country\": \"US\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:3000/api/books/", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3000", + "path": [ + "api", + "books", + "" + ] + } + }, + "response": [ + { + "name": "201 - successful", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "x-api-key", + "value": "random-api-key", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"title\": \"To Kill a Mockingbird\",\n \"author\": \"Harper Lee\",\n \"publisher\": \"J. B. Lippincott & Co.\",\n \"publishedDate\": \"1960-07-11\",\n \"country\": \"US\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:3000/api/books/", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3000", + "path": [ + "api", + "books", + "" + ] + } + }, + "status": "Created", + "code": 201, + "_postman_previewlanguage": null, + "header": [ + { + "key": "X-Powered-By", + "value": "Express" + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8" + }, + { + "key": "Content-Length", + "value": "317" + }, + { + "key": "ETag", + "value": "W/\"13d-Wf+pzoal/bHmBfyqOZn7W0l7JC8\"" + }, + { + "key": "Date", + "value": "Tue, 21 Apr 2026 13:09:25 GMT" + }, + { + "key": "Connection", + "value": "keep-alive" + }, + { + "key": "Keep-Alive", + "value": "timeout=5" + } + ], + "cookie": [], + "body": "{\n \"message\": \"Book created successfully!\",\n \"data\": {\n \"title\": \"To Kill a Mockingbird\",\n \"author\": \"Harper Lee\",\n \"publisher\": \"J. B. Lippincott & Co.\",\n \"publishedDate\": \"1960-07-11T00:00:00.000Z\",\n \"country\": \"US\",\n \"_id\": \"69e77705effa371223de38ef\",\n \"createdAt\": \"2026-04-21T13:09:25.361Z\",\n \"updatedAt\": \"2026-04-21T13:09:25.361Z\",\n \"__v\": 0\n }\n}" + }, + { + "name": "400 - empty body", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "x-api-key", + "value": "random-api-key", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:3000/api/books/", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3000", + "path": [ + "api", + "books", + "" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": null, + "header": [ + { + "key": "X-Powered-By", + "value": "Express" + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8" + }, + { + "key": "Content-Length", + "value": "35" + }, + { + "key": "ETag", + "value": "W/\"23-/BOcvHYU7ROHI/frnLxyUMeBJ54\"" + }, + { + "key": "Date", + "value": "Tue, 21 Apr 2026 13:06:45 GMT" + }, + { + "key": "Connection", + "value": "keep-alive" + }, + { + "key": "Keep-Alive", + "value": "timeout=5" + } + ], + "cookie": [], + "body": "{\n \"errors\": [\n \"Invalid request.body\"\n ]\n}" + }, + { + "name": "400 - invalid body", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "x-api-key", + "value": "random-api-key", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:3000/api/books/", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3000", + "path": [ + "api", + "books", + "" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": null, + "header": [ + { + "key": "X-Powered-By", + "value": "Express" + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8" + }, + { + "key": "Content-Length", + "value": "187" + }, + { + "key": "ETag", + "value": "W/\"bb-OB8efZubIiU6FiVHP6FwfA3VDOc\"" + }, + { + "key": "Date", + "value": "Tue, 21 Apr 2026 13:07:10 GMT" + }, + { + "key": "Connection", + "value": "keep-alive" + }, + { + "key": "Keep-Alive", + "value": "timeout=5" + } + ], + "cookie": [], + "body": "{\n \"errors\": [\n \"title is required and must be a string.\",\n \"author is required and must be a string.\",\n \"publisher is required and must be a string.\",\n \"country is required and must be a string.\"\n ]\n}" + }, + { + "name": "400 - duplicated resource", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "x-api-key", + "value": "random-api-key", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"title\": \"Structure and Interpretation of Computer Programs\",\n \"author\": \"Harold Abelson\",\n \"publisher\": \"MIT Press\",\n \"publishedDate\": \"1996-09-01T00:00:00.000Z\",\n \"country\": \"US\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:3000/api/books/", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3000", + "path": [ + "api", + "books", + "" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": null, + "header": [ + { + "key": "X-Powered-By", + "value": "Express" + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8" + }, + { + "key": "Content-Length", + "value": "75" + }, + { + "key": "ETag", + "value": "W/\"4b-mZ0moxSeMJHFI+0tG37EReaHsm0\"" + }, + { + "key": "Date", + "value": "Tue, 21 Apr 2026 13:07:38 GMT" + }, + { + "key": "Connection", + "value": "keep-alive" + }, + { + "key": "Keep-Alive", + "value": "timeout=5" + } + ], + "cookie": [], + "body": "{\n \"error\": \"[createBook] Book with the same title and author already exists\"\n}" + } + ] + }, + { + "name": "delete", + "request": { + "method": "DELETE", + "header": [ + { + "key": "x-api-key", + "value": "random-api-key", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "http://localhost:3000/api/books/69e77705effa371223de38ef?", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3000", + "path": [ + "api", + "books", + "69e77705effa371223de38ef" + ], + "query": [ + { + "key": "", + "value": null + } + ] + } + }, + "response": [ + { + "name": "200 - succesful", + "originalRequest": { + "method": "DELETE", + "header": [ + { + "key": "x-api-key", + "value": "random-api-key", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"title\": \"To Kill a Mockingbird\",\n \"author\": \"Harper Lee\",\n \"publisher\": \"J. B. Lippincott & Co.\",\n \"publishedDate\": \"1960-07-11\",\n \"country\": \"US\"\n}" + }, + "url": { + "raw": "http://localhost:3000/api/books/69e77705effa371223de38ef", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3000", + "path": [ + "api", + "books", + "69e77705effa371223de38ef" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": null, + "header": [ + { + "key": "X-Powered-By", + "value": "Express" + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8" + }, + { + "key": "Content-Length", + "value": "40" + }, + { + "key": "ETag", + "value": "W/\"28-Mk/nuNFZqcaXhEZBPwag4KIpGPY\"" + }, + { + "key": "Date", + "value": "Tue, 21 Apr 2026 13:10:27 GMT" + }, + { + "key": "Connection", + "value": "keep-alive" + }, + { + "key": "Keep-Alive", + "value": "timeout=5" + } + ], + "cookie": [], + "body": "{\n \"message\": \"Book deleted successfully!\"\n}" + }, + { + "name": "404 - not found", + "originalRequest": { + "method": "DELETE", + "header": [ + { + "key": "x-api-key", + "value": "random-api-key", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"title\": \"To Kill a Mockingbird\",\n \"author\": \"Harper Lee\",\n \"publisher\": \"J. B. Lippincott & Co.\",\n \"publishedDate\": \"1960-07-11\",\n \"country\": \"US\"\n}" + }, + "url": { + "raw": "http://localhost:3000/api/books/69e77705effa371223de38ef", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3000", + "path": [ + "api", + "books", + "69e77705effa371223de38ef" + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": null, + "header": [ + { + "key": "X-Powered-By", + "value": "Express" + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8" + }, + { + "key": "Content-Length", + "value": "39" + }, + { + "key": "ETag", + "value": "W/\"27-Ptpx238v1EBSMArWXUzJAesBCtE\"" + }, + { + "key": "Date", + "value": "Tue, 21 Apr 2026 13:10:41 GMT" + }, + { + "key": "Connection", + "value": "keep-alive" + }, + { + "key": "Keep-Alive", + "value": "timeout=5" + } + ], + "cookie": [], + "body": "{\n \"error\": \"[deleteBook] Book not found\"\n}" + } + ] + }, + { + "name": "update", + "request": { + "method": "PATCH", + "header": [ + { + "key": "x-api-key", + "value": "random-api-key", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"title\": \"To Kill a Mockingbird\",\n \"author\": \"Harper Lee\",\n \"publisher\": \"J. B. Lippincott & Co.\",\n \"publishedDate\": \"1960-07-11\",\n \"country\": \"US\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:3000/api/books/123123", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3000", + "path": [ + "api", + "books", + "123123" + ] + } + }, + "response": [ + { + "name": "200 - updated succesful", + "originalRequest": { + "method": "PATCH", + "header": [ + { + "key": "x-api-key", + "value": "random-api-key", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"title\": \"To Kill a Mockingbird\",\n \"author\": \"Harper Lee\",\n \"publisher\": \"J. B. Lippincott & Co.\",\n \"publishedDate\": \"1960-07-11\",\n \"country\": \"US\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:3000/api/books/507f1f77bcf86cd799439001", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3000", + "path": [ + "api", + "books", + "507f1f77bcf86cd799439001" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": null, + "header": [ + { + "key": "X-Powered-By", + "value": "Express" + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8" + }, + { + "key": "Content-Length", + "value": "317" + }, + { + "key": "ETag", + "value": "W/\"13d-WSnrJ+86zlBlrDmoasU73DLAAWk\"" + }, + { + "key": "Date", + "value": "Tue, 21 Apr 2026 13:15:58 GMT" + }, + { + "key": "Connection", + "value": "keep-alive" + }, + { + "key": "Keep-Alive", + "value": "timeout=5" + } + ], + "cookie": [], + "body": "{\n \"message\": \"Book updated successfully!\",\n \"data\": {\n \"_id\": \"507f1f77bcf86cd799439001\",\n \"title\": \"To Kill a Mockingbird\",\n \"author\": \"Harper Lee\",\n \"publisher\": \"J. B. Lippincott & Co.\",\n \"publishedDate\": \"1960-07-11T00:00:00.000Z\",\n \"country\": \"US\",\n \"__v\": 0,\n \"createdAt\": \"2026-04-21T13:12:59.681Z\",\n \"updatedAt\": \"2026-04-21T13:15:58.444Z\"\n }\n}" + }, + { + "name": "400 - invalid id", + "originalRequest": { + "method": "PATCH", + "header": [ + { + "key": "x-api-key", + "value": "random-api-key", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"title\": \"To Kill a Mockingbird\",\n \"author\": \"Harper Lee\",\n \"publisher\": \"J. B. Lippincott & Co.\",\n \"publishedDate\": \"1960-07-11\",\n \"country\": \"US\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:3000/api/books/123123", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3000", + "path": [ + "api", + "books", + "123123" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": null, + "header": [ + { + "key": "X-Powered-By", + "value": "Express" + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8" + }, + { + "key": "Content-Length", + "value": "54" + }, + { + "key": "ETag", + "value": "W/\"36-vHl1B0TTs13WDNPBwQkFDSrXGm4\"" + }, + { + "key": "Date", + "value": "Tue, 21 Apr 2026 13:13:14 GMT" + }, + { + "key": "Connection", + "value": "keep-alive" + }, + { + "key": "Keep-Alive", + "value": "timeout=5" + } + ], + "cookie": [], + "body": "{\n \"message\": \"Bad Request\",\n \"errors\": [\n \"Invalid book ID\"\n ]\n}" + }, + { + "name": "400 - duplicated resource", + "originalRequest": { + "method": "PATCH", + "header": [ + { + "key": "x-api-key", + "value": "random-api-key", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"title\": \"To Kill a Mockingbird\",\n \"author\": \"Harper Lee\",\n \"publisher\": \"J. B. Lippincott & Co.\",\n \"publishedDate\": \"1960-07-11\",\n \"country\": \"US\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:3000/api/books/507f1f77bcf86cd799439001", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3000", + "path": [ + "api", + "books", + "507f1f77bcf86cd799439001" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": null, + "header": [ + { + "key": "X-Powered-By", + "value": "Express" + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8" + }, + { + "key": "Content-Length", + "value": "75" + }, + { + "key": "ETag", + "value": "W/\"4b-mZ0moxSeMJHFI+0tG37EReaHsm0\"" + }, + { + "key": "Date", + "value": "Tue, 21 Apr 2026 13:18:30 GMT" + }, + { + "key": "Connection", + "value": "keep-alive" + }, + { + "key": "Keep-Alive", + "value": "timeout=5" + } + ], + "cookie": [], + "body": "{\n \"error\": \"[createBook] Book with the same title and author already exists\"\n}" + }, + { + "name": "404 - not found", + "originalRequest": { + "method": "PATCH", + "header": [ + { + "key": "x-api-key", + "value": "random-api-key", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"title\": \"To Kill a Mockingbird\",\n \"author\": \"Harper Lee\",\n \"publisher\": \"J. B. Lippincott & Co.\",\n \"publishedDate\": \"1960-07-11\",\n \"country\": \"US\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:3000/api/books/507f1f77bcf86cd79943900a", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3000", + "path": [ + "api", + "books", + "507f1f77bcf86cd79943900a" + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": null, + "header": [ + { + "key": "X-Powered-By", + "value": "Express" + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8" + }, + { + "key": "Content-Length", + "value": "39" + }, + { + "key": "ETag", + "value": "W/\"27-3nxHF8DbRgjhxrtc4Z4kSYT3T3Y\"" + }, + { + "key": "Date", + "value": "Tue, 21 Apr 2026 13:15:43 GMT" + }, + { + "key": "Connection", + "value": "keep-alive" + }, + { + "key": "Keep-Alive", + "value": "timeout=5" + } + ], + "cookie": [], + "body": "{\n \"error\": \"[updateBook] Book not found\"\n}" + } + ] + }, + { + "name": "get", + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:3000/api/books/507f1f77bcf86cd79943900a", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3000", + "path": [ + "api", + "books", + "507f1f77bcf86cd79943900a" + ] + } + }, + "response": [ + { + "name": "200 - successful", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:3000/api/books/507f1f77bcf86cd799439001", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3000", + "path": [ + "api", + "books", + "507f1f77bcf86cd799439001" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": null, + "header": [ + { + "key": "X-Powered-By", + "value": "Express" + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8" + }, + { + "key": "Content-Length", + "value": "301" + }, + { + "key": "ETag", + "value": "W/\"12d-kphg4s9ML9tSO/rn0PZD+HrNVw8\"" + }, + { + "key": "Date", + "value": "Tue, 21 Apr 2026 13:19:29 GMT" + }, + { + "key": "Connection", + "value": "keep-alive" + }, + { + "key": "Keep-Alive", + "value": "timeout=5" + } + ], + "cookie": [], + "body": "{\n \"message\": \"Book found successfully!\",\n \"data\": {\n \"_id\": \"507f1f77bcf86cd799439001\",\n \"title\": \"Clean Code\",\n \"author\": \"Robert C. Martin\",\n \"publisher\": \"Prentice Hall\",\n \"publishedDate\": \"2008-08-01T00:00:00.000Z\",\n \"country\": \"US\",\n \"__v\": 0,\n \"createdAt\": \"2026-04-21T13:19:00.594Z\",\n \"updatedAt\": \"2026-04-21T13:19:00.594Z\"\n }\n}" + }, + { + "name": "400 - invalid id", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:3000/api/books/507f1f77bcf86cd", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3000", + "path": [ + "api", + "books", + "507f1f77bcf86cd" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": null, + "header": [ + { + "key": "X-Powered-By", + "value": "Express" + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8" + }, + { + "key": "Content-Length", + "value": "54" + }, + { + "key": "ETag", + "value": "W/\"36-vHl1B0TTs13WDNPBwQkFDSrXGm4\"" + }, + { + "key": "Date", + "value": "Tue, 21 Apr 2026 13:20:05 GMT" + }, + { + "key": "Connection", + "value": "keep-alive" + }, + { + "key": "Keep-Alive", + "value": "timeout=5" + } + ], + "cookie": [], + "body": "{\n \"message\": \"Bad Request\",\n \"errors\": [\n \"Invalid book ID\"\n ]\n}" + }, + { + "name": "404 - not found", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:3000/api/books/507f1f77bcf86cd79943900a", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3000", + "path": [ + "api", + "books", + "507f1f77bcf86cd79943900a" + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": null, + "header": [ + { + "key": "X-Powered-By", + "value": "Express" + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8" + }, + { + "key": "Content-Length", + "value": "40" + }, + { + "key": "ETag", + "value": "W/\"28-UPbQeuHdXUpALMJCUZdY59A4ghw\"" + }, + { + "key": "Date", + "value": "Tue, 21 Apr 2026 13:19:46 GMT" + }, + { + "key": "Connection", + "value": "keep-alive" + }, + { + "key": "Keep-Alive", + "value": "timeout=5" + } + ], + "cookie": [], + "body": "{\n \"error\": \"[getBookById] Book not found\"\n}" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/problem5/README.md b/src/problem5/README.md new file mode 100644 index 0000000000..18359bed60 --- /dev/null +++ b/src/problem5/README.md @@ -0,0 +1,152 @@ +# Books API + +A RESTful API for managing book resources (books), built with Express.js and MongoDB. + +## Tech Stack + +- Node.js (Express.js) +- MongoDB with Mongoose ODM +- Docker compose +- Postman collections: [99prob5.postman_collection.json](./99prob5.postman_collection.json) +- **API documentation is hosted at**: +- Testing: unit test with `jest` and e2e test with `superrequest` + +## Prerequisites + +- Node.js 18+ +- MongoDB (local or via Docker) + +## Configuration + +The application uses environment variables. Create a `.env` file in the root directory: + +```env +MONGODB_URI=mongodb://localhost:27017/99problem5 +PORT=3000 +X_API_KEY=your-secret-api-key +``` + +| Variable | Default | Description | +|----------|---------|-------------| +| `MONGODB_URI` | `mongodb://localhost:27017/99problem5` | MongoDB connection string | +| `PORT` | `3000` | Server port | +| `X_API_KEY` | `random-api-key` | API key for authentication | + +## Running the Application + +Start MongoDB container: + +```bash +docker-compose up -d +``` + +Install dependencies and run: + +```bash +yarn install +yarn dev +``` + +The server starts at `http://localhost:3000`. + +## API Endpoints + +### Base URL + +``` +http://localhost:3000/api/books +``` + +### Authentication + +Some book endpoints require the `X-API-KEY` header (endpoints that will modify the resource): + +```env +X-API-KEY: your-secret-api-key +``` + +### Endpoints + +| Method | Endpoint | Description | Required Auth | +|--------|----------|-------------|---------------| +| `GET` | `/api/books` | List all books | No | +| `GET` | `/api/books/:id` | Get a book by ID | No | +| `POST` | `/api/books` | Create a new book | Yes | +| `PATCH` | `/api/books/:id` | Update a book | Yes | +| `DELETE` | `/api/books/:id` | Delete a book | Yes | + +### Book Schema + +```json +{ + "title": "string (required)", + "author": "string (required)", + "publisher": "string (required)", + "publishedDate": "string (required)", + "country": "string (required)" +} +``` + +## Available Scripts + +| Script | Description | +|--------|-------------| +| `yarn dev` | Run with nodemon (development) | +| `yarn test` | Run unit and E2E tests | +| `yarn test:watch` | Run tests in watch mode | +| `yarn test:coverage` | Run tests with coverage | + +## Testing + +``` +64 tests passing (unit + E2E) +``` + +| Test File | Description | +|-----------|-------------| +| `src/modules/books/__tests__/book.service.spec.ts` | Service layer unit tests | +| `src/modules/books/__tests__/book.validator.spec.ts` | Validator unit tests | +| `src/modules/books/__tests__/book.controller.spec.ts` | Controller unit tests | +| `src/middlewares/__tests__/auth.middleware.spec.ts` | Auth middleware tests | +| `src/middlewares/__tests__/errors-handler.middleware.spec.ts` | Error handler tests | +| `test/app.e2e.test.ts` | Full API E2E integration tests | + +## Project Structure + +``` +src/ +├── index.ts # Application entry point +├── env.ts # Environment configuration +├── common/ +│ └── error-codes.ts # Error definitions and AppError class +├── mocks/ +│ └── seeds.ts # Database seeding +├── modules/ +│ └── books/ +│ ├── __tests__/ # Unit tests for books module +│ │ ├── book.controller.spec.ts +│ │ ├── book.service.spec.ts +│ │ └── book.validator.spec.ts +│ ├── book.constant.ts +│ ├── book.controller.ts +│ ├── book.schema.ts +│ ├── book.service.ts +│ ├── book.validator.ts +│ └── index.ts +├── route/ +│ ├── index.ts +│ └── book.route.ts # Book routes +└── middlewares/ + ├── __tests__/ # Middleware unit tests + │ ├── auth.middleware.spec.ts + │ └── errors-handler.middleware.spec.ts + ├── errors-handler.middleware.ts + └── auth.middleware.ts + +test/ +└── app.e2e.test.ts # E2E integration tests +``` + +Seed data is located at [mocks/seed.ts](./src/mocks/seeds.ts). The data will be automatically renew once the server is started. + +The postman collection also contains example request/response. diff --git a/src/problem5/docker-compose.yml b/src/problem5/docker-compose.yml new file mode 100644 index 0000000000..5b635bf1c9 --- /dev/null +++ b/src/problem5/docker-compose.yml @@ -0,0 +1,13 @@ +services: + mongodb: + image: mongo:latest + container_name: 99problem5-mongodb + ports: + - "27017:27017" + environment: + MONGO_INITDB_DATABASE: 99problem5 + volumes: + - mongo_data:/data/db + +volumes: + mongo_data: diff --git a/src/problem5/jest.config.js b/src/problem5/jest.config.js new file mode 100644 index 0000000000..6303d51ab6 --- /dev/null +++ b/src/problem5/jest.config.js @@ -0,0 +1,12 @@ +/** @type {import('jest').Config} */ +module.exports = { + preset: "ts-jest", + testEnvironment: "node", + roots: ["/src", "/test"], + testMatch: ["**/__tests__/**/*.ts", "**/*.spec.ts", "**/*.e2e.test.ts"], + moduleFileExtensions: ["ts", "js", "json"], + clearMocks: true, + moduleNameMapper: { + "^(\\.{1,2}/.*)\\.js$": "$1", + }, +}; diff --git a/src/problem5/package.json b/src/problem5/package.json new file mode 100644 index 0000000000..6cc905e3df --- /dev/null +++ b/src/problem5/package.json @@ -0,0 +1,33 @@ +{ + "name": "problem5", + "version": "1.0.0", + "description": "", + "main": "src/index.ts", + "scripts": { + "start": "ts-node src/index.ts", + "dev": "nodemon src/index.ts", + "test": "jest", + "test:watch": "jest --watch", + "test:coverage": "jest --coverage" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "commonjs", + "devDependencies": { + "@types/express": "^5.0.6", + "@types/jest": "^30.0.0", + "@types/supertest": "^7.2.0", + "jest": "^30.3.0", + "nodemon": "^3.1.14", + "supertest": "^7.2.2", + "ts-jest": "^29.4.9", + "typescript": "^6.0.3" + }, + "dependencies": { + "dotenv": "^17.4.2", + "express": "^5.2.1", + "mongoose": "^9.5.0", + "ts-node": "^10.9.2" + } +} diff --git a/src/problem5/src/common/error-codes.ts b/src/problem5/src/common/error-codes.ts new file mode 100644 index 0000000000..ac0ed919bc --- /dev/null +++ b/src/problem5/src/common/error-codes.ts @@ -0,0 +1,33 @@ +interface ErrorCode { + status: number; + message: string; +} + +export const ErrorCodes: Record = { + UNAUTHORIZED: { + status: 401, + message: "Unauthorized access", + }, + NOT_FOUND: { + status: 404, + message: "Resource not found", + }, + VALIDATION_ERROR: { + status: 400, + message: "Validation failed", + }, + INTERNAL_SERVER_ERROR: { + status: 500, + message: "Internal server error", + }, +}; + +export class AppError extends Error { + statusCode: number; + + constructor(message: string, statusCode: number) { + super(message); + this.statusCode = statusCode; + console.log(this) + } +} diff --git a/src/problem5/src/env.ts b/src/problem5/src/env.ts new file mode 100644 index 0000000000..a123a43e72 --- /dev/null +++ b/src/problem5/src/env.ts @@ -0,0 +1,9 @@ +import dotenv from "dotenv"; + +dotenv.config(); + +export const env = { + MONGODB_URI: process.env.MONGODB_URI || "mongodb://localhost:27017/99problem5", + PORT: process.env.PORT || 3000, + X_API_KEY: process.env.X_API_KEY || "random-api-key", +}; diff --git a/src/problem5/src/index.ts b/src/problem5/src/index.ts new file mode 100644 index 0000000000..2d403d6036 --- /dev/null +++ b/src/problem5/src/index.ts @@ -0,0 +1,42 @@ +import express from "express"; +import mongoose from "mongoose"; + +import { env } from "./env"; +import { + errorHandler, + notFoundHandler, +} from "./middlewares/errors-handler.middleware"; +import { bookRouter } from "./route"; +import { seedBooks } from "./mocks/seeds"; + +const bootstrap = async () => { + await mongoose + .connect(env.MONGODB_URI) + .then(() => console.log("[Mongodb] Connected to the database!")); + + await seedBooks(); + + const app = express(); + app.use(express.json()); + + app.get("/", (_req, res) => { + res.send("Resources (Books) API!"); + }); + + // Register routes + app.use("/api/books", bookRouter); + + // Error handling middlewares + app.use(notFoundHandler); + app.use(errorHandler); + + app.listen(env.PORT, () => { + console.log(`Server is running at http://localhost:${env.PORT}`); + }); +}; + +bootstrap().catch(async (err) => { + console.error("Failed to start the server", err); + await mongoose.disconnect(); + process.exit(1); +}); diff --git a/src/problem5/src/middlewares/__tests__/auth.middleware.spec.ts b/src/problem5/src/middlewares/__tests__/auth.middleware.spec.ts new file mode 100644 index 0000000000..3c63d650f9 --- /dev/null +++ b/src/problem5/src/middlewares/__tests__/auth.middleware.spec.ts @@ -0,0 +1,86 @@ +import { Request, Response, NextFunction } from "express"; +import { requiredAuth } from "../auth.middleware"; +import { env } from "../../env"; + +jest.mock("../../env", () => ({ + env: { + X_API_KEY: "test-api-key", + }, +})); + +describe("auth.middleware", () => { + let mockReq: Partial; + let mockRes: Partial; + let mockNext: NextFunction; + + beforeEach(() => { + mockReq = { + header: jest.fn(), + }; + mockRes = { + status: jest.fn().mockReturnThis(), + json: jest.fn().mockReturnThis(), + }; + mockNext = jest.fn(); + }); + + describe("requiredAuth", () => { + it("should call next() when API key is valid", () => { + (mockReq.header as jest.Mock).mockReturnValue("test-api-key"); + + requiredAuth( + mockReq as Request, + mockRes as Response, + mockNext + ); + + expect(mockNext).toHaveBeenCalled(); + expect(mockRes.status).not.toHaveBeenCalled(); + }); + + it("should return 401 when API key is missing", () => { + (mockReq.header as jest.Mock).mockReturnValue(undefined); + + requiredAuth( + mockReq as Request, + mockRes as Response, + mockNext + ); + + expect(mockRes.status).toHaveBeenCalledWith(401); + expect(mockRes.json).toHaveBeenCalledWith({ + error: "Unauthorized access", + }); + expect(mockNext).not.toHaveBeenCalled(); + }); + + it("should return 401 when API key is invalid", () => { + (mockReq.header as jest.Mock).mockReturnValue("wrong-api-key"); + + requiredAuth( + mockReq as Request, + mockRes as Response, + mockNext + ); + + expect(mockRes.status).toHaveBeenCalledWith(401); + expect(mockRes.json).toHaveBeenCalledWith({ + error: "Unauthorized access", + }); + expect(mockNext).not.toHaveBeenCalled(); + }); + + it("should return 401 when API key is empty string", () => { + (mockReq.header as jest.Mock).mockReturnValue(""); + + requiredAuth( + mockReq as Request, + mockRes as Response, + mockNext + ); + + expect(mockRes.status).toHaveBeenCalledWith(401); + expect(mockNext).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/src/problem5/src/middlewares/__tests__/errors-handler.middleware.spec.ts b/src/problem5/src/middlewares/__tests__/errors-handler.middleware.spec.ts new file mode 100644 index 0000000000..26cd5f395f --- /dev/null +++ b/src/problem5/src/middlewares/__tests__/errors-handler.middleware.spec.ts @@ -0,0 +1,94 @@ +import { Request, Response, NextFunction } from "express"; +import { notFoundHandler, errorHandler } from "../errors-handler.middleware"; +import { AppError } from "../../common/error-codes"; + +describe("errors-handler.middleware", () => { + let mockReq: Partial; + let mockRes: Partial; + let mockNext: NextFunction; + + beforeEach(() => { + mockReq = {}; + mockRes = { + status: jest.fn().mockReturnThis(), + json: jest.fn().mockReturnThis(), + }; + mockNext = jest.fn(); + }); + + describe("notFoundHandler", () => { + it("should return 404 with Not Found error", () => { + notFoundHandler(mockReq as Request, mockRes as Response); + + expect(mockRes.status).toHaveBeenCalledWith(404); + expect(mockRes.json).toHaveBeenCalledWith({ error: "Not Found" }); + }); + }); + + describe("errorHandler", () => { + it("should return 500 with default error message for generic Error", () => { + const err = new Error("Something went wrong"); + + errorHandler( + err, + mockReq as Request, + mockRes as Response, + mockNext + ); + + expect(mockRes.status).toHaveBeenCalledWith(500); + expect(mockRes.json).toHaveBeenCalledWith({ + error: "Something went wrong", + }); + }); + + it("should use statusCode from error when available", () => { + const err = new Error("Bad Request") as Error & { statusCode: number }; + err.statusCode = 400; + + errorHandler( + err, + mockReq as Request, + mockRes as Response, + mockNext + ); + + expect(mockRes.status).toHaveBeenCalledWith(400); + expect(mockRes.json).toHaveBeenCalledWith({ + error: "Bad Request", + }); + }); + + it("should handle AppError with statusCode", () => { + const err = new AppError("Resource not found", 404); + + errorHandler( + err, + mockReq as Request, + mockRes as Response, + mockNext + ); + + expect(mockRes.status).toHaveBeenCalledWith(404); + expect(mockRes.json).toHaveBeenCalledWith({ + error: "Resource not found", + }); + }); + + it("should default to 500 status and Internal Server Error message", () => { + const err = new Error(); + + errorHandler( + err, + mockReq as Request, + mockRes as Response, + mockNext + ); + + expect(mockRes.status).toHaveBeenCalledWith(500); + expect(mockRes.json).toHaveBeenCalledWith({ + error: "Internal Server Error", + }); + }); + }); +}); diff --git a/src/problem5/src/middlewares/auth.middleware.ts b/src/problem5/src/middlewares/auth.middleware.ts new file mode 100644 index 0000000000..3f0e120fbf --- /dev/null +++ b/src/problem5/src/middlewares/auth.middleware.ts @@ -0,0 +1,21 @@ +import { NextFunction, Request, Response } from "express"; +import { ErrorCodes } from "../common/error-codes"; +import { env } from "../env"; + +export const requiredAuth = ( + req: Request, + res: Response, + next: NextFunction, +) => { + const apiKey = req.header("x-api-key"); + + if (apiKey !== env.X_API_KEY) { + console.debug("[Auth] Invalid API key"); + + return res + .status(ErrorCodes.UNAUTHORIZED.status) + .json({ error: ErrorCodes.UNAUTHORIZED.message }); + } + + next(); +}; diff --git a/src/problem5/src/middlewares/errors-handler.middleware.ts b/src/problem5/src/middlewares/errors-handler.middleware.ts new file mode 100644 index 0000000000..1779ed2aff --- /dev/null +++ b/src/problem5/src/middlewares/errors-handler.middleware.ts @@ -0,0 +1,17 @@ +import { Request, Response, NextFunction } from "express"; + +export const notFoundHandler = (_req: Request, res: Response) => { + return res.status(404).json({ error: "Not Found" }); +}; + +export const errorHandler = ( + err: Error, + _req: Request, + res: Response, + _next: NextFunction, +) => { + const errorMessage = err.message || "Internal Server Error"; + const statusCode = (err as any).statusCode || 500; + + res.status(statusCode).json({ error: errorMessage }); +}; diff --git a/src/problem5/src/mocks/seeds.ts b/src/problem5/src/mocks/seeds.ts new file mode 100644 index 0000000000..5d5d7a1df6 --- /dev/null +++ b/src/problem5/src/mocks/seeds.ts @@ -0,0 +1,177 @@ +import { Book } from "../modules/books/book.schema"; + +const data = [ + { + _id: "507f1f77bcf86cd799439001", + title: "Clean Code", + author: "Robert C. Martin", + publisher: "Prentice Hall", + publishedDate: "2008-08-01", + country: "US", + }, + { + _id: "507f1f77bcf86cd799439002", + title: "The Pragmatic Programmer", + author: "Andrew Hunt", + publisher: "Addison-Wesley", + publishedDate: "1999-10-20", + country: "US", + }, + { + _id: "507f1f77bcf86cd799439003", + title: "Design Patterns", + author: "Erich Gamma", + publisher: "Addison-Wesley", + publishedDate: "1994-11-10", + country: "US", + }, + { + _id: "507f1f77bcf86cd799439004", + title: "The Mythical Man-Month", + author: "Frederick P. Brooks Jr.", + publisher: "Addison-Wesley", + publishedDate: "1975-01-01", + country: "US", + }, + { + _id: "507f1f77bcf86cd799439005", + title: "Code Complete", + author: "Steve McConnell", + publisher: "Microsoft Press", + publishedDate: "2004-06-09", + country: "US", + }, + { + _id: "507f1f77bcf86cd799439006", + title: "Refactoring", + author: "Martin Fowler", + publisher: "Addison-Wesley", + publishedDate: "1999-07-08", + country: "US", + }, + { + _id: "507f1f77bcf86cd799439007", + title: "Introduction to Algorithms", + author: "Thomas H. Cormen", + publisher: "MIT Press", + publishedDate: "1990-07-12", + country: "US", + }, + { + _id: "507f1f77bcf86cd799439008", + title: "The Clean Coder", + author: "Robert C. Martin", + publisher: "Prentice Hall", + publishedDate: "2011-05-13", + country: "US", + }, + { + _id: "507f1f77bcf86cd799439009", + title: "Working Effectively with Legacy Code", + author: "Michael Feathers", + publisher: "Prentice Hall", + publishedDate: "2004-10-02", + country: "US", + }, + { + _id: "507f1f77bcf86cd799439010", + title: "Domain-Driven Design", + author: "Eric Evans", + publisher: "Addison-Wesley", + publishedDate: "2003-08-30", + country: "US", + }, + { + _id: "507f1f77bcf86cd799439011", + title: "Structure and Interpretation of Computer Programs", + author: "Harold Abelson", + publisher: "MIT Press", + publishedDate: "1996-09-01", + country: "US", + }, + { + _id: "507f1f77bcf86cd799439012", + title: "The Art of Computer Programming", + author: "Donald E. Knuth", + publisher: "Addison-Wesley", + publishedDate: "1968-01-01", + country: "US", + }, + { + _id: "507f1f77bcf86cd799439013", + title: "Continuous Delivery", + author: "Jez Humble", + publisher: "Addison-Wesley", + publishedDate: "2010-07-27", + country: "UK", + }, + { + _id: "507f1f77bcf86cd799439014", + title: "The Phoenix Project", + author: "Gene Kim", + publisher: "IT Revolution Press", + publishedDate: "2013-01-10", + country: "US", + }, + { + _id: "507f1f77bcf86cd799439015", + title: "Cracking the Coding Interview", + author: "Gayle Laakmann McDowell", + publisher: "CareerCup", + publishedDate: "2015-07-01", + country: "US", + }, + { + _id: "507f1f77bcf86cd799439016", + title: "Head First Design Patterns", + author: "Eric Freeman", + publisher: "O'Reilly Media", + publishedDate: "2004-10-25", + country: "US", + }, + { + _id: "507f1f77bcf86cd799439017", + title: "You Don't Know JS", + author: "Kyle Simpson", + publisher: "O'Reilly Media", + publishedDate: "2015-12-27", + country: "US", + }, + { + _id: "507f1f77bcf86cd799439018", + title: "The Linux Command Line", + author: "William Shotts", + publisher: "No Starch Press", + publishedDate: "2012-01-01", + country: "US", + }, + { + _id: "507f1f77bcf86cd799439019", + title: "Algorithms", + author: "Robert Sedgewick", + publisher: "Addison-Wesley", + publishedDate: "2011-03-24", + country: "US", + }, + { + _id: "507f1f77bcf86cd799439020", + title: "Computer Networks", + author: "Andrew S. Tanenbaum", + publisher: "Prentice Hall", + publishedDate: "2010-10-07", + country: "NL", + }, +]; + +export async function seedBooks() { + try { + console.log("[Mongodb] Seeding"); + + await Book.deleteMany({}); // clear collection + await Book.insertMany(data); // bulk insert + + console.log("[Mongodb] Seeded"); + } catch (err) { + console.error("[Mongodb] Seed failed", err); + } +} diff --git a/src/problem5/src/modules/books/__tests__/book.controller.spec.ts b/src/problem5/src/modules/books/__tests__/book.controller.spec.ts new file mode 100644 index 0000000000..a80d97acdd --- /dev/null +++ b/src/problem5/src/modules/books/__tests__/book.controller.spec.ts @@ -0,0 +1,176 @@ +import express from "express"; +import request from "supertest"; + +jest.mock("../book.service"); +jest.mock("../book.schema"); + +import * as bookService from "../book.service"; + +const createTestApp = () => { + const app = express(); + app.use(express.json()); + + // Inline routes to avoid importing from index which has side effects + const { + createBook, + listBooks, + getBookById, + updateBook, + deleteBook, + } = require("../book.controller"); + const { bookIdValidator, createBookValidator } = require("../book.validator"); + + app.post("/books", createBook); + app.get("/books", listBooks); + app.get("/books/:id", getBookById); + app.put("/books/:id", updateBook); + app.delete("/books/:id", deleteBook); + + return app; +}; + +describe("book.controller", () => { + let app: express.Express; + + beforeEach(() => { + jest.clearAllMocks(); + app = createTestApp(); + }); + + const mockBook = { + _id: "507f1f77bcf86cd799439011", + title: "Test Book", + author: "Test Author", + publisher: "Test Publisher", + publishedDate: "2024-01-01T00:00:00.000Z", + country: "US", + createdAt: "2026-04-21T17:10:39.731Z", + updatedAt: "2026-04-21T17:10:39.731Z", + }; + + describe("POST /books", () => { + it("should create a book and return 201", async () => { + (bookService.createBook as jest.Mock).mockResolvedValue(mockBook); + + const response = await request(app) + .post("/books") + .send({ + title: "Test Book", + author: "Test Author", + publisher: "Test Publisher", + publishedDate: "2024-01-01", + country: "US", + }); + + expect(response.status).toBe(201); + expect(response.body.message).toBe("Book created successfully!"); + expect(response.body.data).toEqual(mockBook); + }); + + it("should return 400 when validation fails", async () => { + const response = await request(app) + .post("/books") + .send({ + title: "Test Book", + // missing required fields + }); + + expect(response.status).toBe(400); + expect(response.body.errors).toBeDefined(); + expect(Array.isArray(response.body.errors)).toBe(true); + }); + }); + + describe("GET /books", () => { + it("should return paginated list of books", async () => { + const mockListResult = { + total: 1, + page: 1, + limit: 10, + data: [mockBook], + }; + (bookService.listBooks as jest.Mock).mockResolvedValue(mockListResult); + + const response = await request(app).get("/books"); + + expect(response.status).toBe(200); + expect(response.body.message).toBe("List books successfully!"); + expect(response.body.data).toEqual(mockListResult); + }); + + it("should pass query parameters to service", async () => { + const mockListResult = { total: 0, page: 1, limit: 10, data: [] }; + (bookService.listBooks as jest.Mock).mockResolvedValue(mockListResult); + + const response = await request(app) + .get("/books") + .query({ title: "Test", author: "Jane" }); + + expect(response.status).toBe(200); + expect(bookService.listBooks).toHaveBeenCalledWith( + expect.objectContaining({ title: "Test", author: "Jane" }) + ); + }); + }); + + describe("GET /books/:id", () => { + it("should return book when found", async () => { + (bookService.getBookById as jest.Mock).mockResolvedValue(mockBook); + + const response = await request(app).get("/books/507f1f77bcf86cd799439011"); + + expect(response.status).toBe(200); + expect(response.body.message).toBe("Book found successfully!"); + expect(response.body.data).toEqual(mockBook); + }); + + it("should return 400 for invalid book ID", async () => { + const response = await request(app).get("/books/invalid-id"); + + expect(response.status).toBe(400); + expect(response.body.errors).toBeDefined(); + }); + }); + + describe("PUT /books/:id", () => { + it("should update book and return 200", async () => { + const updatedBook = { ...mockBook, title: "Updated Title" }; + (bookService.updateBook as jest.Mock).mockResolvedValue(updatedBook); + + const response = await request(app) + .put("/books/507f1f77bcf86cd799439011") + .send({ title: "Updated Title" }); + + expect(response.status).toBe(200); + expect(response.body.message).toBe("Book updated successfully!"); + expect(response.body.data.title).toBe("Updated Title"); + }); + + it("should return 400 for invalid book ID", async () => { + const response = await request(app) + .put("/books/invalid-id") + .send({ title: "Updated Title" }); + + expect(response.status).toBe(400); + expect(response.body.errors).toBeDefined(); + }); + }); + + describe("DELETE /books/:id", () => { + it("should delete book and return 200", async () => { + (bookService.deleteBook as jest.Mock).mockResolvedValue(undefined); + + const response = await request(app).delete("/books/507f1f77bcf86cd799439011"); + + expect(response.status).toBe(200); + expect(response.body.message).toBe("Book deleted successfully!"); + }); + + it("should return 400 for invalid book ID", async () => { + const response = await request(app).delete("/books/invalid-id"); + + expect(response.status).toBe(400); + expect(response.body.errors).toBeDefined(); + }); + }); +}); diff --git a/src/problem5/src/modules/books/__tests__/book.service.spec.ts b/src/problem5/src/modules/books/__tests__/book.service.spec.ts new file mode 100644 index 0000000000..95dc308b1d --- /dev/null +++ b/src/problem5/src/modules/books/__tests__/book.service.spec.ts @@ -0,0 +1,233 @@ +import mongoose from "mongoose"; +import { AppError } from "../../../common/error-codes"; + +const mockBook = { + _id: new mongoose.Types.ObjectId(), + title: "Test Book", + author: "Test Author", + publisher: "Test Publisher", + publishedDate: new Date("2024-01-01"), + country: "US", + createdAt: new Date(), + updatedAt: new Date(), +}; + +const mockBookModel = { + findOne: jest.fn(), + find: jest.fn(), + findById: jest.fn(), + findByIdAndUpdate: jest.fn(), + findByIdAndDelete: jest.fn(), + countDocuments: jest.fn(), +}; + +const MockBook = jest.fn().mockImplementation((data: any) => ({ + ...data, + _id: mockBook._id, + save: jest.fn().mockResolvedValue({ ...mockBook, ...data }), +})); + +Object.assign(MockBook, mockBookModel); + +jest.mock("../book.schema", () => ({ + Book: MockBook, +})); + +import * as bookService from "../book.service"; + +describe("book.service", () => { + beforeEach(() => { + jest.clearAllMocks(); + MockBook.mockImplementation((data: any) => ({ + ...data, + _id: mockBook._id, + save: jest.fn().mockResolvedValue({ ...mockBook, ...data }), + })); + }); + + describe("createBook", () => { + it("should create a book successfully when no duplicate exists", async () => { + mockBookModel.findOne.mockReturnValue({ exec: jest.fn().mockResolvedValue(null) }); + + const result = await bookService.createBook({ + title: "Test Book", + author: "Test Author", + publisher: "Test Publisher", + publishedDate: new Date("2024-01-01"), + country: "US", + }); + + expect(mockBookModel.findOne).toHaveBeenCalledWith({ title: "Test Book", author: "Test Author" }); + expect(result.title).toBe("Test Book"); + }); + + it("should throw AppError when book with same title and author already exists", async () => { + mockBookModel.findOne.mockReturnValue({ exec: jest.fn().mockResolvedValue(mockBook) }); + + await expect( + bookService.createBook({ + title: "Test Book", + author: "Test Author", + publisher: "Test Publisher", + publishedDate: new Date("2024-01-01"), + country: "US", + }) + ).rejects.toThrow(AppError); + + await expect( + bookService.createBook({ + title: "Test Book", + author: "Test Author", + publisher: "Test Publisher", + publishedDate: new Date("2024-01-01"), + country: "US", + }) + ).rejects.toMatchObject({ + statusCode: 400, + message: "[createBook] Book with the same title and author already exists", + }); + }); + }); + + describe("listBooks", () => { + it("should return paginated list of books", async () => { + const mockBooks = [mockBook, { ...mockBook, _id: new mongoose.Types.ObjectId() }]; + mockBookModel.countDocuments.mockResolvedValue(2); + mockBookModel.find.mockReturnValue({ + skip: jest.fn().mockReturnThis(), + limit: jest.fn().mockReturnThis(), + exec: jest.fn().mockResolvedValue(mockBooks), + }); + + const result = await bookService.listBooks({}); + + expect(result).toEqual({ + total: 2, + page: 1, + limit: 10, + data: mockBooks, + }); + }); + + it("should use default page and limit when not provided", async () => { + mockBookModel.countDocuments.mockResolvedValue(0); + mockBookModel.find.mockReturnValue({ + skip: jest.fn().mockReturnThis(), + limit: jest.fn().mockReturnThis(), + exec: jest.fn().mockResolvedValue([]), + }); + + await bookService.listBooks({}); + + expect(mockBookModel.find).toHaveBeenCalledWith({}); + const findCall = mockBookModel.find.mock.results[0].value; + expect(findCall.skip).toHaveBeenCalledWith(0); + expect(findCall.limit).toHaveBeenCalledWith(10); + }); + + it("should calculate correct skip for pagination", async () => { + mockBookModel.countDocuments.mockResolvedValue(50); + mockBookModel.find.mockReturnValue({ + skip: jest.fn().mockReturnThis(), + limit: jest.fn().mockReturnThis(), + exec: jest.fn().mockResolvedValue([]), + }); + + await bookService.listBooks({ page: "3", limit: "20" }); + + const findCall = mockBookModel.find.mock.results[0].value; + expect(findCall.skip).toHaveBeenCalledWith(40); // (3-1) * 20 + expect(findCall.limit).toHaveBeenCalledWith(20); + }); + + it("should apply query filters when parameters provided", async () => { + mockBookModel.countDocuments.mockResolvedValue(1); + mockBookModel.find.mockReturnValue({ + skip: jest.fn().mockReturnThis(), + limit: jest.fn().mockReturnThis(), + exec: jest.fn().mockResolvedValue([mockBook]), + }); + + await bookService.listBooks({ title: "Test", author: "Jane" }); + + expect(mockBookModel.find).toHaveBeenCalledWith({ + title: { $regex: ".*Test.*", $options: "i" }, + author: { $regex: ".*Jane.*", $options: "i" }, + }); + }); + }); + + describe("getBookById", () => { + it("should return book when found", async () => { + mockBookModel.findById.mockReturnValue({ exec: jest.fn().mockResolvedValue(mockBook) }); + + const id = mockBook._id.toString(); + const result = await bookService.getBookById(id); + + expect(mockBookModel.findById).toHaveBeenCalledWith(id); + expect(result).toEqual(mockBook); + }); + + it("should throw AppError with 404 when book not found", async () => { + mockBookModel.findById.mockReturnValue({ exec: jest.fn().mockResolvedValue(null) }); + + const id = new mongoose.Types.ObjectId().toString(); + await expect(bookService.getBookById(id)).rejects.toThrow(AppError); + await expect(bookService.getBookById(id)).rejects.toMatchObject({ + statusCode: 404, + message: "[getBookById] Book not found", + }); + }); + }); + + describe("updateBook", () => { + it("should update book successfully", async () => { + const updatedBook = { ...mockBook, title: "Updated Title" }; + mockBookModel.findOne.mockReturnValue({ exec: jest.fn().mockResolvedValue(null) }); + mockBookModel.findByIdAndUpdate.mockReturnValue({ exec: jest.fn().mockResolvedValue(updatedBook) }); + + const id = mockBook._id.toString(); + const result = await bookService.updateBook(id, { title: "Updated Title" }); + + expect(mockBookModel.findByIdAndUpdate).toHaveBeenCalledWith( + id, + expect.objectContaining({ title: "Updated Title" }), + { new: true } + ); + expect(result).toEqual(updatedBook); + }); + + it("should throw AppError with 404 when book to update not found", async () => { + mockBookModel.findOne.mockReturnValue({ exec: jest.fn().mockResolvedValue(null) }); + mockBookModel.findByIdAndUpdate.mockReturnValue({ exec: jest.fn().mockResolvedValue(null) }); + + const id = new mongoose.Types.ObjectId().toString(); + await expect(bookService.updateBook(id, { title: "Updated" })).rejects.toThrow(AppError); + await expect(bookService.updateBook(id, { title: "Updated" })).rejects.toMatchObject({ + statusCode: 404, + message: "[updateBook] Book not found", + }); + }); + }); + + describe("deleteBook", () => { + it("should delete book successfully", async () => { + mockBookModel.findByIdAndDelete.mockReturnValue({ exec: jest.fn().mockResolvedValue(mockBook) }); + + const id = mockBook._id.toString(); + await expect(bookService.deleteBook(id)).resolves.toBeUndefined(); + expect(mockBookModel.findByIdAndDelete).toHaveBeenCalledWith(id); + }); + + it("should throw AppError with 404 when book to delete not found", async () => { + mockBookModel.findByIdAndDelete.mockReturnValue({ exec: jest.fn().mockResolvedValue(null) }); + + const id = new mongoose.Types.ObjectId().toString(); + await expect(bookService.deleteBook(id)).rejects.toThrow(AppError); + await expect(bookService.deleteBook(id)).rejects.toMatchObject({ + statusCode: 404, + message: "[deleteBook] Book not found", + }); + }); + }); +}); diff --git a/src/problem5/src/modules/books/__tests__/book.validator.spec.ts b/src/problem5/src/modules/books/__tests__/book.validator.spec.ts new file mode 100644 index 0000000000..bfaf53c9d8 --- /dev/null +++ b/src/problem5/src/modules/books/__tests__/book.validator.spec.ts @@ -0,0 +1,167 @@ +import { createBookValidator, bookIdValidator } from "../book.validator"; +import mongoose from "mongoose"; + +describe("book.validator", () => { + describe("createBookValidator", () => { + it("should return valid=true when all required fields are provided", () => { + const result = createBookValidator({ + title: "Test Book", + author: "Test Author", + publisher: "Test Publisher", + publishedDate: "2024-01-01", + country: "US", + }); + + expect(result.valid).toBe(true); + expect(result.errors).toEqual([]); + }); + + it("should return valid=false when data is null", () => { + const result = createBookValidator(null); + + expect(result.valid).toBe(false); + expect(result.errors).toContain("Invalid request.body"); + }); + + it("should return valid=false when data is not an object", () => { + const result = createBookValidator("string"); + + expect(result.valid).toBe(false); + expect(result.errors).toContain("Invalid request.body"); + }); + + it("should return valid=false when title is missing", () => { + const result = createBookValidator({ + author: "Test Author", + publisher: "Test Publisher", + publishedDate: "2024-01-01", + country: "US", + }); + + expect(result.valid).toBe(false); + expect(result.errors).toContain("title is required and must be a string."); + }); + + it("should return valid=false when title is not a string", () => { + const result = createBookValidator({ + title: 123, + author: "Test Author", + publisher: "Test Publisher", + publishedDate: "2024-01-01", + country: "US", + }); + + expect(result.valid).toBe(false); + expect(result.errors).toContain("title is required and must be a string."); + }); + + it("should return valid=false when author is missing", () => { + const result = createBookValidator({ + title: "Test Book", + publisher: "Test Publisher", + publishedDate: "2024-01-01", + country: "US", + }); + + expect(result.valid).toBe(false); + expect(result.errors).toContain("author is required and must be a string."); + }); + + it("should return valid=false when publisher is missing", () => { + const result = createBookValidator({ + title: "Test Book", + author: "Test Author", + publishedDate: "2024-01-01", + country: "US", + }); + + expect(result.valid).toBe(false); + expect(result.errors).toContain("publisher is required and must be a string."); + }); + + it("should return valid=false when publishedDate is invalid", () => { + const result = createBookValidator({ + title: "Test Book", + author: "Test Author", + publisher: "Test Publisher", + publishedDate: "invalid-date", + country: "US", + }); + + expect(result.valid).toBe(false); + expect(result.errors).toContain("publishedDate is required and must be a valid date string."); + }); + + it("should return valid=false when country is missing", () => { + const result = createBookValidator({ + title: "Test Book", + author: "Test Author", + publisher: "Test Publisher", + publishedDate: "2024-01-01", + }); + + expect(result.valid).toBe(false); + expect(result.errors).toContain("country is required and must be a string."); + }); + + it("should return multiple errors when multiple fields are invalid", () => { + const result = createBookValidator({ + title: 123, + author: 456, + publisher: "Test Publisher", + publishedDate: "invalid-date", + country: "US", + }); + + expect(result.valid).toBe(false); + expect(result.errors).toContain("title is required and must be a string."); + expect(result.errors).toContain("author is required and must be a string."); + expect(result.errors).toContain("publishedDate is required and must be a valid date string."); + }); + }); + + describe("bookIdValidator", () => { + it("should return valid=true for a valid ObjectId", () => { + const validId = new mongoose.Types.ObjectId().toString(); + const result = bookIdValidator(validId); + + expect(result.valid).toBe(true); + expect(result.id).toBe(validId); + }); + + it("should return valid=false for null id", () => { + const result = bookIdValidator(null); + + expect(result.valid).toBe(false); + expect(result.errors).toContain("Invalid book ID"); + }); + + it("should return valid=false for undefined id", () => { + const result = bookIdValidator(undefined); + + expect(result.valid).toBe(false); + expect(result.errors).toContain("Invalid book ID"); + }); + + it("should return valid=false for non-string id", () => { + const result = bookIdValidator(123); + + expect(result.valid).toBe(false); + expect(result.errors).toContain("Invalid book ID"); + }); + + it("should return valid=false for invalid ObjectId string", () => { + const result = bookIdValidator("not-a-valid-objectid"); + + expect(result.valid).toBe(false); + expect(result.errors).toContain("Invalid book ID"); + }); + + it("should return valid=false for empty string", () => { + const result = bookIdValidator(""); + + expect(result.valid).toBe(false); + expect(result.errors).toContain("Invalid book ID"); + }); + }); +}); diff --git a/src/problem5/src/modules/books/book.constant.ts b/src/problem5/src/modules/books/book.constant.ts new file mode 100644 index 0000000000..7c8cce47af --- /dev/null +++ b/src/problem5/src/modules/books/book.constant.ts @@ -0,0 +1,12 @@ +export const ValidationFieldMessages = { + INVALID_DATA: "Invalid request.body", + INVALID_TITLE: "title is required and must be a string.", + INVALID_AUTHOR: "author is required and must be a string.", + INVALID_PUBLISHER: "publisher is required and must be a string.", + INVALID_PUBLISHED_DATE: + "publishedDate is required and must be a valid date string.", + INVALID_COUNTRY: "country is required and must be a string.", +} as const; + +export const DEFAULT_BOOKS_PER_PAGE = 10; +export const DEFAULT_PAGE_NUMBER = 1; diff --git a/src/problem5/src/modules/books/book.controller.ts b/src/problem5/src/modules/books/book.controller.ts new file mode 100644 index 0000000000..2382aaeb3d --- /dev/null +++ b/src/problem5/src/modules/books/book.controller.ts @@ -0,0 +1,69 @@ +import { Request, Response } from "express"; + +import * as bookService from "./book.service"; +import { bookIdValidator, createBookValidator } from "./book.validator"; + +export const createBook = async (req: Request, res: Response) => { + const validatedData = createBookValidator(req.body); + if (!validatedData.valid) { + return res.status(400).json({ errors: validatedData.errors }); + } + + const book = await bookService.createBook(req.body); + + return res + .status(201) + .json({ message: "Book created successfully!", data: book }); +}; + +export const listBooks = async (req: Request, res: Response) => { + const result = await bookService.listBooks(req.query); + return res.status(200).json({ + message: "List books successfully!", + data: result, + }); +}; + +export const getBookById = async (req: Request, res: Response) => { + const bookIdValidation = bookIdValidator(req.params.id); + if (!bookIdValidation.valid) { + return res + .status(400) + .json({ message: "Bad Request", errors: bookIdValidation.errors }); + } + + const book = await bookService.getBookById(bookIdValidation.id); + return res.status(200).json({ + message: "Book found successfully!", + data: book, + }); +}; + +export const updateBook = async (req: Request, res: Response) => { + const bookIdValidation = bookIdValidator(req.params.id); + if (!bookIdValidation.valid) { + return res + .status(400) + .json({ message: "Bad Request", errors: bookIdValidation.errors }); + } + + const updatedBook = await bookService.updateBook(bookIdValidation.id, req.body); + return res.status(200).json({ + message: "Book updated successfully!", + data: updatedBook, + }); +}; + +export const deleteBook = async (req: Request, res: Response) => { + const bookIdValidation = bookIdValidator(req.params.id); + if (!bookIdValidation.valid) { + return res + .status(400) + .json({ message: "Bad Request", errors: bookIdValidation.errors }); + } + + await bookService.deleteBook(bookIdValidation.id); + return res.status(200).json({ + message: "Book deleted successfully!", + }); +}; diff --git a/src/problem5/src/modules/books/book.schema.ts b/src/problem5/src/modules/books/book.schema.ts new file mode 100644 index 0000000000..846714b33d --- /dev/null +++ b/src/problem5/src/modules/books/book.schema.ts @@ -0,0 +1,18 @@ +import mongoose, { InferSchemaType } from "mongoose"; + +export const BookSchema = new mongoose.Schema( + { + title: { type: String, required: true }, + author: { type: String, required: true }, + publisher: { type: String, required: true }, + publishedDate: { type: Date, required: true }, + country: { type: String, required: true }, + }, + { + timestamps: true, + }, +); + +export const Book = mongoose.model("Book", BookSchema); + +export type BookDocument = InferSchemaType; \ No newline at end of file diff --git a/src/problem5/src/modules/books/book.service.ts b/src/problem5/src/modules/books/book.service.ts new file mode 100644 index 0000000000..429a5a2d02 --- /dev/null +++ b/src/problem5/src/modules/books/book.service.ts @@ -0,0 +1,180 @@ +import { AppError } from "../../common/error-codes"; +import { DEFAULT_BOOKS_PER_PAGE, DEFAULT_PAGE_NUMBER } from "./book.constant"; +import { Book, BookDocument } from "./book.schema"; + +const _queryBuilder = (query: any) => { + const filter: any = {}; + if (query.title) { + filter.title = { $regex: ".*" + query.title + ".*", $options: "i" }; + } + + if (query.author) { + filter.author = { $regex: ".*" + query.author + ".*", $options: "i" }; + } + + if (query.publisher) { + filter.publisher = { $regex: ".*" + query.publisher + ".*", $options: "i" }; + } + + if (query.country) { + filter.country = { $regex: ".*" + query.country + ".*", $options: "i" }; + } + + return filter; +}; + +const _checkExistedBook = async ( + title: string, + author: string, +): Promise => { + const findExisting = await Book.findOne({ + title, + author, + }).exec(); + + if (findExisting) { + throw new AppError( + "[createBook] Book with the same title and author already exists", + 400, + ); + } +}; + +export const createBook = async ( + bookData: Partial, +): Promise => { + try { + await _checkExistedBook( + bookData.title as string, + bookData.author as string, + ); + + const book = new Book(bookData); + return await book.save(); + } catch (error) { + if (error instanceof AppError) { + throw error; + } + + const errorMessage = + error instanceof Error ? error.message : "Unknown error"; + throw new Error("[createBook] Failed to create book: " + errorMessage); + } +}; + +export const listBooks = async ( + query: any, +): Promise<{ + total: number; + page: number; + limit: number; + data: BookDocument[]; +}> => { + try { + const page = parseInt(query.page as string) || DEFAULT_PAGE_NUMBER; + const limit = parseInt(query.limit as string) || DEFAULT_BOOKS_PER_PAGE; + const skip = (page - 1) * limit; + + const _query = _queryBuilder(query); + + const [total, list] = await Promise.all([ + Book.countDocuments(_query), + Book.find(_query).skip(skip).limit(limit).exec(), + ]); + + return { total, page, limit, data: list }; + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : "Unknown error"; + throw new AppError( + "[listBooks] Failed to list books: " + errorMessage, + 500, + ); + } +}; + +export const getBookById = async (id: string): Promise => { + try { + const book = await Book.findById(id).exec(); + if (!book) { + throw new AppError("[getBookById] Book not found", 404); + } + + return book; + } catch (error) { + if (error instanceof AppError) { + throw error; + } + + const errorMessage = + error instanceof Error ? error.message : "Unknown error"; + + throw new AppError( + "[getBookById] Failed to get book: " + errorMessage, + 500, + ); + } +}; + +export const updateBook = async ( + id: string, + updateData: Partial, +): Promise => { + try { + await _checkExistedBook( + updateData.title as string, + updateData.author as string, + ); + + const _updateData: Partial = { + title: updateData.title, + author: updateData.author, + publisher: updateData.publisher, + publishedDate: updateData.publishedDate, + country: updateData.country, + }; + + const updatedBook = await Book.findByIdAndUpdate(id, _updateData, { + new: true, + }).exec(); + + if (!updatedBook) { + throw new AppError("[updateBook] Book not found", 404); + } + + return updatedBook; + } catch (error) { + if (error instanceof AppError) { + throw error; + } + + const errorMessage = + error instanceof Error ? error.message : "Unknown error"; + + throw new AppError( + "[updateBook] Failed to update book: " + errorMessage, + 500, + ); + } +}; + +export const deleteBook = async (id: string): Promise => { + try { + const deletedBook = await Book.findByIdAndDelete(id).exec(); + if (!deletedBook) { + throw new AppError("[deleteBook] Book not found", 404); + } + } catch (error) { + if (error instanceof AppError) { + throw error; + } + + const errorMessage = + error instanceof Error ? error.message : "Unknown error"; + + throw new AppError( + "[deleteBook] Failed to delete book: " + errorMessage, + 500, + ); + } +}; diff --git a/src/problem5/src/modules/books/book.validator.ts b/src/problem5/src/modules/books/book.validator.ts new file mode 100644 index 0000000000..b65dee6a12 --- /dev/null +++ b/src/problem5/src/modules/books/book.validator.ts @@ -0,0 +1,62 @@ +import mongoose from "mongoose"; +import { ValidationFieldMessages } from "./book.constant"; + +interface ValidationResult { + valid: boolean; + errors?: string[]; +} + +export const createBookValidator = (data: any): ValidationResult => { + if (!data || typeof data !== "object") { + return { + valid: false, + errors: [ValidationFieldMessages.INVALID_DATA], + }; + } + + const errors: string[] = []; + + if (!data.title || typeof data.title !== "string") { + errors.push(ValidationFieldMessages.INVALID_TITLE); + } + + if (!data.author || typeof data.author !== "string") { + errors.push(ValidationFieldMessages.INVALID_AUTHOR); + } + + if (!data.publisher || typeof data.publisher !== "string") { + errors.push(ValidationFieldMessages.INVALID_PUBLISHER); + } + + if (data.publishedDate && isNaN(Date.parse(data.publishedDate))) { + errors.push(ValidationFieldMessages.INVALID_PUBLISHED_DATE); + } + + if (!data.country || typeof data.country !== "string") { + errors.push(ValidationFieldMessages.INVALID_COUNTRY); + } + + return { + valid: errors.length === 0, + errors, + }; +}; + +export const bookIdValidator = (id: any): ValidationResult & { id: string } => { + if ( + !id || + typeof id !== "string" || + mongoose.Types.ObjectId.isValid(id) === false + ) { + return { + id, + valid: false, + errors: ["Invalid book ID"], + }; + } + + return { + valid: true, + id, + }; +}; diff --git a/src/problem5/src/modules/books/index.ts b/src/problem5/src/modules/books/index.ts new file mode 100644 index 0000000000..a87de1183a --- /dev/null +++ b/src/problem5/src/modules/books/index.ts @@ -0,0 +1,7 @@ +export { + createBook, + deleteBook, + getBookById, + listBooks, + updateBook, +} from "./book.controller"; diff --git a/src/problem5/src/route/book.route.ts b/src/problem5/src/route/book.route.ts new file mode 100644 index 0000000000..c27dd9f9ff --- /dev/null +++ b/src/problem5/src/route/book.route.ts @@ -0,0 +1,18 @@ +import { Router } from "express"; + +import { + createBook, + listBooks, + getBookById, + updateBook, + deleteBook, +} from "../modules/books"; +import { requiredAuth } from "../middlewares/auth.middleware"; + +export const bookRouter = Router(); + +bookRouter.post("/", requiredAuth, createBook); +bookRouter.get("/", listBooks); +bookRouter.get("/:id", getBookById); +bookRouter.patch("/:id", requiredAuth, updateBook); +bookRouter.delete("/:id", requiredAuth, deleteBook); diff --git a/src/problem5/src/route/index.ts b/src/problem5/src/route/index.ts new file mode 100644 index 0000000000..9d624b1808 --- /dev/null +++ b/src/problem5/src/route/index.ts @@ -0,0 +1 @@ +export { bookRouter } from "./book.route"; diff --git a/src/problem5/test/app.e2e.test.ts b/src/problem5/test/app.e2e.test.ts new file mode 100644 index 0000000000..9dc439d132 --- /dev/null +++ b/src/problem5/test/app.e2e.test.ts @@ -0,0 +1,261 @@ +import express from "express"; +import request from "supertest"; + +jest.mock("../src/modules/books/book.service"); +jest.mock("../src/mocks/seeds"); + +import * as bookService from "../src/modules/books/book.service"; + +const createApp = () => { + const app = express(); + app.use(express.json()); + + const { + errorHandler, + notFoundHandler, + } = require("../src/middlewares/errors-handler.middleware"); + const { bookRouter } = require("../src/route/book.route"); + + app.get("/", (_req: express.Request, res: express.Response) => { + res.send("Resources (Books) API!"); + }); + + app.use("/api/books", bookRouter); + app.use(notFoundHandler); + app.use(errorHandler); + + return app; +}; + +describe("Books API E2E", () => { + const app = createApp(); + const validApiKey = "random-api-key"; + + const mockBook = { + _id: "507f1f77bcf86cd799439011", + title: "The Great Gatsby", + author: "F. Scott Fitzgerald", + publisher: "Scribner", + publishedDate: "1925-04-10", + country: "US", + createdAt: "2024-01-01T00:00:00.000Z", + updatedAt: "2024-01-01T00:00:00.000Z", + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe("GET /", () => { + it("should return welcome message", async () => { + const response = await request(app).get("/"); + + expect(response.status).toBe(200); + expect(response.text).toBe("Resources (Books) API!"); + }); + }); + + describe("POST /api/books", () => { + it("should create a new book with valid data and API key", async () => { + (bookService.createBook as jest.Mock).mockResolvedValue(mockBook); + + const response = await request(app) + .post("/api/books") + .set("x-api-key", validApiKey) + .send({ + title: "The Great Gatsby", + author: "F. Scott Fitzgerald", + publisher: "Scribner", + publishedDate: "1925-04-10", + country: "US", + }); + + expect(response.status).toBe(201); + expect(response.body.message).toBe("Book created successfully!"); + expect(response.body.data).toBeDefined(); + }); + + it("should return 401 without API key", async () => { + const response = await request(app) + .post("/api/books") + .send({ + title: "The Great Gatsby", + author: "F. Scott Fitzgerald", + publisher: "Scribner", + publishedDate: "1925-04-10", + country: "US", + }); + + expect(response.status).toBe(401); + expect(response.body.error).toBe("Unauthorized access"); + }); + + it("should return 400 with invalid request body", async () => { + const response = await request(app) + .post("/api/books") + .set("x-api-key", validApiKey) + .send({ + title: "The Great Gatsby", + }); + + expect(response.status).toBe(400); + expect(response.body.errors).toBeDefined(); + expect(Array.isArray(response.body.errors)).toBe(true); + }); + }); + + describe("GET /api/books", () => { + it("should return list of books", async () => { + (bookService.listBooks as jest.Mock).mockResolvedValue({ + total: 1, + page: 1, + limit: 10, + data: [mockBook], + }); + + const response = await request(app).get("/api/books"); + + expect(response.status).toBe(200); + expect(response.body.message).toBe("List books successfully!"); + expect(response.body.data.data).toHaveLength(1); + expect(response.body.data.total).toBe(1); + }); + + it("should pass query parameters to listBooks", async () => { + (bookService.listBooks as jest.Mock).mockResolvedValue({ + total: 0, + page: 1, + limit: 10, + data: [], + }); + + const response = await request(app) + .get("/api/books") + .query({ title: "Gatsby", author: "Fitzgerald" }); + + expect(response.status).toBe(200); + expect(bookService.listBooks).toHaveBeenCalledWith( + expect.objectContaining({ + title: "Gatsby", + author: "Fitzgerald", + }) + ); + }); + + it("should support pagination parameters", async () => { + (bookService.listBooks as jest.Mock).mockResolvedValue({ + total: 100, + page: 2, + limit: 20, + data: [], + }); + + const response = await request(app) + .get("/api/books") + .query({ page: "2", limit: "20" }); + + expect(response.status).toBe(200); + expect(bookService.listBooks).toHaveBeenCalledWith( + expect.objectContaining({ page: "2", limit: "20" }) + ); + }); + }); + + describe("GET /api/books/:id", () => { + it("should return a book by ID", async () => { + (bookService.getBookById as jest.Mock).mockResolvedValue(mockBook); + + const response = await request(app).get("/api/books/507f1f77bcf86cd799439011"); + + expect(response.status).toBe(200); + expect(response.body.message).toBe("Book found successfully!"); + expect(response.body.data.title).toBe("The Great Gatsby"); + }); + + it("should return 400 for invalid book ID format", async () => { + const response = await request(app).get("/api/books/invalid-id"); + + expect(response.status).toBe(400); + expect(response.body.errors).toBeDefined(); + }); + + it("should return 404 when book not found", async () => { + const AppError = require("../src/common/error-codes").AppError; + (bookService.getBookById as jest.Mock).mockRejectedValue(new AppError("Book not found", 404)); + + const response = await request(app).get("/api/books/507f1f77bcf86cd799439011"); + + expect(response.status).toBe(404); + }); + }); + + describe("PATCH /api/books/:id", () => { + it("should update a book with valid data", async () => { + const updatedBook = { ...mockBook, title: "Updated Title" }; + (bookService.updateBook as jest.Mock).mockResolvedValue(updatedBook); + + const response = await request(app) + .patch("/api/books/507f1f77bcf86cd799439011") + .set("x-api-key", validApiKey) + .send({ title: "Updated Title" }); + + expect(response.status).toBe(200); + expect(response.body.message).toBe("Book updated successfully!"); + expect(response.body.data.title).toBe("Updated Title"); + }); + + it("should return 401 without API key", async () => { + const response = await request(app) + .patch("/api/books/507f1f77bcf86cd799439011") + .send({ title: "Updated Title" }); + + expect(response.status).toBe(401); + }); + + it("should return 400 for invalid book ID", async () => { + const response = await request(app) + .patch("/api/books/invalid-id") + .set("x-api-key", validApiKey) + .send({ title: "Updated Title" }); + + expect(response.status).toBe(400); + }); + }); + + describe("DELETE /api/books/:id", () => { + it("should delete a book", async () => { + (bookService.deleteBook as jest.Mock).mockResolvedValue(undefined); + + const response = await request(app) + .delete("/api/books/507f1f77bcf86cd799439011") + .set("x-api-key", validApiKey); + + expect(response.status).toBe(200); + expect(response.body.message).toBe("Book deleted successfully!"); + }); + + it("should return 401 without API key", async () => { + const response = await request(app) + .delete("/api/books/507f1f77bcf86cd799439011"); + + expect(response.status).toBe(401); + }); + + it("should return 400 for invalid book ID", async () => { + const response = await request(app) + .delete("/api/books/invalid-id") + .set("x-api-key", validApiKey); + + expect(response.status).toBe(400); + }); + }); + + describe("404 handling", () => { + it("should return 404 for unknown routes", async () => { + const response = await request(app).get("/api/unknown"); + + expect(response.status).toBe(404); + expect(response.body.error).toBe("Not Found"); + }); + }); +}); diff --git a/src/problem5/tsconfig.json b/src/problem5/tsconfig.json new file mode 100644 index 0000000000..611198421d --- /dev/null +++ b/src/problem5/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "declaration": true, + "outDir": "dist", + "rootDir": ".", + "esModuleInterop": true, + "skipLibCheck": true, + "isolatedModules": true, + "types": ["jest", "node"] + }, + "include": ["src/**/*.ts", "test/**/*.ts"] +} diff --git a/src/problem5/yarn.lock b/src/problem5/yarn.lock new file mode 100644 index 0000000000..c1baea1ee8 --- /dev/null +++ b/src/problem5/yarn.lock @@ -0,0 +1,3397 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.27.1", "@babel/code-frame@^7.28.6", "@babel/code-frame@^7.29.0": + version "7.29.0" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz#7cd7a59f15b3cc0dcd803038f7792712a7d0b15c" + integrity sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw== + dependencies: + "@babel/helper-validator-identifier" "^7.28.5" + js-tokens "^4.0.0" + picocolors "^1.1.1" + +"@babel/compat-data@^7.28.6": + version "7.29.0" + resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz#00d03e8c0ac24dd9be942c5370990cbe1f17d88d" + integrity sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg== + +"@babel/core@^7.23.9", "@babel/core@^7.27.4": + version "7.29.0" + resolved "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz#5286ad785df7f79d656e88ce86e650d16ca5f322" + integrity sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA== + dependencies: + "@babel/code-frame" "^7.29.0" + "@babel/generator" "^7.29.0" + "@babel/helper-compilation-targets" "^7.28.6" + "@babel/helper-module-transforms" "^7.28.6" + "@babel/helpers" "^7.28.6" + "@babel/parser" "^7.29.0" + "@babel/template" "^7.28.6" + "@babel/traverse" "^7.29.0" + "@babel/types" "^7.29.0" + "@jridgewell/remapping" "^2.3.5" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.27.5", "@babel/generator@^7.29.0": + version "7.29.1" + resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz#d09876290111abbb00ef962a7b83a5307fba0d50" + integrity sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw== + dependencies: + "@babel/parser" "^7.29.0" + "@babel/types" "^7.29.0" + "@jridgewell/gen-mapping" "^0.3.12" + "@jridgewell/trace-mapping" "^0.3.28" + jsesc "^3.0.2" + +"@babel/helper-compilation-targets@^7.28.6": + version "7.28.6" + resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz#32c4a3f41f12ed1532179b108a4d746e105c2b25" + integrity sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA== + dependencies: + "@babel/compat-data" "^7.28.6" + "@babel/helper-validator-option" "^7.27.1" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-globals@^7.28.0": + version "7.28.0" + resolved "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" + integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== + +"@babel/helper-module-imports@^7.28.6": + version "7.28.6" + resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz#60632cbd6ffb70b22823187201116762a03e2d5c" + integrity sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw== + dependencies: + "@babel/traverse" "^7.28.6" + "@babel/types" "^7.28.6" + +"@babel/helper-module-transforms@^7.28.6": + version "7.28.6" + resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz#9312d9d9e56edc35aeb6e95c25d4106b50b9eb1e" + integrity sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA== + dependencies: + "@babel/helper-module-imports" "^7.28.6" + "@babel/helper-validator-identifier" "^7.28.5" + "@babel/traverse" "^7.28.6" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.28.6", "@babel/helper-plugin-utils@^7.8.0": + version "7.28.6" + resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz#6f13ea251b68c8532e985fd532f28741a8af9ac8" + integrity sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug== + +"@babel/helper-string-parser@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" + integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== + +"@babel/helper-validator-identifier@^7.28.5": + version "7.28.5" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz#010b6938fab7cb7df74aa2bbc06aa503b8fe5fb4" + integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q== + +"@babel/helper-validator-option@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f" + integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== + +"@babel/helpers@^7.28.6": + version "7.29.2" + resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz#9cfbccb02b8e229892c0b07038052cc1a8709c49" + integrity sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw== + dependencies: + "@babel/template" "^7.28.6" + "@babel/types" "^7.29.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.28.6", "@babel/parser@^7.29.0": + version "7.29.2" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz#58bd50b9a7951d134988a1ae177a35ef9a703ba1" + integrity sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA== + dependencies: + "@babel/types" "^7.29.0" + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.13": + version "7.12.13" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-import-attributes@^7.24.7": + version "7.28.6" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz#b71d5914665f60124e133696f17cd7669062c503" + integrity sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-syntax-import-meta@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.27.1": + version "7.28.6" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz#f8ca28bbd84883b5fea0e447c635b81ba73997ee" + integrity sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-top-level-await@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.27.1": + version "7.28.6" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz#c7b2ddf1d0a811145b1de800d1abd146af92e3a2" + integrity sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/template@^7.28.6": + version "7.28.6" + resolved "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz#0e7e56ecedb78aeef66ce7972b082fce76a23e57" + integrity sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ== + dependencies: + "@babel/code-frame" "^7.28.6" + "@babel/parser" "^7.28.6" + "@babel/types" "^7.28.6" + +"@babel/traverse@^7.28.6", "@babel/traverse@^7.29.0": + version "7.29.0" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz#f323d05001440253eead3c9c858adbe00b90310a" + integrity sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA== + dependencies: + "@babel/code-frame" "^7.29.0" + "@babel/generator" "^7.29.0" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.29.0" + "@babel/template" "^7.28.6" + "@babel/types" "^7.29.0" + debug "^4.3.1" + +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.28.6", "@babel/types@^7.29.0": + version "7.29.0" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz#9f5b1e838c446e72cf3cd4b918152b8c605e37c7" + integrity sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.28.5" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + +"@emnapi/core@^1.4.3": + version "1.10.0" + resolved "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz#380ccc8f2412ea22d1d972df7f8ee23a3b9c7467" + integrity sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw== + dependencies: + "@emnapi/wasi-threads" "1.2.1" + tslib "^2.4.0" + +"@emnapi/runtime@^1.4.3": + version "1.10.0" + resolved "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz#4b260c0d3534204e98c6110b8db1a987d26ec87c" + integrity sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA== + dependencies: + tslib "^2.4.0" + +"@emnapi/wasi-threads@1.2.1": + version "1.2.1" + resolved "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz#28fed21a1ba1ce797c44a070abc94d42f3ae8548" + integrity sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w== + dependencies: + tslib "^2.4.0" + +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + dependencies: + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": + version "0.1.6" + resolved "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz#8dc9afa2ac1506cb1a58f89940f1c124446c8df3" + integrity sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw== + +"@jest/console@30.3.0": + version "30.3.0" + resolved "https://registry.npmjs.org/@jest/console/-/console-30.3.0.tgz#42ccc3f995d400a8fe35b8850cfe10a8d4804cdf" + integrity sha512-PAwCvFJ4696XP2qZj+LAn1BWjZaJ6RjG6c7/lkMaUJnkyMS34ucuIsfqYvfskVNvUI27R/u4P1HMYFnlVXG/Ww== + dependencies: + "@jest/types" "30.3.0" + "@types/node" "*" + chalk "^4.1.2" + jest-message-util "30.3.0" + jest-util "30.3.0" + slash "^3.0.0" + +"@jest/core@30.3.0": + version "30.3.0" + resolved "https://registry.npmjs.org/@jest/core/-/core-30.3.0.tgz#d06bb8456f35350f6494fd2405bcec4abb97b994" + integrity sha512-U5mVPsBxLSO6xYbf+tgkymLx+iAhvZX43/xI1+ej2ZOPnPdkdO1CzDmFKh2mZBn2s4XZixszHeQnzp1gm/DIxw== + dependencies: + "@jest/console" "30.3.0" + "@jest/pattern" "30.0.1" + "@jest/reporters" "30.3.0" + "@jest/test-result" "30.3.0" + "@jest/transform" "30.3.0" + "@jest/types" "30.3.0" + "@types/node" "*" + ansi-escapes "^4.3.2" + chalk "^4.1.2" + ci-info "^4.2.0" + exit-x "^0.2.2" + graceful-fs "^4.2.11" + jest-changed-files "30.3.0" + jest-config "30.3.0" + jest-haste-map "30.3.0" + jest-message-util "30.3.0" + jest-regex-util "30.0.1" + jest-resolve "30.3.0" + jest-resolve-dependencies "30.3.0" + jest-runner "30.3.0" + jest-runtime "30.3.0" + jest-snapshot "30.3.0" + jest-util "30.3.0" + jest-validate "30.3.0" + jest-watcher "30.3.0" + pretty-format "30.3.0" + slash "^3.0.0" + +"@jest/diff-sequences@30.3.0": + version "30.3.0" + resolved "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.3.0.tgz#25b0818d3d83f00b9c7b04e069b8810f9014b143" + integrity sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA== + +"@jest/environment@30.3.0": + version "30.3.0" + resolved "https://registry.npmjs.org/@jest/environment/-/environment-30.3.0.tgz#b0657c2944b6ef3352f7b25903cc3a23e6ab70f6" + integrity sha512-SlLSF4Be735yQXyh2+mctBOzNDx5s5uLv88/j8Qn1wH679PDcwy67+YdADn8NJnGjzlXtN62asGH/T4vWOkfaw== + dependencies: + "@jest/fake-timers" "30.3.0" + "@jest/types" "30.3.0" + "@types/node" "*" + jest-mock "30.3.0" + +"@jest/expect-utils@30.3.0": + version "30.3.0" + resolved "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.3.0.tgz#c45b2da9802ffed33bf43b3e019ddb95e5ad95e8" + integrity sha512-j0+W5iQQ8hBh7tHZkTQv3q2Fh/M7Je72cIsYqC4OaktgtO7v1So9UTjp6uPBHIaB6beoF/RRsCgMJKvti0wADA== + dependencies: + "@jest/get-type" "30.1.0" + +"@jest/expect@30.3.0": + version "30.3.0" + resolved "https://registry.npmjs.org/@jest/expect/-/expect-30.3.0.tgz#08ee7f5b610167b0068743246c0b568f4c40c773" + integrity sha512-76Nlh4xJxk2D/9URCn3wFi98d2hb19uWE1idLsTt2ywhvdOldbw3S570hBgn25P4ICUZ/cBjybrBex2g17IDbg== + dependencies: + expect "30.3.0" + jest-snapshot "30.3.0" + +"@jest/fake-timers@30.3.0": + version "30.3.0" + resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.3.0.tgz#2b2868130c1d28233a79566874c42cae1c5a70bc" + integrity sha512-WUQDs8SOP9URStX1DzhD425CqbN/HxUYCTwVrT8sTVBfMvFqYt/s61EK5T05qnHu0po6RitXIvP9otZxYDzTGQ== + dependencies: + "@jest/types" "30.3.0" + "@sinonjs/fake-timers" "^15.0.0" + "@types/node" "*" + jest-message-util "30.3.0" + jest-mock "30.3.0" + jest-util "30.3.0" + +"@jest/get-type@30.1.0": + version "30.1.0" + resolved "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz#4fcb4dc2ebcf0811be1c04fd1cb79c2dba431cbc" + integrity sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA== + +"@jest/globals@30.3.0": + version "30.3.0" + resolved "https://registry.npmjs.org/@jest/globals/-/globals-30.3.0.tgz#40f4c90e5602629ecda1ca773a8fb21575bb64ea" + integrity sha512-+owLCBBdfpgL3HU+BD5etr1SvbXpSitJK0is1kiYjJxAAJggYMRQz5hSdd5pq1sSggfxPbw2ld71pt4x5wwViA== + dependencies: + "@jest/environment" "30.3.0" + "@jest/expect" "30.3.0" + "@jest/types" "30.3.0" + jest-mock "30.3.0" + +"@jest/pattern@30.0.1": + version "30.0.1" + resolved "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz#d5304147f49a052900b4b853dedb111d080e199f" + integrity sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA== + dependencies: + "@types/node" "*" + jest-regex-util "30.0.1" + +"@jest/reporters@30.3.0": + version "30.3.0" + resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-30.3.0.tgz#0c1065f6c892665e5a051df22b19df4466ed816b" + integrity sha512-a09z89S+PkQnL055bVj8+pe2Caed2PBOaczHcXCykW5ngxX9EWx/1uAwncxc/HiU0oZqfwseMjyhxgRjS49qPw== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "30.3.0" + "@jest/test-result" "30.3.0" + "@jest/transform" "30.3.0" + "@jest/types" "30.3.0" + "@jridgewell/trace-mapping" "^0.3.25" + "@types/node" "*" + chalk "^4.1.2" + collect-v8-coverage "^1.0.2" + exit-x "^0.2.2" + glob "^10.5.0" + graceful-fs "^4.2.11" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^6.0.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^5.0.0" + istanbul-reports "^3.1.3" + jest-message-util "30.3.0" + jest-util "30.3.0" + jest-worker "30.3.0" + slash "^3.0.0" + string-length "^4.0.2" + v8-to-istanbul "^9.0.1" + +"@jest/schemas@30.0.5": + version "30.0.5" + resolved "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz#7bdf69fc5a368a5abdb49fd91036c55225846473" + integrity sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA== + dependencies: + "@sinclair/typebox" "^0.34.0" + +"@jest/snapshot-utils@30.3.0": + version "30.3.0" + resolved "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.3.0.tgz#ca003c91a3e1e4e4956dee716a2aaf04b6707f31" + integrity sha512-ORbRN9sf5PP82v3FXNSwmO1OTDR2vzR2YTaR+E3VkSBZ8zadQE6IqYdYEeFH1NIkeB2HIGdF02dapb6K0Mj05g== + dependencies: + "@jest/types" "30.3.0" + chalk "^4.1.2" + graceful-fs "^4.2.11" + natural-compare "^1.4.0" + +"@jest/source-map@30.0.1": + version "30.0.1" + resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz#305ebec50468f13e658b3d5c26f85107a5620aaa" + integrity sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg== + dependencies: + "@jridgewell/trace-mapping" "^0.3.25" + callsites "^3.1.0" + graceful-fs "^4.2.11" + +"@jest/test-result@30.3.0": + version "30.3.0" + resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-30.3.0.tgz#cd8882d683d467fcffb98c09501a65687a76aae9" + integrity sha512-e/52nJGuD74AKTSe0P4y5wFRlaXP0qmrS17rqOMHeSwm278VyNyXE3gFO/4DTGF9w+65ra3lo3VKj0LBrzmgdQ== + dependencies: + "@jest/console" "30.3.0" + "@jest/types" "30.3.0" + "@types/istanbul-lib-coverage" "^2.0.6" + collect-v8-coverage "^1.0.2" + +"@jest/test-sequencer@30.3.0": + version "30.3.0" + resolved "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.3.0.tgz#27002b2093f4e0d9e0e1ebb0bc274a242fdadc14" + integrity sha512-dgbWy9b8QDlQeRZcv7LNF+/jFiiYHTKho1xirauZ7kVwY7avjFF6uTT0RqlgudB5OuIPagFdVtfFMosjVbk1eA== + dependencies: + "@jest/test-result" "30.3.0" + graceful-fs "^4.2.11" + jest-haste-map "30.3.0" + slash "^3.0.0" + +"@jest/transform@30.3.0": + version "30.3.0" + resolved "https://registry.npmjs.org/@jest/transform/-/transform-30.3.0.tgz#9e6f78ffa205449bf956e269fd707c160f47ce2f" + integrity sha512-TLKY33fSLVd/lKB2YI1pH69ijyUblO/BQvCj566YvnwuzoTNr648iE0j22vRvVNk2HsPwByPxATg3MleS3gf5A== + dependencies: + "@babel/core" "^7.27.4" + "@jest/types" "30.3.0" + "@jridgewell/trace-mapping" "^0.3.25" + babel-plugin-istanbul "^7.0.1" + chalk "^4.1.2" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.11" + jest-haste-map "30.3.0" + jest-regex-util "30.0.1" + jest-util "30.3.0" + pirates "^4.0.7" + slash "^3.0.0" + write-file-atomic "^5.0.1" + +"@jest/types@30.3.0": + version "30.3.0" + resolved "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz#cada800d323cb74945c24ac74615fdb312a6c85f" + integrity sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw== + dependencies: + "@jest/pattern" "30.0.1" + "@jest/schemas" "30.0.5" + "@types/istanbul-lib-coverage" "^2.0.6" + "@types/istanbul-reports" "^3.0.4" + "@types/node" "*" + "@types/yargs" "^17.0.33" + chalk "^4.1.2" + +"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5": + version "0.3.13" + resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" + integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/remapping@^2.3.5": + version "2.3.5" + resolved "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz#375c476d1972947851ba1e15ae8f123047445aa1" + integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": + version "1.5.5" + resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.23", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25", "@jridgewell/trace-mapping@^0.3.28": + version "0.3.31" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" + integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@mongodb-js/saslprep@^1.3.0": + version "1.4.8" + resolved "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.8.tgz#f978459b1c213a2d806fea0e02a03e9d7086b985" + integrity sha512-kpjr2jy2w71w0oqAMI8oibBmiF9lXxWkEQs5gMkW4hVE48bsqINGLxnCSYW62ck/NHXJQpQEfA9WlJ1sY0eqBg== + dependencies: + sparse-bitfield "^3.0.3" + +"@napi-rs/wasm-runtime@^0.2.11": + version "0.2.12" + resolved "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz#3e78a8b96e6c33a6c517e1894efbd5385a7cb6f2" + integrity sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ== + dependencies: + "@emnapi/core" "^1.4.3" + "@emnapi/runtime" "^1.4.3" + "@tybys/wasm-util" "^0.10.0" + +"@noble/hashes@^1.1.5": + version "1.8.0" + resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz#cee43d801fcef9644b11b8194857695acd5f815a" + integrity sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A== + +"@paralleldrive/cuid2@^2.2.2": + version "2.3.1" + resolved "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz#3d62ea9e7be867d3fa94b9897fab5b0ae187d784" + integrity sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw== + dependencies: + "@noble/hashes" "^1.1.5" + +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + +"@pkgr/core@^0.2.9": + version "0.2.9" + resolved "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz#d229a7b7f9dac167a156992ef23c7f023653f53b" + integrity sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA== + +"@sinclair/typebox@^0.34.0": + version "0.34.49" + resolved "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz#4f1369234f2ecf693866476c3b2e1b54d2a9d68e" + integrity sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A== + +"@sinonjs/commons@^3.0.1": + version "3.0.1" + resolved "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" + integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^15.0.0": + version "15.3.2" + resolved "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.3.2.tgz#afecc36681e26aab9e0fe809fd9ad578096a3058" + integrity sha512-mrn35Jl2pCpns+mE3HaZa1yPN5EYCRgiMI+135COjr2hr8Cls9DXqIZ57vZe2cz7y2XVSq92tcs6kGQcT1J8Rw== + dependencies: + "@sinonjs/commons" "^3.0.1" + +"@tsconfig/node10@^1.0.7": + version "1.0.12" + resolved "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz#be57ceac1e4692b41be9de6be8c32a106636dba4" + integrity sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.4" + resolved "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== + +"@tybys/wasm-util@^0.10.0": + version "0.10.1" + resolved "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz#ecddd3205cf1e2d5274649ff0eedd2991ed7f414" + integrity sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg== + dependencies: + tslib "^2.4.0" + +"@types/babel__core@^7.20.5": + version "7.20.5" + resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.27.0" + resolved "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz#b5819294c51179957afaec341442f9341e4108a9" + integrity sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.4" + resolved "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" + integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*": + version "7.28.0" + resolved "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz#07d713d6cce0d265c9849db0cbe62d3f61f36f74" + integrity sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q== + dependencies: + "@babel/types" "^7.28.2" + +"@types/body-parser@*": + version "1.19.6" + resolved "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz#1859bebb8fd7dac9918a45d54c1971ab8b5af474" + integrity sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.38" + resolved "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" + integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== + dependencies: + "@types/node" "*" + +"@types/cookiejar@^2.1.5": + version "2.1.5" + resolved "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz#14a3e83fa641beb169a2dd8422d91c3c345a9a78" + integrity sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q== + +"@types/express-serve-static-core@^5.0.0": + version "5.1.1" + resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz#1a77faffee9572d39124933259be2523837d7eaa" + integrity sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + +"@types/express@^5.0.6": + version "5.0.6" + resolved "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz#2d724b2c990dcb8c8444063f3580a903f6d500cc" + integrity sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^5.0.0" + "@types/serve-static" "^2" + +"@types/http-errors@*": + version "2.0.5" + resolved "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz#5b749ab2b16ba113423feb1a64a95dcd30398472" + integrity sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg== + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.1", "@types/istanbul-lib-coverage@^2.0.6": + version "2.0.6" + resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + +"@types/istanbul-lib-report@*": + version "3.0.3" + resolved "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" + integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.4": + version "3.0.4" + resolved "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" + integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@^30.0.0": + version "30.0.0" + resolved "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz#5e85ae568006712e4ad66f25433e9bdac8801f1d" + integrity sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA== + dependencies: + expect "^30.0.0" + pretty-format "^30.0.0" + +"@types/methods@^1.1.4": + version "1.1.4" + resolved "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz#d3b7ac30ac47c91054ea951ce9eed07b1051e547" + integrity sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ== + +"@types/node@*": + version "25.6.0" + resolved "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz#4e09bad9b469871f2d0f68140198cbd714f4edca" + integrity sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ== + dependencies: + undici-types "~7.19.0" + +"@types/qs@*": + version "6.15.0" + resolved "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz#963ab61779843fe910639a50661b48f162bc7f79" + integrity sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow== + +"@types/range-parser@*": + version "1.2.7" + resolved "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" + integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== + +"@types/send@*": + version "1.2.1" + resolved "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz#6a784e45543c18c774c049bff6d3dbaf045c9c74" + integrity sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ== + dependencies: + "@types/node" "*" + +"@types/serve-static@^2": + version "2.2.0" + resolved "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz#d4a447503ead0d1671132d1ab6bd58b805d8de6a" + integrity sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ== + dependencies: + "@types/http-errors" "*" + "@types/node" "*" + +"@types/stack-utils@^2.0.3": + version "2.0.3" + resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" + integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== + +"@types/superagent@^8.1.0": + version "8.1.9" + resolved "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.9.tgz#28bfe4658e469838ed0bf66d898354bcab21f49f" + integrity sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ== + dependencies: + "@types/cookiejar" "^2.1.5" + "@types/methods" "^1.1.4" + "@types/node" "*" + form-data "^4.0.0" + +"@types/supertest@^7.2.0": + version "7.2.0" + resolved "https://registry.npmjs.org/@types/supertest/-/supertest-7.2.0.tgz#9620bc998d3e26bcbedba7d31da8fe4c82fc1620" + integrity sha512-uh2Lv57xvggst6lCqNdFAmDSvoMG7M/HDtX4iUCquxQ5EGPtaPM5PL5Hmi7LCvOG8db7YaCPNJEeoI8s/WzIQw== + dependencies: + "@types/methods" "^1.1.4" + "@types/superagent" "^8.1.0" + +"@types/webidl-conversions@*": + version "7.0.3" + resolved "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz#1306dbfa53768bcbcfc95a1c8cde367975581859" + integrity sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA== + +"@types/whatwg-url@^13.0.0": + version "13.0.0" + resolved "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-13.0.0.tgz#2b11e32772fd321c0dedf4d655953ea8ce587b2a" + integrity sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q== + dependencies: + "@types/webidl-conversions" "*" + +"@types/yargs-parser@*": + version "21.0.3" + resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" + integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== + +"@types/yargs@^17.0.33": + version "17.0.35" + resolved "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz#07013e46aa4d7d7d50a49e15604c1c5340d4eb24" + integrity sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg== + dependencies: + "@types/yargs-parser" "*" + +"@ungap/structured-clone@^1.3.0": + version "1.3.0" + resolved "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" + integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== + +"@unrs/resolver-binding-android-arm-eabi@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz#9f5b04503088e6a354295e8ea8fe3cb99e43af81" + integrity sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw== + +"@unrs/resolver-binding-android-arm64@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz#7414885431bd7178b989aedc4d25cccb3865bc9f" + integrity sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g== + +"@unrs/resolver-binding-darwin-arm64@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz#b4a8556f42171fb9c9f7bac8235045e82aa0cbdf" + integrity sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g== + +"@unrs/resolver-binding-darwin-x64@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz#fd4d81257b13f4d1a083890a6a17c00de571f0dc" + integrity sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ== + +"@unrs/resolver-binding-freebsd-x64@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz#d2513084d0f37c407757e22f32bd924a78cfd99b" + integrity sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw== + +"@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz#844d2605d057488d77fab09705f2866b86164e0a" + integrity sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw== + +"@unrs/resolver-binding-linux-arm-musleabihf@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz#204892995cefb6bd1d017d52d097193bc61ddad3" + integrity sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw== + +"@unrs/resolver-binding-linux-arm64-gnu@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz#023eb0c3aac46066a10be7a3f362e7b34f3bdf9d" + integrity sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ== + +"@unrs/resolver-binding-linux-arm64-musl@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz#9e6f9abb06424e3140a60ac996139786f5d99be0" + integrity sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w== + +"@unrs/resolver-binding-linux-ppc64-gnu@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz#b111417f17c9d1b02efbec8e08398f0c5527bb44" + integrity sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA== + +"@unrs/resolver-binding-linux-riscv64-gnu@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz#92ffbf02748af3e99873945c9a8a5ead01d508a9" + integrity sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ== + +"@unrs/resolver-binding-linux-riscv64-musl@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz#0bec6f1258fc390e6b305e9ff44256cb207de165" + integrity sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew== + +"@unrs/resolver-binding-linux-s390x-gnu@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz#577843a084c5952f5906770633ccfb89dac9bc94" + integrity sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg== + +"@unrs/resolver-binding-linux-x64-gnu@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz#36fb318eebdd690f6da32ac5e0499a76fa881935" + integrity sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w== + +"@unrs/resolver-binding-linux-x64-musl@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz#bfb9af75f783f98f6a22c4244214efe4df1853d6" + integrity sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA== + +"@unrs/resolver-binding-wasm32-wasi@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz#752c359dd875684b27429500d88226d7cc72f71d" + integrity sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ== + dependencies: + "@napi-rs/wasm-runtime" "^0.2.11" + +"@unrs/resolver-binding-win32-arm64-msvc@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz#ce5735e600e4c2fbb409cd051b3b7da4a399af35" + integrity sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw== + +"@unrs/resolver-binding-win32-ia32-msvc@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz#72fc57bc7c64ec5c3de0d64ee0d1810317bc60a6" + integrity sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ== + +"@unrs/resolver-binding-win32-x64-msvc@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz#538b1e103bf8d9864e7b85cc96fa8d6fb6c40777" + integrity sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g== + +accepts@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz#bbcf4ba5075467f3f2131eab3cffc73c2f5d7895" + integrity sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng== + dependencies: + mime-types "^3.0.0" + negotiator "^1.0.0" + +acorn-walk@^8.1.1: + version "8.3.5" + resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz#8a6b8ca8fc5b34685af15dabb44118663c296496" + integrity sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw== + dependencies: + acorn "^8.11.0" + +acorn@^8.11.0, acorn@^8.4.1: + version "8.16.0" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz#4ce79c89be40afe7afe8f3adb902a1f1ce9ac08a" + integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw== + +ansi-escapes@^4.3.2: + version "4.3.2" + resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-regex@^6.2.2: + version "6.2.2" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz#60216eea464d864597ce2832000738a0589650c1" + integrity sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg== + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.2.0: + version "5.2.0" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +ansi-styles@^6.1.0: + version "6.2.3" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz#c044d5dcc521a076413472597a1acb1f103c4041" + integrity sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg== + +anymatch@^3.1.3, anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +asap@^2.0.0: + version "2.0.6" + resolved "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +babel-jest@30.3.0: + version "30.3.0" + resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-30.3.0.tgz#3ff5553fa3bcbb8738d2d7335a4dbdc3bd1a0eb5" + integrity sha512-gRpauEU2KRrCox5Z296aeVHR4jQ98BCnu0IO332D/xpHNOsIH/bgSRk9k6GbKIbBw8vFeN6ctuu6tV8WOyVfYQ== + dependencies: + "@jest/transform" "30.3.0" + "@types/babel__core" "^7.20.5" + babel-plugin-istanbul "^7.0.1" + babel-preset-jest "30.3.0" + chalk "^4.1.2" + graceful-fs "^4.2.11" + slash "^3.0.0" + +babel-plugin-istanbul@^7.0.1: + version "7.0.1" + resolved "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz#d8b518c8ea199364cf84ccc82de89740236daf92" + integrity sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.3" + istanbul-lib-instrument "^6.0.2" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@30.3.0: + version "30.3.0" + resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.3.0.tgz#235ad714a45c18b12566becf439e1c604e277015" + integrity sha512-+TRkByhsws6sfPjVaitzadk1I0F5sPvOVUH5tyTSzhePpsGIVrdeunHSw/C36QeocS95OOk8lunc4rlu5Anwsg== + dependencies: + "@types/babel__core" "^7.20.5" + +babel-preset-current-node-syntax@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz#20730d6cdc7dda5d89401cab10ac6a32067acde6" + integrity sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-import-attributes" "^7.24.7" + "@babel/plugin-syntax-import-meta" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + +babel-preset-jest@30.3.0: + version "30.3.0" + resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.3.0.tgz#21cf3d19a6f5e9924426c879ee0b7f092636d043" + integrity sha512-6ZcUbWHC+dMz2vfzdNwi87Z1gQsLNK2uLuK1Q89R11xdvejcivlYYwDlEv0FHX3VwEXpbBQ9uufB/MUNpZGfhQ== + dependencies: + babel-plugin-jest-hoist "30.3.0" + babel-preset-current-node-syntax "^1.2.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +balanced-match@^4.0.2: + version "4.0.4" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz#bfb10662feed8196a2c62e7c68e17720c274179a" + integrity sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA== + +baseline-browser-mapping@^2.10.12: + version "2.10.20" + resolved "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.20.tgz#7c99b86d43ae9be3810cac515f4675802e1f6b87" + integrity sha512-1AaXxEPfXT+GvTBJFuy4yXVHWJBXa4OdbIebGN/wX5DlsIkU0+wzGnd2lOzokSk51d5LUmqjgBLRLlypLUqInQ== + +binary-extensions@^2.0.0: + version "2.3.0" + resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + +body-parser@^2.2.1: + version "2.2.2" + resolved "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz#1a32cdb966beaf68de50a9dfbe5b58f83cb8890c" + integrity sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA== + dependencies: + bytes "^3.1.2" + content-type "^1.0.5" + debug "^4.4.3" + http-errors "^2.0.0" + iconv-lite "^0.7.0" + on-finished "^2.4.1" + qs "^6.14.1" + raw-body "^3.0.1" + type-is "^2.0.1" + +brace-expansion@^1.1.7: + version "1.1.14" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz#d9de602370d91347cd9ddad1224d4fd701eb348b" + integrity sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.2: + version "2.1.0" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz#4f41a41190216ee36067ec381526fe9539c4f0ae" + integrity sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w== + dependencies: + balanced-match "^1.0.0" + +brace-expansion@^5.0.5: + version "5.0.5" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz#dcc3a37116b79f3e1b46db994ced5d570e930fdb" + integrity sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ== + dependencies: + balanced-match "^4.0.2" + +braces@~3.0.2: + version "3.0.3" + resolved "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +browserslist@^4.24.0: + version "4.28.2" + resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz#f50b65362ef48974ca9f50b3680566d786b811d2" + integrity sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg== + dependencies: + baseline-browser-mapping "^2.10.12" + caniuse-lite "^1.0.30001782" + electron-to-chromium "^1.5.328" + node-releases "^2.0.36" + update-browserslist-db "^1.2.3" + +bs-logger@^0.2.6: + version "0.2.6" + resolved "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +bson@^7.1.1: + version "7.2.0" + resolved "https://registry.npmjs.org/bson/-/bson-7.2.0.tgz#1a496a42d9ff130b9f3ab8efd465459c758c747f" + integrity sha512-YCEo7KjMlbNlyHhz7zAZNDpIpQbd+wOEHJYezv0nMYTn4x31eIUM2yomNNubclAt63dObUzKHWsBLJ9QcZNSnQ== + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +bytes@^3.1.2, bytes@~3.1.2: + version "3.1.2" + resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + +call-bound@^1.0.2: + version "1.0.4" + resolved "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" + integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== + dependencies: + call-bind-apply-helpers "^1.0.2" + get-intrinsic "^1.3.0" + +callsites@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.3.0: + version "6.3.0" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30001782: + version "1.0.30001788" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001788.tgz#31e97d1bfec332b3f2d7eea7781460c97629b3bf" + integrity sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ== + +chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +chokidar@^3.5.2: + version "3.6.0" + resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +ci-info@^4.2.0: + version "4.4.0" + resolved "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz#7d54eff9f54b45b62401c26032696eb59c8bd18c" + integrity sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg== + +cjs-module-lexer@^2.1.0: + version "2.2.0" + resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz#b3ca5101843389259ade7d88c77bd06ce55849ca" + integrity sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ== + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.npmjs.org/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + +collect-v8-coverage@^1.0.2: + version "1.0.3" + resolved "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz#cc1f01eb8d02298cbc9a437c74c70ab4e5210b80" + integrity sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw== + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +component-emitter@^1.3.1: + version "1.3.1" + resolved "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz#ef1d5796f7d93f135ee6fb684340b26403c97d17" + integrity sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +content-disposition@^1.0.0: + version "1.1.0" + resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz#f3db789c752d45564cc7e9e1e0b31790d4a38e17" + integrity sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g== + +content-type@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +cookie-signature@^1.2.1, cookie-signature@^1.2.2: + version "1.2.2" + resolved "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz#57c7fc3cc293acab9fec54d73e15690ebe4a1793" + integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg== + +cookie@^0.7.1: + version "0.7.2" + resolved "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" + integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== + +cookiejar@^2.1.4: + version "2.1.4" + resolved "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz#ee669c1fea2cf42dc31585469d193fef0d65771b" + integrity sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw== + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +cross-spawn@^7.0.3, cross-spawn@^7.0.6: + version "7.0.6" + resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +debug@^4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.7, debug@^4.4.0, debug@^4.4.3: + version "4.4.3" + resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + +dedent@^1.6.0: + version "1.7.2" + resolved "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz#34e2264ab538301e27cf7b07bf2369c19baa8dd9" + integrity sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA== + +deepmerge@^4.3.1: + version "4.3.1" + resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +depd@^2.0.0, depd@~2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +detect-newline@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +dezalgo@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz#751235260469084c132157dfa857f386d4c33d81" + integrity sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig== + dependencies: + asap "^2.0.0" + wrappy "1" + +diff@^4.0.1: + version "4.0.4" + resolved "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz#7a6dbfda325f25f07517e9b518f897c08332e07d" + integrity sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ== + +dotenv@^17.4.2: + version "17.4.2" + resolved "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz#c07e54a746e11eba021dd9e1047ced5afdc1c034" + integrity sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw== + +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +electron-to-chromium@^1.5.328: + version "1.5.341" + resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.341.tgz#427b826ac41d406162b7700e6032c2b9cc4ecc5c" + integrity sha512-1sZTssferjgDgaqRTc0ieP+ozzpOy7LQTPTtEW3yQFn4+ORdIAZWV5BthXPyHF7YqLvFJCUPhNhdAJQYlYUgiw== + +emittery@^0.13.1: + version "0.13.1" + resolved "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" + integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + +encodeurl@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" + integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== + +error-ex@^1.3.1: + version "1.3.4" + resolved "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz#b3a8d8bb6f92eecc1629e3e27d3c8607a8a32414" + integrity sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ== + dependencies: + is-arrayish "^0.2.1" + +es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + +es-set-tostringtag@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" + integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== + dependencies: + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + +escalade@^3.1.1, escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +escape-html@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +etag@^1.8.1: + version "1.8.1" + resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + +execa@^5.1.1: + version "5.1.1" + resolved "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +exit-x@^0.2.2: + version "0.2.2" + resolved "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz#1f9052de3b8d99a696b10dad5bced9bdd5c3aa64" + integrity sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ== + +expect@30.3.0, expect@^30.0.0: + version "30.3.0" + resolved "https://registry.npmjs.org/expect/-/expect-30.3.0.tgz#1b82111517d1ab030f3db0cf1b4061c8aa644f61" + integrity sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q== + dependencies: + "@jest/expect-utils" "30.3.0" + "@jest/get-type" "30.1.0" + jest-matcher-utils "30.3.0" + jest-message-util "30.3.0" + jest-mock "30.3.0" + jest-util "30.3.0" + +express@^5.2.1: + version "5.2.1" + resolved "https://registry.npmjs.org/express/-/express-5.2.1.tgz#8f21d15b6d327f92b4794ecf8cb08a72f956ac04" + integrity sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw== + dependencies: + accepts "^2.0.0" + body-parser "^2.2.1" + content-disposition "^1.0.0" + content-type "^1.0.5" + cookie "^0.7.1" + cookie-signature "^1.2.1" + debug "^4.4.0" + depd "^2.0.0" + encodeurl "^2.0.0" + escape-html "^1.0.3" + etag "^1.8.1" + finalhandler "^2.1.0" + fresh "^2.0.0" + http-errors "^2.0.0" + merge-descriptors "^2.0.0" + mime-types "^3.0.0" + on-finished "^2.4.1" + once "^1.4.0" + parseurl "^1.3.3" + proxy-addr "^2.0.7" + qs "^6.14.0" + range-parser "^1.2.1" + router "^2.2.0" + send "^1.1.0" + serve-static "^2.2.0" + statuses "^2.0.1" + type-is "^2.0.1" + vary "^1.1.2" + +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-safe-stringify@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" + integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== + +fb-watchman@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== + dependencies: + bser "2.1.1" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +finalhandler@^2.1.0: + version "2.1.1" + resolved "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz#a2c517a6559852bcdb06d1f8bd7f51b68fad8099" + integrity sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA== + dependencies: + debug "^4.4.0" + encodeurl "^2.0.0" + escape-html "^1.0.3" + on-finished "^2.4.1" + parseurl "^1.3.3" + statuses "^2.0.1" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +foreground-child@^3.1.0: + version "3.3.1" + resolved "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz#32e8e9ed1b68a3497befb9ac2b6adf92a638576f" + integrity sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw== + dependencies: + cross-spawn "^7.0.6" + signal-exit "^4.0.1" + +form-data@^4.0.0, form-data@^4.0.5: + version "4.0.5" + resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz#b49e48858045ff4cbf6b03e1805cebcad3679053" + integrity sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + es-set-tostringtag "^2.1.0" + hasown "^2.0.2" + mime-types "^2.1.12" + +formidable@^3.5.4: + version "3.5.4" + resolved "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz#ac9a593b951e829b3298f21aa9a2243932f32ed9" + integrity sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug== + dependencies: + "@paralleldrive/cuid2" "^2.2.2" + dezalgo "^1.0.4" + once "^1.4.0" + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fresh@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz#8dd7df6a1b3a1b3a5cf186c05a5dd267622635a4" + integrity sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@^2.3.3, fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + function-bind "^1.1.2" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob@^10.5.0: + version "10.5.0" + resolved "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz#8ec0355919cd3338c28428a23d4f24ecc5fe738c" + integrity sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg== + dependencies: + foreground-child "^3.1.0" + jackspeak "^3.1.2" + minimatch "^9.0.4" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^1.11.1" + +glob@^7.1.4: + version "7.2.3" + resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + +graceful-fs@^4.2.11: + version "4.2.11" + resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +handlebars@^4.7.9: + version "4.7.9" + resolved "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz#6f139082ab58dc4e5a0e51efe7db5ae890d56a0f" + integrity sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ== + dependencies: + minimist "^1.2.5" + neo-async "^2.6.2" + source-map "^0.6.1" + wordwrap "^1.0.0" + optionalDependencies: + uglify-js "^3.1.4" + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-symbols@^1.0.3, has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + +has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + +hasown@^2.0.2: + version "2.0.3" + resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz#5e5c2b15b60370a4c7930c383dfb76bf17bc403c" + integrity sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg== + dependencies: + function-bind "^1.1.2" + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +http-errors@^2.0.0, http-errors@^2.0.1, http-errors@~2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz#36d2f65bc909c8790018dd36fb4d93da6caae06b" + integrity sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ== + dependencies: + depd "~2.0.0" + inherits "~2.0.4" + setprototypeof "~1.2.0" + statuses "~2.0.2" + toidentifier "~1.0.1" + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +iconv-lite@^0.7.0, iconv-lite@~0.7.0: + version "0.7.2" + resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz#d0bdeac3f12b4835b7359c2ad89c422a4d1cc72e" + integrity sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +ignore-by-default@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" + integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== + +import-local@^3.2.0: + version "3.2.0" + resolved "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" + integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@~2.0.4: + version "2.0.4" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-promise@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz#42ff9f84206c1991d26debf520dd5c01042dd2f3" + integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.2" + resolved "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== + +istanbul-lib-instrument@^6.0.0, istanbul-lib-instrument@^6.0.2: + version "6.0.3" + resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" + integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== + dependencies: + "@babel/core" "^7.23.9" + "@babel/parser" "^7.23.9" + "@istanbuljs/schema" "^0.1.3" + istanbul-lib-coverage "^3.2.0" + semver "^7.5.4" + +istanbul-lib-report@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^4.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^5.0.0: + version "5.0.6" + resolved "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz#acaef948df7747c8eb5fbf1265cb980f6353a441" + integrity sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A== + dependencies: + "@jridgewell/trace-mapping" "^0.3.23" + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + +istanbul-reports@^3.1.3: + version "3.2.0" + resolved "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz#cb4535162b5784aa623cee21a7252cf2c807ac93" + integrity sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jackspeak@^3.1.2: + version "3.4.3" + resolved "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" + integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + +jest-changed-files@30.3.0: + version "30.3.0" + resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.3.0.tgz#055849df695f9a9fcde0ae44024f815bbc627f3a" + integrity sha512-B/7Cny6cV5At6M25EWDgf9S617lHivamL8vl6KEpJqkStauzcG4e+WPfDgMMF+H4FVH4A2PLRyvgDJan4441QA== + dependencies: + execa "^5.1.1" + jest-util "30.3.0" + p-limit "^3.1.0" + +jest-circus@30.3.0: + version "30.3.0" + resolved "https://registry.npmjs.org/jest-circus/-/jest-circus-30.3.0.tgz#153614c11ab35867f371bd93496ecb9690b92077" + integrity sha512-PyXq5szeSfR/4f1lYqCmmQjh0vqDkURUYi9N6whnHjlRz4IUQfMcXkGLeEoiJtxtyPqgUaUUfyQlApXWBSN1RA== + dependencies: + "@jest/environment" "30.3.0" + "@jest/expect" "30.3.0" + "@jest/test-result" "30.3.0" + "@jest/types" "30.3.0" + "@types/node" "*" + chalk "^4.1.2" + co "^4.6.0" + dedent "^1.6.0" + is-generator-fn "^2.1.0" + jest-each "30.3.0" + jest-matcher-utils "30.3.0" + jest-message-util "30.3.0" + jest-runtime "30.3.0" + jest-snapshot "30.3.0" + jest-util "30.3.0" + p-limit "^3.1.0" + pretty-format "30.3.0" + pure-rand "^7.0.0" + slash "^3.0.0" + stack-utils "^2.0.6" + +jest-cli@30.3.0: + version "30.3.0" + resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-30.3.0.tgz#5ed75a337f486a1f1c5acbb2de8acddb106ead6c" + integrity sha512-l6Tqx+j1fDXJEW5bqYykDQQ7mQg+9mhWXtnj+tQZrTWYHyHoi6Be8HPumDSA+UiX2/2buEgjA58iJzdj146uCw== + dependencies: + "@jest/core" "30.3.0" + "@jest/test-result" "30.3.0" + "@jest/types" "30.3.0" + chalk "^4.1.2" + exit-x "^0.2.2" + import-local "^3.2.0" + jest-config "30.3.0" + jest-util "30.3.0" + jest-validate "30.3.0" + yargs "^17.7.2" + +jest-config@30.3.0: + version "30.3.0" + resolved "https://registry.npmjs.org/jest-config/-/jest-config-30.3.0.tgz#b969e0aaaf5964419e62953bb712c16d15972425" + integrity sha512-WPMAkMAtNDY9P/oKObtsRG/6KTrhtgPJoBTmk20uDn4Uy6/3EJnnaZJre/FMT1KVRx8cve1r7/FlMIOfRVWL4w== + dependencies: + "@babel/core" "^7.27.4" + "@jest/get-type" "30.1.0" + "@jest/pattern" "30.0.1" + "@jest/test-sequencer" "30.3.0" + "@jest/types" "30.3.0" + babel-jest "30.3.0" + chalk "^4.1.2" + ci-info "^4.2.0" + deepmerge "^4.3.1" + glob "^10.5.0" + graceful-fs "^4.2.11" + jest-circus "30.3.0" + jest-docblock "30.2.0" + jest-environment-node "30.3.0" + jest-regex-util "30.0.1" + jest-resolve "30.3.0" + jest-runner "30.3.0" + jest-util "30.3.0" + jest-validate "30.3.0" + parse-json "^5.2.0" + pretty-format "30.3.0" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@30.3.0: + version "30.3.0" + resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-30.3.0.tgz#e0a4c84ef350ffd790ffd5b0016acabeecf5f759" + integrity sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ== + dependencies: + "@jest/diff-sequences" "30.3.0" + "@jest/get-type" "30.1.0" + chalk "^4.1.2" + pretty-format "30.3.0" + +jest-docblock@30.2.0: + version "30.2.0" + resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz#42cd98d69f887e531c7352309542b1ce4ee10256" + integrity sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA== + dependencies: + detect-newline "^3.1.0" + +jest-each@30.3.0: + version "30.3.0" + resolved "https://registry.npmjs.org/jest-each/-/jest-each-30.3.0.tgz#faa7229bf7a9fa6426dc604057a7d2a173493b1e" + integrity sha512-V8eMndg/aZ+3LnCJgSm13IxS5XSBM22QSZc9BtPK8Dek6pm+hfUNfwBdvsB3d342bo1q7wnSkC38zjX259qZNA== + dependencies: + "@jest/get-type" "30.1.0" + "@jest/types" "30.3.0" + chalk "^4.1.2" + jest-util "30.3.0" + pretty-format "30.3.0" + +jest-environment-node@30.3.0: + version "30.3.0" + resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.3.0.tgz#aa8a57c5d0c4af0f8b1f7403ba737fec6b3aabbe" + integrity sha512-4i6HItw/JSiJVsC5q0hnKIe/hbYfZLVG9YJ/0pU9Hz2n/9qZe3Rhn5s5CUZA5ORZlcdT/vmAXRMyONXJwPrmYQ== + dependencies: + "@jest/environment" "30.3.0" + "@jest/fake-timers" "30.3.0" + "@jest/types" "30.3.0" + "@types/node" "*" + jest-mock "30.3.0" + jest-util "30.3.0" + jest-validate "30.3.0" + +jest-haste-map@30.3.0: + version "30.3.0" + resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.3.0.tgz#1ea6843e6e45c077d91270666a4fcba958c24cd5" + integrity sha512-mMi2oqG4KRU0R9QEtscl87JzMXfUhbKaFqOxmjb2CKcbHcUGFrJCBWHmnTiUqi6JcnzoBlO4rWfpdl2k/RfLCA== + dependencies: + "@jest/types" "30.3.0" + "@types/node" "*" + anymatch "^3.1.3" + fb-watchman "^2.0.2" + graceful-fs "^4.2.11" + jest-regex-util "30.0.1" + jest-util "30.3.0" + jest-worker "30.3.0" + picomatch "^4.0.3" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.3" + +jest-leak-detector@30.3.0: + version "30.3.0" + resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.3.0.tgz#a695a851e353f517a554a2f5c91c2742fc131c98" + integrity sha512-cuKmUUGIjfXZAiGJ7TbEMx0bcqNdPPI6P1V+7aF+m/FUJqFDxkFR4JqkTu8ZOiU5AaX/x0hZ20KaaIPXQzbMGQ== + dependencies: + "@jest/get-type" "30.1.0" + pretty-format "30.3.0" + +jest-matcher-utils@30.3.0: + version "30.3.0" + resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.3.0.tgz#d6c739fec1ecd33809f2d2b1348f6ab01d2f2493" + integrity sha512-HEtc9uFQgaUHkC7nLSlQL3Tph4Pjxt/yiPvkIrrDCt9jhoLIgxaubo1G+CFOnmHYMxHwwdaSN7mkIFs6ZK8OhA== + dependencies: + "@jest/get-type" "30.1.0" + chalk "^4.1.2" + jest-diff "30.3.0" + pretty-format "30.3.0" + +jest-message-util@30.3.0: + version "30.3.0" + resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.3.0.tgz#4d723544d36890ba862ac3961db52db5b0d1ba39" + integrity sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw== + dependencies: + "@babel/code-frame" "^7.27.1" + "@jest/types" "30.3.0" + "@types/stack-utils" "^2.0.3" + chalk "^4.1.2" + graceful-fs "^4.2.11" + picomatch "^4.0.3" + pretty-format "30.3.0" + slash "^3.0.0" + stack-utils "^2.0.6" + +jest-mock@30.3.0: + version "30.3.0" + resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-30.3.0.tgz#e0fa4184a596a6c4fdec53d4f412158418923747" + integrity sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog== + dependencies: + "@jest/types" "30.3.0" + "@types/node" "*" + jest-util "30.3.0" + +jest-pnp-resolver@^1.2.3: + version "1.2.3" + resolved "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" + integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== + +jest-regex-util@30.0.1: + version "30.0.1" + resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz#f17c1de3958b67dfe485354f5a10093298f2a49b" + integrity sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA== + +jest-resolve-dependencies@30.3.0: + version "30.3.0" + resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.3.0.tgz#4d638c9f0d93a62a6ed25dec874bfd7e756c8ce5" + integrity sha512-9ev8s3YN6Hsyz9LV75XUwkCVFlwPbaFn6Wp75qnI0wzAINYWY8Fb3+6y59Rwd3QaS3kKXffHXsZMziMavfz/nw== + dependencies: + jest-regex-util "30.0.1" + jest-snapshot "30.3.0" + +jest-resolve@30.3.0: + version "30.3.0" + resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.3.0.tgz#b7bee9927279805b1b50715d2170a545553b87ff" + integrity sha512-NRtTAHQlpd15F9rUR36jqwelbrDV/dY4vzNte3S2kxCKUJRYNd5/6nTSbYiak1VX5g8IoFF23Uj5TURkUW8O5g== + dependencies: + chalk "^4.1.2" + graceful-fs "^4.2.11" + jest-haste-map "30.3.0" + jest-pnp-resolver "^1.2.3" + jest-util "30.3.0" + jest-validate "30.3.0" + slash "^3.0.0" + unrs-resolver "^1.7.11" + +jest-runner@30.3.0: + version "30.3.0" + resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-30.3.0.tgz#fa970fc4e45d418ad7e7d581b24cac7af5944cb7" + integrity sha512-gDv6C9LGKWDPLia9TSzZwf4h3kMQCqyTpq+95PODnTRDO0g9os48XIYYkS6D236vjpBir2fF63YmJFtqkS5Duw== + dependencies: + "@jest/console" "30.3.0" + "@jest/environment" "30.3.0" + "@jest/test-result" "30.3.0" + "@jest/transform" "30.3.0" + "@jest/types" "30.3.0" + "@types/node" "*" + chalk "^4.1.2" + emittery "^0.13.1" + exit-x "^0.2.2" + graceful-fs "^4.2.11" + jest-docblock "30.2.0" + jest-environment-node "30.3.0" + jest-haste-map "30.3.0" + jest-leak-detector "30.3.0" + jest-message-util "30.3.0" + jest-resolve "30.3.0" + jest-runtime "30.3.0" + jest-util "30.3.0" + jest-watcher "30.3.0" + jest-worker "30.3.0" + p-limit "^3.1.0" + source-map-support "0.5.13" + +jest-runtime@30.3.0: + version "30.3.0" + resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.3.0.tgz#1a9bec7a9b68db12dfe4136bbe41ab883ea2c996" + integrity sha512-CgC+hIBJbuh78HEffkhNKcbXAytQViplcl8xupqeIWyKQF50kCQA8J7GeJCkjisC6hpnC9Muf8jV5RdtdFbGng== + dependencies: + "@jest/environment" "30.3.0" + "@jest/fake-timers" "30.3.0" + "@jest/globals" "30.3.0" + "@jest/source-map" "30.0.1" + "@jest/test-result" "30.3.0" + "@jest/transform" "30.3.0" + "@jest/types" "30.3.0" + "@types/node" "*" + chalk "^4.1.2" + cjs-module-lexer "^2.1.0" + collect-v8-coverage "^1.0.2" + glob "^10.5.0" + graceful-fs "^4.2.11" + jest-haste-map "30.3.0" + jest-message-util "30.3.0" + jest-mock "30.3.0" + jest-regex-util "30.0.1" + jest-resolve "30.3.0" + jest-snapshot "30.3.0" + jest-util "30.3.0" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-snapshot@30.3.0: + version "30.3.0" + resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.3.0.tgz#6e7ea75069dda86e36311a0f73189e830d4f51ad" + integrity sha512-f14c7atpb4O2DeNhwcvS810Y63wEn8O1HqK/luJ4F6M4NjvxmAKQwBUWjbExUtMxWJQ0wVgmCKymeJK6NZMnfQ== + dependencies: + "@babel/core" "^7.27.4" + "@babel/generator" "^7.27.5" + "@babel/plugin-syntax-jsx" "^7.27.1" + "@babel/plugin-syntax-typescript" "^7.27.1" + "@babel/types" "^7.27.3" + "@jest/expect-utils" "30.3.0" + "@jest/get-type" "30.1.0" + "@jest/snapshot-utils" "30.3.0" + "@jest/transform" "30.3.0" + "@jest/types" "30.3.0" + babel-preset-current-node-syntax "^1.2.0" + chalk "^4.1.2" + expect "30.3.0" + graceful-fs "^4.2.11" + jest-diff "30.3.0" + jest-matcher-utils "30.3.0" + jest-message-util "30.3.0" + jest-util "30.3.0" + pretty-format "30.3.0" + semver "^7.7.2" + synckit "^0.11.8" + +jest-util@30.3.0: + version "30.3.0" + resolved "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz#95a4fbacf2dac20e768e2f1744b70519f2ba7980" + integrity sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg== + dependencies: + "@jest/types" "30.3.0" + "@types/node" "*" + chalk "^4.1.2" + ci-info "^4.2.0" + graceful-fs "^4.2.11" + picomatch "^4.0.3" + +jest-validate@30.3.0: + version "30.3.0" + resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-30.3.0.tgz#215e11b8fcc5e2ca4b99ea5d730a5b4c969e4355" + integrity sha512-I/xzC8h5G+SHCb2P2gWkJYrNiTbeL47KvKeW5EzplkyxzBRBw1ssSHlI/jXec0ukH2q7x2zAWQm7015iusg62Q== + dependencies: + "@jest/get-type" "30.1.0" + "@jest/types" "30.3.0" + camelcase "^6.3.0" + chalk "^4.1.2" + leven "^3.1.0" + pretty-format "30.3.0" + +jest-watcher@30.3.0: + version "30.3.0" + resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.3.0.tgz#3afa1af355b9fe80f0261eb8a23981a315858596" + integrity sha512-PJ1d9ThtTR8aMiBWUdcownq9mDdLXsQzJayTk4kmaBRHKvwNQn+ANveuhEBUyNI2hR1TVhvQ8D5kHubbzBHR/w== + dependencies: + "@jest/test-result" "30.3.0" + "@jest/types" "30.3.0" + "@types/node" "*" + ansi-escapes "^4.3.2" + chalk "^4.1.2" + emittery "^0.13.1" + jest-util "30.3.0" + string-length "^4.0.2" + +jest-worker@30.3.0: + version "30.3.0" + resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-30.3.0.tgz#ae4dc1f1d93d0cba1415624fcedaec40ea764f14" + integrity sha512-DrCKkaQwHexjRUFTmPzs7sHQe0TSj9nvDALKGdwmK5mW9v7j90BudWirKAJHt3QQ9Dhrg1F7DogPzhChppkJpQ== + dependencies: + "@types/node" "*" + "@ungap/structured-clone" "^1.3.0" + jest-util "30.3.0" + merge-stream "^2.0.0" + supports-color "^8.1.1" + +jest@^30.3.0: + version "30.3.0" + resolved "https://registry.npmjs.org/jest/-/jest-30.3.0.tgz#6460b889dd805e9677400505f16f1d9b14c285a3" + integrity sha512-AkXIIFcaazymvey2i/+F94XRnM6TsVLZDhBMLsd1Sf/W0wzsvvpjeyUrCZD6HGG4SDYPgDJDBKeiJTBb10WzMg== + dependencies: + "@jest/core" "30.3.0" + "@jest/types" "30.3.0" + import-local "^3.2.0" + jest-cli "30.3.0" + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.2" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz#77485ce1dd7f33c061fd1b16ecea23b55fcb04b0" + integrity sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsesc@^3.0.2: + version "3.1.0" + resolved "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +kareem@3.3.0: + version "3.3.0" + resolved "https://registry.npmjs.org/kareem/-/kareem-3.3.0.tgz#174048bd00e2b4a8fc2504f0f53c315b806894da" + integrity sha512-kpSuLD3/7RenBnjnJdOHXCKC8dTd1JzeOiJhN0necWWci6cC+qX+VuwPnMVgb+a4+KNJSfgqahpnfWaeDXCimw== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + +lru-cache@^10.2.0: + version "10.4.3" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + dependencies: + semver "^7.5.3" + +make-error@^1.1.1, make-error@^1.3.6: + version "1.3.6" + resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + +media-typer@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz#6ab74b8f2d3320f2064b2a87a38e7931ff3a5561" + integrity sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw== + +memory-pager@^1.0.2: + version "1.5.0" + resolved "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5" + integrity sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg== + +merge-descriptors@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz#ea922f660635a2249ee565e0449f951e6b603808" + integrity sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g== + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +methods@^1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-db@^1.54.0: + version "1.54.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" + integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime-types@^3.0.0, mime-types@^3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz#39002d4182575d5af036ffa118100f2524b2e2ab" + integrity sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A== + dependencies: + mime-db "^1.54.0" + +mime@2.6.0: + version "2.6.0" + resolved "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" + integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimatch@^10.2.1: + version "10.2.5" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz#bd48687a0be38ed2961399105600f832095861d1" + integrity sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg== + dependencies: + brace-expansion "^5.0.5" + +minimatch@^3.0.4, minimatch@^3.1.1: + version "3.1.5" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz#580c88f8d5445f2bd6aa8f3cadefa0de79fbd69e" + integrity sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^9.0.4: + version "9.0.9" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz#9b0cb9fcb78087f6fd7eababe2511c4d3d60574e" + integrity sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg== + dependencies: + brace-expansion "^2.0.2" + +minimist@^1.2.5: + version "1.2.8" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: + version "7.1.3" + resolved "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz#79389b4eb1bb2d003a9bba87d492f2bd37bdc65b" + integrity sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A== + +mongodb-connection-string-url@^7.0.0: + version "7.0.1" + resolved "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-7.0.1.tgz#347b664cd9e6ddff10d5c1c6010d6d8dbfe9272d" + integrity sha512-h0AZ9A7IDVwwHyMxmdMXKy+9oNlF0zFoahHiX3vQ8e3KFcSP3VmsmfvtRSuLPxmyv2vjIDxqty8smTgie/SNRQ== + dependencies: + "@types/whatwg-url" "^13.0.0" + whatwg-url "^14.1.0" + +mongodb@~7.1: + version "7.1.1" + resolved "https://registry.npmjs.org/mongodb/-/mongodb-7.1.1.tgz#d08479221b81ba66f1f4a858af621edaaa598d95" + integrity sha512-067DXiMjcpYQl6bGjWQoTUEE9UoRViTtKFcoqX7z08I+iDZv/emH1g8XEFiO3qiDfXAheT5ozl1VffDTKhIW/w== + dependencies: + "@mongodb-js/saslprep" "^1.3.0" + bson "^7.1.1" + mongodb-connection-string-url "^7.0.0" + +mongoose@^9.5.0: + version "9.5.0" + resolved "https://registry.npmjs.org/mongoose/-/mongoose-9.5.0.tgz#180458f89977ebdf33652deabb6c127373b25ca1" + integrity sha512-B4blGFkFL1s0G24URuMvx0qTlx+gRVLmfO7WcSz8NcmW/XHEJ3G69capdyW1iRsGKiycp1tkwKHnxHbnwjwmPw== + dependencies: + kareem "3.3.0" + mongodb "~7.1" + mpath "0.9.0" + mquery "6.0.0" + ms "2.1.3" + sift "17.1.3" + +mpath@0.9.0: + version "0.9.0" + resolved "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz#0c122fe107846e31fc58c75b09c35514b3871904" + integrity sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew== + +mquery@6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/mquery/-/mquery-6.0.0.tgz#ef6d744619ff13368c1b137d09a7adc5530fce9e" + integrity sha512-b2KQNsmgtkscfeDgkYMcWGn9vZI9YoXh802VDEwE6qc50zxBFQ0Oo8ROkawbPAsXCY1/Z1yp0MagqsZStPWJjw== + +ms@2.1.3, ms@^2.1.3: + version "2.1.3" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +napi-postinstall@^0.3.0: + version "0.3.4" + resolved "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz#7af256d6588b5f8e952b9190965d6b019653bbb9" + integrity sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +negotiator@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a" + integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg== + +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + +node-releases@^2.0.36: + version "2.0.37" + resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz#9bd4f10b77ba39c2b9402d4e8399c482a797f671" + integrity sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg== + +nodemon@^3.1.14: + version "3.1.14" + resolved "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz#8487ca379c515301d221ec007f27f24ecafa2b51" + integrity sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw== + dependencies: + chokidar "^3.5.2" + debug "^4" + ignore-by-default "^1.0.1" + minimatch "^10.2.1" + pstree.remy "^1.1.8" + semver "^7.5.3" + simple-update-notifier "^2.0.0" + supports-color "^5.5.0" + touch "^3.1.0" + undefsafe "^2.0.5" + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +object-inspect@^1.13.3, object-inspect@^1.13.4: + version "1.13.4" + resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" + integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== + +on-finished@^2.4.1: + version "2.4.1" + resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +once@^1.3.0, once@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +package-json-from-dist@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" + integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== + +parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +parseurl@^1.3.3: + version "1.3.3" + resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-scurry@^1.11.1: + version "1.11.1" + resolved "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" + integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== + dependencies: + lru-cache "^10.2.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + +path-to-regexp@^8.0.0: + version "8.4.2" + resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz#795c420c4f7ca45c5b887366f622ee0c9852cccd" + integrity sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA== + +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +picomatch@^2.0.4, picomatch@^2.2.1: + version "2.3.2" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz#5a942915e26b372dc0f0e6753149a16e6b1c5601" + integrity sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA== + +picomatch@^4.0.3: + version "4.0.4" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz#fd6f5e00a143086e074dffe4c924b8fb293b0589" + integrity sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A== + +pirates@^4.0.7: + version "4.0.7" + resolved "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz#643b4a18c4257c8a65104b73f3049ce9a0a15e22" + integrity sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +pretty-format@30.3.0, pretty-format@^30.0.0: + version "30.3.0" + resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz#e977eed4bcd1b6195faed418af8eac68b9ea1f29" + integrity sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ== + dependencies: + "@jest/schemas" "30.0.5" + ansi-styles "^5.2.0" + react-is "^18.3.1" + +proxy-addr@^2.0.7: + version "2.0.7" + resolved "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +pstree.remy@^1.1.8: + version "1.1.8" + resolved "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" + integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== + +punycode@^2.3.1: + version "2.3.1" + resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +pure-rand@^7.0.0: + version "7.0.1" + resolved "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz#6f53a5a9e3e4a47445822af96821ca509ed37566" + integrity sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ== + +qs@^6.14.0, qs@^6.14.1: + version "6.15.1" + resolved "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz#bdb55aed06bfac257a90c44a446a73fba5575c8f" + integrity sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg== + dependencies: + side-channel "^1.1.0" + +range-parser@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@^3.0.1: + version "3.0.2" + resolved "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz#3e3ada5ae5568f9095d84376fd3a49b8fb000a51" + integrity sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA== + dependencies: + bytes "~3.1.2" + http-errors "~2.0.1" + iconv-lite "~0.7.0" + unpipe "~1.0.0" + +react-is@^18.3.1: + version "18.3.1" + resolved "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +router@^2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/router/-/router-2.2.0.tgz#019be620b711c87641167cc79b99090f00b146ef" + integrity sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ== + dependencies: + debug "^4.4.0" + depd "^2.0.0" + is-promise "^4.0.0" + parseurl "^1.3.3" + path-to-regexp "^8.0.0" + +"safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +semver@^6.3.1: + version "6.3.1" + resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.5.3, semver@^7.5.4, semver@^7.7.2, semver@^7.7.4: + version "7.7.4" + resolved "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz#28464e36060e991fa7a11d0279d2d3f3b57a7e8a" + integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA== + +send@^1.1.0, send@^1.2.0: + version "1.2.1" + resolved "https://registry.npmjs.org/send/-/send-1.2.1.tgz#9eab743b874f3550f40a26867bf286ad60d3f3ed" + integrity sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ== + dependencies: + debug "^4.4.3" + encodeurl "^2.0.0" + escape-html "^1.0.3" + etag "^1.8.1" + fresh "^2.0.0" + http-errors "^2.0.1" + mime-types "^3.0.2" + ms "^2.1.3" + on-finished "^2.4.1" + range-parser "^1.2.1" + statuses "^2.0.2" + +serve-static@^2.2.0: + version "2.2.1" + resolved "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz#7f186a4a4e5f5b663ad7a4294ff1bf37cf0e98a9" + integrity sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw== + dependencies: + encodeurl "^2.0.0" + escape-html "^1.0.3" + parseurl "^1.3.3" + send "^1.2.0" + +setprototypeof@~1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +side-channel-list@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz#c2e0b5a14a540aebee3bbc6c3f8666cc9b509127" + integrity sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.4" + +side-channel-map@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" + integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + +side-channel-weakmap@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" + integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + side-channel-map "^1.0.1" + +side-channel@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" + integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + side-channel-list "^1.0.0" + side-channel-map "^1.0.1" + side-channel-weakmap "^1.0.2" + +sift@17.1.3: + version "17.1.3" + resolved "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz#9d2000d4d41586880b0079b5183d839c7a142bf7" + integrity sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ== + +signal-exit@^3.0.3: + version "3.0.7" + resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + +simple-update-notifier@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz#d70b92bdab7d6d90dfd73931195a30b6e3d7cebb" + integrity sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w== + dependencies: + semver "^7.5.3" + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sparse-bitfield@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz#ff4ae6e68656056ba4b3e792ab3334d38273ca11" + integrity sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ== + dependencies: + memory-pager "^1.0.2" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +stack-utils@^2.0.6: + version "2.0.6" + resolved "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + +statuses@^2.0.1, statuses@^2.0.2, statuses@~2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz#8f75eecef765b5e1cfcdc080da59409ed424e382" + integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw== + +string-length@^4.0.2: + version "4.0.2" + resolved "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^7.0.1: + version "7.2.0" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz#d22a269522836a627af8d04b5c3fd2c7fa3e32e3" + integrity sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w== + dependencies: + ansi-regex "^6.2.2" + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +superagent@^10.3.0: + version "10.3.0" + resolved "https://registry.npmjs.org/superagent/-/superagent-10.3.0.tgz#ff1e39e7976b63f8084291d65f5bfbbbbd156989" + integrity sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ== + dependencies: + component-emitter "^1.3.1" + cookiejar "^2.1.4" + debug "^4.3.7" + fast-safe-stringify "^2.1.1" + form-data "^4.0.5" + formidable "^3.5.4" + methods "^1.1.2" + mime "2.6.0" + qs "^6.14.1" + +supertest@^7.2.2: + version "7.2.2" + resolved "https://registry.npmjs.org/supertest/-/supertest-7.2.2.tgz#dac3ee25a2aa59942a7f641e50c838a7c8819204" + integrity sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA== + dependencies: + cookie-signature "^1.2.2" + methods "^1.1.2" + superagent "^10.3.0" + +supports-color@^5.5.0: + version "5.5.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.1.1: + version "8.1.1" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +synckit@^0.11.8: + version "0.11.12" + resolved "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz#abe74124264fbc00a48011b0d98bdc1cffb64a7b" + integrity sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ== + dependencies: + "@pkgr/core" "^0.2.9" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +toidentifier@~1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +touch@^3.1.0: + version "3.1.1" + resolved "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz#097a23d7b161476435e5c1344a95c0f75b4a5694" + integrity sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA== + +tr46@^5.1.0: + version "5.1.1" + resolved "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz#96ae867cddb8fdb64a49cc3059a8d428bcf238ca" + integrity sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw== + dependencies: + punycode "^2.3.1" + +ts-jest@^29.4.9: + version "29.4.9" + resolved "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.9.tgz#47dc33d0f5c36bddcedd16afefae285e0b049d2d" + integrity sha512-LTb9496gYPMCqjeDLdPrKuXtncudeV1yRZnF4Wo5l3SFi0RYEnYRNgMrFIdg+FHvfzjCyQk1cLncWVqiSX+EvQ== + dependencies: + bs-logger "^0.2.6" + fast-json-stable-stringify "^2.1.0" + handlebars "^4.7.9" + json5 "^2.2.3" + lodash.memoize "^4.1.2" + make-error "^1.3.6" + semver "^7.7.4" + type-fest "^4.41.0" + yargs-parser "^21.1.1" + +ts-node@^10.9.2: + version "10.9.2" + resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" + integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + +tslib@^2.4.0: + version "2.8.1" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +type-fest@^4.41.0: + version "4.41.0" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz#6ae1c8e5731273c2bf1f58ad39cbae2c91a46c58" + integrity sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA== + +type-is@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz#64f6cf03f92fce4015c2b224793f6bdd4b068c97" + integrity sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw== + dependencies: + content-type "^1.0.5" + media-typer "^1.1.0" + mime-types "^3.0.0" + +typescript@^6.0.3: + version "6.0.3" + resolved "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz#90251dc007916e972786cb94d74d15b185577d21" + integrity sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw== + +uglify-js@^3.1.4: + version "3.19.3" + resolved "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz#82315e9bbc6f2b25888858acd1fff8441035b77f" + integrity sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ== + +undefsafe@^2.0.5: + version "2.0.5" + resolved "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" + integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== + +undici-types@~7.19.0: + version "7.19.2" + resolved "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz#1b67fc26d0f157a0cba3a58a5b5c1e2276b8ba2a" + integrity sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg== + +unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +unrs-resolver@^1.7.11: + version "1.11.1" + resolved "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz#be9cd8686c99ef53ecb96df2a473c64d304048a9" + integrity sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg== + dependencies: + napi-postinstall "^0.3.0" + optionalDependencies: + "@unrs/resolver-binding-android-arm-eabi" "1.11.1" + "@unrs/resolver-binding-android-arm64" "1.11.1" + "@unrs/resolver-binding-darwin-arm64" "1.11.1" + "@unrs/resolver-binding-darwin-x64" "1.11.1" + "@unrs/resolver-binding-freebsd-x64" "1.11.1" + "@unrs/resolver-binding-linux-arm-gnueabihf" "1.11.1" + "@unrs/resolver-binding-linux-arm-musleabihf" "1.11.1" + "@unrs/resolver-binding-linux-arm64-gnu" "1.11.1" + "@unrs/resolver-binding-linux-arm64-musl" "1.11.1" + "@unrs/resolver-binding-linux-ppc64-gnu" "1.11.1" + "@unrs/resolver-binding-linux-riscv64-gnu" "1.11.1" + "@unrs/resolver-binding-linux-riscv64-musl" "1.11.1" + "@unrs/resolver-binding-linux-s390x-gnu" "1.11.1" + "@unrs/resolver-binding-linux-x64-gnu" "1.11.1" + "@unrs/resolver-binding-linux-x64-musl" "1.11.1" + "@unrs/resolver-binding-wasm32-wasi" "1.11.1" + "@unrs/resolver-binding-win32-arm64-msvc" "1.11.1" + "@unrs/resolver-binding-win32-ia32-msvc" "1.11.1" + "@unrs/resolver-binding-win32-x64-msvc" "1.11.1" + +update-browserslist-db@^1.2.3: + version "1.2.3" + resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d" + integrity sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.1" + +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + +v8-to-istanbul@^9.0.1: + version "9.3.0" + resolved "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175" + integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA== + dependencies: + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^2.0.0" + +vary@^1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + +walker@^1.0.8: + version "1.0.8" + resolved "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +webidl-conversions@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" + integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== + +whatwg-url@^14.1.0: + version "14.2.0" + resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz#4ee02d5d725155dae004f6ae95c73e7ef5d95663" + integrity sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw== + dependencies: + tr46 "^5.1.0" + webidl-conversions "^7.0.0" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wordwrap@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== + +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write-file-atomic@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz#68df4717c55c6fa4281a7860b4c2ba0a6d2b11e7" + integrity sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^4.0.1" + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.7.2: + version "17.7.2" + resolved "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/src/problem6/README.md b/src/problem6/README.md new file mode 100644 index 0000000000..5c6ae2e130 --- /dev/null +++ b/src/problem6/README.md @@ -0,0 +1,389 @@ +# Problem 6: Live Scoreboard API Module + +## 1. Overview + +This document specifies a backend API module that provides live top-10 scoreboard functionality. The module handles score persistence, leaderboard queries, and real-time update delivery to connected clients. + +### 1.1 Core Responsibilities + +- Persist and update user scores upon action completion +- Serve top-10 leaderboard data for display +- Push leaderboard changes to clients via WebSocket in real-time +- Prevent unauthorized or malicious score manipulation + +### 1.2 Scope Definition + +**In Scope:** +- Score update endpoint (`POST /actions/complete`) +- Real-time leaderboard updates via WebSocket +- Top-10 query endpoint (`GET /leaderboard`) +- Authentication, authorization, and abuse prevention controls + +**Out of Scope:** +- Definition of user actions (the triggering event is not specified here) +- Frontend UI behavior and presentation +- User profile management + +--- + +## 2. Architecture + +### 2.1 Design Pattern: CQRS + +This module implements Command Query Responsibility Segregation (CQRS). The pattern separates write operations (commands) from read operations (queries) to optimize each for different requirements. + +**Command Side:** Handles score update requests. Writes go to PostgreSQL as the source of truth. + +**Query Side:** Handles leaderboard read requests. Reads are served from Redis cache for low latency. + +**Synchronization:** A projection worker consumes change events from PostgreSQL and updates the Redis read model. + +### 2.2 System Architecture + +```mermaid +flowchart LR + C[Client] --> GW[API Gateway] + GW --> Cmd[Command API] + GW --> Qry[Query API] + GW --> Sock[Socket Gateway] + + Cmd --> PG[(PostgreSQL)] + + PG -->|"change event"| Proj[Projection Worker] + Proj --> Redis[(Redis)] + Redis --> Sock + Sock --> C + + Qry --> Redis +``` + +### 2.3 Component Responsibilities + +| Component | Responsibility | +|-----------|---------------| +| API Gateway | Request routing, TLS termination, rate limiting | +| Command API | Authentication, validation, score writes | +| Query API | Leaderboard read requests | +| Projection Worker | Sync PostgreSQL changes to Redis | +| Socket Gateway | Real-time update fanout to clients | +| PostgreSQL | Source of truth for all data | +| Redis | Cached leaderboard data for fast reads | + +### 2.4 Data Flow + +1. **Write Path:** `POST /actions/complete` → Command API → PostgreSQL +2. **Sync Path:** PostgreSQL → Projection Worker → Redis +3. **Read Path:** `GET /leaderboard` → Query API → Redis +4. **Realtime Path:** Redis change → Socket Gateway → Client + +PostgreSQL serves as the authoritative data store. Redis is a derived cache that can be rebuilt from PostgreSQL if necessary. + +--- + +## 3. Functional Specification + +### 3.1 Requirements + +1. The system shall display the top 10 users ranked by score +2. The leaderboard shall update automatically without user intervention +3. Completing an action shall trigger a score increase via API call +4. The system shall reject unauthorized or malicious score updates + +### 3.2 API Endpoints + +#### 3.2.1 POST /actions/complete + +Submits a completed action and updates the user's score. + +**Request Body:** +```json +{ + "actionId": "daily-login", + "idempotencyKey": "uuid", + "clientTimestamp": "2026-04-17T11:12:03Z" +} +``` + +**Success Response (200):** +```json +{ + "requestId": "uuid", + "userId": "user-123", + "scoreDelta": 50, + "newTotalScore": 1500, + "acceptedAt": "2026-04-17T11:12:03Z" +} +``` + +**Error Responses:** +- 400 — Invalid request payload +- 401 — Missing or invalid authentication +- 409 — Duplicate request (idempotency key already processed) +- 429 — Rate limit exceeded + +#### 3.2.2 GET /leaderboard + +Retrieves the current top scorers. + +**Query Parameters:** +- `limit` — Number of results (default: 10, maximum: 100) + +**Success Response (200):** +```json +{ + "generatedAt": "2026-04-17T11:12:03Z", + "items": [ + { "rank": 1, "userId": "user-742", "displayName": "Sam", "score": 21250 } + ] +} +``` + +#### 3.2.3 WSS /leaderboard/live + +WebSocket endpoint for real-time leaderboard updates. + +**Server Push Event:** +```json +{ + "event": "leaderboard:update", + "data": { + "items": [ + { "rank": 1, "userId": "user-742", "displayName": "Sam", "score": 21250 } + ] + } +} +``` + +--- + +## 4. Write Flow + +```mermaid +sequenceDiagram + participant C as Client + participant API as Command API + participant PG as PostgreSQL + participant R as Redis + participant W as Socket + + C->>API: POST /actions/complete + API->>API: Auth + validate + API->>PG: BEGIN + check idempotency + + alt duplicate key + PG-->>API: existing row + API-->>C: Return stored response + else new request + API->>PG: INSERT score event + API->>PG: COMMIT + API-->>C: Accepted + PG->>R: Update Redis + R->>W: Push update + W-->>C: Realtime update + end +``` + +**Key Observations:** + +1. **Idempotency:** Every request includes an idempotency key that prevents duplicate processing. Client retries return the original response without re-processing. + +2. **Durability:** Score events are written to PostgreSQL first, ensuring durability before any cache update occurs. + +3. **Eventual Consistency:** Redis is updated after the PostgreSQL commit. The cache may briefly lag behind the authoritative data. + +--- + +## 5. Idempotency Mechanism + +```mermaid +flowchart TD + A[Request received] --> B{Idempotency key exists?} + B -->|Yes| C[Return stored response] + B -->|No| D[Validate action] + D -->|Invalid| E[Return error] + D -->|Valid| F[Insert + commit] + F --> G[Store response] + G --> H[Return success] +``` + +The idempotency constraint ensures that client retry attempts do not result in duplicate score awards. + +--- + +## 6. Security + +### 6.1 Authentication and Authorization + +| Control | Implementation | +|---------|---------------| +| Authentication | JWT Bearer token with 15-60 minute expiry | +| Authorization | User identity verified on every request; users may only modify their own scores | +| Token Validation | Signature, expiry, and audience claims verified | +| Account Status | Suspended users receive 403 Forbidden | + +### 6.2 Abuse Prevention + +| Control | Description | +|---------|-------------| +| Idempotency | Unique constraint on `(user_id, idempotency_key)` prevents duplicate scoring | +| Rate Limiting | Per-user: 10 score updates/minute; Per-IP: 100 requests/minute | +| Server-side Scoring | `scoreDelta` is derived from `actionId` on the server; client values are ignored | +| Timestamp Validation | `clientTimestamp` must fall within a 5-minute skew window | +| Fraud Monitoring | Per-user action frequency tracked; anomalies flagged for review | + +### 6.3 Fraud Detection + +```mermaid +flowchart TD + A[Request] --> B{Valid token?} + B -->|No| E[Reject 401] + B -->|Yes| C{Key exists?} + C -->|Yes| F[Return stored] + C -->|No| D{Action valid?} + D -->|No| G[Reject 403/422] + D -->|Yes| H{Suspicious?} + H -->|Yes| I[Log flag] + H -->|No| J[Process] + I --> J +``` + +**Monitored Signals:** +- Action frequency per user (velocity spike detection) +- Multiple accounts from the same IP address +- Repeated action completion within an abnormally short interval +- Score increments that deviate significantly from established patterns + +--- + +## 7. Data Model + +### 7.1 PostgreSQL Schema + +| Table | Fields | Purpose | +|-------|--------|---------| +| users | id, email, nick_name, score (BIGINT), status, created_at, updated_at | User accounts and current scores | +| score_events | id, user_id, action_id, score_delta, occurred_at | Append-only audit log of all score changes | +| idempotency_keys | user_id, key, response_body, created_at | Stores processed request responses for deduplication | + +**Implementation Note:** The `score_events` table is append-only. No UPDATE or DELETE operations are permitted on this table, as it serves as the authoritative audit trail. + +### 7.2 Redis Schema + +| Key | Type | Purpose | +|-----|------|---------| +| leaderboard:global | Sorted Set | Global ranking by score (ZINCRBY for updates) | +| user:score:{userId} | String | Per-user score snapshot | + +--- + +## 8. Error Handling + +### 8.1 Error Response Format + +```json +{ + "error": { + "code": "ERROR_CODE", + "message": "Human-readable description", + "requestId": "uuid" + } +} +``` + +### 8.2 Error Codes + +| Code | HTTP Status | Description | +|------|-------------|-------------| +| INVALID_TOKEN | 401 | Missing or invalid authentication token | +| FORBIDDEN | 403 | User not authorized for this operation | +| INVALID_PAYLOAD | 400 | Request body validation failed | +| DUPLICATE_ACTION | 409 | Idempotency key already processed with different payload | +| RATE_LIMITED | 429 | Request rate limit exceeded | + +--- + +## 9. Non-Functional Requirements + +### 9.1 Performance Targets + +| Metric | Target | +|--------|--------| +| Read latency (p95) | < 100ms | +| Write latency (p95) | < 200ms | +| Realtime update delivery | < 1 second from commit | + +### 9.2 Scalability Targets + +| Resource | Capacity | +|----------|----------| +| Write throughput | 1,000 score updates/second (burst) | +| Read throughput | 10,000 leaderboard queries/second | +| WebSocket connections | 50,000 concurrent | + +--- + +## 10. Scaling Architecture + +### 10.1 Horizontal Scaling Strategy + +| Component | Scaling Approach | +|-----------|-----------------| +| Command API | Stateless; scale horizontally behind load balancer | +| Query API | Stateless; scale horizontally behind load balancer | +| Projection Worker | Add consumer instances to increase event processing throughput | +| Socket Gateway | Stateful; use Redis pub/sub adapter for multi-node deployments | +| PostgreSQL | Read replicas for Query API; primary for Command API writes | +| Redis | Redis Cluster for horizontal cache scaling | + +### 10.2 Concurrency Handling + +- **Idempotency keys** serve as the primary defense against race conditions and duplicate processing +- **PostgreSQL transactions** with row-level locking ensure atomic score updates +- **At-least-once delivery** with consumer-side deduplication by `event_id` + +### 10.3 Failure Recovery + +| Failure Scenario | Recovery Procedure | +|-----------------|-------------------| +| Redis cache loss | Rebuild from `score_events` table via backfill job | +| Projection worker failure | Events queue in message broker; resume on worker restart | +| API instance crash | Client retries with same idempotency key; stored response returned | +| PostgreSQL primary failure | Failover to read replica; promote replica to primary | + +--- + +## 11. Implementation Guidelines + +### 11.1 Recommended Technology Stack + +- **Database:** PostgreSQL with PgBouncer connection pooling +- **Cache:** Redis with sorted set operations for leaderboard ranking +- **Message Queue:** PostgreSQL outbox table with CDC or direct worker +- **WebSocket:** Socket.IO or native WebSocket with Redis adapter + +### 11.2 Critical Implementation Notes + +1. Use `BIGINT` for all score fields to prevent overflow at high values +2. Maintain append-only semantics for `score_events` table +3. Create index on `(score DESC, updated_at ASC)` for efficient leaderboard queries +4. Batch Redis updates in projection worker to reduce write pressure +5. Emit WebSocket updates only when the visible top-10 changes, not on every score update + +### 11.3 Observability Requirements + +The following should be logged for operational monitoring and debugging: + +- All rejected requests with reason, userId, and IP address +- Projection lag (time between PostgreSQL commit and Redis update) +- WebSocket delivery latency + +--- + +## 12. Future Considerations + +The following enhancements are out of scope for the initial implementation but may be considered for future phases: + +- Scoped leaderboards (daily, weekly, monthly resets) +- Administrative interface for score corrections +- Multi-region socket fanout for global user base +- Advanced fraud detection with machine learning