diff --git a/docs/specs/2026-09-14-v5-data-platform-design.md b/docs/specs/2026-09-14-v5-data-platform-design.md index 2d85831..65d5c54 100644 --- a/docs/specs/2026-09-14-v5-data-platform-design.md +++ b/docs/specs/2026-09-14-v5-data-platform-design.md @@ -991,3 +991,24 @@ Decisions the lab made on 2026-09-14 are in the body. What is left: | 5 | **Mirror token key.** Derived from `AUTH_SECRET` (§8) or a separate `MIRROR_KEY` env var that survives session-secret rotation | Derived, for now; a separate key if rotation ever becomes routine | Isaac | Phase 8 | Settled since the first draft: projects are for anyone *signed in*, never anonymous visitors; permissions are declared in code, the ordinary way; `ies22@cornell.edu` is permanent, so the super-admin floor needs no succession plan; the import is a one-time script run by hand. + +--- + +## Amendments + +Appended per [`DRIFT.md`](DRIFT.md). Original text above is never edited — the reason a design changed usually outlives the change. + +### 2026-09-14 — Phase 1 built, with as-built details + +**What changed.** Phase 1 (§9) is implemented: `src/lib/db/` (schema, three migrations, the PGlite and Neon constructors, `getDb()`, the demo seed, slugs), `src/lib/import/` (vocabulary mapping, pre-flight, the ticket-description parser, row mappers, the file copier, the Notion snapshot reader and the runner), and three scripts. Details the spec did not name: + +- **npm scripts.** `db:generate` runs `drizzle-kit generate` after a schema edit. `db:migrate` applies committed migrations to `DATABASE_URL` and exits 0 with a note when it is unset. `import:notion` is the one-time import, with `--dry-run` (reads Notion into an in-memory PGlite, copies no files, prints the report — the rehearsal §5.7 describes, needing no Neon) and `--skip-files`. `verify:import` is §5.7's verification script. +- **`DB_MIGRATIONS_DIR`** overrides where the migrations folder is resolved from (`src/lib/db/migrations` under the working directory by default). Every entry point runs from `v5/`, so it is unset in practice; it exists for anything that does not. +- **`attachments.source_key`** (text, unique, nullable) is the import's idempotency key — Notion page id, property and index — so a re-run recognises a file it already copied. Null on files uploaded through the app. +- **`created_by`-style columns have no foreign key yet.** They are `text` and reference Better Auth's `user.id` once Phase 4 creates that table; Phase 4's migration adds the constraints. +- **Scripts load the database and import modules under plain Node** (type stripping). Those modules therefore use relative imports with `.ts` extensions, no `@/` alias, no `server-only`, and no TypeScript-only class syntax. Nothing under `src/lib/db/` or `src/lib/import/` is imported by a client component. +- **`rawRows(db, sql)`** is the one place raw SQL results are typed, because the driver-agnostic `Db` handle cannot know a driver's result shape. +- **The five-tool field-by-field comparison** in §5.7's verification waits for Phase 2, which is what renders the Postgres side. `verify:import` checks counts, relations and Blob bytes now. +- **`db:migrate` is not yet part of the Vercel build.** Phase 2 adds it to the build command when Neon exists; until then a build needs no database. + +**Status.** Accepted. Each is a detail the spec left to implementation. diff --git a/v5/.env.example b/v5/.env.example index cabce93..f514281 100644 --- a/v5/.env.example +++ b/v5/.env.example @@ -87,6 +87,13 @@ CRON_SECRET=YourCronSecretHere # PRIVATE. BLOB_READ_WRITE_TOKEN=vercel_blob_rw_YourTokenHere +# ── Database (Neon Postgres) ───────────────────────────────────────── +# Injected automatically when Neon is installed from the Vercel Marketplace. +# With it unset the app, the tests and `npm run import:notion -- --dry-run` +# use an in-process Postgres (PGlite) with sample data, and `npm run +# db:migrate` does nothing. Set it to import for real and to run migrations. +DATABASE_URL= + # ── Sign-in (Google Workspace) ─────────────────────────────────────── # Optional. With these unset the app runs exactly as before: everyone is # anonymous, the catalog and the assistant still work, and /api/auth returns diff --git a/v5/drizzle.config.ts b/v5/drizzle.config.ts new file mode 100644 index 0000000..c3317a4 --- /dev/null +++ b/v5/drizzle.config.ts @@ -0,0 +1,23 @@ +import { defineConfig } from "drizzle-kit"; + +/** + * drizzle-kit configuration (data platform design spec 2026-09-14 §3.2). + * + * `npm run db:generate` diffs `src/lib/db/schema/` against the migrations + * already in `src/lib/db/migrations/` and writes a new SQL file. Migrations are + * committed and applied by `npm run db:migrate` (Neon) or on first use (PGlite); + * nothing runs `push` against a live database. + * + * `DATABASE_URL` is only needed for `drizzle-kit studio` / `migrate` — not for + * `generate`, which is pure computation. + */ +export default defineConfig({ + dialect: "postgresql", + schema: "./src/lib/db/schema/index.ts", + out: "./src/lib/db/migrations", + dbCredentials: { + url: process.env.DATABASE_URL ?? "", + }, + strict: true, + verbose: true, +}); diff --git a/v5/package-lock.json b/v5/package-lock.json index 3a060b5..1c737e5 100644 --- a/v5/package-lock.json +++ b/v5/package-lock.json @@ -11,10 +11,13 @@ "@ai-sdk/anthropic": "^3.0.46", "@ai-sdk/gateway": "^3.0.160", "@ai-sdk/react": "^3.0.97", + "@electric-sql/pglite": "^0.5.8", "@modelcontextprotocol/sdk": "^1.29.0", + "@neondatabase/serverless": "^1.1.0", "@vercel/blob": "^2.6.1", "ai": "^6.0.95", "better-auth": "^1.6.25", + "drizzle-orm": "^0.45.2", "match-sorter": "^8.3.0", "next": "16.1.6", "next-intl": "^4.13.0", @@ -37,6 +40,7 @@ "@types/react-dom": "^19", "@vitejs/plugin-react": "^6.0.2", "@vitest/coverage-v8": "^4.1.8", + "drizzle-kit": "^0.31.10", "eslint": "^9", "eslint-config-next": "16.1.6", "jsdom": "^29.1.1", @@ -791,6 +795,20 @@ "node": ">=20.19.0" } }, + "node_modules/@drizzle-team/brocli": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.10.2.tgz", + "integrity": "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@electric-sql/pglite": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.5.8.tgz", + "integrity": "sha512-n9tsbUOhwx2epK1V0ZG9Ar4SHWUju04dhmzZXiSBXwBoleOvIfals33NAaWgagQVAL4Rbvx/Ptsu3P+pA09f6Q==", + "license": "Apache-2.0", + "peer": true + }, "node_modules/@emnapi/core": { "version": "1.11.3", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.3.tgz", @@ -824,2337 +842,2473 @@ "tslib": "^2.4.0" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", - "dev": true, + "node_modules/@esbuild-kit/core-utils": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz", + "integrity": "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==", + "deprecated": "Merged into tsx: https://tsx.hirok.io", + "devOptional": true, "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "esbuild": "~0.18.20", + "source-map-support": "^0.5.21" } }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=12" } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node": ">=12" } }, - "node_modules/@eslint/config-array": { - "version": "0.21.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", - "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.7", - "debug": "^4.3.1", - "minimatch": "^3.1.5" - }, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=12" } }, - "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=12" } }, - "node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=12" } }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", - "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "ajv": "^6.14.0", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", - "minimatch": "^3.1.5", - "strip-json-comments": "^3.1.1" - }, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=12" } }, - "node_modules/@eslint/js": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", - "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" + "node": ">=12" } }, - "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=12" } }, - "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0", - "levn": "^0.4.1" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=12" } }, - "node_modules/@exodus/bytes": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz", - "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - }, - "peerDependencies": { - "@noble/hashes": "^1.8.0 || ^2.0.0" - }, - "peerDependenciesMeta": { - "@noble/hashes": { - "optional": true - } + "node": ">=12" } }, - "node_modules/@fastify/busboy": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", - "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=14" + "node": ">=12" } }, - "node_modules/@formatjs/fast-memoize": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.5.tgz", - "integrity": "sha512-KLi3fan6WnCHmigd9pmEEN8Hid0v4wiFBW576M/d07KMWYecf1CvyMI3n34vCmHT4AoVqG2n702kiHbXjzZX2A==", - "license": "MIT" + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } }, - "node_modules/@formatjs/icu-messageformat-parser": { - "version": "3.5.10", - "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.10.tgz", - "integrity": "sha512-XeJihYLy1lCe19xfK1KWKG/betBOK2rB0luL8lSkjfvJj0zP+LTJvkC+RKd0jsFI8mWxN71LrarHSrEXE8xxOQ==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@formatjs/icu-skeleton-parser": "2.1.9" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@formatjs/icu-skeleton-parser": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.9.tgz", - "integrity": "sha512-rsxswgHMfU1zUgB2byc08fesf83wLGjFnzLCEtuf00mx2doiqc6pYrf67raI37XqdRcGUviQepk2UKGqpng74Q==", - "license": "MIT" - }, - "node_modules/@formatjs/intl-localematcher": { - "version": "0.8.9", - "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.8.9.tgz", - "integrity": "sha512-GmB0F/gYh4Hdl4rLWjgDsgT+x4pB54fkJeRh8kAZ4XFzKeCK8dGs+SBJWXO42QZtOUni+IDWKNuCw6wiL4lTvw==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@formatjs/fast-memoize": "3.1.5" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@hono/node-server": { - "version": "1.19.14", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", - "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=18.14.1" - }, - "peerDependencies": { - "hono": "^4" + "node": ">=12" } }, - "node_modules/@humanfs/core": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", - "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/types": "^0.15.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=18.18.0" + "node": ">=12" } }, - "node_modules/@humanfs/node": { - "version": "0.16.8", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", - "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.2", - "@humanfs/types": "^0.15.0", - "@humanwhocodes/retry": "^0.4.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=18.18.0" + "node": ">=12" } }, - "node_modules/@humanfs/types": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", - "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">=18.18.0" + "node": ">=12" } }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "node": ">=12" } }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "node": ">=12" } }, - "node_modules/@img/colour": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", - "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "dev": true, "license": "MIT", "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=18" + "node": ">=12" } }, - "node_modules/@img/sharp-darwin-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", - "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", "cpu": [ - "arm64" + "x64" ], - "license": "Apache-2.0", + "dev": true, + "license": "MIT", "optional": true, "os": [ - "darwin" + "win32" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "devOptional": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" }, - "funding": { - "url": "https://opencollective.com/libvips" + "engines": { + "node": ">=12" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.2.4" + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "node_modules/@esbuild-kit/esm-loader": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/@esbuild-kit/esm-loader/-/esm-loader-2.6.5.tgz", + "integrity": "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==", + "deprecated": "Merged into tsx: https://tsx.hirok.io", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@esbuild-kit/core-utils": "^3.3.2", + "get-tsconfig": "^4.7.0" } }, - "node_modules/@img/sharp-darwin-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", - "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", "cpu": [ - "x64" + "ppc64" ], - "license": "Apache-2.0", + "dev": true, + "license": "MIT", "optional": true, "os": [ - "darwin" + "aix" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.2.4" + "node": ">=18" } }, - "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", - "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", "cpu": [ - "arm64" + "arm" ], - "license": "LGPL-3.0-or-later", + "dev": true, + "license": "MIT", "optional": true, "os": [ - "darwin" + "android" ], - "funding": { - "url": "https://opencollective.com/libvips" + "engines": { + "node": ">=18" } }, - "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", - "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", "cpu": [ - "x64" + "arm64" ], - "license": "LGPL-3.0-or-later", + "dev": true, + "license": "MIT", "optional": true, "os": [ - "darwin" + "android" ], - "funding": { - "url": "https://opencollective.com/libvips" + "engines": { + "node": ">=18" } }, - "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", - "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", "cpu": [ - "arm" + "x64" ], - "license": "LGPL-3.0-or-later", + "dev": true, + "license": "MIT", "optional": true, "os": [ - "linux" + "android" ], - "funding": { - "url": "https://opencollective.com/libvips" + "engines": { + "node": ">=18" } }, - "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", - "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", "cpu": [ "arm64" ], - "license": "LGPL-3.0-or-later", + "dev": true, + "license": "MIT", "optional": true, "os": [ - "linux" + "darwin" ], - "funding": { - "url": "https://opencollective.com/libvips" + "engines": { + "node": ">=18" } }, - "node_modules/@img/sharp-libvips-linux-ppc64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", - "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", "cpu": [ - "ppc64" + "x64" ], - "license": "LGPL-3.0-or-later", + "dev": true, + "license": "MIT", "optional": true, "os": [ - "linux" + "darwin" ], - "funding": { - "url": "https://opencollective.com/libvips" + "engines": { + "node": ">=18" } }, - "node_modules/@img/sharp-libvips-linux-riscv64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", - "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", "cpu": [ - "riscv64" + "arm64" ], - "license": "LGPL-3.0-or-later", + "dev": true, + "license": "MIT", "optional": true, "os": [ - "linux" + "freebsd" ], - "funding": { - "url": "https://opencollective.com/libvips" + "engines": { + "node": ">=18" } }, - "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", - "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", "cpu": [ - "s390x" + "x64" ], - "license": "LGPL-3.0-or-later", + "dev": true, + "license": "MIT", "optional": true, "os": [ - "linux" + "freebsd" ], - "funding": { - "url": "https://opencollective.com/libvips" + "engines": { + "node": ">=18" } }, - "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", - "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", "cpu": [ - "x64" + "arm" ], - "license": "LGPL-3.0-or-later", + "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], - "funding": { - "url": "https://opencollective.com/libvips" + "engines": { + "node": ">=18" } }, - "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", - "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", "cpu": [ "arm64" ], - "license": "LGPL-3.0-or-later", + "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], - "funding": { - "url": "https://opencollective.com/libvips" + "engines": { + "node": ">=18" } }, - "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", - "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", "cpu": [ - "x64" + "ia32" ], - "license": "LGPL-3.0-or-later", + "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], - "funding": { - "url": "https://opencollective.com/libvips" + "engines": { + "node": ">=18" } }, - "node_modules/@img/sharp-linux-arm": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", - "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", "cpu": [ - "arm" + "loong64" ], - "license": "Apache-2.0", + "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.2.4" + "node": ">=18" } }, - "node_modules/@img/sharp-linux-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", - "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", "cpu": [ - "arm64" + "mips64el" ], - "license": "Apache-2.0", + "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.2.4" + "node": ">=18" } }, - "node_modules/@img/sharp-linux-ppc64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", - "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", "cpu": [ "ppc64" ], - "license": "Apache-2.0", + "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-ppc64": "1.2.4" + "node": ">=18" } }, - "node_modules/@img/sharp-linux-riscv64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", - "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", "cpu": [ "riscv64" ], - "license": "Apache-2.0", + "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-riscv64": "1.2.4" + "node": ">=18" } }, - "node_modules/@img/sharp-linux-s390x": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", - "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", "cpu": [ "s390x" ], - "license": "Apache-2.0", + "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.2.4" + "node": ">=18" } }, - "node_modules/@img/sharp-linux-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", - "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", "cpu": [ "x64" ], - "license": "Apache-2.0", + "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.2.4" + "node": ">=18" } }, - "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", - "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", "cpu": [ "arm64" ], - "license": "Apache-2.0", + "dev": true, + "license": "MIT", "optional": true, "os": [ - "linux" + "netbsd" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + "node": ">=18" } }, - "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", - "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", "cpu": [ "x64" ], - "license": "Apache-2.0", + "dev": true, + "license": "MIT", "optional": true, "os": [ - "linux" + "netbsd" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + "node": ">=18" } }, - "node_modules/@img/sharp-wasm32": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", - "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", "cpu": [ - "wasm32" + "arm64" ], - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "dev": true, + "license": "MIT", "optional": true, - "dependencies": { - "@emnapi/runtime": "^1.7.0" - }, + "os": [ + "openbsd" + ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" + "node": ">=18" } }, - "node_modules/@img/sharp-win32-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", - "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", "cpu": [ - "arm64" + "x64" ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", + "dev": true, + "license": "MIT", "optional": true, "os": [ - "win32" + "openbsd" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" + "node": ">=18" } }, - "node_modules/@img/sharp-win32-ia32": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", - "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", "cpu": [ - "ia32" + "arm64" ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", + "dev": true, + "license": "MIT", "optional": true, "os": [ - "win32" + "openharmony" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" + "node": ">=18" } }, - "node_modules/@img/sharp-win32-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", - "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", "cpu": [ "x64" ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", + "dev": true, + "license": "MIT", "optional": true, "os": [ - "win32" + "sunos" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" + "node": ">=18" } }, - "node_modules/@inquirer/ansi": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.6.tgz", - "integrity": "sha512-I/INw4sHGlVZ/afZOckpLiDP9SmbMl1g/GCqeHjLw1Afw/0PlRs2tRFgTGWmdI0hoNuWZn3y2iHNmG1vyECyQQ==", + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=18" } }, - "node_modules/@inquirer/confirm": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-6.1.0.tgz", - "integrity": "sha512-USpeB76eqK7yGricDlGAupxWlp4a59qpeZOoNWaxO/nJln7agpJveyNkQ1d5u8YXG6TOqxZtQpKPORQQDrdVsA==", + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", - "dependencies": { - "@inquirer/core": "^11.2.0", - "@inquirer/type": "^4.0.6" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "node": ">=18" } }, - "node_modules/@inquirer/core": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-11.2.0.tgz", - "integrity": "sha512-joR1YS2sI0us+9d0I8ViqFbrRLONO8CFTuyvBX4ZVBSch+VsZiugUABdrhBXXJR1VyEzvpz5SQCix3keETQ58g==", + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/ansi": "^2.0.6", - "@inquirer/figures": "^2.0.6", - "@inquirer/type": "^4.0.6", - "cli-width": "^4.1.0", - "fast-wrap-ansi": "^0.2.0", - "mute-stream": "^4.0.0", - "signal-exit": "^4.1.0" + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "peerDependencies": { - "@types/node": ">=18" + "funding": { + "url": "https://opencollective.com/eslint" }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@inquirer/figures": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-2.0.6.tgz", - "integrity": "sha512-dsZgQtH2t5Q6ah3aPbZbeEZAxsD9qQu0DXf01AltuEfRTm+NoLN6+rLVbr+4edeEbNCp/wBNM6mALRWtsQpfkw==", + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@inquirer/type": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.0.6.tgz", - "integrity": "sha512-J+9tdxOskuYuGjsvGaq00AamhDgjR7anhEW2dP4QdQpFCMPngCeC/bCYWQ5NsMWZRdsy53is7kAHb/+7cwDk2g==", + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, "license": "MIT", "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" }, - "peerDependencies": { - "@types/node": ">=18" + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "node_modules/@eslint/eslintrc": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "node_modules/@eslint/js": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", "dev": true, "license": "MIT", "engines": { - "node": ">=6.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "devOptional": true, - "license": "MIT" + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@modelcontextprotocol/sdk": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", - "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", + "node_modules/@exodus/bytes": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz", + "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==", + "dev": true, "license": "MIT", - "dependencies": { - "@hono/node-server": "^1.19.9", - "ajv": "^8.17.1", - "ajv-formats": "^3.0.1", - "content-type": "^1.0.5", - "cors": "^2.8.5", - "cross-spawn": "^7.0.5", - "eventsource": "^3.0.2", - "eventsource-parser": "^3.0.0", - "express": "^5.2.1", - "express-rate-limit": "^8.2.1", - "hono": "^4.11.4", - "jose": "^6.1.3", - "json-schema-typed": "^8.0.2", - "pkce-challenge": "^5.0.0", - "raw-body": "^3.0.0", - "zod": "^3.25 || ^4.0", - "zod-to-json-schema": "^3.25.1" - }, "engines": { - "node": ">=18" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@cfworker/json-schema": "^4.1.1", - "zod": "^3.25 || ^4.0" + "@noble/hashes": "^1.8.0 || ^2.0.0" }, "peerDependenciesMeta": { - "@cfworker/json-schema": { + "@noble/hashes": { "optional": true - }, - "zod": { - "optional": false } } }, - "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@formatjs/fast-memoize": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.5.tgz", + "integrity": "sha512-KLi3fan6WnCHmigd9pmEEN8Hid0v4wiFBW576M/d07KMWYecf1CvyMI3n34vCmHT4AoVqG2n702kiHbXjzZX2A==", + "license": "MIT" + }, + "node_modules/@formatjs/icu-messageformat-parser": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.10.tgz", + "integrity": "sha512-XeJihYLy1lCe19xfK1KWKG/betBOK2rB0luL8lSkjfvJj0zP+LTJvkC+RKd0jsFI8mWxN71LrarHSrEXE8xxOQ==", "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "@formatjs/icu-skeleton-parser": "2.1.9" } }, - "node_modules/@modelcontextprotocol/sdk/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "node_modules/@formatjs/icu-skeleton-parser": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.9.tgz", + "integrity": "sha512-rsxswgHMfU1zUgB2byc08fesf83wLGjFnzLCEtuf00mx2doiqc6pYrf67raI37XqdRcGUviQepk2UKGqpng74Q==", "license": "MIT" }, - "node_modules/@mswjs/interceptors": { - "version": "0.41.9", - "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.41.9.tgz", - "integrity": "sha512-VVPPgHyQ6ShqnrmDWuxjmUIsO9gWyOZFmuOfLd9LfBGQJwZfy0gvv9pbHSJuoFNIYC7ZDX9aoFwowjcdSC4E8w==", - "dev": true, + "node_modules/@formatjs/intl-localematcher": { + "version": "0.8.9", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.8.9.tgz", + "integrity": "sha512-GmB0F/gYh4Hdl4rLWjgDsgT+x4pB54fkJeRh8kAZ4XFzKeCK8dGs+SBJWXO42QZtOUni+IDWKNuCw6wiL4lTvw==", "license": "MIT", "dependencies": { - "@open-draft/deferred-promise": "^2.2.0", - "@open-draft/logger": "^0.3.0", - "@open-draft/until": "^2.0.0", - "is-node-process": "^1.2.0", - "outvariant": "^1.4.3", - "strict-event-emitter": "^0.5.1" - }, + "@formatjs/fast-memoize": "3.1.5" + } + }, + "node_modules/@hono/node-server": { + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", + "license": "MIT", "engines": { - "node": ">=18" + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" } }, - "node_modules/@mswjs/interceptors/node_modules/@open-draft/deferred-promise": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", - "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, - "license": "MIT" + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", - "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "dev": true, - "license": "MIT", - "optional": true, + "license": "Apache-2.0", "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@tybys/wasm-util": "^0.10.0" + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" } }, - "node_modules/@next/env": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.6.tgz", - "integrity": "sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==", - "license": "MIT" + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } }, - "node_modules/@next/eslint-plugin-next": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.1.6.tgz", - "integrity": "sha512-/Qq3PTagA6+nYVfryAtQ7/9FEr/6YVyvOtl6rZnGsbReGLf0jZU6gkpr1FuChAQpvV46a78p4cmHOVP8mbfSMQ==", + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", "license": "MIT", - "dependencies": { - "fast-glob": "3.3.1" + "optional": true, + "engines": { + "node": ">=18" } }, - "node_modules/@next/swc-darwin-arm64": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.6.tgz", - "integrity": "sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw==", + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", "cpu": [ "arm64" ], - "license": "MIT", + "license": "Apache-2.0", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">= 10" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" } }, - "node_modules/@next/swc-darwin-x64": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.6.tgz", - "integrity": "sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ==", + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", "cpu": [ "x64" ], - "license": "MIT", + "license": "Apache-2.0", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">= 10" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" } }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.6.tgz", - "integrity": "sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==", + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", "cpu": [ "arm64" ], - "license": "MIT", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ - "linux" + "darwin" ], - "engines": { - "node": ">= 10" + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.6.tgz", - "integrity": "sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==", + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", "cpu": [ - "arm64" + "x64" ], - "license": "MIT", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ - "linux" + "darwin" ], - "engines": { - "node": ">= 10" + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.6.tgz", - "integrity": "sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==", + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", "cpu": [ - "x64" + "arm" ], - "license": "MIT", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" ], - "engines": { - "node": ">= 10" + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.6.tgz", - "integrity": "sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==", + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", "cpu": [ - "x64" + "arm64" ], - "license": "MIT", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" ], - "engines": { - "node": ">= 10" + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.6.tgz", - "integrity": "sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==", + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", "cpu": [ - "arm64" + "ppc64" ], - "license": "MIT", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ - "win32" + "linux" ], - "engines": { - "node": ">= 10" + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.6.tgz", - "integrity": "sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A==", + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", "cpu": [ - "x64" + "riscv64" ], - "license": "MIT", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ - "win32" + "linux" ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@noble/ciphers": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-2.2.0.tgz", - "integrity": "sha512-Z6pjIZ/8IJcCGzb2S/0Px5J81yij85xASuk1teLNeg75bfT07MV3a/O2Mtn1I2se43k3lkVEcFaR10N4cgQcZA==", - "license": "MIT", - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/hashes": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", - "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==", - "license": "MIT", - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nolyfill/is-core-module": { - "version": "1.0.39", - "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", - "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.4.0" - } - }, - "node_modules/@open-draft/deferred-promise": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-3.0.0.tgz", - "integrity": "sha512-XW375UK8/9SqUVNVa6M0yEy8+iTi4QN5VZ7aZuRFQmy76LRwI9wy5F4YIBU6T+eTe2/DNDo8tqu8RHlwLHM6RA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@open-draft/logger": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", - "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-node-process": "^1.2.0", - "outvariant": "^1.4.0" - } - }, - "node_modules/@open-draft/until": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", - "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@opentelemetry/api": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", - "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==", - "license": "Apache-2.0", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@opentelemetry/semantic-conventions": { - "version": "1.43.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.43.0.tgz", - "integrity": "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/@oxc-project/types": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.132.0.tgz", - "integrity": "sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==", - "devOptional": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, - "node_modules/@parcel/watcher": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", - "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.3", - "is-glob": "^4.0.3", - "node-addon-api": "^7.0.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">= 10.0.0" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "@parcel/watcher-android-arm64": "2.5.6", - "@parcel/watcher-darwin-arm64": "2.5.6", - "@parcel/watcher-darwin-x64": "2.5.6", - "@parcel/watcher-freebsd-x64": "2.5.6", - "@parcel/watcher-linux-arm-glibc": "2.5.6", - "@parcel/watcher-linux-arm-musl": "2.5.6", - "@parcel/watcher-linux-arm64-glibc": "2.5.6", - "@parcel/watcher-linux-arm64-musl": "2.5.6", - "@parcel/watcher-linux-x64-glibc": "2.5.6", - "@parcel/watcher-linux-x64-musl": "2.5.6", - "@parcel/watcher-win32-arm64": "2.5.6", - "@parcel/watcher-win32-ia32": "2.5.6", - "@parcel/watcher-win32-x64": "2.5.6" + "url": "https://opencollective.com/libvips" } }, - "node_modules/@parcel/watcher-android-arm64": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", - "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", "cpu": [ - "arm64" + "s390x" ], - "license": "MIT", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ - "android" + "linux" ], - "engines": { - "node": ">= 10.0.0" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/libvips" } }, - "node_modules/@parcel/watcher-darwin-arm64": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", - "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", "cpu": [ - "arm64" + "x64" ], - "license": "MIT", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ - "darwin" + "linux" ], - "engines": { - "node": ">= 10.0.0" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/libvips" } }, - "node_modules/@parcel/watcher-darwin-x64": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", - "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", "cpu": [ - "x64" + "arm64" ], - "license": "MIT", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ - "darwin" + "linux" ], - "engines": { - "node": ">= 10.0.0" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/libvips" } }, - "node_modules/@parcel/watcher-freebsd-x64": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", - "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", "cpu": [ "x64" ], - "license": "MIT", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ - "freebsd" + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" ], "engines": { - "node": ">= 10.0.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" } }, - "node_modules/@parcel/watcher-linux-arm-glibc": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", - "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", "cpu": [ - "arm" + "arm64" ], - "license": "MIT", + "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { - "node": ">= 10.0.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" } }, - "node_modules/@parcel/watcher-linux-arm-musl": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", - "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", "cpu": [ - "arm" + "ppc64" ], - "license": "MIT", + "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { - "node": ">= 10.0.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" } }, - "node_modules/@parcel/watcher-linux-arm64-glibc": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", - "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", "cpu": [ - "arm64" + "riscv64" ], - "license": "MIT", + "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { - "node": ">= 10.0.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" } }, - "node_modules/@parcel/watcher-linux-arm64-musl": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", - "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", "cpu": [ - "arm64" + "s390x" ], - "license": "MIT", + "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { - "node": ">= 10.0.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" } }, - "node_modules/@parcel/watcher-linux-x64-glibc": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", - "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", "cpu": [ "x64" ], - "license": "MIT", + "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { - "node": ">= 10.0.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" } }, - "node_modules/@parcel/watcher-linux-x64-musl": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", - "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", "cpu": [ "x64" ], - "license": "MIT", + "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { - "node": ">= 10.0.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" } }, - "node_modules/@parcel/watcher-win32-arm64": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", - "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", "cpu": [ "arm64" ], - "license": "MIT", + "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ "win32" ], "engines": { - "node": ">= 10.0.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/libvips" } }, - "node_modules/@parcel/watcher-win32-ia32": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", - "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", "cpu": [ "ia32" ], - "license": "MIT", + "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ "win32" ], "engines": { - "node": ">= 10.0.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/libvips" } }, - "node_modules/@parcel/watcher-win32-x64": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", - "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", "cpu": [ "x64" ], - "license": "MIT", + "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ "win32" ], "engines": { - "node": ">= 10.0.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/libvips" } }, - "node_modules/@parcel/watcher/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "node_modules/@inquirer/ansi": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.6.tgz", + "integrity": "sha512-I/INw4sHGlVZ/afZOckpLiDP9SmbMl1g/GCqeHjLw1Afw/0PlRs2tRFgTGWmdI0hoNuWZn3y2iHNmG1vyECyQQ==", + "dev": true, "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" } }, - "node_modules/@playwright/test": { - "version": "1.60.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.60.0.tgz", - "integrity": "sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==", - "devOptional": true, - "license": "Apache-2.0", - "peer": true, + "node_modules/@inquirer/confirm": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-6.1.0.tgz", + "integrity": "sha512-USpeB76eqK7yGricDlGAupxWlp4a59qpeZOoNWaxO/nJln7agpJveyNkQ1d5u8YXG6TOqxZtQpKPORQQDrdVsA==", + "dev": true, + "license": "MIT", "dependencies": { - "playwright": "1.60.0" - }, - "bin": { - "playwright": "cli.js" + "@inquirer/core": "^11.2.0", + "@inquirer/type": "^4.0.6" }, "engines": { - "node": ">=18" + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.2.tgz", - "integrity": "sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==", - "cpu": [ - "arm64" - ], + "node_modules/@inquirer/core": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-11.2.0.tgz", + "integrity": "sha512-joR1YS2sI0us+9d0I8ViqFbrRLONO8CFTuyvBX4ZVBSch+VsZiugUABdrhBXXJR1VyEzvpz5SQCix3keETQ58g==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "dependencies": { + "@inquirer/ansi": "^2.0.6", + "@inquirer/figures": "^2.0.6", + "@inquirer/type": "^4.0.6", + "cli-width": "^4.1.0", + "fast-wrap-ansi": "^0.2.0", + "mute-stream": "^4.0.0", + "signal-exit": "^4.1.0" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.2.tgz", - "integrity": "sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w==", - "cpu": [ - "arm64" - ], + "node_modules/@inquirer/figures": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-2.0.6.tgz", + "integrity": "sha512-dsZgQtH2t5Q6ah3aPbZbeEZAxsD9qQu0DXf01AltuEfRTm+NoLN6+rLVbr+4edeEbNCp/wBNM6mALRWtsQpfkw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" } }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.2.tgz", - "integrity": "sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA==", - "cpu": [ - "x64" - ], + "node_modules/@inquirer/type": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.0.6.tgz", + "integrity": "sha512-J+9tdxOskuYuGjsvGaq00AamhDgjR7anhEW2dP4QdQpFCMPngCeC/bCYWQ5NsMWZRdsy53is7kAHb/+7cwDk2g==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.2.tgz", - "integrity": "sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA==", - "cpu": [ - "x64" - ], + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.2.tgz", - "integrity": "sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w==", - "cpu": [ - "arm" - ], + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.2.tgz", - "integrity": "sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig==", - "cpu": [ - "arm64" - ], + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.0.0" } }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.2.tgz", - "integrity": "sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "devOptional": true, + "license": "MIT" }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.2.tgz", - "integrity": "sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==", - "cpu": [ - "ppc64" - ], + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.2.tgz", - "integrity": "sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==", - "cpu": [ - "s390x" - ], - "dev": true, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } } }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.2.tgz", - "integrity": "sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.2.tgz", - "integrity": "sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==", - "cpu": [ - "x64" - ], + "node_modules/@modelcontextprotocol/sdk/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/@mswjs/interceptors": { + "version": "0.41.9", + "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.41.9.tgz", + "integrity": "sha512-VVPPgHyQ6ShqnrmDWuxjmUIsO9gWyOZFmuOfLd9LfBGQJwZfy0gvv9pbHSJuoFNIYC7ZDX9aoFwowjcdSC4E8w==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@open-draft/deferred-promise": "^2.2.0", + "@open-draft/logger": "^0.3.0", + "@open-draft/until": "^2.0.0", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "strict-event-emitter": "^0.5.1" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" } }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.2.tgz", - "integrity": "sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==", - "cpu": [ - "arm64" - ], + "node_modules/@mswjs/interceptors/node_modules/@open-draft/deferred-promise": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", + "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + "license": "MIT" }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.2.tgz", - "integrity": "sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ==", - "cpu": [ - "wasm32" - ], + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" } }, - "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", - "dev": true, + "node_modules/@neondatabase/serverless": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@neondatabase/serverless/-/serverless-1.1.0.tgz", + "integrity": "sha512-r3ZZhRjEcfEdKIZnoB1RusNgvHuaBRqfCzV4Gi+5A9yUX0S4HTws/ASWqt13wL4y4I+0rqsWGdA2w7EQXHi3+Q==", "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" + "peer": true, + "engines": { + "node": ">=19.0.0" } }, - "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", - "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "node_modules/@next/env": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.6.tgz", + "integrity": "sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==", + "license": "MIT" + }, + "node_modules/@next/eslint-plugin-next": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.1.6.tgz", + "integrity": "sha512-/Qq3PTagA6+nYVfryAtQ7/9FEr/6YVyvOtl6rZnGsbReGLf0jZU6gkpr1FuChAQpvV46a78p4cmHOVP8mbfSMQ==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "@tybys/wasm-util": "^0.10.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" + "fast-glob": "3.3.1" } }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.2.tgz", - "integrity": "sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A==", + "node_modules/@next/swc-darwin-arm64": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.6.tgz", + "integrity": "sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "win32" + "darwin" ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">= 10" } }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.2.tgz", - "integrity": "sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ==", + "node_modules/@next/swc-darwin-x64": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.6.tgz", + "integrity": "sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "win32" + "darwin" ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">= 10" } }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", - "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/@rtsao/scc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", - "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", - "dev": true, - "license": "MIT" - }, - "node_modules/@schummar/icu-type-parser": { - "version": "1.21.5", - "resolved": "https://registry.npmjs.org/@schummar/icu-type-parser/-/icu-type-parser-1.21.5.tgz", - "integrity": "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==", - "license": "MIT" - }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "license": "MIT" - }, - "node_modules/@swc/core-darwin-arm64": { - "version": "1.15.40", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.40.tgz", - "integrity": "sha512-PaYyclfmQ++77D8ityYvmmVzHv9aG8ROwt2GfG6/ccloy4Hgf80qtOnzb9VYvPsUT7Ty1uhuDRhv3XYpf62qhQ==", + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.6.tgz", + "integrity": "sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==", "cpu": [ "arm64" ], - "license": "Apache-2.0 AND MIT", + "license": "MIT", "optional": true, "os": [ - "darwin" + "linux" ], "engines": { - "node": ">=10" + "node": ">= 10" } }, - "node_modules/@swc/core-darwin-x64": { - "version": "1.15.40", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.40.tgz", - "integrity": "sha512-HbbPzvfLBUXjIB1Ezks+//lNUjmLjfyd63XSwprJgrZaXYdm70kohXPJUWdqKZozolFxbPaO+xtBaiUp6BoueA==", + "node_modules/@next/swc-linux-arm64-musl": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.6.tgz", + "integrity": "sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==", "cpu": [ - "x64" + "arm64" ], - "license": "Apache-2.0 AND MIT", + "license": "MIT", "optional": true, "os": [ - "darwin" + "linux" ], "engines": { - "node": ">=10" + "node": ">= 10" } }, - "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.15.40", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.40.tgz", - "integrity": "sha512-SlRZsCjOCPR2LvFs0Ri/Xrx/5o5TCt8vl4gW6mX1hEZOG0a625RxzRHpHdAQNGykmAN/7IeaFAJG+QnNmxlHcA==", + "node_modules/@next/swc-linux-x64-gnu": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.6.tgz", + "integrity": "sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==", "cpu": [ - "arm" + "x64" ], - "license": "Apache-2.0", + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=10" + "node": ">= 10" } }, - "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.15.40", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.40.tgz", - "integrity": "sha512-Q8byxJt2fh8CR3EUX6snBpy47AoBVm+In/+Z3rjDHMjC38ZvR9/gtUUNCT0tfrn4EdVsO8/QPi59nxrxvqxvBQ==", + "node_modules/@next/swc-linux-x64-musl": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.6.tgz", + "integrity": "sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==", "cpu": [ - "arm64" + "x64" ], - "license": "Apache-2.0 AND MIT", + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=10" + "node": ">= 10" } }, - "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.15.40", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.40.tgz", - "integrity": "sha512-4z0MgHU+7M0pZDqBN1El7mFXDI1SBwinfcUkAyA4v8QrhOIUOZltySt2aStQLZGrdXVXM4Y4ylfiTC04ED+MoQ==", + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.6.tgz", + "integrity": "sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==", "cpu": [ "arm64" ], - "license": "Apache-2.0 AND MIT", + "license": "MIT", "optional": true, "os": [ - "linux" + "win32" ], "engines": { - "node": ">=10" + "node": ">= 10" } }, - "node_modules/@swc/core-linux-ppc64-gnu": { - "version": "1.15.40", - "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.40.tgz", - "integrity": "sha512-fLI4iUgeSZu0eRWUXwe6YzPFx9gHbFiPkl8Rp3mJfP8OpNR3nTQCGPvHdDh9xniW7mVvgMY4ni7A4VzqI1KrpA==", + "node_modules/@next/swc-win32-x64-msvc": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.6.tgz", + "integrity": "sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A==", "cpu": [ - "ppc64" + "x64" ], - "license": "Apache-2.0 AND MIT", + "license": "MIT", "optional": true, "os": [ - "linux" + "win32" ], "engines": { - "node": ">=10" + "node": ">= 10" } }, - "node_modules/@swc/core-linux-s390x-gnu": { - "version": "1.15.40", - "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.40.tgz", - "integrity": "sha512-YqeKMAb7d4nQSGMJQ454IlaCENpzcDqhvBE9+CPfdnYpnUXxd+BSrB6Xk0YjW8UyoEhUj4p6quATCxbsp6J3jg==", - "cpu": [ - "s390x" - ], - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/@noble/ciphers": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-2.2.0.tgz", + "integrity": "sha512-Z6pjIZ/8IJcCGzb2S/0Px5J81yij85xASuk1teLNeg75bfT07MV3a/O2Mtn1I2se43k3lkVEcFaR10N4cgQcZA==", + "license": "MIT", "engines": { - "node": ">=10" + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.15.40", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.40.tgz", - "integrity": "sha512-7HOuS1iGcme/j/TuL1TfmmLGiMQrjv/GmjyZeydl00FKPtpGXEldwqfI56xgd1YzrzoB2svWjxbGGyQ0TEASxg==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/@noble/hashes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", + "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==", + "license": "MIT", "engines": { - "node": ">=10" + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@swc/core-linux-x64-musl": { - "version": "1.15.40", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.40.tgz", - "integrity": "sha512-h4kZYHc7dpc9P9u4brRJaS8Pl7tPVHAeiLSzw7T5RfIJgAoSdaCMKzI/2Uay9gFhaw8uyCDl0L5q37r0EpAfIA==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, "engines": { - "node": ">=10" + "node": ">= 8" } }, - "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.15.40", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.40.tgz", - "integrity": "sha512-+mQgKZXSj6mV38Zh05QaxSjUDmGP/R2JWlXZTDLSPkDzHU6p3GxN9eeSf5dfyDVU86946fmCvSzyl/ucImx8+A==", + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.4.0" + } + }, + "node_modules/@open-draft/deferred-promise": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-3.0.0.tgz", + "integrity": "sha512-XW375UK8/9SqUVNVa6M0yEy8+iTi4QN5VZ7aZuRFQmy76LRwI9wy5F4YIBU6T+eTe2/DNDo8tqu8RHlwLHM6RA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@open-draft/logger": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", + "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-node-process": "^1.2.0", + "outvariant": "^1.4.0" + } + }, + "node_modules/@open-draft/until": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", + "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", + "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.43.0.tgz", + "integrity": "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.132.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.132.0.tgz", + "integrity": "sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==", + "devOptional": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", + "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.6", + "@parcel/watcher-darwin-arm64": "2.5.6", + "@parcel/watcher-darwin-x64": "2.5.6", + "@parcel/watcher-freebsd-x64": "2.5.6", + "@parcel/watcher-linux-arm-glibc": "2.5.6", + "@parcel/watcher-linux-arm-musl": "2.5.6", + "@parcel/watcher-linux-arm64-glibc": "2.5.6", + "@parcel/watcher-linux-arm64-musl": "2.5.6", + "@parcel/watcher-linux-x64-glibc": "2.5.6", + "@parcel/watcher-linux-x64-musl": "2.5.6", + "@parcel/watcher-win32-arm64": "2.5.6", + "@parcel/watcher-win32-ia32": "2.5.6", + "@parcel/watcher-win32-x64": "2.5.6" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", + "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", "cpu": [ "arm64" ], - "license": "Apache-2.0 AND MIT", + "license": "MIT", "optional": true, "os": [ - "win32" + "android" ], "engines": { - "node": ">=10" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.15.40", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.40.tgz", - "integrity": "sha512-yvwdPLGd25mcj/mNatjNQ0lZujtQD6psH3v9PNmMb+fSzjbNG8KIDxjFWrcV+fsFVLOkyOmdJsFmX7NAFjVyPw==", + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", + "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", "cpu": [ - "ia32" + "arm64" ], - "license": "Apache-2.0 AND MIT", + "license": "MIT", "optional": true, "os": [ - "win32" + "darwin" ], "engines": { - "node": ">=10" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.15.40", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.40.tgz", - "integrity": "sha512-OXtKsLU1bVtInzzDEAY2sYiF/rl4tvAnLLLpuMp3HzAOQZ5A+i69AKDhA1YLQTaMAqO3vzyYNVAYVRMPtSYD4w==", + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", + "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", "cpu": [ "x64" ], - "license": "Apache-2.0 AND MIT", + "license": "MIT", "optional": true, "os": [ - "win32" + "darwin" ], "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/counter": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", - "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", - "license": "Apache-2.0" - }, - "node_modules/@swc/helpers": { - "version": "0.5.15", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", - "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.8.0" - } - }, - "node_modules/@swc/types": { - "version": "0.1.26", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.26.tgz", - "integrity": "sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==", - "license": "Apache-2.0", - "dependencies": { - "@swc/counter": "^0.1.3" - } - }, - "node_modules/@tailwindcss/node": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.0.tgz", - "integrity": "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "enhanced-resolve": "^5.21.0", - "jiti": "^2.6.1", - "lightningcss": "1.32.0", - "magic-string": "^0.30.21", - "source-map-js": "^1.2.1", - "tailwindcss": "4.3.0" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@tailwindcss/oxide": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.0.tgz", - "integrity": "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==", - "dev": true, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", + "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", + "cpu": [ + "x64" + ], "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">= 20" + "node": ">= 10.0.0" }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.3.0", - "@tailwindcss/oxide-darwin-arm64": "4.3.0", - "@tailwindcss/oxide-darwin-x64": "4.3.0", - "@tailwindcss/oxide-freebsd-x64": "4.3.0", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.0", - "@tailwindcss/oxide-linux-arm64-gnu": "4.3.0", - "@tailwindcss/oxide-linux-arm64-musl": "4.3.0", - "@tailwindcss/oxide-linux-x64-gnu": "4.3.0", - "@tailwindcss/oxide-linux-x64-musl": "4.3.0", - "@tailwindcss/oxide-wasm32-wasi": "4.3.0", - "@tailwindcss/oxide-win32-arm64-msvc": "4.3.0", - "@tailwindcss/oxide-win32-x64-msvc": "4.3.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.0.tgz", - "integrity": "sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==", + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", + "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", "cpu": [ - "arm64" + "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "android" + "linux" ], "engines": { - "node": ">= 20" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.0.tgz", - "integrity": "sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==", + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", + "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", "cpu": [ - "arm64" + "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "darwin" + "linux" ], "engines": { - "node": ">= 20" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.0.tgz", - "integrity": "sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==", + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", + "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", "cpu": [ - "x64" + "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "darwin" + "linux" ], "engines": { - "node": ">= 20" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.0.tgz", - "integrity": "sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==", + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", + "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", "cpu": [ - "x64" + "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "freebsd" + "linux" ], "engines": { - "node": ">= 20" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.0.tgz", - "integrity": "sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==", + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", + "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", "cpu": [ - "arm" + "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">= 20" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.0.tgz", - "integrity": "sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==", + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", + "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", "cpu": [ - "arm64" + "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">= 20" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.0.tgz", - "integrity": "sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==", + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", + "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" + "win32" ], "engines": { - "node": ">= 20" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.0.tgz", - "integrity": "sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==", + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", + "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", "cpu": [ - "x64" + "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" + "win32" ], "engines": { - "node": ">= 20" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.0.tgz", - "integrity": "sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==", + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", + "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" + "win32" ], "engines": { - "node": ">= 20" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.0.tgz", - "integrity": "sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "dev": true, + "node_modules/@parcel/watcher/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", - "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@playwright/test": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.60.0.tgz", + "integrity": "sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==", + "devOptional": true, + "license": "Apache-2.0", + "peer": true, "dependencies": { - "@emnapi/core": "^1.10.0", - "@emnapi/runtime": "^1.10.0", - "@emnapi/wasi-threads": "^1.2.1", - "@napi-rs/wasm-runtime": "^1.1.4", - "@tybys/wasm-util": "^0.10.1", - "tslib": "^2.8.1" + "playwright": "1.60.0" + }, + "bin": { + "playwright": "cli.js" }, "engines": { - "node": ">=14.0.0" + "node": ">=18" } }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.0.tgz", - "integrity": "sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==", + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.2.tgz", + "integrity": "sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==", "cpu": [ "arm64" ], @@ -3162,625 +3316,568 @@ "license": "MIT", "optional": true, "os": [ - "win32" + "android" ], "engines": { - "node": ">= 20" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.0.tgz", - "integrity": "sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==", + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.2.tgz", + "integrity": "sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w==", "cpu": [ - "x64" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "win32" + "darwin" ], "engines": { - "node": ">= 20" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@tailwindcss/postcss": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.3.0.tgz", - "integrity": "sha512-Jm05Tjx+9yCLGv5qw1c+84Psds8MnyrEQYCB+FFk2lgGiUjlRqdxke4mVTuYrj2xnVZqKim2Apr5ySuQRYAw/w==", + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.2.tgz", + "integrity": "sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@alloc/quick-lru": "^5.2.0", - "@tailwindcss/node": "4.3.0", - "@tailwindcss/oxide": "4.3.0", - "postcss": "^8.5.10", - "tailwindcss": "4.3.0" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@testing-library/dom": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", - "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.2.tgz", + "integrity": "sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^5.0.1", - "aria-query": "5.3.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.5.0", - "picocolors": "1.1.1", - "pretty-format": "^27.0.2" - }, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@testing-library/dom/node_modules/aria-query": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", - "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.2.tgz", + "integrity": "sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w==", + "cpu": [ + "arm" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "dequal": "^2.0.3" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@testing-library/jest-dom": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", - "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.2.tgz", + "integrity": "sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@adobe/css-tools": "^4.4.0", - "aria-query": "^5.0.0", - "css.escape": "^1.5.1", - "dom-accessibility-api": "^0.6.3", - "picocolors": "^1.1.1", - "redent": "^3.0.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=14", - "npm": ">=6", - "yarn": ">=1" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", - "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.2.tgz", + "integrity": "sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@testing-library/react": { - "version": "16.3.2", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", - "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.2.tgz", + "integrity": "sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.5" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@testing-library/dom": "^10.0.0", - "@types/react": "^18.0.0 || ^19.0.0", - "@types/react-dom": "^18.0.0 || ^19.0.0", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@testing-library/user-event": { - "version": "14.6.1", - "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", - "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.2.tgz", + "integrity": "sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==", + "cpu": [ + "s390x" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=12", - "npm": ">=6" - }, - "peerDependencies": { - "@testing-library/dom": ">=7.21.4" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", - "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.2.tgz", + "integrity": "sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", "optional": true, - "dependencies": { - "tslib": "^2.4.0" + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@types/aria-query": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", - "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/chai": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" - } - }, - "node_modules/@types/debug": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", - "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", - "license": "MIT", - "dependencies": { - "@types/ms": "*" - } - }, - "node_modules/@types/deep-eql": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", - "license": "MIT" - }, - "node_modules/@types/estree-jsx": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", - "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", - "license": "MIT", - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/@types/hast": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", - "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.2.tgz", + "integrity": "sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT" - }, - "node_modules/@types/mdast": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", - "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", "license": "MIT", - "dependencies": { - "@types/unist": "*" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@types/ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "20.19.41", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz", - "integrity": "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==", + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.2.tgz", + "integrity": "sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@types/qrcode": { - "version": "1.5.6", - "resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.6.tgz", - "integrity": "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==", + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.2.tgz", + "integrity": "sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ==", + "cpu": [ + "wasm32" + ], "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/react": { - "version": "19.2.14", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", - "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", - "license": "MIT", - "peer": true, - "dependencies": { - "csstype": "^3.2.2" - } - }, - "node_modules/@types/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "dev": true, - "license": "MIT", - "peer": true, - "peerDependencies": { - "@types/react": "^19.2.0" + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@types/set-cookie-parser": { - "version": "2.4.10", - "resolved": "https://registry.npmjs.org/@types/set-cookie-parser/-/set-cookie-parser-2.4.10.tgz", - "integrity": "sha512-GGmQVGpQWUe5qglJozEjZV/5dyxbOOZ0LHe/lqyWssB88Y4svNfst0uqBVscdDeIKl5Jy5+aPSvy7mI9tYRguw==", + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@types/node": "*" + "tslib": "^2.4.0" } }, - "node_modules/@types/statuses": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.6.tgz", - "integrity": "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/unist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", - "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", - "license": "MIT" - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.3.tgz", - "integrity": "sha512-PwFvSKsXGShKGW6n5bZOhGHEcCZXM8HofLK9fNsEwZXzFRjoY+XT1Vsf1zgyXdwTr0ZYz1/2tkZ0DBTT9jZjhw==", + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.59.3", - "@typescript-eslint/type-utils": "8.59.3", - "@typescript-eslint/utils": "8.59.3", - "@typescript-eslint/visitor-keys": "8.59.3", - "ignore": "^7.0.5", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "@tybys/wasm-util": "^0.10.1" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.59.3", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.2.tgz", + "integrity": "sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 4" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@typescript-eslint/parser": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.3.tgz", - "integrity": "sha512-HPwA+hVkfcriajbNvTmZv4VRauibay+cWArYUYq7u7W7PmGShMxbPxLvrwDme55a6d5alG3nrYfhyJ/G28XlLg==", + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.2.tgz", + "integrity": "sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@typescript-eslint/scope-manager": "8.59.3", - "@typescript-eslint/types": "8.59.3", - "@typescript-eslint/typescript-estree": "8.59.3", - "@typescript-eslint/visitor-keys": "8.59.3", - "debug": "^4.4.3" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.3.tgz", - "integrity": "sha512-ECiUWa/KYRGDFUqTNehaRgzDshnJfkTABJxVemHk4ko22gcr0ukloKjWvyQ64g8YCV/UI47kN1dbmjf/GaQYng==", + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.59.3", - "@typescript-eslint/types": "^8.59.3", - "debug": "^4.4.3" - }, + "license": "MIT" + }, + "node_modules/@schummar/icu-type-parser": { + "version": "1.21.5", + "resolved": "https://registry.npmjs.org/@schummar/icu-type-parser/-/icu-type-parser-1.21.5.tgz", + "integrity": "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==", + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.15.40", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.40.tgz", + "integrity": "sha512-PaYyclfmQ++77D8ityYvmmVzHv9aG8ROwt2GfG6/ccloy4Hgf80qtOnzb9VYvPsUT7Ty1uhuDRhv3XYpf62qhQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" + "node": ">=10" } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.3.tgz", - "integrity": "sha512-t2LvZnoEfzKtnPjgeEu41xw5gxq9mQVfYy4OoZ4Vlt0sk3JwxmhCca/AR7DwOiHrjWgjAj6as4AhRLKSDfvZIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.59.3", - "@typescript-eslint/visitor-keys": "8.59.3" - }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.15.40", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.40.tgz", + "integrity": "sha512-HbbPzvfLBUXjIB1Ezks+//lNUjmLjfyd63XSwprJgrZaXYdm70kohXPJUWdqKZozolFxbPaO+xtBaiUp6BoueA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=10" } }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.3.tgz", - "integrity": "sha512-PcIJHjmaREXLgIAIzLnSY9VucEzz8FKXsRgFa1DmdGCK/5tJpW03TKJF01Q6VZd1lLdz2sIKPWaDUZN9dp//dw==", - "dev": true, - "license": "MIT", + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.15.40", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.40.tgz", + "integrity": "sha512-SlRZsCjOCPR2LvFs0Ri/Xrx/5o5TCt8vl4gW6mX1hEZOG0a625RxzRHpHdAQNGykmAN/7IeaFAJG+QnNmxlHcA==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" + "node": ">=10" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.3.tgz", - "integrity": "sha512-g71d8QD8UaiHGvrJwyIS1hCX5r63w6Jll+4VEYhEAHXTDIqX1JgxhTAbEHtKntL9kuc4jRo7/GWw5xfCepSccQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.59.3", - "@typescript-eslint/typescript-estree": "8.59.3", - "@typescript-eslint/utils": "8.59.3", - "debug": "^4.4.3", - "ts-api-utils": "^2.5.0" - }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.15.40", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.40.tgz", + "integrity": "sha512-Q8byxJt2fh8CR3EUX6snBpy47AoBVm+In/+Z3rjDHMjC38ZvR9/gtUUNCT0tfrn4EdVsO8/QPi59nxrxvqxvBQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" + "node": ">=10" } }, - "node_modules/@typescript-eslint/types": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.3.tgz", - "integrity": "sha512-ePFoH0g4ludssdRFqqDxQePCxU4WQyRa9+XVwjm7yLn0FKhMeoetC+qBEEI1Eyb1pGSDveTIT09Bvw2WhlGayg==", - "dev": true, - "license": "MIT", + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.15.40", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.40.tgz", + "integrity": "sha512-4z0MgHU+7M0pZDqBN1El7mFXDI1SBwinfcUkAyA4v8QrhOIUOZltySt2aStQLZGrdXVXM4Y4ylfiTC04ED+MoQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=10" } }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.3.tgz", - "integrity": "sha512-CbRjVRAf7Lr9Kr8RopKcbY45p2VfmmHrm0ygOCYFi7oU8q19m0Fs/6iHS7kNOmwpp+ob07ZVcAqlxUod9lYdmg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.59.3", - "@typescript-eslint/tsconfig-utils": "8.59.3", - "@typescript-eslint/types": "8.59.3", - "@typescript-eslint/visitor-keys": "8.59.3", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.5.0" - }, + "node_modules/@swc/core-linux-ppc64-gnu": { + "version": "1.15.40", + "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.40.tgz", + "integrity": "sha512-fLI4iUgeSZu0eRWUXwe6YzPFx9gHbFiPkl8Rp3mJfP8OpNR3nTQCGPvHdDh9xniW7mVvgMY4ni7A4VzqI1KrpA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" + "node": ">=10" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", + "node_modules/@swc/core-linux-s390x-gnu": { + "version": "1.15.40", + "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.40.tgz", + "integrity": "sha512-YqeKMAb7d4nQSGMJQ454IlaCENpzcDqhvBE9+CPfdnYpnUXxd+BSrB6Xk0YjW8UyoEhUj4p6quATCxbsp6J3jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "18 || 20 || >=22" + "node": ">=10" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.15.40", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.40.tgz", + "integrity": "sha512-7HOuS1iGcme/j/TuL1TfmmLGiMQrjv/GmjyZeydl00FKPtpGXEldwqfI56xgd1YzrzoB2svWjxbGGyQ0TEASxg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "18 || 20 || >=22" + "node": ">=10" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.5" - }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.15.40", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.40.tgz", + "integrity": "sha512-h4kZYHc7dpc9P9u4brRJaS8Pl7tPVHAeiLSzw7T5RfIJgAoSdaCMKzI/2Uay9gFhaw8uyCDl0L5q37r0EpAfIA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=10" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.15.40", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.40.tgz", + "integrity": "sha512-+mQgKZXSj6mV38Zh05QaxSjUDmGP/R2JWlXZTDLSPkDzHU6p3GxN9eeSf5dfyDVU86946fmCvSzyl/ucImx8+A==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { "node": ">=10" } }, - "node_modules/@typescript-eslint/utils": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.3.tgz", - "integrity": "sha512-JAvT14goBzRzzzZyqq3P9BLArIxTtQURUtFgQ/V7FO+eU+Gg6ES+5ymOPP1wRxXcxAYeivCk4uS3jCKWI1K8Zg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.59.3", - "@typescript-eslint/types": "8.59.3", - "@typescript-eslint/typescript-estree": "8.59.3" - }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.15.40", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.40.tgz", + "integrity": "sha512-yvwdPLGd25mcj/mNatjNQ0lZujtQD6psH3v9PNmMb+fSzjbNG8KIDxjFWrcV+fsFVLOkyOmdJsFmX7NAFjVyPw==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" + "node": ">=10" } }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.3.tgz", - "integrity": "sha512-f1UQF7ggd42YiwI5wGrRaPsa+P0CINBlrkLPmGfpq/u/I/oVtecoEIfFR9ag/oa1sLOsRNZ6xehf6qMZhQGBDg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.59.3", - "eslint-visitor-keys": "^5.0.0" - }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.15.40", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.40.tgz", + "integrity": "sha512-OXtKsLU1bVtInzzDEAY2sYiF/rl4tvAnLLLpuMp3HzAOQZ5A+i69AKDhA1YLQTaMAqO3vzyYNVAYVRMPtSYD4w==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=10" } }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", - "dev": true, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "license": "Apache-2.0" + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "dependencies": { + "tslib": "^2.8.0" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", - "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", - "license": "ISC" + "node_modules/@swc/types": { + "version": "0.1.26", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.26.tgz", + "integrity": "sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==", + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } }, - "node_modules/@unrs/resolver-binding-android-arm-eabi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", - "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", - "cpu": [ - "arm" - ], + "node_modules/@tailwindcss/node": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.0.tgz", + "integrity": "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ] + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.21.0", + "jiti": "^2.6.1", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.0" + } }, - "node_modules/@unrs/resolver-binding-android-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", - "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "node_modules/@tailwindcss/oxide": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.0.tgz", + "integrity": "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.0", + "@tailwindcss/oxide-darwin-arm64": "4.3.0", + "@tailwindcss/oxide-darwin-x64": "4.3.0", + "@tailwindcss/oxide-freebsd-x64": "4.3.0", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.0", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.0", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.0", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.0", + "@tailwindcss/oxide-linux-x64-musl": "4.3.0", + "@tailwindcss/oxide-wasm32-wasi": "4.3.0", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.0", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.0" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.0.tgz", + "integrity": "sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==", "cpu": [ "arm64" ], @@ -3789,12 +3886,15 @@ "optional": true, "os": [ "android" - ] + ], + "engines": { + "node": ">= 20" + } }, - "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", - "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.0.tgz", + "integrity": "sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==", "cpu": [ "arm64" ], @@ -3803,12 +3903,15 @@ "optional": true, "os": [ "darwin" - ] + ], + "engines": { + "node": ">= 20" + } }, - "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", - "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.0.tgz", + "integrity": "sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==", "cpu": [ "x64" ], @@ -3817,12 +3920,15 @@ "optional": true, "os": [ "darwin" - ] + ], + "engines": { + "node": ">= 20" + } }, - "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", - "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.0.tgz", + "integrity": "sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==", "cpu": [ "x64" ], @@ -3831,12 +3937,15 @@ "optional": true, "os": [ "freebsd" - ] + ], + "engines": { + "node": ">= 20" + } }, - "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", - "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.0.tgz", + "integrity": "sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==", "cpu": [ "arm" ], @@ -3845,26 +3954,15 @@ "optional": true, "os": [ "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", - "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", - "cpu": [ - "arm" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": ">= 20" + } }, - "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", - "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.0.tgz", + "integrity": "sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==", "cpu": [ "arm64" ], @@ -3873,12 +3971,15 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": ">= 20" + } }, - "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", - "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.0.tgz", + "integrity": "sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==", "cpu": [ "arm64" ], @@ -3887,68 +3988,96 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": ">= 20" + } }, - "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", - "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.0.tgz", + "integrity": "sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==", "cpu": [ - "ppc64" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": ">= 20" + } }, - "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", - "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.0.tgz", + "integrity": "sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==", "cpu": [ - "riscv64" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": ">= 20" + } }, - "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", - "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.0.tgz", + "integrity": "sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], "cpu": [ - "riscv64" + "wasm32" ], "dev": true, "license": "MIT", "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@emnapi/core": "^1.10.0", + "@emnapi/runtime": "^1.10.0", + "@emnapi/wasi-threads": "^1.2.1", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } }, - "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", - "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.0.tgz", + "integrity": "sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==", "cpu": [ - "s390x" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" - ] + "win32" + ], + "engines": { + "node": ">= 20" + } }, - "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", - "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.0.tgz", + "integrity": "sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==", "cpu": [ "x64" ], @@ -3956,545 +4085,1816 @@ "license": "MIT", "optional": true, "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", - "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", - "cpu": [ - "x64" + "win32" ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.3.0.tgz", + "integrity": "sha512-Jm05Tjx+9yCLGv5qw1c+84Psds8MnyrEQYCB+FFk2lgGiUjlRqdxke4mVTuYrj2xnVZqKim2Apr5ySuQRYAw/w==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.3.0", + "@tailwindcss/oxide": "4.3.0", + "postcss": "^8.5.10", + "tailwindcss": "4.3.0" + } }, - "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", - "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", - "cpu": [ - "wasm32" - ], + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, "license": "MIT", - "optional": true, + "peer": true, "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.11" + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18" } }, - "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", - "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", - "cpu": [ - "arm64" - ], + "node_modules/@testing-library/dom/node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } }, - "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", - "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", - "cpu": [ - "ia32" - ], + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@testing-library/user-event": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.41", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz", + "integrity": "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/qrcode": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.6.tgz", + "integrity": "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/set-cookie-parser": { + "version": "2.4.10", + "resolved": "https://registry.npmjs.org/@types/set-cookie-parser/-/set-cookie-parser-2.4.10.tgz", + "integrity": "sha512-GGmQVGpQWUe5qglJozEjZV/5dyxbOOZ0LHe/lqyWssB88Y4svNfst0uqBVscdDeIKl5Jy5+aPSvy7mI9tYRguw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/statuses": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.6.tgz", + "integrity": "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.3.tgz", + "integrity": "sha512-PwFvSKsXGShKGW6n5bZOhGHEcCZXM8HofLK9fNsEwZXzFRjoY+XT1Vsf1zgyXdwTr0ZYz1/2tkZ0DBTT9jZjhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.59.3", + "@typescript-eslint/type-utils": "8.59.3", + "@typescript-eslint/utils": "8.59.3", + "@typescript-eslint/visitor-keys": "8.59.3", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.59.3", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.3.tgz", + "integrity": "sha512-HPwA+hVkfcriajbNvTmZv4VRauibay+cWArYUYq7u7W7PmGShMxbPxLvrwDme55a6d5alG3nrYfhyJ/G28XlLg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.59.3", + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/typescript-estree": "8.59.3", + "@typescript-eslint/visitor-keys": "8.59.3", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.3.tgz", + "integrity": "sha512-ECiUWa/KYRGDFUqTNehaRgzDshnJfkTABJxVemHk4ko22gcr0ukloKjWvyQ64g8YCV/UI47kN1dbmjf/GaQYng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.59.3", + "@typescript-eslint/types": "^8.59.3", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.3.tgz", + "integrity": "sha512-t2LvZnoEfzKtnPjgeEu41xw5gxq9mQVfYy4OoZ4Vlt0sk3JwxmhCca/AR7DwOiHrjWgjAj6as4AhRLKSDfvZIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/visitor-keys": "8.59.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.3.tgz", + "integrity": "sha512-PcIJHjmaREXLgIAIzLnSY9VucEzz8FKXsRgFa1DmdGCK/5tJpW03TKJF01Q6VZd1lLdz2sIKPWaDUZN9dp//dw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.3.tgz", + "integrity": "sha512-g71d8QD8UaiHGvrJwyIS1hCX5r63w6Jll+4VEYhEAHXTDIqX1JgxhTAbEHtKntL9kuc4jRo7/GWw5xfCepSccQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/typescript-estree": "8.59.3", + "@typescript-eslint/utils": "8.59.3", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.3.tgz", + "integrity": "sha512-ePFoH0g4ludssdRFqqDxQePCxU4WQyRa9+XVwjm7yLn0FKhMeoetC+qBEEI1Eyb1pGSDveTIT09Bvw2WhlGayg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.3.tgz", + "integrity": "sha512-CbRjVRAf7Lr9Kr8RopKcbY45p2VfmmHrm0ygOCYFi7oU8q19m0Fs/6iHS7kNOmwpp+ob07ZVcAqlxUod9lYdmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.59.3", + "@typescript-eslint/tsconfig-utils": "8.59.3", + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/visitor-keys": "8.59.3", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.3.tgz", + "integrity": "sha512-JAvT14goBzRzzzZyqq3P9BLArIxTtQURUtFgQ/V7FO+eU+Gg6ES+5ymOPP1wRxXcxAYeivCk4uS3jCKWI1K8Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.59.3", + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/typescript-estree": "8.59.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.3.tgz", + "integrity": "sha512-f1UQF7ggd42YiwI5wGrRaPsa+P0CINBlrkLPmGfpq/u/I/oVtecoEIfFR9ag/oa1sLOsRNZ6xehf6qMZhQGBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.3", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", + "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", + "license": "ISC" + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@vercel/blob": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@vercel/blob/-/blob-2.6.1.tgz", + "integrity": "sha512-KTJytw85j1XQBxjN5d6UXI7fIWNQe1jotn4nWN+0hePqLs+Qi1B3jHdQcSKFGF0m2rsy9uhPT6GOXMtHe3qNzg==", + "license": "Apache-2.0", + "dependencies": { + "@vercel/oidc": "^3.6.1", + "async-retry": "^1.3.3", + "is-buffer": "^2.0.5", + "is-node-process": "^1.2.0", + "throttleit": "^2.1.0", + "undici": "^6.23.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@vercel/blob/node_modules/@vercel/oidc": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.8.1.tgz", + "integrity": "sha512-ufdalm2MWOYksyj8KVpWjoOFPJO6zoYpuyvIggIQ2bB0CFCjTCiTkGXHqAKwG77GVRjOaN3/8S5ITlZpXWmqOw==", + "license": "Apache-2.0", + "dependencies": { + "@vercel/cli-config": "0.2.1", + "@vercel/cli-exec": "1.0.0", + "jose": "^5.9.6" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@vercel/blob/node_modules/jose": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz", + "integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/@vercel/blob/node_modules/undici": { + "version": "6.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.28.0.tgz", + "integrity": "sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/@vercel/cli-config": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@vercel/cli-config/-/cli-config-0.2.1.tgz", + "integrity": "sha512-RhfyXmRLHdbnry8RJqHDc+5rGxMZ0bu+fpysZjtv3bE+BubpuwxTancHOKiH5zKQREsdwFVr3mOI2kOvxlOyxA==", + "license": "Apache-2.0", + "dependencies": { + "xdg-app-paths": "5", + "zod": "4.1.11" + } + }, + "node_modules/@vercel/cli-config/node_modules/zod": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.11.tgz", + "integrity": "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@vercel/cli-exec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@vercel/cli-exec/-/cli-exec-1.0.0.tgz", + "integrity": "sha512-kQF8LGie/Hbdq9/psJxLE7owRTcqMQMhgybU04gCeR7cbQAr5t8OrjefDNColJv1QSSucFt4pLwRiARVmlOnug==", + "license": "Apache-2.0", + "dependencies": { + "execa": "5.1.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@vercel/oidc": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.2.0.tgz", + "integrity": "sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug==", + "license": "Apache-2.0", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.2.tgz", + "integrity": "sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } + } + }, + "node_modules/@vitest/coverage-v8": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.8.tgz", + "integrity": "sha512-lt3kovsyHwYe00wq4D1ti0Z974fWj4NLp6siqiyEufUpyFwK9Yhi7rBhac9JL5aA0zoMrJqc4vYPZRUnI7l7nw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.1.8", + "ast-v8-to-istanbul": "^1.0.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.2", + "obug": "^2.1.1", + "std-env": "^4.0.0-rc.1", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.1.8", + "vitest": "4.1.8" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.8.tgz", + "integrity": "sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.8", + "@vitest/utils": "4.1.8", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.8.tgz", + "integrity": "sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.8", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.8.tgz", + "integrity": "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.8.tgz", + "integrity": "sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.8", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.8.tgz", + "integrity": "sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.8", + "@vitest/utils": "4.1.8", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.8.tgz", + "integrity": "sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==", + "devOptional": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.8.tgz", + "integrity": "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.8", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } }, - "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", - "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", - "cpu": [ - "x64" - ], + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } }, - "node_modules/@vercel/blob": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@vercel/blob/-/blob-2.6.1.tgz", - "integrity": "sha512-KTJytw85j1XQBxjN5d6UXI7fIWNQe1jotn4nWN+0hePqLs+Qi1B3jHdQcSKFGF0m2rsy9uhPT6GOXMtHe3qNzg==", + "node_modules/ai": { + "version": "6.0.182", + "resolved": "https://registry.npmjs.org/ai/-/ai-6.0.182.tgz", + "integrity": "sha512-ooJdziFjYrYRcsCx107roqA8gDTI3P82nUfroNWIhVvwrkYzEN3W1l50YK+XNqkUew8AiimaW0/SLBewRXMuHQ==", "license": "Apache-2.0", "dependencies": { - "@vercel/oidc": "^3.6.1", - "async-retry": "^1.3.3", - "is-buffer": "^2.0.5", - "is-node-process": "^1.2.0", - "throttleit": "^2.1.0", - "undici": "^6.23.0" + "@ai-sdk/gateway": "3.0.114", + "@ai-sdk/provider": "3.0.10", + "@ai-sdk/provider-utils": "4.0.27", + "@opentelemetry/api": "^1.9.0" }, "engines": { - "node": ">=20.0.0" + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" } }, - "node_modules/@vercel/blob/node_modules/@vercel/oidc": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.8.1.tgz", - "integrity": "sha512-ufdalm2MWOYksyj8KVpWjoOFPJO6zoYpuyvIggIQ2bB0CFCjTCiTkGXHqAKwG77GVRjOaN3/8S5ITlZpXWmqOw==", + "node_modules/ai/node_modules/@ai-sdk/gateway": { + "version": "3.0.114", + "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.114.tgz", + "integrity": "sha512-MqkZ5sd+qiq6RgIxELkoFQXg2/JwK+WCMaot7U+rtrZpWJl3fSyYvc28SC03b256o4F7OXjQtdjTqs81B2w+dA==", "license": "Apache-2.0", "dependencies": { - "@vercel/cli-config": "0.2.1", - "@vercel/cli-exec": "1.0.0", - "jose": "^5.9.6" + "@ai-sdk/provider": "3.0.10", + "@ai-sdk/provider-utils": "4.0.27", + "@vercel/oidc": "3.2.0" }, "engines": { - "node": ">= 20" + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" } }, - "node_modules/@vercel/blob/node_modules/jose": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz", - "integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==", + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, "funding": { - "url": "https://github.com/sponsors/panva" + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@vercel/blob/node_modules/undici": { - "version": "6.28.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.28.0.tgz", - "integrity": "sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA==", + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", "license": "MIT", - "engines": { - "node": ">=18.17" - } - }, - "node_modules/@vercel/cli-config": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@vercel/cli-config/-/cli-config-0.2.1.tgz", - "integrity": "sha512-RhfyXmRLHdbnry8RJqHDc+5rGxMZ0bu+fpysZjtv3bE+BubpuwxTancHOKiH5zKQREsdwFVr3mOI2kOvxlOyxA==", - "license": "Apache-2.0", "dependencies": { - "xdg-app-paths": "5", - "zod": "4.1.11" + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } } }, - "node_modules/@vercel/cli-config/node_modules/zod": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.11.tgz", - "integrity": "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg==", + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, "funding": { - "url": "https://github.com/sponsors/colinhacks" + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@vercel/cli-exec": { + "node_modules/ajv-formats/node_modules/json-schema-traverse": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@vercel/cli-exec/-/cli-exec-1.0.0.tgz", - "integrity": "sha512-kQF8LGie/Hbdq9/psJxLE7owRTcqMQMhgybU04gCeR7cbQAr5t8OrjefDNColJv1QSSucFt4pLwRiARVmlOnug==", - "license": "Apache-2.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", "dependencies": { - "execa": "5.1.1" + "color-convert": "^2.0.1" }, "engines": { - "node": ">= 18" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@vercel/oidc": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.2.0.tgz", - "integrity": "sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug==", + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, "license": "Apache-2.0", "engines": { - "node": ">= 20" + "node": ">= 0.4" } }, - "node_modules/@vitejs/plugin-react": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.2.tgz", - "integrity": "sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==", + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", "dev": true, "license": "MIT", "dependencies": { - "@rolldown/pluginutils": "^1.0.0" + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" }, "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "peerDependencies": { - "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", - "babel-plugin-react-compiler": "^1.0.0", - "vite": "^8.0.0" + "node": ">= 0.4" }, - "peerDependenciesMeta": { - "@rolldown/plugin-babel": { - "optional": true - }, - "babel-plugin-react-compiler": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@vitest/coverage-v8": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.8.tgz", - "integrity": "sha512-lt3kovsyHwYe00wq4D1ti0Z974fWj4NLp6siqiyEufUpyFwK9Yhi7rBhac9JL5aA0zoMrJqc4vYPZRUnI7l7nw==", + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.1.8", - "ast-v8-to-istanbul": "^1.0.0", - "istanbul-lib-coverage": "^3.2.2", - "istanbul-lib-report": "^3.0.1", - "istanbul-reports": "^3.2.0", - "magicast": "^0.5.2", - "obug": "^2.1.1", - "std-env": "^4.0.0-rc.1", - "tinyrainbow": "^3.1.0" + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { - "url": "https://opencollective.com/vitest" + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" }, - "peerDependencies": { - "@vitest/browser": "4.1.8", - "vitest": "4.1.8" + "engines": { + "node": ">= 0.4" }, - "peerDependenciesMeta": { - "@vitest/browser": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@vitest/expect": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.8.tgz", - "integrity": "sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==", - "devOptional": true, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, "license": "MIT", "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.8", - "@vitest/utils": "4.1.8", - "chai": "^6.2.2", - "tinyrainbow": "^3.1.0" + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { - "url": "https://opencollective.com/vitest" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@vitest/mocker": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.8.tgz", - "integrity": "sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==", - "devOptional": true, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.8", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + "engines": { + "node": ">= 0.4" }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@vitest/pretty-format": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.8.tgz", - "integrity": "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==", - "devOptional": true, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, "license": "MIT", "dependencies": { - "tinyrainbow": "^3.1.0" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { - "url": "https://opencollective.com/vitest" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@vitest/runner": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.8.tgz", - "integrity": "sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==", - "devOptional": true, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.8", - "pathe": "^2.0.3" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" }, - "funding": { - "url": "https://opencollective.com/vitest" + "engines": { + "node": ">= 0.4" } }, - "node_modules/@vitest/snapshot": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.8.tgz", - "integrity": "sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==", - "devOptional": true, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.8", - "@vitest/utils": "4.1.8", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" }, "funding": { - "url": "https://opencollective.com/vitest" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@vitest/spy": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.8.tgz", - "integrity": "sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==", + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "devOptional": true, "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" + "engines": { + "node": ">=12" } }, - "node_modules/@vitest/utils": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.8.tgz", - "integrity": "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==", - "devOptional": true, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/ast-v8-to-istanbul": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.3.tgz", + "integrity": "sha512-jCMQ6ZylLPudp0CDfBmQBZUsrh1/8psbmu9ibeVWKuHWD0YrH9YABwlKu5kVEFoT0GCQQW9Z/SxfuEbbkGQCRg==", + "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.8", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" } }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, "engines": { - "node": ">= 0.6" + "node": ">= 0.4" } }, - "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "node_modules/async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "license": "MIT", + "dependencies": { + "retry": "0.13.1" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, "license": "MIT", - "peer": true, - "bin": { - "acorn": "bin/acorn" + "dependencies": { + "possible-typed-array-names": "^1.0.0" }, "engines": { - "node": ">=0.4.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "node_modules/axe-core": { + "version": "4.11.4", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.4.tgz", + "integrity": "sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA==", "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "license": "MPL-2.0", + "engines": { + "node": ">=4" } }, - "node_modules/ai": { - "version": "6.0.182", - "resolved": "https://registry.npmjs.org/ai/-/ai-6.0.182.tgz", - "integrity": "sha512-ooJdziFjYrYRcsCx107roqA8gDTI3P82nUfroNWIhVvwrkYzEN3W1l50YK+XNqkUew8AiimaW0/SLBewRXMuHQ==", + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/gateway": "3.0.114", - "@ai-sdk/provider": "3.0.10", - "@ai-sdk/provider-utils": "4.0.27", - "@opentelemetry/api": "^1.9.0" - }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" + "node": ">= 0.4" } }, - "node_modules/ai/node_modules/@ai-sdk/gateway": { - "version": "3.0.114", - "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.114.tgz", - "integrity": "sha512-MqkZ5sd+qiq6RgIxELkoFQXg2/JwK+WCMaot7U+rtrZpWJl3fSyYvc28SC03b256o4F7OXjQtdjTqs81B2w+dA==", + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.29", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.29.tgz", + "integrity": "sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==", "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "3.0.10", - "@ai-sdk/provider-utils": "4.0.27", - "@vercel/oidc": "3.2.0" + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" + "node": ">=6.0.0" } }, - "node_modules/ajv": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", - "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", - "dev": true, + "node_modules/better-auth": { + "version": "1.6.25", + "resolved": "https://registry.npmjs.org/better-auth/-/better-auth-1.6.25.tgz", + "integrity": "sha512-fvoq+oCO+FF5fpP3XfU7znRyGFpHB77UG2EyxsKNy+Cak7Q5pELu+auvvDveQbWQxcoKugZ7jYQQPFQLpUTGOw==", "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "@better-auth/core": "1.6.25", + "@better-auth/drizzle-adapter": "1.6.25", + "@better-auth/kysely-adapter": "1.6.25", + "@better-auth/memory-adapter": "1.6.25", + "@better-auth/mongo-adapter": "1.6.25", + "@better-auth/prisma-adapter": "1.6.25", + "@better-auth/telemetry": "1.6.25", + "@better-auth/utils": "0.4.2", + "@better-fetch/fetch": "1.3.1", + "@noble/ciphers": "^2.1.1", + "@noble/hashes": "^2.0.1", + "better-call": "1.3.7", + "defu": "^6.1.4", + "jose": "^6.1.3", + "kysely": "^0.28.17 || ^0.29.0", + "nanostores": "^1.1.1", + "zod": "^4.3.6" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "peerDependencies": { + "@lynx-js/react": "*", + "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", + "@sveltejs/kit": "^2.0.0", + "@tanstack/react-start": "^1.0.0", + "@tanstack/solid-start": "^1.0.0", + "better-sqlite3": "^12.0.0", + "drizzle-kit": ">=0.31.4", + "drizzle-orm": "^0.45.2", + "mongodb": "^6.0.0 || ^7.0.0", + "mysql2": "^3.0.0", + "next": "^14.0.0 || ^15.0.0 || ^16.0.0", + "pg": "^8.0.0", + "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0", + "solid-js": "^1.0.0", + "svelte": "^4.0.0 || ^5.0.0", + "vitest": "^2.0.0 || ^3.0.0 || ^4.0.0", + "vue": "^3.0.0" + }, + "peerDependenciesMeta": { + "@lynx-js/react": { + "optional": true + }, + "@prisma/client": { + "optional": true + }, + "@sveltejs/kit": { + "optional": true + }, + "@tanstack/react-start": { + "optional": true + }, + "@tanstack/solid-start": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "drizzle-kit": { + "optional": true + }, + "drizzle-orm": { + "optional": true + }, + "mongodb": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "next": { + "optional": true + }, + "pg": { + "optional": true + }, + "prisma": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "solid-js": { + "optional": true + }, + "svelte": { + "optional": true + }, + "vitest": { + "optional": true + }, + "vue": { + "optional": true + } } }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "node_modules/better-call": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/better-call/-/better-call-1.3.7.tgz", + "integrity": "sha512-Al51/hjp2SSp6CRTa3F2ptcx4yQVS1xWKoY6jcVXqNYOap6mHFP2jUBn5EwIL4iIed1/Sq4hlQ+Umm6EflZG+w==", "license": "MIT", + "peer": true, "dependencies": { - "ajv": "^8.0.0" + "@better-auth/utils": "^0.4.0", + "@better-fetch/fetch": "^1.1.21", + "rou3": "^0.7.12", + "set-cookie-parser": "^3.0.1" }, "peerDependencies": { - "ajv": "^8.0.0" + "zod": "^4.0.0" }, "peerDependenciesMeta": { - "ajv": { + "zod": { "optional": true } } }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" + } + }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">=18" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" + "node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, "engines": { "node": ">=8" } }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", + "peer": true, "dependencies": { - "color-convert": "^2.0.1" + "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" }, - "engines": { - "node": ">=8" + "bin": { + "browserslist": "cli.js" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/aria-query": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", - "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", - "dev": true, - "license": "Apache-2.0", "engines": { - "node": ">= 0.4" + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", - "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", - "dev": true, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "is-array-buffer": "^3.0.5" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 0.8" } }, - "node_modules/array-includes": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", - "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.24.0", - "es-object-atoms": "^1.1.1", + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", "get-intrinsic": "^1.3.0", - "is-string": "^1.1.1", - "math-intrinsics": "^1.1.0" + "set-function-length": "^1.2.2" }, "engines": { "node": ">= 0.4" @@ -4503,41 +5903,27 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array.prototype.findlast": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", - "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", - "dev": true, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-shim-unscopables": "^1.0.2" + "function-bind": "^1.1.2" }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array.prototype.findlastindex": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", - "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", - "dev": true, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "es-shim-unscopables": "^1.1.0" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { "node": ">= 0.4" @@ -4546,442 +5932,327 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array.prototype.flat": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", - "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=6" } }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", - "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" - }, "engines": { - "node": ">= 0.4" - }, + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001792", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001792.tgz", + "integrity": "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/array.prototype.tosorted": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", - "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", - "dev": true, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "devOptional": true, "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.3", - "es-errors": "^1.3.0", - "es-shim-unscopables": "^1.0.2" - }, "engines": { - "node": ">= 0.4" + "node": ">=18" } }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", - "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "is-array-buffer": "^3.0.4" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "devOptional": true, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", "license": "MIT", - "engines": { - "node": ">=12" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/ast-types-flow": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", - "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", - "dev": true, - "license": "MIT" + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } }, - "node_modules/ast-v8-to-istanbul": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.3.tgz", - "integrity": "sha512-jCMQ6ZylLPudp0CDfBmQBZUsrh1/8psbmu9ibeVWKuHWD0YrH9YABwlKu5kVEFoT0GCQQW9Z/SxfuEbbkGQCRg==", - "dev": true, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.31", - "estree-walker": "^3.0.3", - "js-tokens": "^10.0.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", - "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", - "dev": true, - "license": "MIT" + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } }, - "node_modules/async-function": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", - "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", "dev": true, - "license": "MIT", + "license": "ISC", "engines": { - "node": ">= 0.4" + "node": ">= 12" } }, - "node_modules/async-retry": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", - "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", - "license": "MIT", - "dependencies": { - "retry": "0.13.1" - } + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "possible-typed-array-names": "^1.0.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=12" } }, - "node_modules/axe-core": { - "version": "4.11.4", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.4.tgz", - "integrity": "sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA==", + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "license": "MPL-2.0", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, "engines": { - "node": ">=4" + "node": ">=7.0.0" } }, - "node_modules/axobject-query": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", - "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } + "license": "MIT" }, - "node_modules/bail": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", - "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true, "license": "MIT" }, - "node_modules/baseline-browser-mapping": { - "version": "2.10.29", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.29.tgz", - "integrity": "sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==", - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", "engines": { - "node": ">=6.0.0" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/better-auth": { - "version": "1.6.25", - "resolved": "https://registry.npmjs.org/better-auth/-/better-auth-1.6.25.tgz", - "integrity": "sha512-fvoq+oCO+FF5fpP3XfU7znRyGFpHB77UG2EyxsKNy+Cak7Q5pELu+auvvDveQbWQxcoKugZ7jYQQPFQLpUTGOw==", + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "license": "MIT", - "dependencies": { - "@better-auth/core": "1.6.25", - "@better-auth/drizzle-adapter": "1.6.25", - "@better-auth/kysely-adapter": "1.6.25", - "@better-auth/memory-adapter": "1.6.25", - "@better-auth/mongo-adapter": "1.6.25", - "@better-auth/prisma-adapter": "1.6.25", - "@better-auth/telemetry": "1.6.25", - "@better-auth/utils": "0.4.2", - "@better-fetch/fetch": "1.3.1", - "@noble/ciphers": "^2.1.1", - "@noble/hashes": "^2.0.1", - "better-call": "1.3.7", - "defu": "^6.1.4", - "jose": "^6.1.3", - "kysely": "^0.28.17 || ^0.29.0", - "nanostores": "^1.1.1", - "zod": "^4.3.6" - }, - "peerDependencies": { - "@lynx-js/react": "*", - "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", - "@sveltejs/kit": "^2.0.0", - "@tanstack/react-start": "^1.0.0", - "@tanstack/solid-start": "^1.0.0", - "better-sqlite3": "^12.0.0", - "drizzle-kit": ">=0.31.4", - "drizzle-orm": "^0.45.2", - "mongodb": "^6.0.0 || ^7.0.0", - "mysql2": "^3.0.0", - "next": "^14.0.0 || ^15.0.0 || ^16.0.0", - "pg": "^8.0.0", - "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0", - "solid-js": "^1.0.0", - "svelte": "^4.0.0 || ^5.0.0", - "vitest": "^2.0.0 || ^3.0.0 || ^4.0.0", - "vue": "^3.0.0" - }, - "peerDependenciesMeta": { - "@lynx-js/react": { - "optional": true - }, - "@prisma/client": { - "optional": true - }, - "@sveltejs/kit": { - "optional": true - }, - "@tanstack/react-start": { - "optional": true - }, - "@tanstack/solid-start": { - "optional": true - }, - "better-sqlite3": { - "optional": true - }, - "drizzle-kit": { - "optional": true - }, - "drizzle-orm": { - "optional": true - }, - "mongodb": { - "optional": true - }, - "mysql2": { - "optional": true - }, - "next": { - "optional": true - }, - "pg": { - "optional": true - }, - "prisma": { - "optional": true - }, - "react": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "solid-js": { - "optional": true - }, - "svelte": { - "optional": true - }, - "vitest": { - "optional": true - }, - "vue": { - "optional": true - } + "engines": { + "node": ">= 0.6" } }, - "node_modules/better-call": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/better-call/-/better-call-1.3.7.tgz", - "integrity": "sha512-Al51/hjp2SSp6CRTa3F2ptcx4yQVS1xWKoY6jcVXqNYOap6mHFP2jUBn5EwIL4iIed1/Sq4hlQ+Umm6EflZG+w==", - "license": "MIT", - "peer": true, - "dependencies": { - "@better-auth/utils": "^0.4.0", - "@better-fetch/fetch": "^1.1.21", - "rou3": "^0.7.12", - "set-cookie-parser": "^3.0.1" - }, - "peerDependencies": { - "zod": "^4.0.0" - }, - "peerDependenciesMeta": { - "zod": { - "optional": true - } + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" } }, - "node_modules/bidi-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", - "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", - "dev": true, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", "license": "MIT", - "dependencies": { - "require-from-string": "^2.0.2" + "engines": { + "node": ">=6.6.0" } }, - "node_modules/body-parser": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", - "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", "license": "MIT", "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" + "object-assign": "^4", + "vary": "^1" }, "engines": { - "node": ">=18" + "node": ">= 0.10" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/express" } }, - "node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", - "dev": true, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", "dev": true, "license": "MIT", "dependencies": { - "fill-range": "^7.1.1" + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" }, "engines": { - "node": ">=8" + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" } }, - "node_modules/browserslist": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", - "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], "license": "MIT", - "peer": true, "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" - }, - "bin": { - "browserslist": "cli.js" + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" }, "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, - "node_modules/call-bind": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", - "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "get-intrinsic": "^1.3.0", - "set-function-length": "^1.2.2" + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -4990,27 +6261,34 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/call-bind-apply-helpers": { + "node_modules/data-view-byte-length": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, "license": "MIT", "dependencies": { + "call-bound": "^1.0.3", "es-errors": "^1.3.0", - "function-bind": "^1.1.2" + "is-data-view": "^1.0.2" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" } }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -5019,534 +6297,796 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, "engines": { - "node": ">=6" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", "dev": true, "license": "MIT", "engines": { - "node": ">=6" + "node": ">=0.10.0" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001792", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001792.tgz", - "integrity": "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" }, - "node_modules/ccount": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", - "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", - "devOptional": true, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, "engines": { - "node": ">=18" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/character-entities": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", - "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "license": "MIT" + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "engines": { + "node": ">= 0.8" } }, - "node_modules/character-entities-html4": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", - "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "engines": { + "node": ">=6" } }, - "node_modules/character-entities-legacy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", - "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" } }, - "node_modules/character-reference-invalid": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", - "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/cli-width": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", - "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "node_modules/dijkstrajs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", "dev": true, - "license": "ISC", - "engines": { - "node": ">= 12" - } - }, - "node_modules/client-only": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", - "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "license": "MIT" }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, - "license": "ISC", + "license": "Apache-2.0", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "esutils": "^2.0.2" }, "engines": { - "node": ">=12" + "node": ">=0.10.0" } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, + "license": "MIT" + }, + "node_modules/drizzle-kit": { + "version": "0.31.10", + "resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.31.10.tgz", + "integrity": "sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw==", + "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { - "color-name": "~1.1.4" + "@drizzle-team/brocli": "^0.10.2", + "@esbuild-kit/esm-loader": "^2.5.5", + "esbuild": "^0.25.4", + "tsx": "^4.21.0" }, + "bin": { + "drizzle-kit": "bin.cjs" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], "engines": { - "node": ">=7.0.0" + "node": ">=18" } }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "node_modules/drizzle-kit/node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/comma-separated-tokens": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", - "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "node_modules/drizzle-kit/node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "node_modules/drizzle-kit/node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/content-disposition": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", - "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "node_modules/drizzle-kit/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" } }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "node_modules/drizzle-kit/node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">= 0.6" + "node": ">=18" } }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "devOptional": true, - "license": "MIT" + "node_modules/drizzle-kit/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "node_modules/drizzle-kit/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">= 0.6" + "node": ">=18" } }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "node_modules/drizzle-kit/node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.6.0" + "node": ">=18" } }, - "node_modules/cors": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", - "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "node_modules/drizzle-kit/node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">=18" } }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "node_modules/drizzle-kit/node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 8" + "node": ">=18" } }, - "node_modules/css-tree": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", - "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "node_modules/drizzle-kit/node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], "dev": true, "license": "MIT", - "dependencies": { - "mdn-data": "2.27.1", - "source-map-js": "^1.2.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + "node": ">=18" } }, - "node_modules/css.escape": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", - "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", - "dev": true, - "license": "MIT" - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "license": "MIT" - }, - "node_modules/damerau-levenshtein": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", - "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/data-urls": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", - "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "node_modules/drizzle-kit/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], "dev": true, "license": "MIT", - "dependencies": { - "whatwg-mimetype": "^5.0.0", - "whatwg-url": "^16.0.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": ">=18" } }, - "node_modules/data-view-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", - "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "node_modules/drizzle-kit/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=18" } }, - "node_modules/data-view-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", - "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "node_modules/drizzle-kit/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], "dev": true, "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/inspect-js" + "node": ">=18" } }, - "node_modules/data-view-byte-offset": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", - "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "node_modules/drizzle-kit/node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], "dev": true, "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=18" } }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/drizzle-kit/node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=18" } }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "node_modules/drizzle-kit/node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "node_modules/decimal.js": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", - "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "node_modules/drizzle-kit/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT" - }, - "node_modules/decode-named-character-reference": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", - "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", "license": "MIT", - "dependencies": { - "character-entities": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "node_modules/drizzle-kit/node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=18" } }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "node_modules/drizzle-kit/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=18" } }, - "node_modules/defu": { - "version": "6.1.7", - "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", - "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", - "license": "MIT" - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "node_modules/drizzle-kit/node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], "engines": { - "node": ">= 0.8" + "node": ">=18" } }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "node_modules/drizzle-kit/node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], "engines": { - "node": ">=6" + "node": ">=18" } }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "license": "Apache-2.0", + "node_modules/drizzle-kit/node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/devlop": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", - "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "node_modules/drizzle-kit/node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, "license": "MIT", - "dependencies": { - "dequal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" } }, - "node_modules/dijkstrajs": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", - "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", + "node_modules/drizzle-kit/node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" + "node_modules/drizzle-kit/node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "devOptional": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" }, "engines": { - "node": ">=0.10.0" + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/drizzle-orm": { + "version": "0.45.2", + "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.45.2.tgz", + "integrity": "sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q==", + "license": "Apache-2.0", + "peer": true, + "peerDependencies": { + "@aws-sdk/client-rds-data": ">=3", + "@cloudflare/workers-types": ">=4", + "@electric-sql/pglite": ">=0.2.0", + "@libsql/client": ">=0.10.0", + "@libsql/client-wasm": ">=0.10.0", + "@neondatabase/serverless": ">=0.10.0", + "@op-engineering/op-sqlite": ">=2", + "@opentelemetry/api": "^1.4.1", + "@planetscale/database": ">=1.13", + "@prisma/client": "*", + "@tidbcloud/serverless": "*", + "@types/better-sqlite3": "*", + "@types/pg": "*", + "@types/sql.js": "*", + "@upstash/redis": ">=1.34.7", + "@vercel/postgres": ">=0.8.0", + "@xata.io/client": "*", + "better-sqlite3": ">=7", + "bun-types": "*", + "expo-sqlite": ">=14.0.0", + "gel": ">=2", + "knex": "*", + "kysely": "*", + "mysql2": ">=2", + "pg": ">=8", + "postgres": ">=3", + "sql.js": ">=1", + "sqlite3": ">=5" + }, + "peerDependenciesMeta": { + "@aws-sdk/client-rds-data": { + "optional": true + }, + "@cloudflare/workers-types": { + "optional": true + }, + "@electric-sql/pglite": { + "optional": true + }, + "@libsql/client": { + "optional": true + }, + "@libsql/client-wasm": { + "optional": true + }, + "@neondatabase/serverless": { + "optional": true + }, + "@op-engineering/op-sqlite": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@prisma/client": { + "optional": true + }, + "@tidbcloud/serverless": { + "optional": true + }, + "@types/better-sqlite3": { + "optional": true + }, + "@types/pg": { + "optional": true + }, + "@types/sql.js": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/postgres": { + "optional": true + }, + "@xata.io/client": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "bun-types": { + "optional": true + }, + "expo-sqlite": { + "optional": true + }, + "gel": { + "optional": true + }, + "knex": { + "optional": true + }, + "kysely": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "pg": { + "optional": true + }, + "postgres": { + "optional": true + }, + "prisma": { + "optional": true + }, + "sql.js": { + "optional": true + }, + "sqlite3": { + "optional": true + } } }, - "node_modules/dom-accessibility-api": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", - "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", - "dev": true, - "license": "MIT" - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -5798,6 +7338,49 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "devOptional": true, + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -6770,7 +8353,7 @@ "version": "4.14.0", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "resolve-pkg-maps": "^1.0.0" @@ -10790,7 +12373,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, + "devOptional": true, "license": "MIT", "funding": { "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" @@ -11277,6 +12860,16 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "devOptional": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -11286,6 +12879,17 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/space-separated-tokens": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", @@ -11896,6 +13500,41 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tsx": { + "version": "4.23.13", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.13.tgz", + "integrity": "sha512-BL5MGkRln6aDYhb0xbQlEAGw743BaZYWdbWtdJOBriYJboKgUUYCadFp2/FpBBZquBC/ezNBn7wMMPx7FDZUDw==", + "devOptional": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tsx/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/v5/package.json b/v5/package.json index 4efc0be..2fde6ec 100644 --- a/v5/package.json +++ b/v5/package.json @@ -19,16 +19,23 @@ "migrate:resources": "node --env-file=.env.local --experimental-strip-types scripts/migrate-tools-to-resources.ts", "drop:deprecated-columns": "node --env-file=.env.local --experimental-strip-types scripts/drop-deprecated-notion-columns.ts", "clear:migration-notes": "node --env-file=.env.local --experimental-strip-types scripts/clear-resource-migration-notes.ts", - "spec:coverage": "node --experimental-strip-types scripts/check-spec-coverage.ts" + "spec:coverage": "node --experimental-strip-types scripts/check-spec-coverage.ts", + "db:generate": "drizzle-kit generate", + "db:migrate": "node --env-file-if-exists=.env.local --experimental-strip-types scripts/db-migrate.ts", + "import:notion": "node --env-file-if-exists=.env.local --experimental-strip-types scripts/import-notion.ts", + "verify:import": "node --env-file-if-exists=.env.local --experimental-strip-types scripts/verify-import.ts" }, "dependencies": { "@ai-sdk/anthropic": "^3.0.46", "@ai-sdk/gateway": "^3.0.160", "@ai-sdk/react": "^3.0.97", + "@electric-sql/pglite": "^0.5.8", "@modelcontextprotocol/sdk": "^1.29.0", + "@neondatabase/serverless": "^1.1.0", "@vercel/blob": "^2.6.1", "ai": "^6.0.95", "better-auth": "^1.6.25", + "drizzle-orm": "^0.45.2", "match-sorter": "^8.3.0", "next": "16.1.6", "next-intl": "^4.13.0", @@ -51,6 +58,7 @@ "@types/react-dom": "^19", "@vitejs/plugin-react": "^6.0.2", "@vitest/coverage-v8": "^4.1.8", + "drizzle-kit": "^0.31.10", "eslint": "^9", "eslint-config-next": "16.1.6", "jsdom": "^29.1.1", diff --git a/v5/scripts/db-migrate.ts b/v5/scripts/db-migrate.ts new file mode 100644 index 0000000..f0ddbd0 --- /dev/null +++ b/v5/scripts/db-migrate.ts @@ -0,0 +1,30 @@ +/** + * Apply committed migrations to the Neon database (data platform design spec + * 2026-09-14 §3.2). + * + * npm run db:migrate + * + * Reads `DATABASE_URL` from the environment (`.env.local` locally; the Vercel + * project's variables in a deploy). Exits 0 with a note when it is unset, so a + * build without a database — a fresh clone, a preview without Neon — still + * succeeds. PGlite never needs this: it migrates itself on first use. + */ +import { createNeonDb, migrateNeon } from "../src/lib/db/neon.ts"; + +async function main(): Promise { + const url = process.env.DATABASE_URL; + if (!url) { + console.log("DATABASE_URL is not set; no migrations to run."); + return; + } + const db = createNeonDb(url); + await migrateNeon(db); + console.log("Migrations applied."); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/v5/scripts/import-notion.ts b/v5/scripts/import-notion.ts new file mode 100644 index 0000000..38bcb95 --- /dev/null +++ b/v5/scripts/import-notion.ts @@ -0,0 +1,76 @@ +/** + * The one-time Notion → Postgres import (data platform design spec 2026-09-14 + * §5.7). Read-only against Notion; idempotent against Postgres. + * + * npm run import:notion -- --dry-run # into an in-memory PGlite, no files; prints the report + * npm run import:notion -- --skip-files # into DATABASE_URL, rows only + * npm run import:notion # into DATABASE_URL, rows and files + * + * Needs `NOTION_API_KEY` and the `NOTION_DB_*` ids (plus `NOTION_DB_PROJECTS` + * if projects should come across). A real run needs `DATABASE_URL`, and + * `BLOB_READ_WRITE_TOKEN` unless `--skip-files`. + * + * The pre-flight stops the run on any Notion option that maps onto no stored + * value, naming the database, property and option. That is deliberate. + */ +import { createNeonDb } from "../src/lib/db/neon.ts"; +import { createPgliteDb } from "../src/lib/db/pglite.ts"; +import { createVercelBlobUploader } from "../src/lib/import/blob-uploader.ts"; +import { createFileCopier } from "../src/lib/import/files.ts"; +import { PreflightError } from "../src/lib/import/preflight.ts"; +import { runImport, type ImportReport } from "../src/lib/import/run.ts"; +import { readNotionSnapshot } from "../src/lib/import/source.ts"; + +const args = new Set(process.argv.slice(2)); +const dryRun = args.has("--dry-run"); +const skipFiles = args.has("--skip-files") || dryRun; + +function printReport(report: ImportReport): void { + console.log("\nRows"); + for (const [entity, { inserted, updated }] of Object.entries(report.counts)) { + console.log(` ${entity.padEnd(18)} ${String(inserted).padStart(5)} inserted ${String(updated).padStart(5)} updated`); + } + console.log(`\nFiles: ${report.files.copied} copied, ${report.files.skipped} skipped, ${report.files.failed} failed`); + if (report.preflightWarnings.length > 0) { + console.log("\nPre-flight warnings"); + for (const w of report.preflightWarnings) console.log(` ${w.table}.${w.property}: ${w.reason}`); + } + if (report.warnings.length > 0) { + console.log(`\nWarnings (${report.warnings.length})`); + for (const w of report.warnings) console.log(` ${w}`); + } +} + +async function main(): Promise { + const log = (line: string) => console.log(` ${line}`); + + console.log(dryRun ? "Dry run: reading Notion into an in-memory database." : "Reading Notion."); + const snapshot = await readNotionSnapshot({ log }); + + let db; + if (dryRun) { + db = await createPgliteDb(); + } else { + const url = process.env.DATABASE_URL; + if (!url) throw new Error("DATABASE_URL is not set. Use --dry-run to rehearse without a database."); + db = createNeonDb(url); + } + + const files = skipFiles ? null : createFileCopier(createVercelBlobUploader()); + + console.log(dryRun ? "\nImporting (in memory)." : "\nImporting."); + const report = await runImport({ db, snapshot, files, log }); + printReport(report); + console.log(dryRun ? "\nDry run complete; nothing was written to a database." : "\nImport complete."); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + if (error instanceof PreflightError) { + console.error(`\n${error.message}`); + } else { + console.error(error); + } + process.exit(1); + }); diff --git a/v5/scripts/verify-import.ts b/v5/scripts/verify-import.ts new file mode 100644 index 0000000..3bc78e6 --- /dev/null +++ b/v5/scripts/verify-import.ts @@ -0,0 +1,98 @@ +/** + * Check an import against its source (data platform design spec 2026-09-14 + * §5.7, "Verification"). + * + * npm run verify:import + * + * Reads Notion again (read-only) and the Postgres database in `DATABASE_URL`, + * then reports: + * - row counts per entity, Notion vs Postgres; + * - relation integrity: every unit and resource whose Notion relation points + * at a tool that exists has a `tool_id`; + * - files: every attachment row has bytes in Blob. + * + * Exits 1 on any mismatch. The field-by-field comparison of five named tools + * between the Notion path and the Postgres path arrives with the Postgres read + * path (Phase 2), which is what renders the Postgres side. + */ +import { head } from "@vercel/blob"; +import { count, isNotNull } from "drizzle-orm"; +import { createNeonDb } from "../src/lib/db/neon.ts"; +import { + attachments, + categories, + feedback, + locations, + maintenanceLogs, + projects, + resources, + tools, + units, +} from "../src/lib/db/schema/index.ts"; +import type { Db } from "../src/lib/db/types.ts"; +import { readNotionSnapshot } from "../src/lib/import/source.ts"; + +let failures = 0; + +function check(label: string, expected: number, actual: number): void { + const ok = expected === actual; + if (!ok) failures += 1; + console.log(` ${ok ? "ok " : "FAIL"} ${label.padEnd(40)} notion ${String(expected).padStart(5)} postgres ${String(actual).padStart(5)}`); +} + +async function rowCount(db: Db, table: typeof tools | typeof units | typeof categories | typeof locations | typeof resources | typeof maintenanceLogs | typeof feedback | typeof projects): Promise { + const [row] = await db.select({ n: count() }).from(table); + return Number(row.n); +} + +async function main(): Promise { + const url = process.env.DATABASE_URL; + if (!url) throw new Error("DATABASE_URL is not set."); + const db = createNeonDb(url); + + console.log("Reading Notion."); + const snapshot = await readNotionSnapshot({ log: (line) => console.log(` ${line}`) }); + + console.log("\nRow counts"); + check("categories", snapshot.categories.length, await rowCount(db, categories)); + check("locations", snapshot.locations.length, await rowCount(db, locations)); + check("tools", snapshot.tools.length, await rowCount(db, tools)); + check("units", snapshot.units.length, await rowCount(db, units)); + check("resources", snapshot.resources.length, await rowCount(db, resources)); + check("maintenance_logs", snapshot.maintenanceLogs.length, await rowCount(db, maintenanceLogs)); + check("feedback", snapshot.flags.length, await rowCount(db, feedback)); + check("projects", snapshot.projects.length, await rowCount(db, projects)); + + console.log("\nRelations (Notion relations that point at an existing tool)"); + const toolIds = new Set(snapshot.tools.map((t) => t.id)); + const linkedUnits = snapshot.units.filter((u) => u.fields.tool?.some((id) => toolIds.has(id))).length; + const [unitsWithTool] = await db.select({ n: count() }).from(units).where(isNotNull(units.toolId)); + check("units with a tool", linkedUnits, Number(unitsWithTool.n)); + const linkedResources = snapshot.resources.filter((r) => r.fields.tool?.some((id) => toolIds.has(id))).length; + const [resourcesWithTool] = await db.select({ n: count() }).from(resources).where(isNotNull(resources.toolId)); + check("resources with a tool", linkedResources, Number(resourcesWithTool.n)); + + console.log("\nFiles (every attachment row has bytes in Blob)"); + const rows = await db.select({ pathname: attachments.blobPathname }).from(attachments); + let missing = 0; + for (const row of rows) { + try { + await head(row.pathname); + } catch { + missing += 1; + console.log(` FAIL missing in Blob: ${row.pathname}`); + } + } + if (missing > 0) failures += 1; + console.log(` ${missing === 0 ? "ok " : "FAIL"} ${rows.length} attachments, ${missing} missing`); + + console.log(failures === 0 ? "\nVerified." : `\n${failures} check(s) failed.`); + if (failures > 0) process.exitCode = 1; +} + +main() + .then(() => process.exit(process.exitCode ?? 0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/v5/src/lib/db/client.test.ts b/v5/src/lib/db/client.test.ts new file mode 100644 index 0000000..86d8efd --- /dev/null +++ b/v5/src/lib/db/client.test.ts @@ -0,0 +1,42 @@ +// @vitest-environment node +import { sql } from "drizzle-orm"; +import { dataSubstrate, getDb, pingDb, resetDbForTests } from "./client"; +import { rawRows } from "./raw"; +import { tools } from "./schema/index"; + +describe("db client", () => { + afterEach(() => { + resetDbForTests(); + }); + + it("chooses PGlite when DATABASE_URL is unset and seeds the demo catalogue", async () => { + vi.stubEnv("DATABASE_URL", ""); + expect(dataSubstrate()).toBe("pglite-demo"); + + const db = await getDb(); + const rows = await db.select({ slug: tools.slug }).from(tools).orderBy(tools.slug); + expect(rows.map((row) => row.slug)).toEqual(["form-4", "trotec-speedy-400"]); + }); + + it("memoises the handle across calls", async () => { + vi.stubEnv("DATABASE_URL", ""); + const first = await getDb(); + const second = await getDb(); + expect(second).toBe(first); + }); + + it("reports the Neon substrate when DATABASE_URL is set, without connecting", () => { + vi.stubEnv("DATABASE_URL", "postgres://user:pass@example.neon.tech/db"); + expect(dataSubstrate()).toBe("neon"); + // Creating the handle is lazy and touches no network; only a query would. + expect(getDb()).toBeInstanceOf(Promise); + }); + + it("pingDb succeeds against PGlite", async () => { + vi.stubEnv("DATABASE_URL", ""); + await expect(pingDb()).resolves.toBeUndefined(); + const db = await getDb(); + const rows = await rawRows<{ one: number }>(db, sql`select 1 as one`); + expect(rows[0].one).toBe(1); + }); +}); diff --git a/v5/src/lib/db/client.ts b/v5/src/lib/db/client.ts new file mode 100644 index 0000000..12e9cd2 --- /dev/null +++ b/v5/src/lib/db/client.ts @@ -0,0 +1,78 @@ +import { sql } from "drizzle-orm"; +import { createNeonDb } from "./neon.ts"; +import { createPgliteDb } from "./pglite.ts"; +import { seedDemo } from "./demo-seed.ts"; +import type { DataSubstrate, Db } from "./types.ts"; + +/** + * The one entry point to the database (spec §3.2). + * + * - `DATABASE_URL` set → Neon. Created lazily on first call, so `next build` + * never needs the variable and a missing one fails at request time with + * {@link DbUnavailableError}, never at build time. + * - `DATABASE_URL` unset → PGlite with the demo seed: tests, E2E, a fresh + * clone. The `DemoDataBanner` tells visitors the catalogue is sample data. + * + * The handle is memoised on `globalThis` so Next's dev-server module reloads + * reuse one PGlite instance instead of starting a new one per reload. There is + * deliberately no `Proxy` wrapper: libraries that inspect the handle (Better + * Auth's adapter among them) break on one. + * + * Not `server-only`: the import and migrate scripts load this module under + * plain Node, where that package cannot resolve. Nothing under `src/lib/db/` + * is imported by client components. + */ + +/** Neon is configured but cannot be reached; callers fail toward stale, not wrong (Article 4). */ +export class DbUnavailableError extends Error { + constructor(cause: unknown) { + super("The database is unavailable.", { cause }); + this.name = "DbUnavailableError"; + } +} + +export function dataSubstrate(): DataSubstrate { + return process.env.DATABASE_URL ? "neon" : "pglite-demo"; +} + +type DbCache = { substrate: DataSubstrate; promise: Promise } | undefined; +const CACHE_KEY = "__makerlab_db__" as const; + +function cache(): DbCache { + return (globalThis as Record)[CACHE_KEY] as DbCache; +} + +function setCache(value: DbCache): void { + (globalThis as Record)[CACHE_KEY] = value; +} + +export function getDb(): Promise { + const substrate = dataSubstrate(); + const cached = cache(); + if (cached && cached.substrate === substrate) return cached.promise; + + const promise = + substrate === "neon" + ? Promise.resolve(createNeonDb(process.env.DATABASE_URL as string)) + : createPgliteDb({ seed: seedDemo }); + setCache({ substrate, promise }); + return promise; +} + +/** + * One round trip. Throws {@link DbUnavailableError} when Neon cannot be + * reached, which is what `/api/health` reports. + */ +export async function pingDb(): Promise { + const db = await getDb(); + try { + await db.execute(sql`select 1`); + } catch (error) { + throw new DbUnavailableError(error); + } +} + +/** Drop the memoised handle so a test can change `DATABASE_URL` and start over. */ +export function resetDbForTests(): void { + setCache(undefined); +} diff --git a/v5/src/lib/db/demo-seed.test.ts b/v5/src/lib/db/demo-seed.test.ts new file mode 100644 index 0000000..b859ea3 --- /dev/null +++ b/v5/src/lib/db/demo-seed.test.ts @@ -0,0 +1,30 @@ +// @vitest-environment node +import { eq } from "drizzle-orm"; +import { seedDemo } from "./demo-seed"; +import { createPgliteDb } from "./pglite"; +import { resources, tools, units } from "./schema/index"; + +describe("seedDemo", () => { + it("inserts the two demo tools with their units and resources", async () => { + const db = await createPgliteDb({ seed: seedDemo }); + + const toolRows = await db.select().from(tools).orderBy(tools.slug); + expect(toolRows.map((row) => row.name)).toEqual(["Form 4", "Trotec Speedy 400"]); + expect(toolRows.every((row) => row.published)).toBe(true); + + const form4 = toolRows[0]; + const form4Units = await db.select().from(units).where(eq(units.toolId, form4.id)); + expect(form4Units).toHaveLength(1); + expect(form4Units[0]).toMatchObject({ unitLabel: "Form 4 // A", status: "in_use", condition: "excellent" }); + + const form4Resources = await db.select().from(resources).where(eq(resources.toolId, form4.id)); + expect(form4Resources.map((row) => row.title)).toEqual(["Form 4 SOP", "Resin handling safety"]); + }); + + it("is idempotent", async () => { + const db = await createPgliteDb({ seed: seedDemo }); + await seedDemo(db); + const toolRows = await db.select().from(tools); + expect(toolRows).toHaveLength(2); + }); +}); diff --git a/v5/src/lib/db/demo-seed.ts b/v5/src/lib/db/demo-seed.ts new file mode 100644 index 0000000..669f378 --- /dev/null +++ b/v5/src/lib/db/demo-seed.ts @@ -0,0 +1,95 @@ +import { categories, locations, resources, tools, units } from "./schema/index.ts"; +import type { Db } from "./types.ts"; + +/** + * Sample data for a database with no `DATABASE_URL` (spec §3.10): the two + * tools the mock catalogue has always shown, now as real rows so the same + * query modules serve tests, E2E and a fresh clone. Idempotent — a database + * that already has tools is left alone. + */ +export async function seedDemo(db: Db): Promise { + const existing = await db.select({ id: tools.id }).from(tools).limit(1); + if (existing.length > 0) return; + + await db.transaction(async (tx) => { + const [resin, co2] = await tx + .insert(categories) + .values([ + { name: "Resin", group: "3D Printing" }, + { name: "CO2", group: "Laser" }, + ]) + .returning({ id: categories.id }); + + const [resinBench, laserBay] = await tx + .insert(locations) + .values([ + { room: "MakerLab", zone: "Resin Bench", mapTag: "ML-RESIN-01" }, + { room: "Laser Room", zone: "Laser Bay", mapTag: "ML-LSR-400" }, + ]) + .returning({ id: locations.id }); + + const [form4, trotec] = await tx + .insert(tools) + .values([ + { + slug: "form-4", + name: "Form 4", + description: + "A production-grade resin printer used for detailed parts that need smooth surfaces, tight tolerances, or engineering material properties. Requires resin handling discipline, post-processing, and ventilation awareness.", + categoryId: resin.id, + locationId: resinBench.id, + materials: ["Standard resin", "Tough resin", "Flexible resin", "Dental resin"], + ppeRequired: ["Nitrile gloves", "Safety glasses", "Lab coat"], + tags: ["Resin", "SLA", "Prototyping"], + trainingRequired: true, + useRestrictions: "Resin handling training required before first print.", + emergencyStop: "Lift the lid to immediately halt the print and pause the build.", + notes: "Always wear nitrile gloves when handling uncured resin. Ventilation must be running.", + published: true, + }, + { + slug: "trotec-speedy-400", + name: "Trotec Speedy 400", + description: + "Large format laser platform for cutting and engraving approved flat stock. Users must verify material compatibility, ventilation, fire watch, and job setup before operation.", + categoryId: co2.id, + locationId: laserBay.id, + materials: ["Acrylic", "Paper", "Cardboard", "Plywood"], + ppeRequired: ["Safety glasses", "Fire watch", "Approved materials only"], + tags: ["Laser", "CO2", "Cutting", "Engraving", "Authorized"], + trainingRequired: true, + useRestrictions: "Authorized users only. Material list must be confirmed with staff.", + emergencyStop: "Press the red E-stop on the right side of the gantry to cut power instantly.", + notes: "Run exhaust for 60 seconds after cuts before opening the lid.", + published: true, + }, + ]) + .returning({ id: tools.id }); + + await tx.insert(units).values([ + { + toolId: form4.id, + unitLabel: "Form 4 // A", + serialNumber: "ML-F4-001", + status: "in_use", + condition: "excellent", + dateAcquired: "2024-08-12", + }, + { + toolId: trotec.id, + unitLabel: "Trotec Speedy 400", + serialNumber: "ML-LSR-400", + status: "available", + condition: "good", + dateAcquired: "2022-04-03", + }, + ]); + + await tx.insert(resources).values([ + { toolId: form4.id, title: "Form 4 SOP", type: "SOP", url: "#" }, + { toolId: form4.id, title: "Resin handling safety", type: "Safety", url: "#" }, + { toolId: trotec.id, title: "Trotec Speedy 400 SOP", type: "SOP", url: "#" }, + { toolId: trotec.id, title: "Approved material list", type: "Safety", url: "#" }, + ]); + }); +} diff --git a/v5/src/lib/db/migrations-folder.ts b/v5/src/lib/db/migrations-folder.ts new file mode 100644 index 0000000..9fd7613 --- /dev/null +++ b/v5/src/lib/db/migrations-folder.ts @@ -0,0 +1,11 @@ +import { resolve } from "node:path"; + +/** + * Where the committed SQL migrations live. Resolved from the working + * directory because every entry point — `next dev`, Vitest, Playwright's dev + * server, and the `db:migrate` / `import:notion` scripts — runs from `v5/`. + * `DB_MIGRATIONS_DIR` overrides it for anything that does not. + */ +export function migrationsFolder(): string { + return process.env.DB_MIGRATIONS_DIR || resolve(process.cwd(), "src/lib/db/migrations"); +} diff --git a/v5/src/lib/db/migrations/0000_extensions.sql b/v5/src/lib/db/migrations/0000_extensions.sql new file mode 100644 index 0000000..2b08858 --- /dev/null +++ b/v5/src/lib/db/migrations/0000_extensions.sql @@ -0,0 +1,4 @@ +-- Extensions the schema depends on (data platform design spec 2026-09-14 §3.2). +-- pg_trgm backs the trigram index on tools.name used by the duplicate check. +-- Neon ships it; PGlite loads it at construction (see src/lib/db/pglite.ts). +CREATE EXTENSION IF NOT EXISTS pg_trgm; diff --git a/v5/src/lib/db/migrations/0001_initial_schema.sql b/v5/src/lib/db/migrations/0001_initial_schema.sql new file mode 100644 index 0000000..6018e1c --- /dev/null +++ b/v5/src/lib/db/migrations/0001_initial_schema.sql @@ -0,0 +1,225 @@ +CREATE TABLE "categories" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "name" text NOT NULL, + "group" text, + "notion_page_id" text, + "created_by" text, + "updated_by" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "categories_notion_page_id_unique" UNIQUE("notion_page_id") +); +--> statement-breakpoint +CREATE TABLE "locations" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "room" text NOT NULL, + "zone" text NOT NULL, + "map_tag" text, + "notion_page_id" text, + "created_by" text, + "updated_by" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "locations_map_tag_unique" UNIQUE("map_tag"), + CONSTRAINT "locations_notion_page_id_unique" UNIQUE("notion_page_id") +); +--> statement-breakpoint +CREATE TABLE "tools" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "slug" text NOT NULL, + "name" text NOT NULL, + "description" text, + "category_id" uuid, + "location_id" uuid, + "materials" text[] DEFAULT '{}'::text[] NOT NULL, + "ppe_required" text[] DEFAULT '{}'::text[] NOT NULL, + "tags" text[] DEFAULT '{}'::text[] NOT NULL, + "training_required" boolean DEFAULT false NOT NULL, + "use_restrictions" text, + "emergency_stop" text, + "notes" text, + "published" boolean DEFAULT false NOT NULL, + "archived_at" timestamp with time zone, + "last_reviewed_at" timestamp with time zone, + "last_reviewed_by" text, + "notion_page_id" text, + "created_by" text, + "updated_by" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "tools_slug_unique" UNIQUE("slug"), + CONSTRAINT "tools_notion_page_id_unique" UNIQUE("notion_page_id") +); +--> statement-breakpoint +CREATE TABLE "units" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "tool_id" uuid, + "unit_label" text NOT NULL, + "serial_number" text, + "asset_tag" text, + "status" text DEFAULT 'available' NOT NULL, + "condition" text, + "date_acquired" date, + "notes" text, + "notion_page_id" text, + "created_by" text, + "updated_by" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "units_notion_page_id_unique" UNIQUE("notion_page_id"), + CONSTRAINT "units_status_check" CHECK ("status" in ('available', 'in_use', 'under_maintenance', 'out_of_service', 'retired')), + CONSTRAINT "units_condition_check" CHECK ("condition" in ('excellent', 'good', 'fair', 'needs_repair', 'new')) +); +--> statement-breakpoint +CREATE TABLE "resources" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "tool_id" uuid, + "title" text NOT NULL, + "type" text, + "url" text, + "notes" text, + "published" boolean DEFAULT true NOT NULL, + "notion_page_id" text, + "created_by" text, + "updated_by" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "resources_notion_page_id_unique" UNIQUE("notion_page_id") +); +--> statement-breakpoint +CREATE TABLE "attachments" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "owner_type" text, + "owner_id" uuid, + "position" integer DEFAULT 0 NOT NULL, + "blob_pathname" text NOT NULL, + "access" text NOT NULL, + "public_url" text, + "content_type" text, + "size_bytes" integer, + "width" integer, + "height" integer, + "original_filename" text, + "source_key" text, + "uploaded_by" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "attachments_source_key_unique" UNIQUE("source_key"), + CONSTRAINT "attachments_owner_type_check" CHECK ("owner_type" in ('tool', 'resource', 'maintenance_log', 'project', 'pending_tool')), + CONSTRAINT "attachments_access_check" CHECK ("access" in ('public', 'private')) +); +--> statement-breakpoint +CREATE TABLE "maintenance_logs" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "title" text NOT NULL, + "type" text, + "priority" text, + "status" text DEFAULT 'open' NOT NULL, + "description" text, + "resolution" text, + "unit_id" uuid, + "tool_id" uuid, + "tool_name" text, + "unit_label" text, + "reported_by_name" text, + "reported_by_email" text, + "reported_by_user_id" text, + "assigned_to_user_id" text, + "assigned_to_name" text, + "date_reported" date, + "date_resolved" date, + "notion_page_id" text, + "created_by" text, + "updated_by" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "maintenance_logs_notion_page_id_unique" UNIQUE("notion_page_id"), + CONSTRAINT "maintenance_logs_type_check" CHECK ("type" in ('issue_report', 'preventive_maintenance', 'repair', 'inspection', 'calibration')), + CONSTRAINT "maintenance_logs_priority_check" CHECK ("priority" in ('low', 'medium', 'high', 'critical')), + CONSTRAINT "maintenance_logs_status_check" CHECK ("status" in ('open', 'in_progress', 'resolved', 'closed')) +); +--> statement-breakpoint +CREATE TABLE "feedback" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "tool_id" uuid, + "field_flagged" text, + "issue_description" text NOT NULL, + "suggested_fix" text, + "reporter_name" text, + "reporter_email" text, + "reporter_user_id" text, + "status" text DEFAULT 'new' NOT NULL, + "notion_page_id" text, + "created_by" text, + "updated_by" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "feedback_notion_page_id_unique" UNIQUE("notion_page_id"), + CONSTRAINT "feedback_field_flagged_check" CHECK ("field_flagged" in ('description', 'image', 'name', 'category', 'location', 'materials', 'safety_info')), + CONSTRAINT "feedback_status_check" CHECK ("status" in ('new', 'reviewed', 'fixed', 'dismissed')) +); +--> statement-breakpoint +CREATE TABLE "project_tools" ( + "project_id" uuid NOT NULL, + "tool_id" uuid NOT NULL, + CONSTRAINT "project_tools_project_id_tool_id_pk" PRIMARY KEY("project_id","tool_id") +); +--> statement-breakpoint +CREATE TABLE "projects" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "slug" text NOT NULL, + "title" text NOT NULL, + "body" text DEFAULT '' NOT NULL, + "link" text, + "materials" text[] DEFAULT '{}'::text[] NOT NULL, + "author_user_id" text, + "author_name" text, + "published" boolean DEFAULT false NOT NULL, + "published_at" timestamp with time zone, + "published_by" text, + "notion_page_id" text, + "created_by" text, + "updated_by" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "projects_slug_unique" UNIQUE("slug"), + CONSTRAINT "projects_notion_page_id_unique" UNIQUE("notion_page_id") +); +--> statement-breakpoint +CREATE TABLE "audit_events" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "at" timestamp with time zone DEFAULT now() NOT NULL, + "actor_user_id" text, + "action" text NOT NULL, + "subject_type" text NOT NULL, + "subject_id" text NOT NULL, + "detail" jsonb +); +--> statement-breakpoint +ALTER TABLE "tools" ADD CONSTRAINT "tools_category_id_categories_id_fk" FOREIGN KEY ("category_id") REFERENCES "public"."categories"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "tools" ADD CONSTRAINT "tools_location_id_locations_id_fk" FOREIGN KEY ("location_id") REFERENCES "public"."locations"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "units" ADD CONSTRAINT "units_tool_id_tools_id_fk" FOREIGN KEY ("tool_id") REFERENCES "public"."tools"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "resources" ADD CONSTRAINT "resources_tool_id_tools_id_fk" FOREIGN KEY ("tool_id") REFERENCES "public"."tools"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "maintenance_logs" ADD CONSTRAINT "maintenance_logs_unit_id_units_id_fk" FOREIGN KEY ("unit_id") REFERENCES "public"."units"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "maintenance_logs" ADD CONSTRAINT "maintenance_logs_tool_id_tools_id_fk" FOREIGN KEY ("tool_id") REFERENCES "public"."tools"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "feedback" ADD CONSTRAINT "feedback_tool_id_tools_id_fk" FOREIGN KEY ("tool_id") REFERENCES "public"."tools"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "project_tools" ADD CONSTRAINT "project_tools_project_id_projects_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "project_tools" ADD CONSTRAINT "project_tools_tool_id_tools_id_fk" FOREIGN KEY ("tool_id") REFERENCES "public"."tools"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE UNIQUE INDEX "categories_name_group_key" ON "categories" USING btree (lower("name"),lower(coalesce("group", '')));--> statement-breakpoint +CREATE UNIQUE INDEX "locations_room_zone_key" ON "locations" USING btree (lower("room"),lower("zone"));--> statement-breakpoint +CREATE INDEX "tools_name_trgm_idx" ON "tools" USING gin ("name" gin_trgm_ops);--> statement-breakpoint +CREATE INDEX "tools_category_idx" ON "tools" USING btree ("category_id");--> statement-breakpoint +CREATE INDEX "tools_location_idx" ON "tools" USING btree ("location_id");--> statement-breakpoint +CREATE INDEX "units_tool_idx" ON "units" USING btree ("tool_id");--> statement-breakpoint +CREATE UNIQUE INDEX "units_tool_serial_key" ON "units" USING btree ("tool_id",lower("serial_number")) WHERE "units"."serial_number" is not null;--> statement-breakpoint +CREATE INDEX "resources_tool_idx" ON "resources" USING btree ("tool_id");--> statement-breakpoint +CREATE INDEX "attachments_owner_idx" ON "attachments" USING btree ("owner_type","owner_id","position");--> statement-breakpoint +CREATE INDEX "maintenance_logs_unit_idx" ON "maintenance_logs" USING btree ("unit_id");--> statement-breakpoint +CREATE INDEX "maintenance_logs_tool_idx" ON "maintenance_logs" USING btree ("tool_id");--> statement-breakpoint +CREATE INDEX "maintenance_logs_status_idx" ON "maintenance_logs" USING btree ("status");--> statement-breakpoint +CREATE INDEX "feedback_tool_idx" ON "feedback" USING btree ("tool_id");--> statement-breakpoint +CREATE INDEX "feedback_status_idx" ON "feedback" USING btree ("status");--> statement-breakpoint +CREATE INDEX "project_tools_tool_idx" ON "project_tools" USING btree ("tool_id");--> statement-breakpoint +CREATE INDEX "projects_published_idx" ON "projects" USING btree ("published");--> statement-breakpoint +CREATE INDEX "audit_events_subject_idx" ON "audit_events" USING btree ("subject_type","subject_id");--> statement-breakpoint +CREATE INDEX "audit_events_at_idx" ON "audit_events" USING btree ("at"); \ No newline at end of file diff --git a/v5/src/lib/db/migrations/0002_updated_at_triggers.sql b/v5/src/lib/db/migrations/0002_updated_at_triggers.sql new file mode 100644 index 0000000..e6dad11 --- /dev/null +++ b/v5/src/lib/db/migrations/0002_updated_at_triggers.sql @@ -0,0 +1,27 @@ +-- updated_at is maintained by the database, not the ORM (spec §4): the import, +-- the demo seed and any manual SQL fix write outside Drizzle, and the Notion +-- mirror selects on this column. One function, one trigger per mutable table. +CREATE OR REPLACE FUNCTION set_updated_at() RETURNS trigger AS $$ +BEGIN + NEW.updated_at = now(); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; +--> statement-breakpoint +CREATE TRIGGER categories_set_updated_at BEFORE UPDATE ON "categories" FOR EACH ROW EXECUTE FUNCTION set_updated_at(); +--> statement-breakpoint +CREATE TRIGGER locations_set_updated_at BEFORE UPDATE ON "locations" FOR EACH ROW EXECUTE FUNCTION set_updated_at(); +--> statement-breakpoint +CREATE TRIGGER tools_set_updated_at BEFORE UPDATE ON "tools" FOR EACH ROW EXECUTE FUNCTION set_updated_at(); +--> statement-breakpoint +CREATE TRIGGER units_set_updated_at BEFORE UPDATE ON "units" FOR EACH ROW EXECUTE FUNCTION set_updated_at(); +--> statement-breakpoint +CREATE TRIGGER resources_set_updated_at BEFORE UPDATE ON "resources" FOR EACH ROW EXECUTE FUNCTION set_updated_at(); +--> statement-breakpoint +CREATE TRIGGER attachments_set_updated_at BEFORE UPDATE ON "attachments" FOR EACH ROW EXECUTE FUNCTION set_updated_at(); +--> statement-breakpoint +CREATE TRIGGER maintenance_logs_set_updated_at BEFORE UPDATE ON "maintenance_logs" FOR EACH ROW EXECUTE FUNCTION set_updated_at(); +--> statement-breakpoint +CREATE TRIGGER feedback_set_updated_at BEFORE UPDATE ON "feedback" FOR EACH ROW EXECUTE FUNCTION set_updated_at(); +--> statement-breakpoint +CREATE TRIGGER projects_set_updated_at BEFORE UPDATE ON "projects" FOR EACH ROW EXECUTE FUNCTION set_updated_at(); diff --git a/v5/src/lib/db/migrations/meta/0000_snapshot.json b/v5/src/lib/db/migrations/meta/0000_snapshot.json new file mode 100644 index 0000000..6831a0c --- /dev/null +++ b/v5/src/lib/db/migrations/meta/0000_snapshot.json @@ -0,0 +1,18 @@ +{ + "id": "7b4fe056-cc96-4017-ac55-975abee880af", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "7", + "dialect": "postgresql", + "tables": {}, + "enums": {}, + "schemas": {}, + "views": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/v5/src/lib/db/migrations/meta/0001_snapshot.json b/v5/src/lib/db/migrations/meta/0001_snapshot.json new file mode 100644 index 0000000..50e529e --- /dev/null +++ b/v5/src/lib/db/migrations/meta/0001_snapshot.json @@ -0,0 +1,1644 @@ +{ + "id": "91a6c777-c02d-4fc4-969b-56ce56959da2", + "prevId": "7b4fe056-cc96-4017-ac55-975abee880af", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.categories": { + "name": "categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "group": { + "name": "group", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notion_page_id": { + "name": "notion_page_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "categories_name_group_key": { + "name": "categories_name_group_key", + "columns": [ + { + "expression": "lower(\"name\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "lower(coalesce(\"group\", ''))", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "categories_notion_page_id_unique": { + "name": "categories_notion_page_id_unique", + "nullsNotDistinct": false, + "columns": [ + "notion_page_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.locations": { + "name": "locations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "room": { + "name": "room", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "zone": { + "name": "zone", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "map_tag": { + "name": "map_tag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notion_page_id": { + "name": "notion_page_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "locations_room_zone_key": { + "name": "locations_room_zone_key", + "columns": [ + { + "expression": "lower(\"room\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "lower(\"zone\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "locations_map_tag_unique": { + "name": "locations_map_tag_unique", + "nullsNotDistinct": false, + "columns": [ + "map_tag" + ] + }, + "locations_notion_page_id_unique": { + "name": "locations_notion_page_id_unique", + "nullsNotDistinct": false, + "columns": [ + "notion_page_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tools": { + "name": "tools", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "category_id": { + "name": "category_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "materials": { + "name": "materials", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "ppe_required": { + "name": "ppe_required", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "tags": { + "name": "tags", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "training_required": { + "name": "training_required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "use_restrictions": { + "name": "use_restrictions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "emergency_stop": { + "name": "emergency_stop", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "published": { + "name": "published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_reviewed_at": { + "name": "last_reviewed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_reviewed_by": { + "name": "last_reviewed_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notion_page_id": { + "name": "notion_page_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tools_name_trgm_idx": { + "name": "tools_name_trgm_idx", + "columns": [ + { + "expression": "\"name\" gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "tools_category_idx": { + "name": "tools_category_idx", + "columns": [ + { + "expression": "category_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tools_location_idx": { + "name": "tools_location_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tools_category_id_categories_id_fk": { + "name": "tools_category_id_categories_id_fk", + "tableFrom": "tools", + "tableTo": "categories", + "columnsFrom": [ + "category_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tools_location_id_locations_id_fk": { + "name": "tools_location_id_locations_id_fk", + "tableFrom": "tools", + "tableTo": "locations", + "columnsFrom": [ + "location_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tools_slug_unique": { + "name": "tools_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + }, + "tools_notion_page_id_unique": { + "name": "tools_notion_page_id_unique", + "nullsNotDistinct": false, + "columns": [ + "notion_page_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.units": { + "name": "units", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "tool_id": { + "name": "tool_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "unit_label": { + "name": "unit_label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serial_number": { + "name": "serial_number", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "asset_tag": { + "name": "asset_tag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'available'" + }, + "condition": { + "name": "condition", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "date_acquired": { + "name": "date_acquired", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notion_page_id": { + "name": "notion_page_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "units_tool_idx": { + "name": "units_tool_idx", + "columns": [ + { + "expression": "tool_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "units_tool_serial_key": { + "name": "units_tool_serial_key", + "columns": [ + { + "expression": "tool_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"serial_number\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"units\".\"serial_number\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "units_tool_id_tools_id_fk": { + "name": "units_tool_id_tools_id_fk", + "tableFrom": "units", + "tableTo": "tools", + "columnsFrom": [ + "tool_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "units_notion_page_id_unique": { + "name": "units_notion_page_id_unique", + "nullsNotDistinct": false, + "columns": [ + "notion_page_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "units_status_check": { + "name": "units_status_check", + "value": "\"status\" in ('available', 'in_use', 'under_maintenance', 'out_of_service', 'retired')" + }, + "units_condition_check": { + "name": "units_condition_check", + "value": "\"condition\" in ('excellent', 'good', 'fair', 'needs_repair', 'new')" + } + }, + "isRLSEnabled": false + }, + "public.resources": { + "name": "resources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "tool_id": { + "name": "tool_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "published": { + "name": "published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "notion_page_id": { + "name": "notion_page_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "resources_tool_idx": { + "name": "resources_tool_idx", + "columns": [ + { + "expression": "tool_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "resources_tool_id_tools_id_fk": { + "name": "resources_tool_id_tools_id_fk", + "tableFrom": "resources", + "tableTo": "tools", + "columnsFrom": [ + "tool_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "resources_notion_page_id_unique": { + "name": "resources_notion_page_id_unique", + "nullsNotDistinct": false, + "columns": [ + "notion_page_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.attachments": { + "name": "attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "owner_type": { + "name": "owner_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "blob_pathname": { + "name": "blob_pathname", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access": { + "name": "access", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public_url": { + "name": "public_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "size_bytes": { + "name": "size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "width": { + "name": "width", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "original_filename": { + "name": "original_filename", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_key": { + "name": "source_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "attachments_owner_idx": { + "name": "attachments_owner_idx", + "columns": [ + { + "expression": "owner_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "attachments_source_key_unique": { + "name": "attachments_source_key_unique", + "nullsNotDistinct": false, + "columns": [ + "source_key" + ] + } + }, + "policies": {}, + "checkConstraints": { + "attachments_owner_type_check": { + "name": "attachments_owner_type_check", + "value": "\"owner_type\" in ('tool', 'resource', 'maintenance_log', 'project', 'pending_tool')" + }, + "attachments_access_check": { + "name": "attachments_access_check", + "value": "\"access\" in ('public', 'private')" + } + }, + "isRLSEnabled": false + }, + "public.maintenance_logs": { + "name": "maintenance_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolution": { + "name": "resolution", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "unit_id": { + "name": "unit_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "tool_id": { + "name": "tool_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "unit_label": { + "name": "unit_label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reported_by_name": { + "name": "reported_by_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reported_by_email": { + "name": "reported_by_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reported_by_user_id": { + "name": "reported_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assigned_to_user_id": { + "name": "assigned_to_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assigned_to_name": { + "name": "assigned_to_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "date_reported": { + "name": "date_reported", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "date_resolved": { + "name": "date_resolved", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "notion_page_id": { + "name": "notion_page_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "maintenance_logs_unit_idx": { + "name": "maintenance_logs_unit_idx", + "columns": [ + { + "expression": "unit_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "maintenance_logs_tool_idx": { + "name": "maintenance_logs_tool_idx", + "columns": [ + { + "expression": "tool_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "maintenance_logs_status_idx": { + "name": "maintenance_logs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "maintenance_logs_unit_id_units_id_fk": { + "name": "maintenance_logs_unit_id_units_id_fk", + "tableFrom": "maintenance_logs", + "tableTo": "units", + "columnsFrom": [ + "unit_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "maintenance_logs_tool_id_tools_id_fk": { + "name": "maintenance_logs_tool_id_tools_id_fk", + "tableFrom": "maintenance_logs", + "tableTo": "tools", + "columnsFrom": [ + "tool_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "maintenance_logs_notion_page_id_unique": { + "name": "maintenance_logs_notion_page_id_unique", + "nullsNotDistinct": false, + "columns": [ + "notion_page_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "maintenance_logs_type_check": { + "name": "maintenance_logs_type_check", + "value": "\"type\" in ('issue_report', 'preventive_maintenance', 'repair', 'inspection', 'calibration')" + }, + "maintenance_logs_priority_check": { + "name": "maintenance_logs_priority_check", + "value": "\"priority\" in ('low', 'medium', 'high', 'critical')" + }, + "maintenance_logs_status_check": { + "name": "maintenance_logs_status_check", + "value": "\"status\" in ('open', 'in_progress', 'resolved', 'closed')" + } + }, + "isRLSEnabled": false + }, + "public.feedback": { + "name": "feedback", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "tool_id": { + "name": "tool_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "field_flagged": { + "name": "field_flagged", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_description": { + "name": "issue_description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "suggested_fix": { + "name": "suggested_fix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reporter_name": { + "name": "reporter_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reporter_email": { + "name": "reporter_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reporter_user_id": { + "name": "reporter_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'new'" + }, + "notion_page_id": { + "name": "notion_page_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "feedback_tool_idx": { + "name": "feedback_tool_idx", + "columns": [ + { + "expression": "tool_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_status_idx": { + "name": "feedback_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "feedback_tool_id_tools_id_fk": { + "name": "feedback_tool_id_tools_id_fk", + "tableFrom": "feedback", + "tableTo": "tools", + "columnsFrom": [ + "tool_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "feedback_notion_page_id_unique": { + "name": "feedback_notion_page_id_unique", + "nullsNotDistinct": false, + "columns": [ + "notion_page_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "feedback_field_flagged_check": { + "name": "feedback_field_flagged_check", + "value": "\"field_flagged\" in ('description', 'image', 'name', 'category', 'location', 'materials', 'safety_info')" + }, + "feedback_status_check": { + "name": "feedback_status_check", + "value": "\"status\" in ('new', 'reviewed', 'fixed', 'dismissed')" + } + }, + "isRLSEnabled": false + }, + "public.project_tools": { + "name": "project_tools", + "schema": "", + "columns": { + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "tool_id": { + "name": "tool_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "project_tools_tool_idx": { + "name": "project_tools_tool_idx", + "columns": [ + { + "expression": "tool_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_tools_project_id_projects_id_fk": { + "name": "project_tools_project_id_projects_id_fk", + "tableFrom": "project_tools", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_tools_tool_id_tools_id_fk": { + "name": "project_tools_tool_id_tools_id_fk", + "tableFrom": "project_tools", + "tableTo": "tools", + "columnsFrom": [ + "tool_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "project_tools_project_id_tool_id_pk": { + "name": "project_tools_project_id_tool_id_pk", + "columns": [ + "project_id", + "tool_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projects": { + "name": "projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "link": { + "name": "link", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "materials": { + "name": "materials", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "author_name": { + "name": "author_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "published": { + "name": "published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "published_at": { + "name": "published_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "published_by": { + "name": "published_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notion_page_id": { + "name": "notion_page_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "projects_published_idx": { + "name": "projects_published_idx", + "columns": [ + { + "expression": "published", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "projects_slug_unique": { + "name": "projects_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + }, + "projects_notion_page_id_unique": { + "name": "projects_notion_page_id_unique", + "nullsNotDistinct": false, + "columns": [ + "notion_page_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.audit_events": { + "name": "audit_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "at": { + "name": "at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_type": { + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_id": { + "name": "subject_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "detail": { + "name": "detail", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "audit_events_subject_idx": { + "name": "audit_events_subject_idx", + "columns": [ + { + "expression": "subject_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_events_at_idx": { + "name": "audit_events_at_idx", + "columns": [ + { + "expression": "at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/v5/src/lib/db/migrations/meta/0002_snapshot.json b/v5/src/lib/db/migrations/meta/0002_snapshot.json new file mode 100644 index 0000000..cc860c0 --- /dev/null +++ b/v5/src/lib/db/migrations/meta/0002_snapshot.json @@ -0,0 +1,1644 @@ +{ + "id": "2929b0ef-9134-4b15-b91d-e81fb0675600", + "prevId": "91a6c777-c02d-4fc4-969b-56ce56959da2", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.categories": { + "name": "categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "group": { + "name": "group", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notion_page_id": { + "name": "notion_page_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "categories_name_group_key": { + "name": "categories_name_group_key", + "columns": [ + { + "expression": "lower(\"name\")", + "isExpression": true, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(coalesce(\"group\", ''))", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "categories_notion_page_id_unique": { + "name": "categories_notion_page_id_unique", + "columns": [ + "notion_page_id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.locations": { + "name": "locations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "room": { + "name": "room", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "zone": { + "name": "zone", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "map_tag": { + "name": "map_tag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notion_page_id": { + "name": "notion_page_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "locations_room_zone_key": { + "name": "locations_room_zone_key", + "columns": [ + { + "expression": "lower(\"room\")", + "isExpression": true, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"zone\")", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "locations_map_tag_unique": { + "name": "locations_map_tag_unique", + "columns": [ + "map_tag" + ], + "nullsNotDistinct": false + }, + "locations_notion_page_id_unique": { + "name": "locations_notion_page_id_unique", + "columns": [ + "notion_page_id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tools": { + "name": "tools", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "category_id": { + "name": "category_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "materials": { + "name": "materials", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "ppe_required": { + "name": "ppe_required", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "tags": { + "name": "tags", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "training_required": { + "name": "training_required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "use_restrictions": { + "name": "use_restrictions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "emergency_stop": { + "name": "emergency_stop", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "published": { + "name": "published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_reviewed_at": { + "name": "last_reviewed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_reviewed_by": { + "name": "last_reviewed_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notion_page_id": { + "name": "notion_page_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tools_name_trgm_idx": { + "name": "tools_name_trgm_idx", + "columns": [ + { + "expression": "\"name\" gin_trgm_ops", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "gin", + "concurrently": false + }, + "tools_category_idx": { + "name": "tools_category_idx", + "columns": [ + { + "expression": "category_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "tools_location_idx": { + "name": "tools_location_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "tools_category_id_categories_id_fk": { + "name": "tools_category_id_categories_id_fk", + "tableFrom": "tools", + "columnsFrom": [ + "category_id" + ], + "tableTo": "categories", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "tools_location_id_locations_id_fk": { + "name": "tools_location_id_locations_id_fk", + "tableFrom": "tools", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tools_slug_unique": { + "name": "tools_slug_unique", + "columns": [ + "slug" + ], + "nullsNotDistinct": false + }, + "tools_notion_page_id_unique": { + "name": "tools_notion_page_id_unique", + "columns": [ + "notion_page_id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.units": { + "name": "units", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "tool_id": { + "name": "tool_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "unit_label": { + "name": "unit_label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serial_number": { + "name": "serial_number", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "asset_tag": { + "name": "asset_tag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'available'" + }, + "condition": { + "name": "condition", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "date_acquired": { + "name": "date_acquired", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notion_page_id": { + "name": "notion_page_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "units_tool_idx": { + "name": "units_tool_idx", + "columns": [ + { + "expression": "tool_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "units_tool_serial_key": { + "name": "units_tool_serial_key", + "columns": [ + { + "expression": "tool_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"serial_number\")", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"units\".\"serial_number\" is not null", + "concurrently": false + } + }, + "foreignKeys": { + "units_tool_id_tools_id_fk": { + "name": "units_tool_id_tools_id_fk", + "tableFrom": "units", + "columnsFrom": [ + "tool_id" + ], + "tableTo": "tools", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "units_notion_page_id_unique": { + "name": "units_notion_page_id_unique", + "columns": [ + "notion_page_id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "units_status_check": { + "name": "units_status_check", + "value": "\"status\" in ('available', 'in_use', 'under_maintenance', 'out_of_service', 'retired')" + }, + "units_condition_check": { + "name": "units_condition_check", + "value": "\"condition\" in ('excellent', 'good', 'fair', 'needs_repair', 'new')" + } + }, + "isRLSEnabled": false + }, + "public.resources": { + "name": "resources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "tool_id": { + "name": "tool_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "published": { + "name": "published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "notion_page_id": { + "name": "notion_page_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "resources_tool_idx": { + "name": "resources_tool_idx", + "columns": [ + { + "expression": "tool_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "resources_tool_id_tools_id_fk": { + "name": "resources_tool_id_tools_id_fk", + "tableFrom": "resources", + "columnsFrom": [ + "tool_id" + ], + "tableTo": "tools", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "resources_notion_page_id_unique": { + "name": "resources_notion_page_id_unique", + "columns": [ + "notion_page_id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.attachments": { + "name": "attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "owner_type": { + "name": "owner_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "blob_pathname": { + "name": "blob_pathname", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access": { + "name": "access", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public_url": { + "name": "public_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "size_bytes": { + "name": "size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "width": { + "name": "width", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "original_filename": { + "name": "original_filename", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_key": { + "name": "source_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "attachments_owner_idx": { + "name": "attachments_owner_idx", + "columns": [ + { + "expression": "owner_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "attachments_source_key_unique": { + "name": "attachments_source_key_unique", + "columns": [ + "source_key" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "attachments_owner_type_check": { + "name": "attachments_owner_type_check", + "value": "\"owner_type\" in ('tool', 'resource', 'maintenance_log', 'project', 'pending_tool')" + }, + "attachments_access_check": { + "name": "attachments_access_check", + "value": "\"access\" in ('public', 'private')" + } + }, + "isRLSEnabled": false + }, + "public.maintenance_logs": { + "name": "maintenance_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolution": { + "name": "resolution", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "unit_id": { + "name": "unit_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "tool_id": { + "name": "tool_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "unit_label": { + "name": "unit_label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reported_by_name": { + "name": "reported_by_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reported_by_email": { + "name": "reported_by_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reported_by_user_id": { + "name": "reported_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assigned_to_user_id": { + "name": "assigned_to_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assigned_to_name": { + "name": "assigned_to_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "date_reported": { + "name": "date_reported", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "date_resolved": { + "name": "date_resolved", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "notion_page_id": { + "name": "notion_page_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "maintenance_logs_unit_idx": { + "name": "maintenance_logs_unit_idx", + "columns": [ + { + "expression": "unit_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "maintenance_logs_tool_idx": { + "name": "maintenance_logs_tool_idx", + "columns": [ + { + "expression": "tool_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "maintenance_logs_status_idx": { + "name": "maintenance_logs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "maintenance_logs_unit_id_units_id_fk": { + "name": "maintenance_logs_unit_id_units_id_fk", + "tableFrom": "maintenance_logs", + "columnsFrom": [ + "unit_id" + ], + "tableTo": "units", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "maintenance_logs_tool_id_tools_id_fk": { + "name": "maintenance_logs_tool_id_tools_id_fk", + "tableFrom": "maintenance_logs", + "columnsFrom": [ + "tool_id" + ], + "tableTo": "tools", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "maintenance_logs_notion_page_id_unique": { + "name": "maintenance_logs_notion_page_id_unique", + "columns": [ + "notion_page_id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "maintenance_logs_type_check": { + "name": "maintenance_logs_type_check", + "value": "\"type\" in ('issue_report', 'preventive_maintenance', 'repair', 'inspection', 'calibration')" + }, + "maintenance_logs_priority_check": { + "name": "maintenance_logs_priority_check", + "value": "\"priority\" in ('low', 'medium', 'high', 'critical')" + }, + "maintenance_logs_status_check": { + "name": "maintenance_logs_status_check", + "value": "\"status\" in ('open', 'in_progress', 'resolved', 'closed')" + } + }, + "isRLSEnabled": false + }, + "public.feedback": { + "name": "feedback", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "tool_id": { + "name": "tool_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "field_flagged": { + "name": "field_flagged", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_description": { + "name": "issue_description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "suggested_fix": { + "name": "suggested_fix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reporter_name": { + "name": "reporter_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reporter_email": { + "name": "reporter_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reporter_user_id": { + "name": "reporter_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'new'" + }, + "notion_page_id": { + "name": "notion_page_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "feedback_tool_idx": { + "name": "feedback_tool_idx", + "columns": [ + { + "expression": "tool_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "feedback_status_idx": { + "name": "feedback_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "feedback_tool_id_tools_id_fk": { + "name": "feedback_tool_id_tools_id_fk", + "tableFrom": "feedback", + "columnsFrom": [ + "tool_id" + ], + "tableTo": "tools", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "feedback_notion_page_id_unique": { + "name": "feedback_notion_page_id_unique", + "columns": [ + "notion_page_id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "feedback_field_flagged_check": { + "name": "feedback_field_flagged_check", + "value": "\"field_flagged\" in ('description', 'image', 'name', 'category', 'location', 'materials', 'safety_info')" + }, + "feedback_status_check": { + "name": "feedback_status_check", + "value": "\"status\" in ('new', 'reviewed', 'fixed', 'dismissed')" + } + }, + "isRLSEnabled": false + }, + "public.project_tools": { + "name": "project_tools", + "schema": "", + "columns": { + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "tool_id": { + "name": "tool_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "project_tools_tool_idx": { + "name": "project_tools_tool_idx", + "columns": [ + { + "expression": "tool_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "project_tools_project_id_projects_id_fk": { + "name": "project_tools_project_id_projects_id_fk", + "tableFrom": "project_tools", + "columnsFrom": [ + "project_id" + ], + "tableTo": "projects", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "project_tools_tool_id_tools_id_fk": { + "name": "project_tools_tool_id_tools_id_fk", + "tableFrom": "project_tools", + "columnsFrom": [ + "tool_id" + ], + "tableTo": "tools", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "project_tools_project_id_tool_id_pk": { + "name": "project_tools_project_id_tool_id_pk", + "columns": [ + "project_id", + "tool_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projects": { + "name": "projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "link": { + "name": "link", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "materials": { + "name": "materials", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "author_name": { + "name": "author_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "published": { + "name": "published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "published_at": { + "name": "published_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "published_by": { + "name": "published_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notion_page_id": { + "name": "notion_page_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "projects_published_idx": { + "name": "projects_published_idx", + "columns": [ + { + "expression": "published", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "projects_slug_unique": { + "name": "projects_slug_unique", + "columns": [ + "slug" + ], + "nullsNotDistinct": false + }, + "projects_notion_page_id_unique": { + "name": "projects_notion_page_id_unique", + "columns": [ + "notion_page_id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.audit_events": { + "name": "audit_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "at": { + "name": "at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_type": { + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_id": { + "name": "subject_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "detail": { + "name": "detail", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "audit_events_subject_idx": { + "name": "audit_events_subject_idx", + "columns": [ + { + "expression": "subject_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "audit_events_at_idx": { + "name": "audit_events_at_idx", + "columns": [ + { + "expression": "at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "views": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/v5/src/lib/db/migrations/meta/_journal.json b/v5/src/lib/db/migrations/meta/_journal.json new file mode 100644 index 0000000..00f6fd7 --- /dev/null +++ b/v5/src/lib/db/migrations/meta/_journal.json @@ -0,0 +1,27 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1789435214059, + "tag": "0000_extensions", + "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1789435214423, + "tag": "0001_initial_schema", + "breakpoints": true + }, + { + "idx": 2, + "version": "7", + "when": 1789435214789, + "tag": "0002_updated_at_triggers", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/v5/src/lib/db/neon.ts b/v5/src/lib/db/neon.ts new file mode 100644 index 0000000..fa8a343 --- /dev/null +++ b/v5/src/lib/db/neon.ts @@ -0,0 +1,28 @@ +import { Pool } from "@neondatabase/serverless"; +import { drizzle } from "drizzle-orm/neon-serverless"; +import { migrate } from "drizzle-orm/neon-serverless/migrator"; +import * as schema from "./schema/index.ts"; +import { migrationsFolder } from "./migrations-folder.ts"; +import type { Db } from "./types.ts"; + +/** + * Neon Postgres over the serverless driver (spec §3.2). The pooled + * (WebSocket) client rather than the HTTP one, because approvals and imports + * need transactions. + * + * The driver needs a global `WebSocket`, which Node 22+ and every Vercel + * runtime provide. The check below turns a missing one into a plain sentence + * instead of a stack trace from inside the driver. + */ +export function createNeonDb(connectionString: string): Db { + if (typeof WebSocket === "undefined") { + throw new Error("The Neon driver needs a global WebSocket; run on Node 22 or newer."); + } + const pool = new Pool({ connectionString }); + return drizzle(pool, { schema }); +} + +/** Apply every committed migration that has not run yet. Used by `db:migrate`. */ +export async function migrateNeon(db: Db): Promise { + await migrate(db as Parameters[0], { migrationsFolder: migrationsFolder() }); +} diff --git a/v5/src/lib/db/pglite.ts b/v5/src/lib/db/pglite.ts new file mode 100644 index 0000000..724580a --- /dev/null +++ b/v5/src/lib/db/pglite.ts @@ -0,0 +1,29 @@ +import { PGlite } from "@electric-sql/pglite"; +import { pg_trgm } from "@electric-sql/pglite/contrib/pg_trgm"; +import { drizzle } from "drizzle-orm/pglite"; +import { migrate } from "drizzle-orm/pglite/migrator"; +import * as schema from "./schema/index.ts"; +import { migrationsFolder } from "./migrations-folder.ts"; +import type { Db } from "./types.ts"; + +/** + * An in-process Postgres (spec §3.2). PGlite is Postgres compiled to + * WebAssembly: real SQL, real constraints, real triggers, and no network. It + * serves the test suite, E2E, local development without `DATABASE_URL`, and + * demo mode — and it is what keeps constitution Article 3 true. + * + * Every instance starts empty and runs the same committed migrations Neon + * runs, so a schema mistake shows up in a unit test before it reaches a deploy. + */ +export interface PgliteOptions { + /** Runs once, after migrations, on a fresh database. */ + seed?: (db: Db) => Promise; +} + +export async function createPgliteDb(options: PgliteOptions = {}): Promise { + const client = new PGlite({ extensions: { pg_trgm } }); + const db = drizzle(client, { schema }); + await migrate(db, { migrationsFolder: migrationsFolder() }); + if (options.seed) await options.seed(db); + return db; +} diff --git a/v5/src/lib/db/raw.ts b/v5/src/lib/db/raw.ts new file mode 100644 index 0000000..3651200 --- /dev/null +++ b/v5/src/lib/db/raw.ts @@ -0,0 +1,16 @@ +import type { SQL } from "drizzle-orm"; +import type { Db } from "./types.ts"; + +/** + * Rows from a raw SQL statement, typed by the caller. + * + * `Db` is the driver-agnostic `PgDatabase`, whose `execute()` cannot know the + * driver's result shape and so returns `unknown`. Both drivers in use (Neon, + * PGlite) return `{ rows }`, and this is the one place that fact is relied on. + * Prefer the query builder; reach for this only where SQL has no builder + * equivalent, such as `pg_trgm` similarity. + */ +export async function rawRows(db: Db, query: SQL): Promise { + const result = (await db.execute(query)) as unknown as { rows: T[] }; + return result.rows; +} diff --git a/v5/src/lib/db/schema.test.ts b/v5/src/lib/db/schema.test.ts new file mode 100644 index 0000000..6d1f129 --- /dev/null +++ b/v5/src/lib/db/schema.test.ts @@ -0,0 +1,164 @@ +// @vitest-environment node +import { eq, sql } from "drizzle-orm"; +import { expectViolation } from "../../../test/db"; +import { createPgliteDb } from "./pglite"; +import { rawRows } from "./raw"; +import { categories, locations, maintenanceLogs, tools, units } from "./schema/index"; +import type { Db } from "./types"; + +/** + * The committed migrations, applied to a real (in-process) Postgres. These + * are the tests that catch a schema mistake before it reaches Neon: every + * CHECK, unique index and trigger the spec names is exercised here. + */ +describe("schema migrations on PGlite", () => { + let db: Db; + + beforeAll(async () => { + db = await createPgliteDb(); + }); + + it("applies every migration and creates the tables", async () => { + const rows = await rawRows<{ table_name: string }>( + db, + sql`select table_name from information_schema.tables where table_schema = 'public' order by table_name` + ); + expect(rows.map((row) => row.table_name)).toEqual( + expect.arrayContaining([ + "categories", + "locations", + "tools", + "units", + "resources", + "attachments", + "maintenance_logs", + "feedback", + "projects", + "project_tools", + "audit_events", + ]) + ); + }); + + it("enables pg_trgm and builds the trigram index on tools.name", async () => { + const ext = await rawRows<{ extname: string }>( + db, + sql`select extname from pg_extension where extname = 'pg_trgm'` + ); + expect(ext).toHaveLength(1); + const idx = await rawRows<{ indexname: string }>( + db, + sql`select indexname from pg_indexes where tablename = 'tools' and indexname = 'tools_name_trgm_idx'` + ); + expect(idx).toHaveLength(1); + }); + + it("maintains updated_at from a trigger, not the ORM", async () => { + const [row] = await db + .insert(categories) + .values({ name: "Trigger check", group: "Test" }) + .returning({ id: categories.id, updatedAt: categories.updatedAt }); + + await new Promise((resolve) => setTimeout(resolve, 5)); + await db.update(categories).set({ name: "Trigger check 2" }).where(eq(categories.id, row.id)); + + const [after] = await db + .select({ updatedAt: categories.updatedAt }) + .from(categories) + .where(eq(categories.id, row.id)); + expect(after.updatedAt.getTime()).toBeGreaterThan(row.updatedAt.getTime()); + }); + + it("refuses a unit status outside the vocabulary", async () => { + await expectViolation( + db.insert(units).values({ unitLabel: "Bad", status: "Available" }), + /units_status_check/ + ); + }); + + it("accepts a null condition (unknown is honest) and the New option", async () => { + await expect( + db.insert(units).values([ + { unitLabel: "Unknown condition", condition: null }, + { unitLabel: "Brand new", condition: "new" }, + ]) + ).resolves.toBeDefined(); + }); + + it("refuses a maintenance status outside the vocabulary but allows closed", async () => { + await expectViolation( + db.insert(maintenanceLogs).values({ title: "t", status: "Open" }), + /maintenance_logs_status_check/ + ); + await expect( + db.insert(maintenanceLogs).values({ title: "t", status: "closed" }) + ).resolves.toBeDefined(); + }); + + it("keeps categories unique per (name, group) case-insensitively, and allows the same name in another group", async () => { + await db.insert(categories).values({ name: "Other", group: "Electronics" }); + await expectViolation( + db.insert(categories).values({ name: "other", group: "electronics" }), + /categories_name_group_key/ + ); + await expect( + db.insert(categories).values({ name: "Other", group: "Textiles" }) + ).resolves.toBeDefined(); + }); + + it("keeps locations unique per (room, zone) and map tags unique when present", async () => { + await db.insert(locations).values({ room: "MakerLab", zone: "Bench 1", mapTag: "ML-01" }); + await expectViolation( + db.insert(locations).values({ room: "makerlab", zone: "bench 1" }), + /locations_room_zone_key/ + ); + await expectViolation( + db.insert(locations).values({ room: "MakerLab", zone: "Bench 2", mapTag: "ML-01" }), + /locations_map_tag_unique/ + ); + await expect( + db.insert(locations).values([ + { room: "MakerLab", zone: "Bench 3" }, + { room: "MakerLab", zone: "Bench 4" }, + ]) + ).resolves.toBeDefined(); + }); + + it("keeps serial numbers unique per tool, case-insensitively, and ignores nulls", async () => { + const [tool] = await db + .insert(tools) + .values({ slug: "serial-test", name: "Serial test" }) + .returning({ id: tools.id }); + await db.insert(units).values({ toolId: tool.id, unitLabel: "A", serialNumber: "SN-1" }); + await expectViolation( + db.insert(units).values({ toolId: tool.id, unitLabel: "B", serialNumber: "sn-1" }), + /units_tool_serial_key/ + ); + await expect( + db.insert(units).values([ + { toolId: tool.id, unitLabel: "C" }, + { toolId: tool.id, unitLabel: "D" }, + ]) + ).resolves.toBeDefined(); + }); + + it("cascades units when a tool row is deleted, and nulls the tool on a category delete", async () => { + const [cat] = await db + .insert(categories) + .values({ name: "Cascade", group: "Test" }) + .returning({ id: categories.id }); + const [tool] = await db + .insert(tools) + .values({ slug: "cascade-test", name: "Cascade test", categoryId: cat.id }) + .returning({ id: tools.id }); + await db.insert(units).values({ toolId: tool.id, unitLabel: "X" }); + + await db.delete(categories).where(eq(categories.id, cat.id)); + const [afterCat] = await db.select({ categoryId: tools.categoryId }).from(tools).where(eq(tools.id, tool.id)); + expect(afterCat.categoryId).toBeNull(); + + await db.delete(tools).where(eq(tools.id, tool.id)); + const orphans = await db.select().from(units).where(eq(units.toolId, tool.id)); + expect(orphans).toHaveLength(0); + }); +}); diff --git a/v5/src/lib/db/schema/attachments.ts b/v5/src/lib/db/schema/attachments.ts new file mode 100644 index 0000000..e72bb59 --- /dev/null +++ b/v5/src/lib/db/schema/attachments.ts @@ -0,0 +1,42 @@ +import { index, integer, pgTable, text, uuid } from "drizzle-orm/pg-core"; +import { inListCheck, timestamps } from "./helpers.ts"; +import { ATTACHMENT_ACCESS, ATTACHMENT_OWNER } from "./vocabulary.ts"; + +/** + * Attachments — every file in Vercel Blob, and which row it belongs to + * (spec §4.7). + * + * The owner is polymorphic (`owner_type` + `owner_id`), so there is no foreign + * key; a test walk checks integrity instead. Both columns are null together + * while a file is uploaded but not yet attached to anything; the daily cron + * deletes those after 24 hours. + * + * `source_key` is the import's idempotency key — `:: + * ` — so a re-run recognises a file it already copied instead of + * uploading it twice. Null on files uploaded through the app. + */ +export const attachments = pgTable( + "attachments", + { + id: uuid("id").primaryKey().defaultRandom(), + ownerType: text("owner_type"), + ownerId: uuid("owner_id"), + position: integer("position").notNull().default(0), + blobPathname: text("blob_pathname").notNull(), + access: text("access").notNull(), + publicUrl: text("public_url"), + contentType: text("content_type"), + sizeBytes: integer("size_bytes"), + width: integer("width"), + height: integer("height"), + originalFilename: text("original_filename"), + sourceKey: text("source_key").unique(), + uploadedBy: text("uploaded_by"), + ...timestamps(), + }, + (t) => [ + inListCheck("attachments_owner_type_check", "owner_type", ATTACHMENT_OWNER), + inListCheck("attachments_access_check", "access", ATTACHMENT_ACCESS), + index("attachments_owner_idx").on(t.ownerType, t.ownerId, t.position), + ] +); diff --git a/v5/src/lib/db/schema/audit.ts b/v5/src/lib/db/schema/audit.ts new file mode 100644 index 0000000..f803a54 --- /dev/null +++ b/v5/src/lib/db/schema/audit.ts @@ -0,0 +1,35 @@ +import { index, jsonb, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"; + +/** + * Audit events — append-only (spec §4.11). The data layer exposes insert and + * select on this table and never update or delete. Security-relevant actions + * only: role changes, bans, publishing, archiving, approving a researched + * tool, connecting or disconnecting a mirror. Ordinary edits are not logged. + */ +export const AUDIT_ACTIONS = [ + "role.changed", + "user.banned", + "tool.published", + "tool.unpublished", + "tool.archived", + "project.published", + "project.unpublished", + "pending.approved", + "mirror.connected", + "mirror.disconnected", +] as const; +export type AuditAction = (typeof AUDIT_ACTIONS)[number]; + +export const auditEvents = pgTable( + "audit_events", + { + id: uuid("id").primaryKey().defaultRandom(), + at: timestamp("at", { withTimezone: true }).notNull().defaultNow(), + actorUserId: text("actor_user_id"), + action: text("action").notNull(), + subjectType: text("subject_type").notNull(), + subjectId: text("subject_id").notNull(), + detail: jsonb("detail").$type>(), + }, + (t) => [index("audit_events_subject_idx").on(t.subjectType, t.subjectId), index("audit_events_at_idx").on(t.at)] +); diff --git a/v5/src/lib/db/schema/feedback.ts b/v5/src/lib/db/schema/feedback.ts new file mode 100644 index 0000000..4b45900 --- /dev/null +++ b/v5/src/lib/db/schema/feedback.ts @@ -0,0 +1,33 @@ +import { index, pgTable, text, uuid } from "drizzle-orm/pg-core"; +import { actorColumns, inListCheck, notionPageId, timestamps } from "./helpers.ts"; +import { tools } from "./tools.ts"; +import { FEEDBACK_STATUS, FLAG_FIELDS } from "./vocabulary.ts"; + +/** + * Feedback — catalogue corrections, today's Notion "Flags" database + * (spec §4.9). Filed by anyone through the correction form or the + * `report_correction` tool; worked by admins on `/admin/corrections`. + */ +export const feedback = pgTable( + "feedback", + { + id: uuid("id").primaryKey().defaultRandom(), + toolId: uuid("tool_id").references(() => tools.id, { onDelete: "set null" }), + fieldFlagged: text("field_flagged"), + issueDescription: text("issue_description").notNull(), + suggestedFix: text("suggested_fix"), + reporterName: text("reporter_name"), + reporterEmail: text("reporter_email"), + reporterUserId: text("reporter_user_id"), + status: text("status").notNull().default("new"), + notionPageId: notionPageId(), + ...actorColumns(), + ...timestamps(), + }, + (t) => [ + inListCheck("feedback_field_flagged_check", "field_flagged", FLAG_FIELDS), + inListCheck("feedback_status_check", "status", FEEDBACK_STATUS), + index("feedback_tool_idx").on(t.toolId), + index("feedback_status_idx").on(t.status), + ] +); diff --git a/v5/src/lib/db/schema/helpers.ts b/v5/src/lib/db/schema/helpers.ts new file mode 100644 index 0000000..b223e2d --- /dev/null +++ b/v5/src/lib/db/schema/helpers.ts @@ -0,0 +1,60 @@ +import { sql, type SQL } from "drizzle-orm"; +import { check, text, timestamp } from "drizzle-orm/pg-core"; + +/** + * Column and constraint helpers shared by every table in `schema/`. + * + * Imports inside `src/lib/db/` use explicit relative paths with `.ts` + * extensions and no `@/` alias, because the import and migrate scripts load + * these modules under plain Node (`--experimental-strip-types`) as well as + * under Next. + */ + +/** + * `created_at` / `updated_at` on every mutable table (spec §4). `updated_at` + * is maintained by a `BEFORE UPDATE` trigger (migration `0002`), not by the + * ORM, because the import, the demo seed and any manual SQL fix write outside + * Drizzle, and the Notion mirror selects on this column. + */ +export function timestamps() { + return { + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), + updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), + }; +} + +/** + * Who created or last changed a row. Plain `text` for now: it will reference + * Better Auth's `user.id` once Phase 4 creates that table, and the foreign key + * is added in that phase's migration. Null on imported rows. + */ +export function actorColumns() { + return { + createdBy: text("created_by"), + updatedBy: text("updated_by"), + }; +} + +/** The legacy Notion page id: the import's upsert key and the `/tools/` redirect key. */ +export function notionPageId() { + return text("notion_page_id").unique(); +} + +/** + * A named CHECK that restricts `column` to `values`. Written with `sql.raw` so + * drizzle-kit renders the literals into the migration instead of `$1` + * placeholders. + */ +export function inListCheck( + name: string, + column: string, + values: readonly string[] +): ReturnType { + return check(name, inList(column, values)); +} + +/** `"column" in ('a', 'b', …)` as raw SQL; a null column value passes the CHECK. */ +export function inList(column: string, values: readonly string[]): SQL { + const literals = values.map((value) => `'${value.replace(/'/g, "''")}'`).join(", "); + return sql.raw(`"${column}" in (${literals})`); +} diff --git a/v5/src/lib/db/schema/index.ts b/v5/src/lib/db/schema/index.ts new file mode 100644 index 0000000..82df228 --- /dev/null +++ b/v5/src/lib/db/schema/index.ts @@ -0,0 +1,19 @@ +/** + * The v5 schema (data platform design spec 2026-09-14 §4). One file per table + * group; this module is what `drizzle.config.ts` points at and what + * `drizzle()` receives as its `schema`, so relational queries see every table. + * + * Later phases add `pending_tools` (Phase 6), the Notion mirror tables + * (Phase 8) and Better Auth's `user` / `session` / `account` / `verification` + * tables (Phase 4), each with its own migration. + */ +export * from "./vocabulary.ts"; +export * from "./taxonomy.ts"; +export * from "./tools.ts"; +export * from "./units.ts"; +export * from "./resources.ts"; +export * from "./attachments.ts"; +export * from "./maintenance.ts"; +export * from "./feedback.ts"; +export * from "./projects.ts"; +export * from "./audit.ts"; diff --git a/v5/src/lib/db/schema/maintenance.ts b/v5/src/lib/db/schema/maintenance.ts new file mode 100644 index 0000000..247ad0f --- /dev/null +++ b/v5/src/lib/db/schema/maintenance.ts @@ -0,0 +1,52 @@ +import { date, index, pgTable, text, uuid } from "drizzle-orm/pg-core"; +import { actorColumns, inListCheck, notionPageId, timestamps } from "./helpers.ts"; +import { tools } from "./tools.ts"; +import { units } from "./units.ts"; +import { MAINTENANCE_PRIORITY, MAINTENANCE_STATUS, MAINTENANCE_TYPE } from "./vocabulary.ts"; + +/** + * Maintenance logs — tickets filed by anyone, worked by admins (spec §4.8). + * + * - No CHECK requires a unit or a tool: most live logs have neither, and a + * ticket with no target is still a ticket. + * - `tool_name` and `unit_label` are snapshots taken at write time so history + * survives a retired unit. + * - `reported_by_email` is set only from a server-resolved session, never from + * tool or request input, and never enters a model prompt or the Notion mirror. + * - Dates are `date`, computed in `LAB_TIMEZONE` by the write path, never from + * the server clock. + */ +export const maintenanceLogs = pgTable( + "maintenance_logs", + { + id: uuid("id").primaryKey().defaultRandom(), + title: text("title").notNull(), + type: text("type"), + priority: text("priority"), + status: text("status").notNull().default("open"), + description: text("description"), + resolution: text("resolution"), + unitId: uuid("unit_id").references(() => units.id, { onDelete: "set null" }), + toolId: uuid("tool_id").references(() => tools.id, { onDelete: "set null" }), + toolName: text("tool_name"), + unitLabel: text("unit_label"), + reportedByName: text("reported_by_name"), + reportedByEmail: text("reported_by_email"), + reportedByUserId: text("reported_by_user_id"), + assignedToUserId: text("assigned_to_user_id"), + assignedToName: text("assigned_to_name"), + dateReported: date("date_reported", { mode: "string" }), + dateResolved: date("date_resolved", { mode: "string" }), + notionPageId: notionPageId(), + ...actorColumns(), + ...timestamps(), + }, + (t) => [ + inListCheck("maintenance_logs_type_check", "type", MAINTENANCE_TYPE), + inListCheck("maintenance_logs_priority_check", "priority", MAINTENANCE_PRIORITY), + inListCheck("maintenance_logs_status_check", "status", MAINTENANCE_STATUS), + index("maintenance_logs_unit_idx").on(t.unitId), + index("maintenance_logs_tool_idx").on(t.toolId), + index("maintenance_logs_status_idx").on(t.status), + ] +); diff --git a/v5/src/lib/db/schema/projects.ts b/v5/src/lib/db/schema/projects.ts new file mode 100644 index 0000000..2a6ed4a --- /dev/null +++ b/v5/src/lib/db/schema/projects.ts @@ -0,0 +1,46 @@ +import { sql } from "drizzle-orm"; +import { boolean, index, pgTable, primaryKey, text, timestamp, uuid } from "drizzle-orm/pg-core"; +import { actorColumns, notionPageId, timestamps } from "./helpers.ts"; +import { tools } from "./tools.ts"; + +/** + * Student projects (spec §4.10). + * + * `author_user_id` is required on rows the app creates (projects need + * sign-in) and null only on imported posts, which predate accounts. Photos are + * `attachments` rows owned by the project; the lowest `position` is the cover. + */ +export const projects = pgTable( + "projects", + { + id: uuid("id").primaryKey().defaultRandom(), + slug: text("slug").notNull().unique(), + title: text("title").notNull(), + body: text("body").notNull().default(""), + link: text("link"), + materials: text("materials").array().notNull().default(sql`'{}'::text[]`), + authorUserId: text("author_user_id"), + authorName: text("author_name"), + published: boolean("published").notNull().default(false), + publishedAt: timestamp("published_at", { withTimezone: true }), + publishedBy: text("published_by"), + notionPageId: notionPageId(), + ...actorColumns(), + ...timestamps(), + }, + (t) => [index("projects_published_idx").on(t.published)] +); + +/** Which tools a project was built with; both sides cascade. */ +export const projectTools = pgTable( + "project_tools", + { + projectId: uuid("project_id") + .notNull() + .references(() => projects.id, { onDelete: "cascade" }), + toolId: uuid("tool_id") + .notNull() + .references(() => tools.id, { onDelete: "cascade" }), + }, + (t) => [primaryKey({ columns: [t.projectId, t.toolId] }), index("project_tools_tool_idx").on(t.toolId)] +); diff --git a/v5/src/lib/db/schema/resources.ts b/v5/src/lib/db/schema/resources.ts new file mode 100644 index 0000000..9b48776 --- /dev/null +++ b/v5/src/lib/db/schema/resources.ts @@ -0,0 +1,29 @@ +import { boolean, index, pgTable, text, uuid } from "drizzle-orm/pg-core"; +import { actorColumns, notionPageId, timestamps } from "./helpers.ts"; +import { tools } from "./tools.ts"; + +/** + * Resources — manuals, SOPs, videos and other links attached to a tool + * (spec §4.6). + * + * `type` is free text with no CHECK: the workspace defines about a dozen + * options and intake narrows them to three, so a constraint built from either + * list would reject values already stored. A resource's file, if it has one, + * is an `attachments` row owned by the resource. + */ +export const resources = pgTable( + "resources", + { + id: uuid("id").primaryKey().defaultRandom(), + toolId: uuid("tool_id").references(() => tools.id, { onDelete: "cascade" }), + title: text("title").notNull(), + type: text("type"), + url: text("url"), + notes: text("notes"), + published: boolean("published").notNull().default(true), + notionPageId: notionPageId(), + ...actorColumns(), + ...timestamps(), + }, + (t) => [index("resources_tool_idx").on(t.toolId)] +); diff --git a/v5/src/lib/db/schema/taxonomy.ts b/v5/src/lib/db/schema/taxonomy.ts new file mode 100644 index 0000000..a2d735c --- /dev/null +++ b/v5/src/lib/db/schema/taxonomy.ts @@ -0,0 +1,47 @@ +import { sql } from "drizzle-orm"; +import { pgTable, text, uniqueIndex, uuid } from "drizzle-orm/pg-core"; +import { actorColumns, notionPageId, timestamps } from "./helpers.ts"; + +/** + * Categories and locations (data platform design spec 2026-09-14 §4.3). + * + * `categories` is unique on `(lower(name), lower(coalesce(group, '')))`, not on + * the name alone: the live workspace has three case-insensitive name collisions + * across different groups, and a name-only constraint would refuse the import. + */ +export const categories = pgTable( + "categories", + { + id: uuid("id").primaryKey().defaultRandom(), + name: text("name").notNull(), + group: text("group"), + notionPageId: notionPageId(), + ...actorColumns(), + ...timestamps(), + }, + (t) => [ + uniqueIndex("categories_name_group_key").on( + sql`lower(${t.name})`, + sql`lower(coalesce(${t.group}, ''))` + ), + ] +); + +/** + * `map_tag` is the printed label on the floor map (Notion's location title, + * e.g. `ML-RESIN-01`). Unique when present; Postgres lets several rows hold + * null, so no partial index is needed. + */ +export const locations = pgTable( + "locations", + { + id: uuid("id").primaryKey().defaultRandom(), + room: text("room").notNull(), + zone: text("zone").notNull(), + mapTag: text("map_tag").unique(), + notionPageId: notionPageId(), + ...actorColumns(), + ...timestamps(), + }, + (t) => [uniqueIndex("locations_room_zone_key").on(sql`lower(${t.room})`, sql`lower(${t.zone})`)] +); diff --git a/v5/src/lib/db/schema/tools.ts b/v5/src/lib/db/schema/tools.ts new file mode 100644 index 0000000..10b505f --- /dev/null +++ b/v5/src/lib/db/schema/tools.ts @@ -0,0 +1,46 @@ +import { sql } from "drizzle-orm"; +import { boolean, index, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"; +import { actorColumns, notionPageId, timestamps } from "./helpers.ts"; +import { categories, locations } from "./taxonomy.ts"; + +/** + * Tools (data platform design spec 2026-09-14 §4.4). + * + * - `slug` is derived from the name once, at creation, and never changes on a + * rename, so `/tools/` links stay valid. + * - `notion_page_id` keeps the legacy URL key: printed QR labels and old links + * of the form `/tools/` redirect through it (spec Goal 2). + * - Tools are archived (`archived_at`), never deleted, because maintenance + * history refers to them. + * - The trigram index backs the duplicate check when equipment is added. + */ +export const tools = pgTable( + "tools", + { + id: uuid("id").primaryKey().defaultRandom(), + slug: text("slug").notNull().unique(), + name: text("name").notNull(), + description: text("description"), + categoryId: uuid("category_id").references(() => categories.id, { onDelete: "set null" }), + locationId: uuid("location_id").references(() => locations.id, { onDelete: "set null" }), + materials: text("materials").array().notNull().default(sql`'{}'::text[]`), + ppeRequired: text("ppe_required").array().notNull().default(sql`'{}'::text[]`), + tags: text("tags").array().notNull().default(sql`'{}'::text[]`), + trainingRequired: boolean("training_required").notNull().default(false), + useRestrictions: text("use_restrictions"), + emergencyStop: text("emergency_stop"), + notes: text("notes"), + published: boolean("published").notNull().default(false), + archivedAt: timestamp("archived_at", { withTimezone: true }), + lastReviewedAt: timestamp("last_reviewed_at", { withTimezone: true }), + lastReviewedBy: text("last_reviewed_by"), + notionPageId: notionPageId(), + ...actorColumns(), + ...timestamps(), + }, + (t) => [ + index("tools_name_trgm_idx").using("gin", sql`${t.name} gin_trgm_ops`), + index("tools_category_idx").on(t.categoryId), + index("tools_location_idx").on(t.locationId), + ] +); diff --git a/v5/src/lib/db/schema/units.ts b/v5/src/lib/db/schema/units.ts new file mode 100644 index 0000000..88f4814 --- /dev/null +++ b/v5/src/lib/db/schema/units.ts @@ -0,0 +1,40 @@ +import { sql } from "drizzle-orm"; +import { date, index, pgTable, text, uniqueIndex, uuid } from "drizzle-orm/pg-core"; +import { actorColumns, inListCheck, notionPageId, timestamps } from "./helpers.ts"; +import { tools } from "./tools.ts"; +import { UNIT_CONDITION, UNIT_STATUS } from "./vocabulary.ts"; + +/** + * Units — the physical machines behind a tool (spec §4.5). + * + * - `tool_id` is nullable: the live workspace has one unlinked unit, and the + * inventory table surfaces unlinked units rather than inventing a tool. + * - `condition` is nullable: unknown is an honest state; inventing `good` is not. + * - Serial numbers are unique per tool when present — the "is this a second + * unit?" check during intake. + */ +export const units = pgTable( + "units", + { + id: uuid("id").primaryKey().defaultRandom(), + toolId: uuid("tool_id").references(() => tools.id, { onDelete: "cascade" }), + unitLabel: text("unit_label").notNull(), + serialNumber: text("serial_number"), + assetTag: text("asset_tag"), + status: text("status").notNull().default("available"), + condition: text("condition"), + dateAcquired: date("date_acquired", { mode: "string" }), + notes: text("notes"), + notionPageId: notionPageId(), + ...actorColumns(), + ...timestamps(), + }, + (t) => [ + inListCheck("units_status_check", "status", UNIT_STATUS), + inListCheck("units_condition_check", "condition", UNIT_CONDITION), + index("units_tool_idx").on(t.toolId), + uniqueIndex("units_tool_serial_key") + .on(t.toolId, sql`lower(${t.serialNumber})`) + .where(sql`${t.serialNumber} is not null`), + ] +); diff --git a/v5/src/lib/db/schema/vocabulary.ts b/v5/src/lib/db/schema/vocabulary.ts new file mode 100644 index 0000000..8b39995 --- /dev/null +++ b/v5/src/lib/db/schema/vocabulary.ts @@ -0,0 +1,79 @@ +/** + * Stored vocabularies (data platform design spec 2026-09-14 §4.1). + * + * Every value here is a machine identifier: lower-case, snake_case, stable. + * Display text comes from `next-intl` (constitution Article 6), never from the + * database. Each list backs a named CHECK constraint on a `text` column rather + * than a `pgEnum`, because adding a value to an enum cannot run inside the + * transaction a migration uses and adding one to a CHECK can. + * + * The lists come from Notion's *defined* option sets, not the values in use — + * the 2026-08-14 audit found `New` (unit condition) and `Closed` (maintenance + * status) defined but unused, and a constraint that omitted them would have + * refused a legitimate write in production. The import's pre-flight compares + * Notion's option sets against these lists and stops on anything unmapped. + */ + +export const ROLES = ["user", "admin", "super_admin"] as const; +export type Role = (typeof ROLES)[number]; + +export const UNIT_STATUS = [ + "available", + "in_use", + "under_maintenance", + "out_of_service", + "retired", +] as const; +export type UnitStatus = (typeof UNIT_STATUS)[number]; + +export const UNIT_CONDITION = ["excellent", "good", "fair", "needs_repair", "new"] as const; +export type UnitCondition = (typeof UNIT_CONDITION)[number]; + +export const MAINTENANCE_TYPE = [ + "issue_report", + "preventive_maintenance", + "repair", + "inspection", + "calibration", +] as const; +export type MaintenanceType = (typeof MAINTENANCE_TYPE)[number]; + +export const MAINTENANCE_PRIORITY = ["low", "medium", "high", "critical"] as const; +export type MaintenancePriority = (typeof MAINTENANCE_PRIORITY)[number]; + +export const MAINTENANCE_STATUS = ["open", "in_progress", "resolved", "closed"] as const; +export type MaintenanceStatus = (typeof MAINTENANCE_STATUS)[number]; + +export const FEEDBACK_STATUS = ["new", "reviewed", "fixed", "dismissed"] as const; +export type FeedbackStatus = (typeof FEEDBACK_STATUS)[number]; + +export const FLAG_FIELDS = [ + "description", + "image", + "name", + "category", + "location", + "materials", + "safety_info", +] as const; +export type FlagField = (typeof FLAG_FIELDS)[number]; + +export const ATTACHMENT_OWNER = [ + "tool", + "resource", + "maintenance_log", + "project", + "pending_tool", +] as const; +export type AttachmentOwner = (typeof ATTACHMENT_OWNER)[number]; + +export const ATTACHMENT_ACCESS = ["public", "private"] as const; +export type AttachmentAccess = (typeof ATTACHMENT_ACCESS)[number]; + +/** True when `value` is one of `list`; narrows the type. */ +export function isOneOf( + list: T, + value: string | null | undefined +): value is T[number] { + return typeof value === "string" && (list as readonly string[]).includes(value); +} diff --git a/v5/src/lib/db/slug.test.ts b/v5/src/lib/db/slug.test.ts new file mode 100644 index 0000000..9122f13 --- /dev/null +++ b/v5/src/lib/db/slug.test.ts @@ -0,0 +1,38 @@ +import { slugify, uniqueSlug } from "./slug"; + +describe("slugify", () => { + it("lower-cases, replaces runs of punctuation and spaces with one dash, and trims", () => { + expect(slugify("Trotec Speedy 400, 80w")).toBe("trotec-speedy-400-80w"); + expect(slugify(" Form 4 ")).toBe("form-4"); + expect(slugify("Bambu Lab X1-Carbon Combo")).toBe("bambu-lab-x1-carbon-combo"); + }); + + it("strips accents and non-ASCII letters", () => { + expect(slugify("Découpeuse laser")).toBe("decoupeuse-laser"); + }); + + it("never returns an empty slug", () => { + expect(slugify("")).toBe("item"); + expect(slugify("///")).toBe("item"); + }); + + it("caps the length without ending on a dash", () => { + const long = slugify("a ".repeat(100)); + expect(long.length).toBeLessThanOrEqual(80); + expect(long.endsWith("-")).toBe(false); + }); +}); + +describe("uniqueSlug", () => { + it("returns the base when free and reserves it", () => { + const taken = new Set(); + expect(uniqueSlug("form-4", taken)).toBe("form-4"); + expect(taken.has("form-4")).toBe(true); + }); + + it("suffixes from 2 upward on collisions", () => { + const taken = new Set(["form-4", "form-4-2"]); + expect(uniqueSlug("form-4", taken)).toBe("form-4-3"); + expect(uniqueSlug("form-4", taken)).toBe("form-4-4"); + }); +}); diff --git a/v5/src/lib/db/slug.ts b/v5/src/lib/db/slug.ts new file mode 100644 index 0000000..2cf4cd3 --- /dev/null +++ b/v5/src/lib/db/slug.ts @@ -0,0 +1,40 @@ +/** + * URL slugs for tools and projects (spec §4.4, §4.13). + * + * A slug is derived from the name once, at creation, and kept across renames. + * Collisions get a numeric suffix (`form-4`, `form-4-2`, `form-4-3`). + */ + +const MAX_SLUG_LENGTH = 80; + +/** `"Trotec Speedy 400, 80w"` → `"trotec-speedy-400-80w"`. Never empty. */ +export function slugify(name: string): string { + const slug = name + .normalize("NFKD") + // Strip combining marks left by NFKD so "é" becomes "e". + .replace(/[̀-ͯ]/g, "") + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-+|-+$/g, "") + .slice(0, MAX_SLUG_LENGTH) + .replace(/-+$/g, ""); + return slug || "item"; +} + +/** + * The first of `base`, `base-2`, `base-3`, … that is not in `taken`. Adds the + * chosen slug to `taken` so a batch of inserts can share one set. + */ +export function uniqueSlug(base: string, taken: Set): string { + if (!taken.has(base)) { + taken.add(base); + return base; + } + for (let n = 2; ; n += 1) { + const candidate = `${base}-${n}`; + if (!taken.has(candidate)) { + taken.add(candidate); + return candidate; + } + } +} diff --git a/v5/src/lib/db/types.ts b/v5/src/lib/db/types.ts new file mode 100644 index 0000000..fa5ad86 --- /dev/null +++ b/v5/src/lib/db/types.ts @@ -0,0 +1,13 @@ +import type { PgDatabase, PgQueryResultHKT } from "drizzle-orm/pg-core"; +import type * as schema from "./schema/index.ts"; + +/** + * The database handle every query module takes. Both drivers — Neon in + * production and preview, PGlite in tests and demo mode — produce a + * `PgDatabase` over the same schema, so nothing outside `src/lib/db/` knows + * which one it holds. + */ +export type Db = PgDatabase; + +/** Which driver `getDb()` will hand out (spec §3.2). */ +export type DataSubstrate = "neon" | "pglite-demo"; diff --git a/v5/src/lib/import/blob-uploader.ts b/v5/src/lib/import/blob-uploader.ts new file mode 100644 index 0000000..8cba2c5 --- /dev/null +++ b/v5/src/lib/import/blob-uploader.ts @@ -0,0 +1,24 @@ +import { put } from "@vercel/blob"; +import type { BlobUploader } from "./files.ts"; + +/** + * The real uploader: Vercel Blob through `@vercel/blob`, authenticated by + * `BLOB_READ_WRITE_TOKEN`. A random suffix is always added so a pathname can + * never be guessed from a filename, and so two files with the same name under + * one owner never collide. + */ +export function createVercelBlobUploader(): BlobUploader { + if (!process.env.BLOB_READ_WRITE_TOKEN) { + throw new Error("BLOB_READ_WRITE_TOKEN is not set; cannot copy files to Vercel Blob."); + } + return { + async put(pathname, body, options) { + const result = await put(pathname, Buffer.from(body.buffer, body.byteOffset, body.byteLength), { + access: options.access, + contentType: options.contentType, + addRandomSuffix: true, + }); + return { pathname: result.pathname, url: result.url }; + }, + }; +} diff --git a/v5/src/lib/import/files.test.ts b/v5/src/lib/import/files.test.ts new file mode 100644 index 0000000..815fd4a --- /dev/null +++ b/v5/src/lib/import/files.test.ts @@ -0,0 +1,83 @@ +import { http, HttpResponse } from "msw"; +import { server } from "../../../test/msw/server"; +import { FileCopyError, createFileCopier, isStaleFileUrl, safeFilename, type BlobUploader } from "./files"; + +function memoryUploader() { + const stored: Array<{ pathname: string; bytes: Uint8Array; access: string; contentType?: string }> = []; + const uploader: BlobUploader = { + async put(pathname, body, options) { + stored.push({ pathname, bytes: body, access: options.access, contentType: options.contentType }); + return { pathname: `${pathname}-abc123`, url: `https://blob.test/${pathname}-abc123` }; + }, + }; + return { uploader, stored }; +} + +describe("createFileCopier", () => { + it("downloads the bytes and uploads them under the requested pathname and access", async () => { + server.use( + http.get("https://files.notion.so/fresh/form4.png", () => + HttpResponse.arrayBuffer(new Uint8Array([1, 2, 3]).buffer, { headers: { "content-type": "image/png" } }) + ) + ); + const { uploader, stored } = memoryUploader(); + const copier = createFileCopier(uploader); + + const copied = await copier.copy("https://files.notion.so/fresh/form4.png", { + pathname: "tools/abc/form4.png", + access: "public", + }); + + expect(stored).toHaveLength(1); + expect(stored[0]).toMatchObject({ pathname: "tools/abc/form4.png", access: "public", contentType: "image/png" }); + expect(Array.from(stored[0].bytes)).toEqual([1, 2, 3]); + expect(copied).toEqual({ + blobPathname: "tools/abc/form4.png-abc123", + publicUrl: "https://blob.test/tools/abc/form4.png-abc123", + contentType: "image/png", + sizeBytes: 3, + }); + }); + + it("returns no public URL for a private file", async () => { + server.use(http.get("https://example.com/photo.jpg", () => HttpResponse.arrayBuffer(new Uint8Array([9]).buffer))); + const { uploader } = memoryUploader(); + const copied = await createFileCopier(uploader).copy("https://example.com/photo.jpg", { + pathname: "maintenance/x/photo.jpg", + access: "private", + }); + expect(copied.publicUrl).toBeNull(); + }); + + it("throws a FileCopyError with the status on a failed download and uploads nothing", async () => { + server.use(http.get("https://example.com/gone.pdf", () => new HttpResponse(null, { status: 404 }))); + const { uploader, stored } = memoryUploader(); + await expect( + createFileCopier(uploader).copy("https://example.com/gone.pdf", { pathname: "x/gone.pdf", access: "public" }) + ).rejects.toMatchObject({ name: "FileCopyError", status: 404 }); + expect(stored).toHaveLength(0); + }); + + it("refuses an empty download", async () => { + server.use(http.get("https://example.com/empty.bin", () => HttpResponse.arrayBuffer(new ArrayBuffer(0)))); + const { uploader } = memoryUploader(); + await expect( + createFileCopier(uploader).copy("https://example.com/empty.bin", { pathname: "x/empty.bin", access: "public" }) + ).rejects.toBeInstanceOf(FileCopyError); + }); +}); + +describe("isStaleFileUrl", () => { + it("recognises the dead Airtable host", () => { + expect(isStaleFileUrl("https://v5.airtableusercontent.com/stale/form4.png")).toBe(true); + expect(isStaleFileUrl("https://files.notion.so/fresh/form4.png")).toBe(false); + }); +}); + +describe("safeFilename", () => { + it("keeps the last path segment and strips anything unsafe", () => { + expect(safeFilename("../../etc/passwd")).toBe("passwd"); + expect(safeFilename("Form 4 (manual).pdf")).toBe("Form-4-manual-.pdf"); + expect(safeFilename("")).toBe("file"); + }); +}); diff --git a/v5/src/lib/import/files.ts b/v5/src/lib/import/files.ts new file mode 100644 index 0000000..fb53246 --- /dev/null +++ b/v5/src/lib/import/files.ts @@ -0,0 +1,95 @@ +/** + * Copying files out of Notion (spec §5.7 step 4). + * + * Notion-hosted file URLs are signed and expire about an hour after they are + * read, so the import downloads each file's bytes during the run and uploads + * them to Vercel Blob. A URL is never stored. The uploader is an interface so + * the run can be tested against an in-memory store with no network. + */ + +export interface BlobUploader { + put( + pathname: string, + body: Uint8Array, + options: { access: "public" | "private"; contentType?: string } + ): Promise<{ pathname: string; url: string }>; +} + +export interface CopiedFile { + blobPathname: string; + /** Set for public blobs only; private ones are read back through the SDK. */ + publicUrl: string | null; + contentType: string | null; + sizeBytes: number; +} + +// Fields are declared and assigned explicitly rather than as constructor +// parameter properties: the import script loads this module under Node's +// type stripping, which erases types but cannot rewrite that TypeScript-only syntax. +export class FileCopyError extends Error { + readonly url: string; + readonly status: number | undefined; + + constructor(url: string, message: string, status?: number) { + super(`${message} (${url})`); + this.name = "FileCopyError"; + this.url = url; + this.status = status; + } +} + +/** Hosts whose URLs are known to be dead; skipped without a request. */ +export const STALE_FILE_HOSTS = ["airtableusercontent.com"]; + +export function isStaleFileUrl(url: string): boolean { + return STALE_FILE_HOSTS.some((host) => url.includes(host)); +} + +/** Larger than anything the catalogue holds; a guard against a stray video. */ +export const MAX_FILE_BYTES = 50 * 1024 * 1024; + +export interface FileCopier { + copy(sourceUrl: string, destination: { pathname: string; access: "public" | "private" }): Promise; +} + +export function createFileCopier(uploader: BlobUploader, fetchImpl: typeof fetch = fetch): FileCopier { + return { + async copy(sourceUrl, destination) { + let response: Response; + try { + response = await fetchImpl(sourceUrl); + } catch (error) { + throw new FileCopyError(sourceUrl, `download failed: ${(error as Error).message}`); + } + if (!response.ok) { + throw new FileCopyError(sourceUrl, `download returned ${response.status}`, response.status); + } + + const bytes = new Uint8Array(await response.arrayBuffer()); + if (bytes.byteLength === 0) throw new FileCopyError(sourceUrl, "download was empty"); + if (bytes.byteLength > MAX_FILE_BYTES) { + throw new FileCopyError(sourceUrl, `file is ${bytes.byteLength} bytes, over the ${MAX_FILE_BYTES} limit`); + } + + const contentType = response.headers.get("content-type")?.split(";")[0].trim() || null; + const stored = await uploader.put(destination.pathname, bytes, { + access: destination.access, + contentType: contentType ?? undefined, + }); + + return { + blobPathname: stored.pathname, + publicUrl: destination.access === "public" ? stored.url : null, + contentType, + sizeBytes: bytes.byteLength, + }; + }, + }; +} + +/** A filename safe to use as the last segment of a blob pathname. */ +export function safeFilename(name: string, fallback = "file"): string { + const base = name.split(/[\\/]/).pop() ?? ""; + const cleaned = base.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, ""); + return cleaned || fallback; +} diff --git a/v5/src/lib/import/mappers.test.ts b/v5/src/lib/import/mappers.test.ts new file mode 100644 index 0000000..91d897b --- /dev/null +++ b/v5/src/lib/import/mappers.test.ts @@ -0,0 +1,189 @@ +import { + categoriesPage, + flagsPage, + locationsPage, + maintenanceLogsPage, + projectsPage, + resourcesPage, + toolsPage, + unitsPage, +} from "../../../test/fixtures/notion"; +import { + pageToCategory, + pageToFlag, + pageToLocation, + pageToMaintenanceLog, + pageToProject, + pageToResource, + pageToTool, + pageToUnit, + type NotionPage, +} from "../notion"; +import { UnmappedOptionError } from "./vocabulary-map"; +import { + isoDate, + toCategoryRow, + toFeedbackRow, + toLocationRow, + toMaintenanceRow, + toProjectRow, + toResourceRow, + toToolRow, + toUnitRow, +} from "./mappers"; + +const asPage = (fixture: unknown) => fixture as NotionPage; + +describe("isoDate", () => { + it("keeps a plain date, trims a datetime, rejects anything else", () => { + expect(isoDate("2024-09-01")).toBe("2024-09-01"); + expect(isoDate("2024-09-01T10:00:00.000Z")).toBe("2024-09-01"); + expect(isoDate("Sept 1")).toBeNull(); + expect(isoDate("")).toBeNull(); + }); +}); + +describe("row mappers over the Notion fixtures", () => { + it("maps a category and a location, keeping the Notion page id and created time", () => { + const category = toCategoryRow(pageToCategory(asPage(categoriesPage))); + expect(category.row).toMatchObject({ name: "Resin", group: "3D Printing", notionPageId: "cat-1" }); + expect(category.row.createdAt).toEqual(new Date("2024-08-12T10:00:00.000Z")); + + const location = toLocationRow(pageToLocation(asPage(locationsPage))); + expect(location.row).toMatchObject({ room: "MakerLab", zone: "Resin Bench", mapTag: "ML-RESIN-01" }); + expect(location.warnings).toEqual([]); + }); + + it("maps a tool with resolved relations and empty strings as null", () => { + const { row, warnings } = toToolRow(pageToTool(asPage(toolsPage)), { + slug: "form-4", + categoryId: "cat-uuid", + locationId: "loc-uuid", + }); + expect(row).toMatchObject({ + slug: "form-4", + name: "Form 4", + categoryId: "cat-uuid", + locationId: "loc-uuid", + materials: ["Standard resin", "Tough resin"], + trainingRequired: true, + published: true, + notionPageId: "tool-1", + }); + expect(warnings).toEqual([]); + }); + + it("warns when a tool's relation could not be resolved", () => { + const { row, warnings } = toToolRow(pageToTool(asPage(toolsPage)), { + slug: "form-4", + categoryId: null, + locationId: null, + }); + expect(row.categoryId).toBeNull(); + expect(warnings).toHaveLength(2); + }); + + it("maps a unit's vocabulary and date, and defaults status when blank", () => { + const { row, warnings } = toUnitRow(pageToUnit(asPage(unitsPage)), { toolId: "tool-uuid" }); + expect(row).toMatchObject({ + toolId: "tool-uuid", + unitLabel: "Form 4 #1", + serialNumber: "ML-F4-001", + status: "available", + condition: "excellent", + dateAcquired: "2024-08-12", + }); + expect(warnings).toEqual([]); + + const blank = pageToUnit(asPage({ ...unitsPage, properties: { unit_label: unitsPage.properties.unit_label } })); + const mapped = toUnitRow(blank, { toolId: null }); + expect(mapped.row.status).toBe("available"); + expect(mapped.row.condition).toBeNull(); + expect(mapped.warnings).toContain("unit Form 4 #1 (unit-1) is not linked to a tool"); + }); + + it("keeps an unparseable acquisition date in the notes rather than dropping it", () => { + const record = pageToUnit(asPage(unitsPage)); + record.fields.date_acquired = "Fall 2023"; + const { row, warnings } = toUnitRow(record, { toolId: "tool-uuid" }); + expect(row.dateAcquired).toBeNull(); + expect(row.notes).toContain("Date acquired: Fall 2023"); + expect(warnings.some((w) => w.includes("not a date"))).toBe(true); + }); + + it("throws on a unit status the vocabulary does not know", () => { + const record = pageToUnit(asPage(unitsPage)); + record.fields.status = "Broken" as never; + expect(() => toUnitRow(record, { toolId: null })).toThrow(UnmappedOptionError); + }); + + it("maps a resource", () => { + const { row } = toResourceRow(pageToResource(asPage(resourcesPage)), { toolId: "tool-uuid" }); + expect(row).toMatchObject({ title: "Form 4 SOP", type: "SOP", url: "https://example.com/form4-sop", published: true }); + }); + + it("maps a maintenance log, taking the reporter email from the raw page and snapshots from refs", () => { + const record = { ...pageToMaintenanceLog(asPage(maintenanceLogsPage)), reporterEmail: "ada@cornell.edu" }; + const { row, warnings } = toMaintenanceRow(record, { + unitId: "unit-uuid", + toolId: "tool-uuid", + toolName: "Form 4", + unitLabel: "Form 4 #1", + }); + expect(row).toMatchObject({ + title: "Resin tank cloudy", + type: "issue_report", + priority: "medium", + status: "open", + description: "The resin tank looks cloudy after the last print.", + reportedByName: "Ada Lovelace", + reportedByEmail: "ada@cornell.edu", + assignedToName: "Lab Staff", + dateReported: "2024-09-01", + dateResolved: null, + toolName: "Form 4", + unitLabel: "Form 4 #1", + }); + expect(warnings).toEqual([]); + }); + + it("splits a composed ticket description back into columns", () => { + const record = { ...pageToMaintenanceLog(asPage(maintenanceLogsPage)), reporterEmail: null }; + record.fields.description = + "**What happened**\nBelt slipping.\n\n**Reported by**\nGrace Hopper\n\n**Date reported**\n2024-10-02\n\n**Priority**\nHigh"; + record.fields.reported_by = ""; + record.fields.priority = undefined; + record.fields.date_reported = ""; + const { row } = toMaintenanceRow(record, { unitId: null, toolId: null, toolName: null, unitLabel: null }); + expect(row.description).toBe("Belt slipping."); + expect(row.reportedByName).toBe("Grace Hopper"); + expect(row.dateReported).toBe("2024-10-02"); + expect(row.priority).toBe("high"); + }); + + it("maps a flag into feedback with the stored vocabulary", () => { + const record = { ...pageToFlag(asPage(flagsPage)), reporterEmail: null }; + const { row } = toFeedbackRow(record, { toolId: "tool-uuid" }); + expect(row).toMatchObject({ + toolId: "tool-uuid", + fieldFlagged: "description", + issueDescription: "The description says 80W but the label says 60W.", + suggestedFix: "Change 80W to 60W.", + reporterName: "Ada Lovelace", + status: "new", + notionPageId: "flag-1", + }); + }); + + it("maps a project", () => { + const { row } = toProjectRow(pageToProject(asPage(projectsPage)), { slug: "lamp" }); + expect(row).toMatchObject({ + slug: "lamp", + title: "Laser-cut lamp", + authorName: "Ada Lovelace", + materials: ["Plywood"], + published: true, + notionPageId: "project-1", + }); + }); +}); diff --git a/v5/src/lib/import/mappers.ts b/v5/src/lib/import/mappers.ts new file mode 100644 index 0000000..c5681f5 --- /dev/null +++ b/v5/src/lib/import/mappers.ts @@ -0,0 +1,274 @@ +import type { + categories, + feedback, + locations, + maintenanceLogs, + projects, + resources, + tools, + units, +} from "../db/schema/index.ts"; +import { + FEEDBACK_STATUS, + FLAG_FIELDS, + MAINTENANCE_PRIORITY, + MAINTENANCE_STATUS, + MAINTENANCE_TYPE, + UNIT_CONDITION, + UNIT_STATUS, +} from "../db/schema/vocabulary.ts"; +import type { + CategoryRecord, + FlagRecord, + LocationRecord, + MaintenanceLogRecord, + ProjectRecord, + ResourceRecord, + ToolRecord, + UnitRecord, +} from "../types.ts"; +import { mapOption } from "./vocabulary-map.ts"; +import { parseTicketDescription } from "./ticket-description.ts"; + +/** + * Notion records → rows (spec §4, §5.7). Pure functions: relations are + * resolved by the caller and passed in as ids, so these can be tested over + * fixtures shaped like the real data with no database. + * + * Every mapper returns the row plus any warnings — a date it could not read, + * a relation it could not resolve — because dropping a value silently is the + * failure mode this whole import is designed against. + */ + +export interface Mapped { + row: T; + warnings: string[]; +} + +/** A Notion maintenance record plus the email property the parsers leave out. */ +export type MaintenanceImportRecord = MaintenanceLogRecord & { reporterEmail: string | null }; +/** A Notion flag record plus its reporter email, read the same way. */ +export type FlagImportRecord = FlagRecord & { reporterEmail: string | null }; + +function nullIfEmpty(value: string | null | undefined): string | null { + const trimmed = (value ?? "").trim(); + return trimmed ? trimmed : null; +} + +/** `2024-09-01` from `2024-09-01` or `2024-09-01T10:00:00.000Z`; null otherwise. */ +export function isoDate(value: string | null | undefined): string | null { + const match = (value ?? "").trim().match(/^(\d{4}-\d{2}-\d{2})/); + return match ? match[1] : null; +} + +function createdAt(record: { createdTime: string }): Date { + return new Date(record.createdTime); +} + +export function toCategoryRow(record: CategoryRecord): Mapped { + return { + row: { + name: record.fields.name.trim() || "Untitled category", + group: nullIfEmpty(record.fields.group), + notionPageId: record.id, + createdAt: createdAt(record), + }, + warnings: record.fields.name.trim() ? [] : [`category ${record.id} has no name`], + }; +} + +export function toLocationRow(record: LocationRecord): Mapped { + const warnings: string[] = []; + const room = record.fields.room.trim(); + const zone = record.fields.zone.trim(); + if (!room || !zone) warnings.push(`location ${record.id} (${record.fields.id}) is missing room or zone`); + return { + row: { + room, + zone, + mapTag: nullIfEmpty(record.fields.id), + notionPageId: record.id, + createdAt: createdAt(record), + }, + warnings, + }; +} + +export interface ToolRefs { + slug: string; + categoryId: string | null; + locationId: string | null; +} + +export function toToolRow(record: ToolRecord, refs: ToolRefs): Mapped { + const f = record.fields; + const warnings: string[] = []; + if (f.category?.length && !refs.categoryId) warnings.push(`tool ${f.name}: category ${f.category[0]} not found`); + if (f.location?.length && !refs.locationId) warnings.push(`tool ${f.name}: location ${f.location[0]} not found`); + return { + row: { + slug: refs.slug, + name: f.name.trim() || "Untitled tool", + description: nullIfEmpty(f.description), + categoryId: refs.categoryId, + locationId: refs.locationId, + materials: f.materials ?? [], + ppeRequired: f.ppe_required ?? [], + tags: f.tags ?? [], + trainingRequired: f.training_required ?? false, + useRestrictions: nullIfEmpty(f.use_restrictions), + emergencyStop: nullIfEmpty(f.emergency_stop), + notes: nullIfEmpty(f.notes), + published: f.published ?? false, + notionPageId: record.id, + createdAt: createdAt(record), + }, + warnings, + }; +} + +export function toUnitRow(record: UnitRecord, refs: { toolId: string | null }): Mapped { + const f = record.fields; + const warnings: string[] = []; + const where = { table: "units" }; + if (f.tool?.length && !refs.toolId) warnings.push(`unit ${f.unit_label}: tool ${f.tool[0]} not found`); + if (!f.tool?.length) warnings.push(`unit ${f.unit_label} (${record.id}) is not linked to a tool`); + const dateAcquired = isoDate(f.date_acquired); + if (f.date_acquired && !dateAcquired) { + warnings.push(`unit ${f.unit_label}: date_acquired "${f.date_acquired}" is not a date; kept in notes`); + } + const notes = [nullIfEmpty(f.notes), f.date_acquired && !dateAcquired ? `Date acquired: ${f.date_acquired}` : null] + .filter(Boolean) + .join("\n"); + return { + row: { + toolId: refs.toolId, + unitLabel: f.unit_label.trim() || "Unit", + serialNumber: nullIfEmpty(f.serial_number), + assetTag: nullIfEmpty(f.asset_tag), + status: mapOption(UNIT_STATUS, f.status, { ...where, property: "status" }) ?? "available", + condition: mapOption(UNIT_CONDITION, f.condition, { ...where, property: "condition" }), + dateAcquired, + notes: notes || null, + notionPageId: record.id, + createdAt: createdAt(record), + }, + warnings, + }; +} + +export function toResourceRow( + record: ResourceRecord, + refs: { toolId: string | null } +): Mapped { + const f = record.fields; + const warnings: string[] = []; + if (f.tool?.length && !refs.toolId) warnings.push(`resource ${f.title}: tool ${f.tool[0]} not found`); + return { + row: { + toolId: refs.toolId, + title: f.title.trim() || "Untitled resource", + type: nullIfEmpty(f.type), + url: nullIfEmpty(f.url), + notes: nullIfEmpty(f.notes), + published: f.published ?? true, + notionPageId: record.id, + createdAt: createdAt(record), + }, + warnings, + }; +} + +export interface MaintenanceRefs { + unitId: string | null; + toolId: string | null; + toolName: string | null; + unitLabel: string | null; +} + +export function toMaintenanceRow( + record: MaintenanceImportRecord, + refs: MaintenanceRefs +): Mapped { + const f = record.fields; + const warnings: string[] = []; + const where = { table: "maintenance_logs" }; + if (f.unit?.length && !refs.unitId) warnings.push(`ticket "${f.title}": unit ${f.unit[0]} not found`); + + const parsed = parseTicketDescription(f.description); + if (parsed.templated && !parsed.exact) { + warnings.push(`ticket "${f.title}": description kept whole (template headings found, but edited around)`); + } + + const dateReported = isoDate(f.date_reported) ?? isoDate(parsed.dateReported); + const dateResolved = isoDate(f.date_resolved); + if (f.date_resolved && !dateResolved) warnings.push(`ticket "${f.title}": date_resolved "${f.date_resolved}" is not a date`); + + return { + row: { + title: f.title.trim() || "Untitled issue", + type: mapOption(MAINTENANCE_TYPE, f.type, { ...where, property: "type" }), + priority: + mapOption(MAINTENANCE_PRIORITY, f.priority, { ...where, property: "priority" }) ?? + mapOption(MAINTENANCE_PRIORITY, parsed.priority, { ...where, property: "priority (description)" }), + status: mapOption(MAINTENANCE_STATUS, f.status, { ...where, property: "status" }) ?? "open", + description: parsed.description, + resolution: nullIfEmpty(f.resolution), + unitId: refs.unitId, + toolId: refs.toolId, + toolName: refs.toolName, + unitLabel: refs.unitLabel, + reportedByName: nullIfEmpty(f.reported_by) ?? parsed.reportedBy, + reportedByEmail: nullIfEmpty(record.reporterEmail), + assignedToName: nullIfEmpty(f.assigned_to), + dateReported, + dateResolved, + notionPageId: record.id, + createdAt: createdAt(record), + }, + warnings, + }; +} + +export function toFeedbackRow( + record: FlagImportRecord, + refs: { toolId: string | null } +): Mapped { + const f = record.fields; + const warnings: string[] = []; + const where = { table: "flags" }; + if (f.tool?.length && !refs.toolId) warnings.push(`correction "${f.title}": tool ${f.tool[0]} not found`); + const issue = nullIfEmpty(f.issue_description) ?? nullIfEmpty(f.title) ?? "(no description)"; + return { + row: { + toolId: refs.toolId, + fieldFlagged: mapOption(FLAG_FIELDS, f.field_flagged, { ...where, property: "field_flagged" }), + issueDescription: issue, + suggestedFix: nullIfEmpty(f.suggested_fix), + reporterName: nullIfEmpty(f.reporter), + reporterEmail: nullIfEmpty(record.reporterEmail), + status: mapOption(FEEDBACK_STATUS, f.status, { ...where, property: "status" }) ?? "new", + notionPageId: record.id, + createdAt: createdAt(record), + }, + warnings, + }; +} + +export function toProjectRow(record: ProjectRecord, refs: { slug: string }): Mapped { + const f = record.fields; + return { + row: { + slug: refs.slug, + title: f.title.trim() || "Untitled project", + body: f.body ?? "", + link: nullIfEmpty(f.link), + materials: f.materials ?? [], + authorName: nullIfEmpty(f.author), + published: f.published ?? false, + notionPageId: record.id, + createdAt: createdAt(record), + }, + warnings: [], + }; +} diff --git a/v5/src/lib/import/preflight.test.ts b/v5/src/lib/import/preflight.test.ts new file mode 100644 index 0000000..f3e4237 --- /dev/null +++ b/v5/src/lib/import/preflight.test.ts @@ -0,0 +1,54 @@ +import { databaseSchema } from "../../../test/fixtures/notion"; +import { PreflightError, preflight } from "./preflight"; + +describe("preflight", () => { + it("passes when every defined option maps onto the vocabulary", () => { + const result = preflight({ + units: databaseSchema("units"), + maintenance_logs: databaseSchema("maintenance_logs"), + flags: databaseSchema("flags"), + }); + expect(result.problems).toEqual([]); + expect(result.warnings).toEqual([]); + }); + + it("stops on a defined option that maps onto nothing, naming database, property and option", () => { + const units = databaseSchema("units"); + units.properties.status.select?.options.push({ name: "Broken" }); + const result = preflight({ + units, + maintenance_logs: databaseSchema("maintenance_logs"), + flags: databaseSchema("flags"), + }); + expect(result.problems).toEqual([ + { table: "units", property: "status", option: "Broken", storedAs: "broken" }, + ]); + const error = new PreflightError(result.problems); + expect(error.message).toContain('units.status: "Broken"'); + }); + + it("accepts the Title-case property names the parsers tolerate", () => { + const units = databaseSchema("units"); + units.properties.Status = units.properties.status; + delete units.properties.status; + const result = preflight({ + units, + maintenance_logs: databaseSchema("maintenance_logs"), + flags: databaseSchema("flags"), + }); + expect(result.problems).toEqual([]); + expect(result.warnings).toEqual([]); + }); + + it("warns, rather than stops, when a checked property or schema is missing", () => { + const units = databaseSchema("units"); + delete units.properties.condition; + const result = preflight({ units, maintenance_logs: databaseSchema("maintenance_logs") }); + expect(result.problems).toEqual([]); + expect(result.warnings).toEqual([ + { table: "units", property: "condition", reason: "property not found" }, + { table: "flags", property: "field_flagged", reason: "database schema not read" }, + { table: "flags", property: "status", reason: "database schema not read" }, + ]); + }); +}); diff --git a/v5/src/lib/import/preflight.ts b/v5/src/lib/import/preflight.ts new file mode 100644 index 0000000..b2c978f --- /dev/null +++ b/v5/src/lib/import/preflight.ts @@ -0,0 +1,90 @@ +import type { NotionDatabaseSchema } from "../notion.ts"; +import { CHECKED_PROPERTIES, toStoredValue, type CheckedProperty } from "./vocabulary-map.ts"; + +/** + * The import's pre-flight (spec §5.7 step 1). + * + * Reads each database's *defined* option sets — not the values in use — and + * compares them with the stored vocabularies. A defined option that maps onto + * nothing is a problem that stops the import, because the constraint it would + * violate is exactly the one a later write would hit in production. A checked + * property that is missing altogether is only a warning: the parsers tolerate + * a missing property, and the mappers fall back to the column default. + */ + +export interface PreflightProblem { + table: CheckedProperty["table"]; + property: string; + option: string; + /** What the mechanical rule produced, for the error message. */ + storedAs: string; +} + +export interface PreflightWarning { + table: CheckedProperty["table"]; + property: string; + reason: string; +} + +export interface PreflightResult { + problems: PreflightProblem[]; + warnings: PreflightWarning[]; +} + +export type SchemasByTable = Partial>; + +export function preflight(schemas: SchemasByTable): PreflightResult { + const problems: PreflightProblem[] = []; + const warnings: PreflightWarning[] = []; + + for (const check of CHECKED_PROPERTIES) { + const schema = schemas[check.table]; + const propertyName = check.names[0]; + if (!schema) { + warnings.push({ table: check.table, property: propertyName, reason: "database schema not read" }); + continue; + } + + const property = check.names.map((name) => schema.properties[name]).find(Boolean); + if (!property) { + warnings.push({ table: check.table, property: propertyName, reason: "property not found" }); + continue; + } + + const options = property.select?.options ?? property.multi_select?.options; + if (!options) { + warnings.push({ + table: check.table, + property: propertyName, + reason: `property is ${property.type}, not a select`, + }); + continue; + } + + for (const option of options) { + const storedAs = toStoredValue(option.name); + if (!check.list.includes(storedAs)) { + problems.push({ table: check.table, property: propertyName, option: option.name, storedAs }); + } + } + } + + return { problems, warnings }; +} + +/** Thrown by the import when the pre-flight finds problems; lists every one. */ +export class PreflightError extends Error { + readonly problems: PreflightProblem[]; + + constructor(problems: PreflightProblem[]) { + super( + [ + "Pre-flight stopped the import. These Notion options map onto no stored value:", + ...problems.map((p) => ` ${p.table}.${p.property}: "${p.option}" (would be "${p.storedAs}")`), + "Add the value to src/lib/db/schema/vocabulary.ts with a migration, or fix the option in Notion.", + ].join("\n") + ); + this.name = "PreflightError"; + this.problems = problems; + } +} diff --git a/v5/src/lib/import/run.test.ts b/v5/src/lib/import/run.test.ts new file mode 100644 index 0000000..622a69d --- /dev/null +++ b/v5/src/lib/import/run.test.ts @@ -0,0 +1,213 @@ +// @vitest-environment node +import { eq } from "drizzle-orm"; +import { http, HttpResponse } from "msw"; +import { + categoriesPage, + databaseSchema, + flagsPage, + locationsPage, + maintenanceLogsPage, + projectsPage, + resourcesPage, + toolsPage, + unitsPage, +} from "../../../test/fixtures/notion"; +import { server } from "../../../test/msw/server"; +import { createPgliteDb } from "../db/pglite"; +import { attachments, feedback, maintenanceLogs, projectTools, projects, tools, units } from "../db/schema/index"; +import type { Db } from "../db/types"; +import { + pageToCategory, + pageToFlag, + pageToLocation, + pageToMaintenanceLog, + pageToProject, + pageToResource, + pageToTool, + pageToUnit, + readEmailProperty, + type NotionPage, +} from "../notion"; +import { createFileCopier, type BlobUploader } from "./files"; +import { PreflightError } from "./preflight"; +import { runImport } from "./run"; +import type { NotionSnapshot } from "./source"; + +const asPage = (fixture: unknown) => fixture as NotionPage; + +/** The fixtures as the reader would return them, with no Notion call. */ +function fixtureSnapshot(): NotionSnapshot { + const maintenance = asPage(maintenanceLogsPage); + const flag = asPage(flagsPage); + return { + schemas: { + units: databaseSchema("units"), + maintenance_logs: databaseSchema("maintenance_logs"), + flags: databaseSchema("flags"), + }, + categories: [pageToCategory(asPage(categoriesPage))], + locations: [pageToLocation(asPage(locationsPage))], + tools: [pageToTool(asPage(toolsPage))], + units: [pageToUnit(asPage(unitsPage))], + resources: [pageToResource(asPage(resourcesPage))], + maintenanceLogs: [ + { ...pageToMaintenanceLog(maintenance), reporterEmail: readEmailProperty(maintenance, ["reporter_email"]) || null }, + ], + flags: [{ ...pageToFlag(flag), reporterEmail: readEmailProperty(flag, ["reporter_email"]) || null }], + projects: [pageToProject(asPage(projectsPage))], + }; +} + +function memoryUploader() { + const stored: string[] = []; + const uploader: BlobUploader = { + async put(pathname) { + stored.push(pathname); + return { pathname: `${pathname}-r1`, url: `https://blob.test/${pathname}-r1` }; + }, + }; + return { uploader, stored }; +} + +/** Serve bytes for every file URL the fixtures reference (except the stale one). */ +function serveFixtureFiles() { + const bytes = () => HttpResponse.arrayBuffer(new Uint8Array([1, 2, 3]).buffer, { headers: { "content-type": "image/png" } }); + server.use( + http.get("https://files.notion.so/*", bytes), + http.get("https://example.com/*", bytes) + ); +} + +describe("runImport", () => { + let db: Db; + + beforeEach(async () => { + db = await createPgliteDb(); + }); + + it("imports every entity in dependency order with relations resolved", async () => { + const report = await runImport({ db, snapshot: fixtureSnapshot() }); + + expect(report.counts).toEqual({ + categories: { inserted: 1, updated: 0 }, + locations: { inserted: 1, updated: 0 }, + tools: { inserted: 1, updated: 0 }, + units: { inserted: 1, updated: 0 }, + resources: { inserted: 1, updated: 0 }, + maintenance_logs: { inserted: 1, updated: 0 }, + feedback: { inserted: 1, updated: 0 }, + projects: { inserted: 1, updated: 0 }, + }); + expect(report.files).toEqual({ copied: 0, skipped: 0, failed: 0 }); + + const [tool] = await db.select().from(tools); + expect(tool).toMatchObject({ slug: "form-4", name: "Form 4", notionPageId: "tool-1", published: true }); + expect(tool.categoryId).not.toBeNull(); + expect(tool.locationId).not.toBeNull(); + + const [unit] = await db.select().from(units); + expect(unit.toolId).toBe(tool.id); + expect(unit.status).toBe("available"); + + const [ticket] = await db.select().from(maintenanceLogs); + expect(ticket).toMatchObject({ + unitId: unit.id, + toolId: tool.id, + toolName: "Form 4", + unitLabel: "Form 4 #1", + status: "open", + priority: "medium", + }); + + const [correction] = await db.select().from(feedback); + expect(correction).toMatchObject({ toolId: tool.id, fieldFlagged: "description", reporterEmail: "ada@cornell.edu" }); + + const [project] = await db.select().from(projects); + expect(project).toMatchObject({ slug: "laser-cut-lamp", published: true }); + const links = await db.select().from(projectTools).where(eq(projectTools.projectId, project.id)); + expect(links).toEqual([{ projectId: project.id, toolId: tool.id }]); + }); + + it("copies files to Blob with the right access, skips dead URLs, and records attachments", async () => { + serveFixtureFiles(); + const { uploader, stored } = memoryUploader(); + const report = await runImport({ db, snapshot: fixtureSnapshot(), files: createFileCopier(uploader) }); + + // tool: 1 stale (skipped) + 1 fresh; resource: 2; maintenance: 1; project: 1 + expect(report.files).toEqual({ copied: 5, skipped: 1, failed: 0 }); + expect(report.warnings.some((w) => w.includes("dead file URL"))).toBe(true); + expect(stored).toHaveLength(5); + + const rows = await db.select().from(attachments); + const byOwner = Object.groupBy(rows, (row) => row.ownerType ?? "none"); + expect(byOwner.tool).toHaveLength(1); + expect(byOwner.tool?.[0]).toMatchObject({ access: "public", position: 1, originalFilename: "fresh.png", sourceKey: "tool-1:image_attachments:1" }); + expect(byOwner.tool?.[0].publicUrl).toMatch(/^https:\/\/blob\.test\/tools\//); + expect(byOwner.resource).toHaveLength(2); + expect(byOwner.maintenance_log?.[0]).toMatchObject({ access: "private", publicUrl: null }); + expect(byOwner.project).toHaveLength(1); + }); + + it("is idempotent: a re-run updates rows, keeps slugs, and copies no file twice", async () => { + serveFixtureFiles(); + const { uploader, stored } = memoryUploader(); + const copier = createFileCopier(uploader); + await runImport({ db, snapshot: fixtureSnapshot(), files: copier }); + + const renamed = fixtureSnapshot(); + renamed.tools[0].fields.name = "Form 4 (renamed)"; + const report = await runImport({ db, snapshot: renamed, files: copier }); + + expect(report.counts.tools).toEqual({ inserted: 0, updated: 1 }); + expect(report.counts.units).toEqual({ inserted: 0, updated: 1 }); + expect(report.files).toEqual({ copied: 0, skipped: 6, failed: 0 }); + expect(stored).toHaveLength(5); + + const [tool] = await db.select().from(tools); + expect(tool.name).toBe("Form 4 (renamed)"); + expect(tool.slug).toBe("form-4"); + expect(await db.select().from(attachments)).toHaveLength(5); + }); + + it("stops on a pre-flight problem and writes nothing", async () => { + const snapshot = fixtureSnapshot(); + snapshot.schemas.units?.properties.status.select?.options.push({ name: "Broken" }); + await expect(runImport({ db, snapshot })).rejects.toBeInstanceOf(PreflightError); + expect(await db.select().from(tools)).toHaveLength(0); + }); + + it("merges a category that appears twice in Notion and keeps both tools pointing at it", async () => { + const snapshot = fixtureSnapshot(); + const twin = pageToCategory(asPage({ ...categoriesPage, id: "cat-2" })); + snapshot.categories.push(twin); + const secondTool = pageToTool(asPage({ ...toolsPage, id: "tool-2" })); + secondTool.fields.category = ["cat-2"]; + snapshot.tools.push(secondTool); + + const report = await runImport({ db, snapshot }); + expect(report.counts.categories).toEqual({ inserted: 1, updated: 0 }); + expect(report.warnings.some((w) => w.includes("appears twice"))).toBe(true); + const rows = await db.select({ categoryId: tools.categoryId }).from(tools); + expect(new Set(rows.map((r) => r.categoryId)).size).toBe(1); + }); + + it("gives a second tool with the same name a suffixed slug", async () => { + const snapshot = fixtureSnapshot(); + snapshot.tools.push(pageToTool(asPage({ ...toolsPage, id: "tool-2" }))); + await runImport({ db, snapshot }); + const rows = await db.select({ slug: tools.slug }).from(tools).orderBy(tools.slug); + expect(rows.map((r) => r.slug)).toEqual(["form-4", "form-4-2"]); + }); + + it("records a failed download as a warning and keeps going", async () => { + server.use( + http.get("https://files.notion.so/*", () => new HttpResponse(null, { status: 403 })), + http.get("https://example.com/*", () => HttpResponse.arrayBuffer(new Uint8Array([1]).buffer)) + ); + const { uploader } = memoryUploader(); + const report = await runImport({ db, snapshot: fixtureSnapshot(), files: createFileCopier(uploader) }); + expect(report.files.failed).toBeGreaterThan(0); + expect(report.files.copied).toBeGreaterThan(0); + expect(report.warnings.some((w) => w.includes("could not copy"))).toBe(true); + }); +}); diff --git a/v5/src/lib/import/run.ts b/v5/src/lib/import/run.ts new file mode 100644 index 0000000..fa4891c --- /dev/null +++ b/v5/src/lib/import/run.ts @@ -0,0 +1,474 @@ +import { eq } from "drizzle-orm"; +import { + attachments, + categories, + feedback, + locations, + maintenanceLogs, + projectTools, + projects, + resources, + tools, + units, +} from "../db/schema/index.ts"; +import { slugify, uniqueSlug } from "../db/slug.ts"; +import type { Db } from "../db/types.ts"; +import type { Attachment } from "../types.ts"; +import { isStaleFileUrl, safeFilename, type FileCopier } from "./files.ts"; +import { + toCategoryRow, + toFeedbackRow, + toLocationRow, + toMaintenanceRow, + toProjectRow, + toResourceRow, + toToolRow, + toUnitRow, +} from "./mappers.ts"; +import { PreflightError, preflight, type PreflightWarning } from "./preflight.ts"; +import type { NotionSnapshot } from "./source.ts"; + +/** + * The import itself (spec §5.7 step 3 onward): a snapshot in, rows out. + * + * - **Pre-flight first.** An unmapped Notion option stops the run before a + * single row is written. + * - **Dependency order**, one transaction per entity: categories, locations, + * tools, units, resources, maintenance logs, feedback, projects. + * - **Idempotent.** Every row is keyed by `notion_page_id`; a re-run updates + * what exists and inserts what is new, and never changes a slug it already + * assigned. Files are keyed by `source_key`, so a re-run never uploads the + * same file twice. + * - **Files last**, outside any transaction, one at a time: a failed download + * is a warning and a count, never a rollback of the rows. + */ + +export type ImportEntity = + | "categories" + | "locations" + | "tools" + | "units" + | "resources" + | "maintenance_logs" + | "feedback" + | "projects"; + +export interface EntityCount { + inserted: number; + updated: number; +} + +export interface ImportReport { + counts: Record; + files: { copied: number; skipped: number; failed: number }; + warnings: string[]; + preflightWarnings: PreflightWarning[]; +} + +export interface RunImportOptions { + db: Db; + snapshot: NotionSnapshot; + /** Null skips file copying entirely (a dry run). */ + files?: FileCopier | null; + log?: (line: string) => void; +} + +/** Notion page id → row id, per entity, built as the run goes. */ +type IdMap = Map; + +/** + * A row minus the columns an update must not touch: the slug, assigned once, + * and `created_at`, which is Notion's creation time and does not move. + */ +function patchOf(row: T, ...keys: (keyof T)[]): Partial { + const patch: Partial = { ...row }; + for (const key of keys) delete patch[key]; + return patch; +} + +export async function runImport(options: RunImportOptions): Promise { + const { db, snapshot } = options; + const files = options.files ?? null; + const log = options.log ?? (() => {}); + const warnings: string[] = []; + + const flight = preflight(snapshot.schemas); + if (flight.problems.length > 0) throw new PreflightError(flight.problems); + + const counts = {} as Record; + const count = (entity: ImportEntity, inserted: number, updated: number) => { + counts[entity] = { inserted, updated }; + log(`${entity}: ${inserted} inserted, ${updated} updated`); + }; + + // ── Categories ──────────────────────────────────────────────────── + const categoryIds: IdMap = new Map(); + { + let inserted = 0; + let updated = 0; + await db.transaction(async (tx) => { + const existing = await tx + .select({ id: categories.id, name: categories.name, group: categories.group, notionPageId: categories.notionPageId }) + .from(categories); + const byNotion = new Map(existing.filter((r) => r.notionPageId).map((r) => [r.notionPageId as string, r.id])); + const byKey = new Map(existing.map((r) => [categoryKey(r.name, r.group), r.id])); + + for (const record of snapshot.categories) { + const { row, warnings: w } = toCategoryRow(record); + warnings.push(...w); + const key = categoryKey(row.name, row.group ?? null); + const known = byNotion.get(record.id); + if (known) { + await tx.update(categories).set({ name: row.name, group: row.group }).where(eq(categories.id, known)); + categoryIds.set(record.id, known); + updated += 1; + continue; + } + const duplicate = byKey.get(key); + if (duplicate) { + warnings.push(`category "${row.name}" (${row.group ?? "no group"}) appears twice in Notion; ${record.id} merged into the first`); + categoryIds.set(record.id, duplicate); + continue; + } + const [created] = await tx.insert(categories).values(row).returning({ id: categories.id }); + categoryIds.set(record.id, created.id); + byKey.set(key, created.id); + inserted += 1; + } + }); + count("categories", inserted, updated); + } + + // ── Locations ───────────────────────────────────────────────────── + const locationIds: IdMap = new Map(); + { + let inserted = 0; + let updated = 0; + await db.transaction(async (tx) => { + const existing = await tx + .select({ id: locations.id, room: locations.room, zone: locations.zone, mapTag: locations.mapTag, notionPageId: locations.notionPageId }) + .from(locations); + const byNotion = new Map(existing.filter((r) => r.notionPageId).map((r) => [r.notionPageId as string, r.id])); + const byKey = new Map(existing.map((r) => [locationKey(r.room, r.zone), r.id])); + const mapTags = new Map(existing.filter((r) => r.mapTag).map((r) => [r.mapTag as string, r.id])); + + for (const record of snapshot.locations) { + const { row, warnings: w } = toLocationRow(record); + warnings.push(...w); + const key = locationKey(row.room, row.zone); + const known = byNotion.get(record.id); + const tagOwner = row.mapTag ? mapTags.get(row.mapTag) : undefined; + if (row.mapTag && tagOwner && tagOwner !== known) { + warnings.push(`location map tag "${row.mapTag}" is used twice in Notion; dropped from ${record.id}`); + row.mapTag = null; + } + if (known) { + await tx.update(locations).set({ room: row.room, zone: row.zone, mapTag: row.mapTag }).where(eq(locations.id, known)); + locationIds.set(record.id, known); + updated += 1; + continue; + } + const duplicate = byKey.get(key); + if (duplicate) { + warnings.push(`location "${row.room} / ${row.zone}" appears twice in Notion; ${record.id} merged into the first`); + locationIds.set(record.id, duplicate); + continue; + } + const [created] = await tx.insert(locations).values(row).returning({ id: locations.id }); + locationIds.set(record.id, created.id); + byKey.set(key, created.id); + if (row.mapTag) mapTags.set(row.mapTag, created.id); + inserted += 1; + } + }); + count("locations", inserted, updated); + } + + // ── Tools ───────────────────────────────────────────────────────── + const toolIds: IdMap = new Map(); + const toolNames = new Map(snapshot.tools.map((t) => [t.id, t.fields.name])); + { + let inserted = 0; + let updated = 0; + await db.transaction(async (tx) => { + const existing = await tx.select({ id: tools.id, slug: tools.slug, notionPageId: tools.notionPageId }).from(tools); + const byNotion = new Map(existing.filter((r) => r.notionPageId).map((r) => [r.notionPageId as string, r.id])); + const slugs = new Set(existing.map((r) => r.slug)); + + for (const record of snapshot.tools) { + const known = byNotion.get(record.id); + const refs = { + slug: known ? "" : uniqueSlug(slugify(record.fields.name), slugs), + categoryId: record.fields.category?.[0] ? (categoryIds.get(record.fields.category[0]) ?? null) : null, + locationId: record.fields.location?.[0] ? (locationIds.get(record.fields.location[0]) ?? null) : null, + }; + const { row, warnings: w } = toToolRow(record, refs); + warnings.push(...w); + if (known) { + await tx.update(tools).set(patchOf(row, "slug", "createdAt")).where(eq(tools.id, known)); + toolIds.set(record.id, known); + updated += 1; + } else { + const [created] = await tx.insert(tools).values(row).returning({ id: tools.id }); + toolIds.set(record.id, created.id); + inserted += 1; + } + } + }); + count("tools", inserted, updated); + } + + // ── Units ───────────────────────────────────────────────────────── + const unitIds: IdMap = new Map(); + { + let inserted = 0; + let updated = 0; + await db.transaction(async (tx) => { + const existing = await tx + .select({ id: units.id, toolId: units.toolId, serialNumber: units.serialNumber, notionPageId: units.notionPageId }) + .from(units); + const byNotion = new Map(existing.filter((r) => r.notionPageId).map((r) => [r.notionPageId as string, r.id])); + const serials = new Map( + existing.filter((r) => r.serialNumber).map((r) => [serialKey(r.toolId, r.serialNumber as string), r.id]) + ); + + for (const record of snapshot.units) { + const toolId = record.fields.tool?.[0] ? (toolIds.get(record.fields.tool[0]) ?? null) : null; + const { row, warnings: w } = toUnitRow(record, { toolId }); + warnings.push(...w); + const known = byNotion.get(record.id); + if (row.serialNumber) { + const owner = serials.get(serialKey(row.toolId ?? null, row.serialNumber)); + if (owner && owner !== known) { + warnings.push(`unit "${row.unitLabel}": serial "${row.serialNumber}" is already on another unit of the same tool; dropped`); + row.serialNumber = null; + } + } + if (known) { + await tx.update(units).set(patchOf(row, "createdAt")).where(eq(units.id, known)); + unitIds.set(record.id, known); + updated += 1; + } else { + const [created] = await tx.insert(units).values(row).returning({ id: units.id }); + unitIds.set(record.id, created.id); + if (row.serialNumber) serials.set(serialKey(row.toolId ?? null, row.serialNumber), created.id); + inserted += 1; + } + } + }); + count("units", inserted, updated); + } + + // ── Resources ───────────────────────────────────────────────────── + const resourceIds: IdMap = new Map(); + { + let inserted = 0; + let updated = 0; + await db.transaction(async (tx) => { + const existing = await tx.select({ id: resources.id, notionPageId: resources.notionPageId }).from(resources); + const byNotion = new Map(existing.filter((r) => r.notionPageId).map((r) => [r.notionPageId as string, r.id])); + for (const record of snapshot.resources) { + const toolId = record.fields.tool?.[0] ? (toolIds.get(record.fields.tool[0]) ?? null) : null; + const { row, warnings: w } = toResourceRow(record, { toolId }); + warnings.push(...w); + const known = byNotion.get(record.id); + if (known) { + await tx.update(resources).set(patchOf(row, "createdAt")).where(eq(resources.id, known)); + resourceIds.set(record.id, known); + updated += 1; + } else { + const [created] = await tx.insert(resources).values(row).returning({ id: resources.id }); + resourceIds.set(record.id, created.id); + inserted += 1; + } + } + }); + count("resources", inserted, updated); + } + + // ── Maintenance logs ────────────────────────────────────────────── + const maintenanceIds: IdMap = new Map(); + { + let inserted = 0; + let updated = 0; + const unitsByNotion = new Map(snapshot.units.map((u) => [u.id, u])); + await db.transaction(async (tx) => { + const existing = await tx.select({ id: maintenanceLogs.id, notionPageId: maintenanceLogs.notionPageId }).from(maintenanceLogs); + const byNotion = new Map(existing.filter((r) => r.notionPageId).map((r) => [r.notionPageId as string, r.id])); + for (const record of snapshot.maintenanceLogs) { + const unitNotionId = record.fields.unit?.[0]; + const unit = unitNotionId ? unitsByNotion.get(unitNotionId) : undefined; + const toolNotionId = unit?.fields.tool?.[0]; + const refs = { + unitId: unitNotionId ? (unitIds.get(unitNotionId) ?? null) : null, + toolId: toolNotionId ? (toolIds.get(toolNotionId) ?? null) : null, + toolName: toolNotionId ? (toolNames.get(toolNotionId) ?? null) : null, + unitLabel: unit?.fields.unit_label ?? null, + }; + const { row, warnings: w } = toMaintenanceRow(record, refs); + warnings.push(...w); + const known = byNotion.get(record.id); + if (known) { + await tx.update(maintenanceLogs).set(patchOf(row, "createdAt")).where(eq(maintenanceLogs.id, known)); + maintenanceIds.set(record.id, known); + updated += 1; + } else { + const [created] = await tx.insert(maintenanceLogs).values(row).returning({ id: maintenanceLogs.id }); + maintenanceIds.set(record.id, created.id); + inserted += 1; + } + } + }); + count("maintenance_logs", inserted, updated); + } + + // ── Feedback (Flags) ────────────────────────────────────────────── + { + let inserted = 0; + let updated = 0; + await db.transaction(async (tx) => { + const existing = await tx.select({ id: feedback.id, notionPageId: feedback.notionPageId }).from(feedback); + const byNotion = new Map(existing.filter((r) => r.notionPageId).map((r) => [r.notionPageId as string, r.id])); + for (const record of snapshot.flags) { + const toolId = record.fields.tool?.[0] ? (toolIds.get(record.fields.tool[0]) ?? null) : null; + const { row, warnings: w } = toFeedbackRow(record, { toolId }); + warnings.push(...w); + const known = byNotion.get(record.id); + if (known) { + await tx.update(feedback).set(patchOf(row, "createdAt")).where(eq(feedback.id, known)); + updated += 1; + } else { + await tx.insert(feedback).values(row); + inserted += 1; + } + } + }); + count("feedback", inserted, updated); + } + + // ── Projects ────────────────────────────────────────────────────── + const projectIds: IdMap = new Map(); + { + let inserted = 0; + let updated = 0; + await db.transaction(async (tx) => { + const existing = await tx.select({ id: projects.id, slug: projects.slug, notionPageId: projects.notionPageId }).from(projects); + const byNotion = new Map(existing.filter((r) => r.notionPageId).map((r) => [r.notionPageId as string, r.id])); + const slugs = new Set(existing.map((r) => r.slug)); + for (const record of snapshot.projects) { + const known = byNotion.get(record.id); + const { row, warnings: w } = toProjectRow(record, { + slug: known ? "" : uniqueSlug(slugify(record.fields.title), slugs), + }); + warnings.push(...w); + let id: string; + if (known) { + await tx.update(projects).set(patchOf(row, "slug", "createdAt")).where(eq(projects.id, known)); + id = known; + updated += 1; + } else { + const [created] = await tx.insert(projects).values(row).returning({ id: projects.id }); + id = created.id; + inserted += 1; + } + projectIds.set(record.id, id); + + const linked = (record.fields.tools_used ?? []) + .map((notionId) => toolIds.get(notionId)) + .filter((toolId): toolId is string => Boolean(toolId)); + await tx.delete(projectTools).where(eq(projectTools.projectId, id)); + if (linked.length > 0) { + await tx.insert(projectTools).values(linked.map((toolId) => ({ projectId: id, toolId }))); + } + } + }); + count("projects", inserted, updated); + } + + // ── Files ───────────────────────────────────────────────────────── + const fileCounts = { copied: 0, skipped: 0, failed: 0 }; + if (files) { + const existingKeys = new Set( + (await db.select({ sourceKey: attachments.sourceKey }).from(attachments)) + .map((r) => r.sourceKey) + .filter((key): key is string => Boolean(key)) + ); + + const copyAll = async ( + ownerType: "tool" | "resource" | "maintenance_log" | "project", + ownerId: string, + folder: string, + access: "public" | "private", + list: Attachment[] | undefined + ) => { + for (const [position, file] of (list ?? []).entries()) { + if (existingKeys.has(file.id)) { + fileCounts.skipped += 1; + continue; + } + if (isStaleFileUrl(file.url)) { + warnings.push(`${ownerType} ${ownerId}: skipped a dead file URL (${file.filename})`); + fileCounts.skipped += 1; + continue; + } + try { + const copied = await files.copy(file.url, { + pathname: `${folder}/${ownerId}/${safeFilename(file.filename)}`, + access, + }); + await db.insert(attachments).values({ + ownerType, + ownerId, + position, + blobPathname: copied.blobPathname, + access, + publicUrl: copied.publicUrl, + contentType: copied.contentType, + sizeBytes: copied.sizeBytes, + originalFilename: file.filename, + sourceKey: file.id, + }); + existingKeys.add(file.id); + fileCounts.copied += 1; + } catch (error) { + warnings.push(`${ownerType} ${ownerId}: could not copy ${file.filename}: ${(error as Error).message}`); + fileCounts.failed += 1; + } + } + }; + + for (const record of snapshot.tools) { + const id = toolIds.get(record.id); + if (id) await copyAll("tool", id, "tools", "public", record.fields.image_attachments); + } + for (const record of snapshot.resources) { + const id = resourceIds.get(record.id); + if (id) await copyAll("resource", id, "resources", "public", record.fields.files); + } + for (const record of snapshot.maintenanceLogs) { + const id = maintenanceIds.get(record.id); + if (id) await copyAll("maintenance_log", id, "maintenance", "private", record.fields.photo_attachments); + } + for (const record of snapshot.projects) { + const id = projectIds.get(record.id); + if (id) await copyAll("project", id, "projects", "public", record.fields.photos); + } + log(`files: ${fileCounts.copied} copied, ${fileCounts.skipped} skipped, ${fileCounts.failed} failed`); + } else { + log("files: skipped (no copier)"); + } + + return { counts, files: fileCounts, warnings, preflightWarnings: flight.warnings }; +} + +function categoryKey(name: string, group: string | null): string { + return `${name.trim().toLowerCase()}|${(group ?? "").trim().toLowerCase()}`; +} + +function locationKey(room: string, zone: string): string { + return `${room.trim().toLowerCase()}|${zone.trim().toLowerCase()}`; +} + +function serialKey(toolId: string | null, serial: string): string { + return `${toolId ?? ""}|${serial.trim().toLowerCase()}`; +} diff --git a/v5/src/lib/import/source.test.ts b/v5/src/lib/import/source.test.ts new file mode 100644 index 0000000..278f1cb --- /dev/null +++ b/v5/src/lib/import/source.test.ts @@ -0,0 +1,50 @@ +import { http, HttpResponse } from "msw"; +import { flagsPage, notionQueryResponse, projectsPage } from "../../../test/fixtures/notion"; +import { DB_IDS } from "../../../test/msw/handlers"; +import { server } from "../../../test/msw/server"; +import { readNotionSnapshot } from "./source"; + +function stubNotionEnv(withProjects = true) { + vi.stubEnv("NOTION_API_KEY", "secret_test"); + vi.stubEnv("NOTION_DB_TOOLS", DB_IDS.tools); + vi.stubEnv("NOTION_DB_CATEGORIES", DB_IDS.categories); + vi.stubEnv("NOTION_DB_LOCATIONS", DB_IDS.locations); + vi.stubEnv("NOTION_DB_UNITS", DB_IDS.units); + vi.stubEnv("NOTION_DB_RESOURCES", DB_IDS.resources); + vi.stubEnv("NOTION_DB_MAINTENANCE_LOGS", DB_IDS.maintenance_logs); + vi.stubEnv("NOTION_DB_FLAGS", DB_IDS.flags); + if (withProjects) vi.stubEnv("NOTION_DB_PROJECTS", DB_IDS.projects); +} + +describe("readNotionSnapshot", () => { + it("reads schemas and every database, drafts included, plus the email properties", async () => { + stubNotionEnv(); + server.use( + http.post(`https://api.notion.com/v1/databases/${DB_IDS.flags}/query`, () => + HttpResponse.json(notionQueryResponse([flagsPage])) + ), + http.post(`https://api.notion.com/v1/databases/${DB_IDS.projects}/query`, () => + HttpResponse.json(notionQueryResponse([projectsPage])) + ) + ); + + const lines: string[] = []; + const snapshot = await readNotionSnapshot({ throttleMs: 0, log: (line) => lines.push(line) }); + + expect(Object.keys(snapshot.schemas)).toEqual(["units", "maintenance_logs", "flags"]); + expect(snapshot.schemas.units?.properties.status.select?.options.map((o) => o.name)).toContain("Retired"); + expect(snapshot.tools).toHaveLength(1); + expect(snapshot.categories).toHaveLength(1); + expect(snapshot.units).toHaveLength(1); + expect(snapshot.maintenanceLogs[0].reporterEmail).toBeNull(); + expect(snapshot.flags[0].reporterEmail).toBe("ada@cornell.edu"); + expect(snapshot.projects[0].fields.title).toBe("Laser-cut lamp"); + expect(lines).toContain("read 1 tools"); + }); + + it("leaves projects empty when NOTION_DB_PROJECTS is unset", async () => { + stubNotionEnv(false); + const snapshot = await readNotionSnapshot({ throttleMs: 0 }); + expect(snapshot.projects).toEqual([]); + }); +}); diff --git a/v5/src/lib/import/source.ts b/v5/src/lib/import/source.ts new file mode 100644 index 0000000..f7fc06b --- /dev/null +++ b/v5/src/lib/import/source.ts @@ -0,0 +1,90 @@ +import { + fetchAllProjects, + getDatabaseSchema, + hasProjectsEnv, + pageToCategory, + pageToFlag, + pageToLocation, + pageToMaintenanceLog, + pageToResource, + pageToTool, + pageToUnit, + queryAllPages, + readEmailProperty, + type CatalogTable, +} from "../notion.ts"; +import type { + CategoryRecord, + LocationRecord, + ProjectRecord, + ResourceRecord, + ToolRecord, + UnitRecord, +} from "../types.ts"; +import type { FlagImportRecord, MaintenanceImportRecord } from "./mappers.ts"; +import type { SchemasByTable } from "./preflight.ts"; + +/** + * Everything the import needs from Notion, read once (spec §5.7 step 2). + * + * Read-only against Notion, drafts included, throttled to stay under the API's + * rate limit. The result is a plain object so the run can be tested without + * Notion and the same snapshot can feed a dry run and then the real one. + */ +export interface NotionSnapshot { + schemas: SchemasByTable; + categories: CategoryRecord[]; + locations: LocationRecord[]; + tools: ToolRecord[]; + units: UnitRecord[]; + resources: ResourceRecord[]; + maintenanceLogs: MaintenanceImportRecord[]; + flags: FlagImportRecord[]; + projects: ProjectRecord[]; +} + +export interface ReadSnapshotOptions { + /** Pause between Notion requests; 350 ms keeps under 3 requests per second. */ + throttleMs?: number; + log?: (line: string) => void; +} + +const SCHEMA_TABLES: Array = ["units", "maintenance_logs", "flags"]; + +export async function readNotionSnapshot(options: ReadSnapshotOptions = {}): Promise { + const throttleMs = options.throttleMs ?? 350; + const log = options.log ?? (() => {}); + const pause = () => new Promise((resolve) => setTimeout(resolve, throttleMs)); + + const schemas: SchemasByTable = {}; + for (const table of SCHEMA_TABLES) { + schemas[table] = await getDatabaseSchema(table); + await pause(); + } + + const read = async (table: CatalogTable) => { + const pages = await queryAllPages(table, { throttleMs }); + log(`read ${pages.length} ${table}`); + await pause(); + return pages; + }; + + const categories = (await read("categories")).map(pageToCategory); + const locations = (await read("locations")).map(pageToLocation); + const tools = (await read("tools")).map(pageToTool); + const units = (await read("units")).map(pageToUnit); + const resources = (await read("resources")).map(pageToResource); + const maintenanceLogs = (await read("maintenance_logs")).map((page) => ({ + ...pageToMaintenanceLog(page), + reporterEmail: readEmailProperty(page, ["reporter_email", "Reporter Email"]) || null, + })); + const flags = (await read("flags")).map((page) => ({ + ...pageToFlag(page), + reporterEmail: readEmailProperty(page, ["reporter_email", "Reporter Email"]) || null, + })); + + const projects = hasProjectsEnv() ? await fetchAllProjects() : []; + log(`read ${projects.length} projects${hasProjectsEnv() ? "" : " (NOTION_DB_PROJECTS unset)"}`); + + return { schemas, categories, locations, tools, units, resources, maintenanceLogs, flags, projects }; +} diff --git a/v5/src/lib/import/ticket-description.test.ts b/v5/src/lib/import/ticket-description.test.ts new file mode 100644 index 0000000..9510dc2 --- /dev/null +++ b/v5/src/lib/import/ticket-description.test.ts @@ -0,0 +1,80 @@ +import { parseTicketDescription } from "./ticket-description"; + +const TEMPLATE = [ + "**What happened**\nThe resin tank looks cloudy after the last print.", + "**Reported by**\nAda Lovelace", + "**Date reported**\n2024-09-01", + "**Priority**\nMedium", +].join("\n\n"); + +describe("parseTicketDescription", () => { + it("splits the exact template back into columns", () => { + expect(parseTicketDescription(TEMPLATE)).toEqual({ + description: "The resin tank looks cloudy after the last print.", + reportedBy: "Ada Lovelace", + dateReported: "2024-09-01", + priority: "Medium", + templated: true, + exact: true, + }); + }); + + it("handles a template with only some sections", () => { + const text = "**What happened**\nBelt slipping.\n\n**Priority**\nHigh"; + expect(parseTicketDescription(text)).toEqual({ + description: "Belt slipping.", + reportedBy: null, + dateReported: null, + priority: "High", + templated: true, + exact: true, + }); + }); + + it("keeps free text whole and reports that no template was found", () => { + expect(parseTicketDescription("Nozzle is clogged again.")).toEqual({ + description: "Nozzle is clogged again.", + reportedBy: null, + dateReported: null, + priority: null, + templated: false, + exact: false, + }); + }); + + it("keeps the whole text as the description when a person edited around the template, but still fills the columns", () => { + const edited = `Note from staff: checked twice.\n\n${TEMPLATE}`; + const parsed = parseTicketDescription(edited); + expect(parsed.templated).toBe(true); + expect(parsed.exact).toBe(false); + expect(parsed.description).toBe(edited); + expect(parsed.reportedBy).toBe("Ada Lovelace"); + expect(parsed.dateReported).toBe("2024-09-01"); + expect(parsed.priority).toBe("Medium"); + }); + + it("treats a repeated heading as not the template", () => { + const text = "**Priority**\nLow\n\n**Priority**\nHigh"; + const parsed = parseTicketDescription(text); + expect(parsed.exact).toBe(false); + expect(parsed.description).toBe(text); + }); + + it("returns nulls for empty input", () => { + expect(parseTicketDescription("")).toEqual({ + description: null, + reportedBy: null, + dateReported: null, + priority: null, + templated: false, + exact: true, + }); + expect(parseTicketDescription(undefined).description).toBeNull(); + }); + + it("tolerates Windows line endings", () => { + const parsed = parseTicketDescription(TEMPLATE.replace(/\n/g, "\r\n")); + expect(parsed.exact).toBe(true); + expect(parsed.priority).toBe("Medium"); + }); +}); diff --git a/v5/src/lib/import/ticket-description.ts b/v5/src/lib/import/ticket-description.ts new file mode 100644 index 0000000..61fff52 --- /dev/null +++ b/v5/src/lib/import/ticket-description.ts @@ -0,0 +1,88 @@ +/** + * Undo `formatTicketDescription` (spec §5.7 step 5). + * + * v5 writes a maintenance ticket's description to Notion as sections: + * + * **What happened** + * + * + * **Reported by** + * + * + * **Date reported** + * + * + * **Priority** + * + * + * The import parses these back into columns. When the text is not exactly that + * shape — hand-edited in Notion, or written before the template existed — the + * original text is kept whole as the description so nothing is lost, and any + * sections that were recognised still fill their columns. + */ + +export interface ParsedTicketDescription { + description: string | null; + reportedBy: string | null; + dateReported: string | null; + priority: string | null; + /** True when at least one template heading was found. */ + templated: boolean; + /** True when the whole text was sections and nothing else. */ + exact: boolean; +} + +const HEADINGS = { + "What happened": "whatHappened", + "Reported by": "reportedBy", + "Date reported": "dateReported", + Priority: "priority", +} as const; + +type Section = (typeof HEADINGS)[keyof typeof HEADINGS]; + +const SECTION_PATTERN = /\*\*(What happened|Reported by|Date reported|Priority)\*\*\n/g; + +export function parseTicketDescription(text: string | null | undefined): ParsedTicketDescription { + const raw = (text ?? "").replace(/\r\n/g, "\n"); + const trimmed = raw.trim(); + if (!trimmed) { + return { description: null, reportedBy: null, dateReported: null, priority: null, templated: false, exact: true }; + } + + const matches = [...trimmed.matchAll(SECTION_PATTERN)]; + if (matches.length === 0) { + return { description: trimmed, reportedBy: null, dateReported: null, priority: null, templated: false, exact: false }; + } + + const sections: Partial> = {}; + let exact = matches[0].index === 0; + for (let i = 0; i < matches.length; i += 1) { + const match = matches[i]; + const key = HEADINGS[match[1] as keyof typeof HEADINGS]; + const start = (match.index ?? 0) + match[0].length; + const end = i + 1 < matches.length ? (matches[i + 1].index ?? trimmed.length) : trimmed.length; + const body = trimmed.slice(start, end).trim(); + if (key in sections) exact = false; // a repeated heading is not the template + sections[key] = body; + } + + // The template joins sections with a blank line; anything else between them + // means a person edited the text, and the whole text is kept as the description. + const rebuilt = matches + .map((match, i) => { + const key = HEADINGS[match[1] as keyof typeof HEADINGS]; + return `**${match[1]}**\n${sections[key] ?? ""}${i + 1 < matches.length ? "\n\n" : ""}`; + }) + .join(""); + if (rebuilt !== trimmed) exact = false; + + return { + description: exact ? sections.whatHappened || null : trimmed, + reportedBy: sections.reportedBy || null, + dateReported: sections.dateReported || null, + priority: sections.priority || null, + templated: true, + exact, + }; +} diff --git a/v5/src/lib/import/vocabulary-map.test.ts b/v5/src/lib/import/vocabulary-map.test.ts new file mode 100644 index 0000000..80dadb2 --- /dev/null +++ b/v5/src/lib/import/vocabulary-map.test.ts @@ -0,0 +1,63 @@ +import { UNIT_CONDITION, UNIT_STATUS, MAINTENANCE_STATUS } from "../db/schema/vocabulary"; +import { CHECKED_PROPERTIES, UnmappedOptionError, mapOption, toStoredValue } from "./vocabulary-map"; + +describe("toStoredValue", () => { + it("lower-cases and joins words with underscores", () => { + expect(toStoredValue("In Use")).toBe("in_use"); + expect(toStoredValue("Needs Repair")).toBe("needs_repair"); + expect(toStoredValue(" Preventive Maintenance ")).toBe("preventive_maintenance"); + expect(toStoredValue("safety-info")).toBe("safety_info"); + }); +}); + +describe("mapOption", () => { + const where = { table: "units", property: "status" }; + + it("maps every defined Notion option onto the stored list", () => { + for (const [notion, stored] of [ + ["Available", "available"], + ["In Use", "in_use"], + ["Under Maintenance", "under_maintenance"], + ["Out of Service", "out_of_service"], + ["Retired", "retired"], + ]) { + expect(mapOption(UNIT_STATUS, notion, where)).toBe(stored); + } + expect(mapOption(UNIT_CONDITION, "New", { table: "units", property: "condition" })).toBe("new"); + expect(mapOption(MAINTENANCE_STATUS, "Closed", { table: "maintenance_logs", property: "status" })).toBe("closed"); + }); + + it("returns null for empty and missing values", () => { + expect(mapOption(UNIT_STATUS, "", where)).toBeNull(); + expect(mapOption(UNIT_STATUS, " ", where)).toBeNull(); + expect(mapOption(UNIT_STATUS, undefined, where)).toBeNull(); + expect(mapOption(UNIT_STATUS, null, where)).toBeNull(); + }); + + it("throws, naming the table, property and option, for anything unmapped", () => { + expect(() => mapOption(UNIT_STATUS, "Broken", where)).toThrow(UnmappedOptionError); + try { + mapOption(UNIT_STATUS, "Broken", where); + } catch (error) { + const e = error as UnmappedOptionError; + expect(e.table).toBe("units"); + expect(e.property).toBe("status"); + expect(e.option).toBe("Broken"); + } + }); +}); + +describe("CHECKED_PROPERTIES", () => { + it("covers every select column that has a CHECK constraint", () => { + const keys = CHECKED_PROPERTIES.map((c) => `${c.table}.${c.names[0]}`); + expect(keys).toEqual([ + "units.status", + "units.condition", + "maintenance_logs.type", + "maintenance_logs.priority", + "maintenance_logs.status", + "flags.field_flagged", + "flags.status", + ]); + }); +}); diff --git a/v5/src/lib/import/vocabulary-map.ts b/v5/src/lib/import/vocabulary-map.ts new file mode 100644 index 0000000..677f9b3 --- /dev/null +++ b/v5/src/lib/import/vocabulary-map.ts @@ -0,0 +1,77 @@ +import { + FEEDBACK_STATUS, + FLAG_FIELDS, + MAINTENANCE_PRIORITY, + MAINTENANCE_STATUS, + MAINTENANCE_TYPE, + UNIT_CONDITION, + UNIT_STATUS, +} from "../db/schema/vocabulary.ts"; + +/** + * Notion option names → stored vocabulary values (spec §4, §5.7). + * + * `"In Use"` becomes `in_use`, `"Needs Repair"` becomes `needs_repair`. The + * rule is mechanical so the pre-flight can apply it to Notion's *defined* + * options and the mappers can apply it to values in use, and both agree. + * Anything that does not land in the stored list is an error, never a + * default: silently mapping to a default is how data gets lost. + */ + +// Fields are assigned explicitly, not as constructor parameter properties, +// because the import script loads this module under Node's type stripping. +export class UnmappedOptionError extends Error { + readonly table: string; + readonly property: string; + readonly option: string; + + constructor(table: string, property: string, option: string) { + super(`Unmapped option "${option}" for ${table}.${property}`); + this.name = "UnmappedOptionError"; + this.table = table; + this.property = property; + this.option = option; + } +} + +/** The mechanical rule: trim, lower-case, spaces and dashes to underscores. */ +export function toStoredValue(option: string): string { + return option.trim().toLowerCase().replace(/[\s-]+/g, "_"); +} + +/** + * Map one Notion value onto `list`. Empty or missing → `null` (the column is + * nullable, or the caller supplies its default). Present but unmapped → throw. + */ +export function mapOption( + list: T, + option: string | null | undefined, + where: { table: string; property: string } +): T[number] | null { + if (option === null || option === undefined) return null; + const trimmed = option.trim(); + if (!trimmed) return null; + const stored = toStoredValue(trimmed); + if ((list as readonly string[]).includes(stored)) return stored as T[number]; + throw new UnmappedOptionError(where.table, where.property, trimmed); +} + +export interface CheckedProperty { + /** The Notion database, by the import's table name. */ + table: "units" | "maintenance_logs" | "flags"; + /** Candidate property names, snake_case first (the parsers tolerate both). */ + names: readonly string[]; + /** The stored vocabulary every defined option must map onto. */ + list: readonly string[]; +} + +/** The select properties whose defined options the pre-flight verifies. */ +export const CHECKED_PROPERTIES: readonly CheckedProperty[] = [ + { table: "units", names: ["status", "Status"], list: UNIT_STATUS }, + { table: "units", names: ["condition", "Condition"], list: UNIT_CONDITION }, + { table: "maintenance_logs", names: ["type", "Type"], list: MAINTENANCE_TYPE }, + { table: "maintenance_logs", names: ["priority", "Priority"], list: MAINTENANCE_PRIORITY }, + { table: "maintenance_logs", names: ["status", "Status"], list: MAINTENANCE_STATUS }, + { table: "flags", names: ["field_flagged", "Field Flagged"], list: FLAG_FIELDS }, + { table: "flags", names: ["status", "Status"], list: FEEDBACK_STATUS }, +]; diff --git a/v5/src/lib/notion.ts b/v5/src/lib/notion.ts index 1f68750..5974b8b 100644 --- a/v5/src/lib/notion.ts +++ b/v5/src/lib/notion.ts @@ -2,6 +2,10 @@ import type { Attachment, CategoryFields, CategoryRecord, + FlagFields, + FlagRecord, + FlagStatus, + FlaggedField, LocationFields, LocationRecord, MaintenanceLogFields, @@ -24,7 +28,7 @@ import type { const NOTION_API_URL = "https://api.notion.com/v1"; const NOTION_VERSION = "2022-06-28"; -type CatalogTable = +export type CatalogTable = | "tools" | "categories" | "locations" @@ -61,9 +65,10 @@ type NotionProperty = { files?: NotionFile[]; date?: { start?: string } | null; number?: number | null; + email?: string | null; }; -type NotionPage = { +export type NotionPage = { object: "page"; id: string; created_time: string; @@ -71,6 +76,25 @@ type NotionPage = { properties: Record; }; +/** One option of a select or multi-select property, as `GET /databases/:id` reports it. */ +export type NotionSelectOption = { name: string }; + +/** + * The part of a database's schema the import pre-flight reads: each + * property's type and, for selects, its defined options. + */ +export type NotionDatabaseSchema = { + id: string; + properties: Record< + string, + { + type: string; + select?: { options: NotionSelectOption[] }; + multi_select?: { options: NotionSelectOption[] }; + } + >; +}; + type NotionQueryResponse = { results: NotionPage[]; has_more: boolean; @@ -147,9 +171,15 @@ async function notionFetch(path: string, init?: RequestInit): Promise { return res.json() as Promise; } +type QueryOptions = { + /** Pause between pages; the import uses it to stay under Notion's rate limit. */ + throttleMs?: number; +}; + async function queryDatabase( table: CatalogTable, - body: QueryBody = {} + body: QueryBody = {}, + options: QueryOptions = {} ): Promise { const { databases } = getNotionEnv(); const pages: NotionPage[] = []; @@ -170,11 +200,31 @@ async function queryDatabase( pages.push(...data.results); startCursor = data.next_cursor || undefined; + if (startCursor && options.throttleMs) { + await new Promise((resolve) => setTimeout(resolve, options.throttleMs)); + } } while (startCursor); return pages; } +/** + * Every page in `table`, drafts included, in Notion's default order. The + * one-time import reads with this; the app's own readers filter and sort. + */ +export async function queryAllPages( + table: CatalogTable, + options: QueryOptions = {} +): Promise { + return queryDatabase(table, {}, options); +} + +/** The database's property schema — types and defined select options. */ +export async function getDatabaseSchema(table: CatalogTable): Promise { + const { databases } = getNotionEnv(); + return notionFetch(`/databases/${databases[table]}`); +} + async function fetchPage(id: string): Promise { return notionFetch(`/pages/${id}`); } @@ -234,6 +284,16 @@ function checkboxValue(page: NotionPage, names: string[]): boolean | undefined { return property?.type === "checkbox" ? property.checkbox : undefined; } +/** + * An Email property's value, or "" when absent. Exported for the import only: + * the app's read path deliberately never surfaces `reporter_email`, so the + * record parsers leave it out and the import reads it from the raw page. + */ +export function readEmailProperty(page: NotionPage, names: string[]): string { + const property = prop(page, names); + return property?.type === "email" ? property.email ?? "" : ""; +} + const STALE_IMAGE_HOSTS = ["airtableusercontent.com"]; function isStaleImageUrl(url: string | undefined): boolean { @@ -274,14 +334,14 @@ function record(page: NotionPage, fields: T): NotionRecord { }; } -function pageToCategory(page: NotionPage): CategoryRecord { +export function pageToCategory(page: NotionPage): CategoryRecord { return record(page, { name: title(page, ["name", "Name"]), group: selectValue(page, ["group", "Group"]), }); } -function pageToLocation(page: NotionPage): LocationRecord { +export function pageToLocation(page: NotionPage): LocationRecord { return record(page, { id: title(page, ["id", "ID", "Name"]), zone: selectValue(page, ["zone", "Zone", "name", "Name"]), @@ -289,7 +349,7 @@ function pageToLocation(page: NotionPage): LocationRecord { }); } -function pageToTool(page: NotionPage): ToolRecord { +export function pageToTool(page: NotionPage): ToolRecord { return record(page, { name: title(page, ["name", "Name"]), description: richTextValue(prop(page, ["description", "Description"])), @@ -307,7 +367,7 @@ function pageToTool(page: NotionPage): ToolRecord { }); } -function pageToUnit(page: NotionPage): UnitRecord { +export function pageToUnit(page: NotionPage): UnitRecord { return record(page, { unit_label: title(page, ["unit_label", "Unit Label", "Name"]), tool: relationIds(page, ["tool", "Tool"]), @@ -320,7 +380,7 @@ function pageToUnit(page: NotionPage): UnitRecord { }); } -function pageToResource(page: NotionPage): ResourceRecord { +export function pageToResource(page: NotionPage): ResourceRecord { return record(page, { title: title(page, ["title", "Title", "name", "Name"]), tool: relationIds(page, ["tool", "Tool"]), @@ -391,7 +451,7 @@ function dateValue(page: NotionPage, names: string[]): string { return richTextValue(property); } -function pageToMaintenanceLog(page: NotionPage): MaintenanceLogRecord { +export function pageToMaintenanceLog(page: NotionPage): MaintenanceLogRecord { return record(page, { title: title(page, ["title", "Title", "name", "Name"]), unit: relationIds(page, ["unit", "Unit"]), @@ -416,6 +476,25 @@ function pageToMaintenanceLog(page: NotionPage): MaintenanceLogRecord { }); } +/** + * A Flags row. The property names are the ones `capabilities/flags.ts` writes. + * Read only by the import; the app has no Flags read path. + */ +export function pageToFlag(page: NotionPage): FlagRecord { + return record(page, { + title: title(page, ["title", "Title", "name", "Name"]), + tool: relationIds(page, ["tool", "Tool"]), + field_flagged: (selectValue(page, ["field_flagged", "Field Flagged"]) || undefined) as + | FlaggedField + | undefined, + issue_description: richTextValue(prop(page, ["issue_description", "Issue Description"])), + suggested_fix: richTextValue(prop(page, ["suggested_fix", "Suggested Fix"])), + reporter: richTextValue(prop(page, ["reporter", "Reporter"])), + status: (selectValue(page, ["status", "Status"]) || undefined) as FlagStatus | undefined, + created_at: page.created_time, + }); +} + export async function fetchMaintenanceLogsByUnit( unitId: string ): Promise { diff --git a/v5/test/db.ts b/v5/test/db.ts new file mode 100644 index 0000000..ff4b5ef --- /dev/null +++ b/v5/test/db.ts @@ -0,0 +1,23 @@ +/** + * Helpers for tests that run against PGlite. + * + * Drizzle wraps a failed statement in a `DrizzleQueryError` whose message is + * "Failed query: …" and whose `cause` carries the driver error — the one that + * names the violated constraint. `expectViolation` looks there. + */ +export async function expectViolation(promise: Promise, constraint: RegExp): Promise { + let thrown: unknown; + try { + await promise; + } catch (error) { + thrown = error; + } + if (thrown === undefined) { + throw new Error(`expected a ${constraint} violation, but the statement succeeded`); + } + const error = thrown as { message?: string; cause?: { message?: string } }; + const text = `${error.message ?? ""}\n${error.cause?.message ?? ""}`; + if (!constraint.test(text)) { + throw new Error(`expected a ${constraint} violation, got: ${text}`); + } +} diff --git a/v5/test/fixtures/notion.ts b/v5/test/fixtures/notion.ts index 9357ca2..c64b4a9 100644 --- a/v5/test/fixtures/notion.ts +++ b/v5/test/fixtures/notion.ts @@ -53,6 +53,11 @@ export const dateProp = (start: string | null) => ({ date: start ? { start } : null, }); +export const emailProp = (email: string | null) => ({ + type: "email" as const, + email, +}); + /** External file (URL-hosted, e.g. a manufacturer link). */ export const externalFile = (name: string, url: string) => ({ name, @@ -217,6 +222,32 @@ export function notionQueryResponse( }; } +// A Flags row, with the property names `capabilities/flags.ts` writes. Not in +// the default query handler (the flags DB returns [] by default); the import +// tests serve it with `server.use(...)`. +export const flagsPage: NotionPageFixture = page("flag-1", { + title: titleProp("Form 4 — description"), + tool: relationProp(["tool-1"]), + field_flagged: selectProp("description"), + issue_description: richTextProp("The description says 80W but the label says 60W."), + suggested_fix: richTextProp("Change 80W to 60W."), + reporter: richTextProp("Ada Lovelace"), + reporter_email: emailProp("ada@cornell.edu"), + status: selectProp("New"), +}); + +// A published project with one photo, built with the Form 4. +export const projectsPage: NotionPageFixture = page("project-1", { + title: titleProp("Laser-cut lamp"), + author: richTextProp("Ada Lovelace"), + body: richTextProp("A lamp cut from plywood."), + photos: filesProp([hostedFile("lamp.jpg", "https://files.notion.so/lamp.jpg")]), + tools_used: relationProp(["tool-1"]), + link: urlProp("https://example.com/lamp"), + materials: multiSelectProp(["Plywood"]), + published: checkboxProp(true), +}); + /** All single-record fixtures keyed by id — handy for `GET /pages/:id`. */ export const pagesById: Record = { [toolsPage.id]: toolsPage, @@ -225,4 +256,65 @@ export const pagesById: Record = { [unitsPage.id]: unitsPage, [resourcesPage.id]: resourcesPage, [maintenanceLogsPage.id]: maintenanceLogsPage, + [flagsPage.id]: flagsPage, + [projectsPage.id]: projectsPage, +}; + +// ── Database schemas (GET /databases/:id) ─────────────────────────── +// +// What the import pre-flight reads: each property's type and, for selects, +// the *defined* options. These are the option sets the live workspace defines +// (2026-08-14 audit), including `New` and `Closed`, which are defined but unused. + +export interface DatabaseSchemaFixture { + object: "database"; + id: string; + properties: Record< + string, + { + type: string; + select?: { options: { name: string }[] }; + multi_select?: { options: { name: string }[] }; + } + >; +} + +export type SchemaTable = + | "tools" + | "categories" + | "locations" + | "units" + | "resources" + | "maintenance_logs" + | "flags" + | "projects"; + +const SELECT_OPTIONS: Record> = { + tools: {}, + categories: { group: ["3D Printing", "Laser"] }, + locations: { room: ["MakerLab", "Laser Room"], zone: ["Resin Bench", "Laser Bay"] }, + units: { + status: ["Available", "In Use", "Under Maintenance", "Out of Service", "Retired"], + condition: ["Excellent", "Good", "Fair", "Needs Repair", "New"], + }, + resources: { type: ["SOP", "Manual", "Video", "Other"] }, + maintenance_logs: { + type: ["Issue Report", "Preventive Maintenance", "Repair", "Inspection", "Calibration"], + priority: ["Critical", "High", "Medium", "Low"], + status: ["Open", "In Progress", "Resolved", "Closed"], + }, + flags: { + field_flagged: ["description", "image", "name", "category", "location", "materials", "safety_info"], + status: ["New", "Reviewed", "Fixed", "Dismissed"], + }, + projects: {}, }; + +/** A fresh, mutable schema fixture for `table` (tests may edit the options). */ +export function databaseSchema(table: SchemaTable): DatabaseSchemaFixture { + const properties: DatabaseSchemaFixture["properties"] = {}; + for (const [name, options] of Object.entries(SELECT_OPTIONS[table])) { + properties[name] = { type: "select", select: { options: options.map((option) => ({ name: option })) } }; + } + return { object: "database", id: `db-${table.replace("_logs", "")}`, properties }; +} diff --git a/v5/test/msw/handlers.ts b/v5/test/msw/handlers.ts index 6792683..6a3a13b 100644 --- a/v5/test/msw/handlers.ts +++ b/v5/test/msw/handlers.ts @@ -1,6 +1,7 @@ import { http, HttpResponse } from "msw"; import { categoriesPage, + databaseSchema, locationsPage, maintenanceLogsPage, notionQueryResponse, @@ -9,6 +10,7 @@ import { toolsPage, unitsPage, type NotionPageFixture, + type SchemaTable, } from "../fixtures/notion"; const NOTION = "https://api.notion.com/v1"; @@ -27,8 +29,13 @@ export const DB_IDS = { resources: "db-resources", maintenance_logs: "db-maintenance", flags: "db-flags", + projects: "db-projects", } as const; +const TABLE_BY_DB_ID: Record = Object.fromEntries( + Object.entries(DB_IDS).map(([table, id]) => [id, table as SchemaTable]) +); + const PAGES_BY_DB: Record = { [DB_IDS.tools]: [toolsPage], [DB_IDS.categories]: [categoriesPage], @@ -70,6 +77,19 @@ export const handlers = [ }); }), + // GET /databases/:id — the database schema (property types and defined + // select options), which the import pre-flight reads. Routed by DB_IDS. + http.get(`${NOTION}/databases/:id`, ({ params }) => { + const table = TABLE_BY_DB_ID[params.id as string]; + if (!table) { + return HttpResponse.json( + { object: "error", status: 404, code: "object_not_found", message: "Not found" }, + { status: 404 } + ); + } + return HttpResponse.json(databaseSchema(table)); + }), + // GET /pages/:id — single page fetch. Returns the matching fixture or 404. http.get(`${NOTION}/pages/:id`, ({ params }) => { const id = params.id as string;