From 22c1c02dfedc9c32ed500f7e21f033c5d9a75135 Mon Sep 17 00:00:00 2001 From: Wilson Tayar Date: Thu, 18 Jun 2026 00:50:25 +0100 Subject: [PATCH] =?UTF-8?q?test:=20su=C3=ADte=20de=20testes=20unit=C3=A1ri?= =?UTF-8?q?os=20para=20a=20API=20(Vitest)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adiciona Vitest e cobre as três camadas da API: - lib/http.ts: validação de payloads (parseCreate/Update Project/Task) e helpers badRequest/notFound. - lib/store.ts: CRUD em memória, contadores e cascata de delete, com reset do singleton globalThis.__devGarageDb entre testes. - app/api/**: route handlers (GET/POST/PATCH/DELETE) cobrindo JSON inválido (400), validação (400), não encontrado (404) e sucesso. Setup: vitest.config.ts (env node, alias @/) e scripts test/test:watch. Closes #1 Co-Authored-By: Claude Opus 4.8 --- app/api/projects/[id]/route.test.ts | 75 ++++ app/api/projects/[id]/tasks/route.test.ts | 52 +++ app/api/projects/route.test.ts | 54 +++ app/api/tasks/[id]/route.test.ts | 66 +++ lib/http.test.ts | 184 ++++++++ lib/store.test.ts | 118 ++++++ package.json | 7 +- pnpm-lock.yaml | 485 ++++++++++++++++++++++ vitest.config.ts | 14 + 9 files changed, 1053 insertions(+), 2 deletions(-) create mode 100644 app/api/projects/[id]/route.test.ts create mode 100644 app/api/projects/[id]/tasks/route.test.ts create mode 100644 app/api/projects/route.test.ts create mode 100644 app/api/tasks/[id]/route.test.ts create mode 100644 lib/http.test.ts create mode 100644 lib/store.test.ts create mode 100644 vitest.config.ts diff --git a/app/api/projects/[id]/route.test.ts b/app/api/projects/[id]/route.test.ts new file mode 100644 index 0000000..79e4087 --- /dev/null +++ b/app/api/projects/[id]/route.test.ts @@ -0,0 +1,75 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +type Route = typeof import("./route"); + +let route: Route; + +beforeEach(async () => { + delete (globalThis as Record).__devGarageDb; + vi.resetModules(); + route = await import("./route"); +}); + +const ctx = (id: string) => ({ params: Promise.resolve({ id }) }); + +const patch = (id: string, body: unknown) => + route.PATCH( + new Request(`http://test/api/projects/${id}`, { + method: "PATCH", + body: JSON.stringify(body), + }), + ctx(id), + ); + +describe("GET /api/projects/[id]", () => { + it("200 para projeto existente", async () => { + const res = await route.GET(new Request("http://test"), ctx("habit-cli")); + expect(res.status).toBe(200); + const body = await res.json(); + expect(body).toMatchObject({ id: "habit-cli" }); + expect(Array.isArray(body.tasks)).toBe(true); + }); + + it("404 para projeto inexistente", async () => { + const res = await route.GET(new Request("http://test"), ctx("nope")); + expect(res.status).toBe(404); + }); +}); + +describe("PATCH /api/projects/[id]", () => { + it("400 para JSON inválido", async () => { + const res = await route.PATCH( + new Request("http://test", { method: "PATCH", body: "{bad" }), + ctx("habit-cli"), + ); + expect(res.status).toBe(400); + }); + + it("400 quando a validação falha", async () => { + const res = await patch("habit-cli", { name: "" }); + expect(res.status).toBe(400); + }); + + it("404 para projeto inexistente", async () => { + const res = await patch("nope", { status: "shipped" }); + expect(res.status).toBe(404); + }); + + it("200 no sucesso", async () => { + const res = await patch("habit-cli", { status: "shipped" }); + expect(res.status).toBe(200); + await expect(res.json()).resolves.toMatchObject({ status: "shipped" }); + }); +}); + +describe("DELETE /api/projects/[id]", () => { + it("204 no sucesso", async () => { + const res = await route.DELETE(new Request("http://test"), ctx("habit-cli")); + expect(res.status).toBe(204); + }); + + it("404 para projeto inexistente", async () => { + const res = await route.DELETE(new Request("http://test"), ctx("nope")); + expect(res.status).toBe(404); + }); +}); diff --git a/app/api/projects/[id]/tasks/route.test.ts b/app/api/projects/[id]/tasks/route.test.ts new file mode 100644 index 0000000..2406f19 --- /dev/null +++ b/app/api/projects/[id]/tasks/route.test.ts @@ -0,0 +1,52 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +type Route = typeof import("./route"); + +let route: Route; + +beforeEach(async () => { + delete (globalThis as Record).__devGarageDb; + vi.resetModules(); + route = await import("./route"); +}); + +const ctx = (id: string) => ({ params: Promise.resolve({ id }) }); + +const post = (id: string, body: unknown) => + route.POST( + new Request(`http://test/api/projects/${id}/tasks`, { + method: "POST", + body: JSON.stringify(body), + }), + ctx(id), + ); + +describe("POST /api/projects/[id]/tasks", () => { + it("400 para JSON inválido", async () => { + const res = await route.POST( + new Request("http://test", { method: "POST", body: "{bad" }), + ctx("habit-cli"), + ); + expect(res.status).toBe(400); + }); + + it("400 quando a validação falha", async () => { + const res = await post("habit-cli", { title: "" }); + expect(res.status).toBe(400); + }); + + it("404 para projeto inexistente", async () => { + const res = await post("nope", { title: "Tarefa" }); + expect(res.status).toBe(404); + }); + + it("201 com a task criada no sucesso", async () => { + const res = await post("habit-cli", { title: "Nova tarefa" }); + expect(res.status).toBe(201); + await expect(res.json()).resolves.toMatchObject({ + projectId: "habit-cli", + title: "Nova tarefa", + done: false, + }); + }); +}); diff --git a/app/api/projects/route.test.ts b/app/api/projects/route.test.ts new file mode 100644 index 0000000..54d8231 --- /dev/null +++ b/app/api/projects/route.test.ts @@ -0,0 +1,54 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +type Route = typeof import("./route"); + +let route: Route; + +beforeEach(async () => { + delete (globalThis as Record).__devGarageDb; + vi.resetModules(); + route = await import("./route"); +}); + +const post = (body: unknown) => + route.POST( + new Request("http://test/api/projects", { + method: "POST", + body: JSON.stringify(body), + }), + ); + +describe("GET /api/projects", () => { + it("retorna 200 com a lista de projetos", async () => { + const res = await route.GET(); + expect(res.status).toBe(200); + const body = await res.json(); + expect(Array.isArray(body)).toBe(true); + expect(body.length).toBeGreaterThan(0); + }); +}); + +describe("POST /api/projects", () => { + it("400 para JSON inválido", async () => { + const res = await route.POST( + new Request("http://test/api/projects", { + method: "POST", + body: "{ not json", + }), + ); + expect(res.status).toBe(400); + }); + + it("400 quando a validação falha", async () => { + const res = await post({ name: "" }); + expect(res.status).toBe(400); + }); + + it("201 com o projeto criado no sucesso", async () => { + const res = await post({ name: "Meu projeto" }); + expect(res.status).toBe(201); + const body = await res.json(); + expect(body).toMatchObject({ name: "Meu projeto", status: "idea" }); + expect(body.id).toBeTruthy(); + }); +}); diff --git a/app/api/tasks/[id]/route.test.ts b/app/api/tasks/[id]/route.test.ts new file mode 100644 index 0000000..fc48338 --- /dev/null +++ b/app/api/tasks/[id]/route.test.ts @@ -0,0 +1,66 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +type Route = typeof import("./route"); + +let route: Route; + +beforeEach(async () => { + delete (globalThis as Record).__devGarageDb; + vi.resetModules(); + route = await import("./route"); +}); + +const ctx = (id: string) => ({ params: Promise.resolve({ id }) }); + +const patch = (id: string, body: unknown) => + route.PATCH( + new Request(`http://test/api/tasks/${id}`, { + method: "PATCH", + body: JSON.stringify(body), + }), + ctx(id), + ); + +describe("PATCH /api/tasks/[id]", () => { + it("400 para JSON inválido", async () => { + const res = await route.PATCH( + new Request("http://test", { method: "PATCH", body: "{bad" }), + ctx("habit-cli-t1"), + ); + expect(res.status).toBe(400); + }); + + it("400 quando a validação falha", async () => { + const res = await patch("habit-cli-t1", {}); + expect(res.status).toBe(400); + }); + + it("404 para task inexistente", async () => { + const res = await patch("nope", { done: true }); + expect(res.status).toBe(404); + }); + + it("200 no sucesso", async () => { + const res = await patch("habit-cli-t4", { done: true }); + expect(res.status).toBe(200); + await expect(res.json()).resolves.toMatchObject({ + id: "habit-cli-t4", + done: true, + }); + }); +}); + +describe("DELETE /api/tasks/[id]", () => { + it("204 no sucesso", async () => { + const res = await route.DELETE( + new Request("http://test"), + ctx("habit-cli-t1"), + ); + expect(res.status).toBe(204); + }); + + it("404 para task inexistente", async () => { + const res = await route.DELETE(new Request("http://test"), ctx("nope")); + expect(res.status).toBe(404); + }); +}); diff --git a/lib/http.test.ts b/lib/http.test.ts new file mode 100644 index 0000000..b20a761 --- /dev/null +++ b/lib/http.test.ts @@ -0,0 +1,184 @@ +import { describe, expect, it } from "vitest"; + +import { + badRequest, + notFound, + parseCreateProject, + parseCreateTask, + parseUpdateProject, + parseUpdateTask, +} from "@/lib/http"; + +describe("parseCreateProject", () => { + it("rejeita corpo que não é objeto", () => { + expect(parseCreateProject("nope")).toMatchObject({ ok: false }); + expect(parseCreateProject(null)).toMatchObject({ ok: false }); + }); + + it("exige 'name' e faz trim", () => { + expect(parseCreateProject({ name: " " })).toMatchObject({ + ok: false, + error: "O campo 'name' é obrigatório", + }); + + const parsed = parseCreateProject({ name: " Meu app " }); + expect(parsed).toMatchObject({ ok: true }); + if (parsed.ok) expect(parsed.value.name).toBe("Meu app"); + }); + + it("limita 'name' a 80 caracteres", () => { + expect(parseCreateProject({ name: "x".repeat(81) })).toMatchObject({ + ok: false, + }); + expect(parseCreateProject({ name: "x".repeat(80) })).toMatchObject({ + ok: true, + }); + }); + + it("aplica defaults para description, status e tags", () => { + const parsed = parseCreateProject({ name: "App" }); + expect(parsed.ok).toBe(true); + if (parsed.ok) { + expect(parsed.value).toEqual({ + name: "App", + description: "", + status: "idea", + tags: [], + }); + } + }); + + it("valida tipo e limite de 'description'", () => { + expect(parseCreateProject({ name: "App", description: 1 })).toMatchObject({ + ok: false, + }); + expect( + parseCreateProject({ name: "App", description: "x".repeat(501) }), + ).toMatchObject({ ok: false }); + + const parsed = parseCreateProject({ name: "App", description: " oi " }); + if (parsed.ok) expect(parsed.value.description).toBe("oi"); + }); + + it("aceita status válido e rejeita inválido", () => { + const parsed = parseCreateProject({ name: "App", status: "building" }); + if (parsed.ok) expect(parsed.value.status).toBe("building"); + expect(parseCreateProject({ name: "App", status: "wat" })).toMatchObject({ + ok: false, + }); + }); + + it("normaliza tags: trim, dedupe, corte em 10 e rejeita não-lista", () => { + expect(parseCreateProject({ name: "App", tags: "nope" })).toMatchObject({ + ok: false, + }); + expect(parseCreateProject({ name: "App", tags: [1] })).toMatchObject({ + ok: false, + }); + + const parsed = parseCreateProject({ + name: "App", + tags: [" a ", "a", "", "b"], + }); + if (parsed.ok) expect(parsed.value.tags).toEqual(["a", "b"]); + + const many = parseCreateProject({ + name: "App", + tags: Array.from({ length: 15 }, (_, i) => `t${i}`), + }); + if (many.ok) expect(many.value.tags).toHaveLength(10); + }); +}); + +describe("parseUpdateProject", () => { + it("rejeita corpo que não é objeto", () => { + expect(parseUpdateProject(42)).toMatchObject({ ok: false }); + }); + + it("retorna patch vazio quando não há campos", () => { + const parsed = parseUpdateProject({}); + expect(parsed).toMatchObject({ ok: true }); + if (parsed.ok) expect(parsed.value).toEqual({}); + }); + + it("rejeita 'name' vazio mas aceita patch parcial", () => { + expect(parseUpdateProject({ name: " " })).toMatchObject({ + ok: false, + error: "'name' não pode ser vazio", + }); + + const parsed = parseUpdateProject({ status: "shipped" }); + if (parsed.ok) expect(parsed.value).toEqual({ status: "shipped" }); + }); + + it("valida tipos/limites de cada campo", () => { + expect(parseUpdateProject({ name: "x".repeat(81) })).toMatchObject({ + ok: false, + }); + expect(parseUpdateProject({ description: 1 })).toMatchObject({ ok: false }); + expect( + parseUpdateProject({ description: "x".repeat(501) }), + ).toMatchObject({ ok: false }); + expect(parseUpdateProject({ status: "wat" })).toMatchObject({ ok: false }); + expect(parseUpdateProject({ tags: "no" })).toMatchObject({ ok: false }); + }); +}); + +describe("parseCreateTask", () => { + it("exige 'title', faz trim e limita a 140", () => { + expect(parseCreateTask({ title: " " })).toMatchObject({ ok: false }); + expect(parseCreateTask({ title: "x".repeat(141) })).toMatchObject({ + ok: false, + }); + + const parsed = parseCreateTask({ title: " Fazer X " }); + if (parsed.ok) expect(parsed.value.title).toBe("Fazer X"); + }); + + it("rejeita corpo que não é objeto", () => { + expect(parseCreateTask(null)).toMatchObject({ ok: false }); + }); +}); + +describe("parseUpdateTask", () => { + it("erro quando não há nada para atualizar", () => { + expect(parseUpdateTask({})).toMatchObject({ + ok: false, + error: "Nada para atualizar", + }); + }); + + it("valida 'title' e 'done'", () => { + expect(parseUpdateTask({ title: " " })).toMatchObject({ ok: false }); + expect(parseUpdateTask({ done: "yes" })).toMatchObject({ ok: false }); + + const parsed = parseUpdateTask({ title: " ok ", done: true }); + if (parsed.ok) expect(parsed.value).toEqual({ title: "ok", done: true }); + }); + + it("aceita patch só com 'done'", () => { + const parsed = parseUpdateTask({ done: false }); + if (parsed.ok) expect(parsed.value).toEqual({ done: false }); + }); +}); + +describe("helpers de resposta", () => { + it("badRequest retorna 400 com corpo de erro", async () => { + const res = badRequest("ops"); + expect(res.status).toBe(400); + await expect(res.json()).resolves.toEqual({ error: "ops" }); + }); + + it("notFound retorna 404 com mensagem padrão e customizada", async () => { + const padrao = notFound(); + expect(padrao.status).toBe(404); + await expect(padrao.json()).resolves.toEqual({ + error: "Recurso não encontrado", + }); + + const custom = notFound("Projeto não encontrado"); + await expect(custom.json()).resolves.toEqual({ + error: "Projeto não encontrado", + }); + }); +}); diff --git a/lib/store.test.ts b/lib/store.test.ts new file mode 100644 index 0000000..c11a35f --- /dev/null +++ b/lib/store.test.ts @@ -0,0 +1,118 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +type Store = typeof import("@/lib/store"); + +let store: Store; + +beforeEach(async () => { + // O store mantém um singleton em globalThis.__devGarageDb; limpamos e + // recarregamos o módulo para reproduzir o seed a cada teste. + delete (globalThis as Record).__devGarageDb; + vi.resetModules(); + store = await import("@/lib/store"); +}); + +describe("listProjects", () => { + it("retorna summaries com contadores corretos e ordenados por createdAt desc", () => { + const projects = store.listProjects(); + expect(projects.map((p) => p.id)).toEqual([ + "mood-playlist", + "habit-cli", + "refund-bot", + "home-dash", + ]); + + const habit = projects.find((p) => p.id === "habit-cli"); + expect(habit).toMatchObject({ taskCount: 5, doneCount: 3 }); + }); +}); + +describe("getProject", () => { + it("inclui tasks ordenadas por createdAt asc", () => { + const project = store.getProject("habit-cli"); + expect(project).toBeDefined(); + expect(project?.tasks).toHaveLength(5); + const createdAts = project!.tasks.map((t) => t.createdAt); + expect([...createdAts].sort()).toEqual(createdAts); + }); + + it("retorna undefined para id inexistente", () => { + expect(store.getProject("nope")).toBeUndefined(); + }); +}); + +describe("createProject", () => { + it("cria projeto e o inclui na listagem", () => { + const created = store.createProject({ + name: "Novo", + description: "desc", + status: "idea", + tags: ["x"], + }); + expect(created.id).toBeTruthy(); + expect(created.createdAt).toBeTruthy(); + + const found = store.getProject(created.id); + expect(found).toMatchObject({ name: "Novo", tags: ["x"] }); + }); +}); + +describe("updateProject", () => { + it("aplica patch parcial", () => { + const updated = store.updateProject("habit-cli", { status: "shipped" }); + expect(updated).toMatchObject({ + id: "habit-cli", + status: "shipped", + name: "Habitica de terminal", + }); + }); + + it("retorna undefined para projeto inexistente", () => { + expect(store.updateProject("nope", { name: "x" })).toBeUndefined(); + }); +}); + +describe("deleteProject", () => { + it("remove o projeto e suas tasks em cascata", () => { + expect(store.deleteProject("habit-cli")).toBe(true); + expect(store.getProject("habit-cli")).toBeUndefined(); + // tasks da habit-cli não devem aparecer em nenhuma atualização + expect(store.updateTask("habit-cli-t1", { done: false })).toBeUndefined(); + }); + + it("retorna false para projeto inexistente", () => { + expect(store.deleteProject("nope")).toBe(false); + }); +}); + +describe("addTask", () => { + it("adiciona task a projeto existente", () => { + const task = store.addTask("mood-playlist", "Nova task"); + expect(task).toMatchObject({ + projectId: "mood-playlist", + title: "Nova task", + done: false, + }); + expect(store.getProject("mood-playlist")?.tasks).toHaveLength(3); + }); + + it("retorna undefined para projeto inexistente", () => { + expect(store.addTask("nope", "x")).toBeUndefined(); + }); +}); + +describe("updateTask / deleteTask", () => { + it("atualiza campos da task", () => { + const updated = store.updateTask("mood-playlist-t1", { done: true }); + expect(updated).toMatchObject({ id: "mood-playlist-t1", done: true }); + }); + + it("retorna undefined ao atualizar task inexistente", () => { + expect(store.updateTask("nope", { done: true })).toBeUndefined(); + }); + + it("remove task existente e false para inexistente", () => { + expect(store.deleteTask("mood-playlist-t1")).toBe(true); + expect(store.deleteTask("mood-playlist-t1")).toBe(false); + }); +}); diff --git a/package.json b/package.json index 4ca87ff..fa4d0a8 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,9 @@ "start": "next start", "lint": "eslint", "format": "prettier --write \"**/*.{ts,tsx}\"", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "test": "vitest run", + "test:watch": "vitest" }, "dependencies": { "class-variance-authority": "^0.7.1", @@ -36,6 +38,7 @@ "prettier": "^3.8.4", "prettier-plugin-tailwindcss": "^0.8.0", "tailwindcss": "^4.3.0", - "typescript": "^5.9.3" + "typescript": "^5.9.3", + "vitest": "^4.1.8" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 01daa9d..2c14f0c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -75,6 +75,9 @@ importers: typescript: specifier: ^5.9.3 version: 5.9.3 + vitest: + specifier: ^4.1.8 + version: 4.1.8(@types/node@20.19.42)(vite@8.0.16(@types/node@20.19.42)(jiti@2.7.0)) packages: @@ -583,6 +586,9 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} + '@oxc-project/types@0.133.0': + resolution: {integrity: sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==} + '@radix-ui/number@1.1.2': resolution: {integrity: sha512-ceTwaxc4I5IOi97DgCotl3pqiyRGvffcc0oOsE2dQYaJOFIDsDt4VWG6xEbg1QePv9QWausCEIppud/tJ1wNig==} @@ -1273,6 +1279,104 @@ packages: '@radix-ui/rect@1.1.2': resolution: {integrity: sha512-xnXE7wG13PI+cxieVssYXlQJuYVRhH9NBoxt3KNwzghDIA69GMm7d4wXRouHIYjE+KvS6U/MsMO73NdS2MH9ZA==} + '@rolldown/binding-android-arm64@1.0.3': + resolution: {integrity: sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.3': + resolution: {integrity: sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.3': + resolution: {integrity: sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.3': + resolution: {integrity: sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.3': + resolution: {integrity: sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.3': + resolution: {integrity: sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.0.3': + resolution: {integrity: sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.0.3': + resolution: {integrity: sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.0.3': + resolution: {integrity: sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.0.3': + resolution: {integrity: sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.0.3': + resolution: {integrity: sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.0.3': + resolution: {integrity: sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.3': + resolution: {integrity: sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.3': + resolution: {integrity: sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.3': + resolution: {integrity: sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} @@ -1283,6 +1387,9 @@ packages: resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} engines: {node: '>=18'} + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} @@ -1384,6 +1491,12 @@ packages: '@tybys/wasm-util@0.10.2': resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} @@ -1586,6 +1699,35 @@ packages: cpu: [x64] os: [win32] + '@vitest/expect@4.1.8': + resolution: {integrity: sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==} + + '@vitest/mocker@4.1.8': + resolution: {integrity: sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.8': + resolution: {integrity: sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==} + + '@vitest/runner@4.1.8': + resolution: {integrity: sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==} + + '@vitest/snapshot@4.1.8': + resolution: {integrity: sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==} + + '@vitest/spy@4.1.8': + resolution: {integrity: sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==} + + '@vitest/utils@4.1.8': + resolution: {integrity: sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==} + accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} @@ -1677,6 +1819,10 @@ packages: resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} engines: {node: '>= 0.4'} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + ast-types-flow@0.0.8: resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} @@ -1759,6 +1905,10 @@ packages: caniuse-lite@1.0.30001797: resolution: {integrity: sha512-l8xKG+gwAIExZGl9FrF7KUwuOmk6wbEPC9Xoy/RtnWv1XG0Q4LFlagaLpUv3Kiza3W/wm27zy0yWJEieYKAP6w==} + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -2003,6 +2153,9 @@ packages: resolution: {integrity: sha512-0PuBxFi+4uPanB97iDxCLWuHeYud2FALrw5HFZGtAF38UpJDbDC8frwp2cnDyae692CQ0dou60UwWfhgsa4U/g==} engines: {node: '>= 0.4'} + es-module-lexer@2.1.0: + resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} + es-object-atoms@1.1.2: resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} engines: {node: '>= 0.4'} @@ -2151,6 +2304,9 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} @@ -2175,6 +2331,10 @@ packages: resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==} engines: {node: ^18.19.0 || >=20.5.0} + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + express-rate-limit@8.5.2: resolution: {integrity: sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==} engines: {node: '>= 16'} @@ -2268,6 +2428,11 @@ packages: resolution: {integrity: sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==} engines: {node: '>=14.14'} + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} @@ -2947,6 +3112,10 @@ packages: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} + obug@2.1.2: + resolution: {integrity: sha512-AWGB9WFcRXOQs48Z/udjI5ZcZMHXwX8XPByNpOydgcGsDLIzjGizhoMWJyKAWze7AVW/2W1i+/gPX4YtKe5cyg==} + engines: {node: '>=12.20.0'} + on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} @@ -3023,6 +3192,9 @@ packages: path-to-regexp@8.4.2: resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -3247,6 +3419,11 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rolldown@1.0.3: + resolution: {integrity: sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + router@2.2.0: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} @@ -3340,6 +3517,9 @@ packages: resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} engines: {node: '>= 0.4'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -3367,10 +3547,16 @@ packages: stable-hash@0.0.5: resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + statuses@2.0.2: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} + std-env@4.1.0: + resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} + stdin-discarder@0.2.2: resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} engines: {node: '>=18'} @@ -3468,10 +3654,21 @@ packages: tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.2.4: + resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==} + engines: {node: '>=18'} + tinyglobby@0.2.17: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -3600,6 +3797,90 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + vite@8.0.16: + resolution: {integrity: sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.1.18 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.1.8: + resolution: {integrity: sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.8 + '@vitest/browser-preview': 4.1.8 + '@vitest/browser-webdriverio': 4.1.8 + '@vitest/coverage-istanbul': 4.1.8 + '@vitest/coverage-v8': 4.1.8 + '@vitest/ui': 4.1.8 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + web-streams-polyfill@3.3.3: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} @@ -3630,6 +3911,11 @@ packages: engines: {node: ^16.13.0 || >=18.0.0} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -4182,6 +4468,8 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} + '@oxc-project/types@0.133.0': {} + '@radix-ui/number@1.1.2': {} '@radix-ui/primitive@1.1.4': {} @@ -4927,12 +5215,65 @@ snapshots: '@radix-ui/rect@1.1.2': {} + '@rolldown/binding-android-arm64@1.0.3': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.3': + optional: true + + '@rolldown/binding-darwin-x64@1.0.3': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.3': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.3': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.3': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.3': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.3': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.3': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.3': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.3': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + '@rtsao/scc@1.1.0': {} '@sec-ant/readable-stream@0.4.1': {} '@sindresorhus/merge-streams@4.0.0': {} + '@standard-schema/spec@1.1.0': {} + '@swc/helpers@0.5.15': dependencies: tslib: 2.8.1 @@ -5017,6 +5358,13 @@ snapshots: tslib: 2.8.1 optional: true + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + '@types/estree@1.0.9': {} '@types/json-schema@7.0.15': {} @@ -5198,6 +5546,47 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.12.2': optional: true + '@vitest/expect@4.1.8': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.8 + '@vitest/utils': 4.1.8 + chai: 6.2.2 + tinyrainbow: 3.1.0 + + '@vitest/mocker@4.1.8(vite@8.0.16(@types/node@20.19.42)(jiti@2.7.0))': + dependencies: + '@vitest/spy': 4.1.8 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 8.0.16(@types/node@20.19.42)(jiti@2.7.0) + + '@vitest/pretty-format@4.1.8': + dependencies: + tinyrainbow: 3.1.0 + + '@vitest/runner@4.1.8': + dependencies: + '@vitest/utils': 4.1.8 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.8': + dependencies: + '@vitest/pretty-format': 4.1.8 + '@vitest/utils': 4.1.8 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.8': {} + + '@vitest/utils@4.1.8': + dependencies: + '@vitest/pretty-format': 4.1.8 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 + accepts@2.0.0: dependencies: mime-types: 3.0.2 @@ -5314,6 +5703,8 @@ snapshots: get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 + assertion-error@2.0.1: {} + ast-types-flow@0.0.8: {} ast-types@0.16.1: @@ -5398,6 +5789,8 @@ snapshots: caniuse-lite@1.0.30001797: {} + chai@6.2.2: {} + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -5659,6 +6052,8 @@ snapshots: iterator.prototype: 1.1.5 math-intrinsics: 1.1.0 + es-module-lexer@2.1.0: {} + es-object-atoms@1.1.2: dependencies: es-errors: 1.3.0 @@ -5891,6 +6286,10 @@ snapshots: estraverse@5.3.0: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + esutils@2.0.3: {} etag@1.8.1: {} @@ -5928,6 +6327,8 @@ snapshots: strip-final-newline: 4.0.0 yoctocolors: 2.1.2 + expect-type@1.3.0: {} + express-rate-limit@8.5.2(express@5.2.1): dependencies: express: 5.2.1 @@ -6056,6 +6457,9 @@ snapshots: jsonfile: 6.2.1 universalify: 2.0.1 + fsevents@2.3.3: + optional: true + function-bind@1.1.2: {} function.prototype.name@1.1.8: @@ -6656,6 +7060,8 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.2 + obug@2.1.2: {} + on-finished@2.4.1: dependencies: ee-first: 1.1.1 @@ -6743,6 +7149,8 @@ snapshots: path-to-regexp@8.4.2: {} + pathe@2.0.3: {} + picocolors@1.1.1: {} picomatch@2.3.2: {} @@ -6966,6 +7374,27 @@ snapshots: reusify@1.1.0: {} + rolldown@1.0.3: + dependencies: + '@oxc-project/types': 0.133.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.3 + '@rolldown/binding-darwin-arm64': 1.0.3 + '@rolldown/binding-darwin-x64': 1.0.3 + '@rolldown/binding-freebsd-x64': 1.0.3 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.3 + '@rolldown/binding-linux-arm64-gnu': 1.0.3 + '@rolldown/binding-linux-arm64-musl': 1.0.3 + '@rolldown/binding-linux-ppc64-gnu': 1.0.3 + '@rolldown/binding-linux-s390x-gnu': 1.0.3 + '@rolldown/binding-linux-x64-gnu': 1.0.3 + '@rolldown/binding-linux-x64-musl': 1.0.3 + '@rolldown/binding-openharmony-arm64': 1.0.3 + '@rolldown/binding-wasm32-wasi': 1.0.3 + '@rolldown/binding-win32-arm64-msvc': 1.0.3 + '@rolldown/binding-win32-x64-msvc': 1.0.3 + router@2.2.0: dependencies: debug: 4.4.3 @@ -7165,6 +7594,8 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} + signal-exit@3.0.7: {} signal-exit@4.1.0: {} @@ -7182,8 +7613,12 @@ snapshots: stable-hash@0.0.5: {} + stackback@0.0.2: {} + statuses@2.0.2: {} + std-env@4.1.0: {} + stdin-discarder@0.2.2: {} stop-iteration-iterator@1.1.0: @@ -7291,11 +7726,17 @@ snapshots: tiny-invariant@1.3.3: {} + tinybench@2.9.0: {} + + tinyexec@1.2.4: {} + tinyglobby@0.2.17: dependencies: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 + tinyrainbow@3.1.0: {} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -7457,6 +7898,45 @@ snapshots: vary@1.1.2: {} + vite@8.0.16(@types/node@20.19.42)(jiti@2.7.0): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.15 + rolldown: 1.0.3 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 20.19.42 + fsevents: 2.3.3 + jiti: 2.7.0 + + vitest@4.1.8(@types/node@20.19.42)(vite@8.0.16(@types/node@20.19.42)(jiti@2.7.0)): + dependencies: + '@vitest/expect': 4.1.8 + '@vitest/mocker': 4.1.8(vite@8.0.16(@types/node@20.19.42)(jiti@2.7.0)) + '@vitest/pretty-format': 4.1.8 + '@vitest/runner': 4.1.8 + '@vitest/snapshot': 4.1.8 + '@vitest/spy': 4.1.8 + '@vitest/utils': 4.1.8 + es-module-lexer: 2.1.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.2 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 4.1.0 + tinybench: 2.9.0 + tinyexec: 1.2.4 + tinyglobby: 0.2.17 + tinyrainbow: 3.1.0 + vite: 8.0.16(@types/node@20.19.42)(jiti@2.7.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 20.19.42 + transitivePeerDependencies: + - msw + web-streams-polyfill@3.3.3: {} which-boxed-primitive@1.1.1: @@ -7508,6 +7988,11 @@ snapshots: dependencies: isexe: 3.1.5 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + word-wrap@1.2.5: {} wrappy@1.0.2: {} diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..683c3cf --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,14 @@ +import { fileURLToPath } from "node:url"; + +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: "node", + }, + resolve: { + alias: { + "@": fileURLToPath(new URL(".", import.meta.url)), + }, + }, +});