diff --git a/README.md b/README.md index b8bd753..86191f0 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ Standalone admin panel with task results storage in SQLite or Postgres database - - [Broker-agnostic admin panel for Taskiq](#broker-agnostic-admin-panel-for-taskiq) - [Previews](#previews) - [Usage](#usage) @@ -12,13 +11,14 @@ Standalone admin panel with task results storage in SQLite or Postgres database - [Development](#development) ### Previews -Tasks Page | Task Details Page -:-------------------------:|:-------------------------: -![Alt text](./docs/images/preview1.png) | ![Alt text](./docs/images/preview2.png) + +| Tasks Page | Task Details Page | +| :-------------------------------------: | :-------------------------------------: | +| ![Alt text](./docs/images/preview1.png) | ![Alt text](./docs/images/preview2.png) | ### Usage -1) Import and connect the middleware to the broker: +1. Import and connect the middleware to the broker: ```python ... @@ -41,9 +41,10 @@ broker = ( ... ``` -2) Pull the image from GitHub Container Registry: `docker pull ghcr.io/taskiq-python/taskiq-admin:latest` +2. Pull the image from GitHub Container Registry: `docker pull ghcr.io/taskiq-python/taskiq-admin:latest` + +3. Replace `TASKIQ_ADMIN_API_TOKEN` with any secret enough string and run: -3) Replace `TASKIQ_ADMIN_API_TOKEN` with any secret enough string and run: ```bash docker run -d --rm \ -p "3000:3000" \ @@ -53,7 +54,7 @@ docker run -d --rm \ "ghcr.io/taskiq-python/taskiq-admin:latest" ``` -4) Go to `http://localhost:3000/tasks` +4. Go to `http://localhost:3000/tasks` ### Docker Compose Example @@ -83,28 +84,51 @@ services: - admin_data:/usr/database/ volumes: - admin_data: + admin_data: ``` ### Running without Docker -1) `cp env-example .env`, enter `.env` file and fill in all needed variables -2) run `make dev` to run it locally in dev mode -3) run `make prod` to run it locally in prod mode + +1. `cp env-example .env`, enter `.env` file and fill in all needed variables +2. run `make dev` to run it locally in dev mode +3. run `make prod` to run it locally in prod mode Environment variables for DB setup: + - `TASKIQ_ADMIN_DB_DRIVER`: required, one of `sqlite` or `postgres` - `TASKIQ_ADMIN_DB_FILE_PATH`: required when `TASKIQ_ADMIN_DB_DRIVER=sqlite` - `TASKIQ_ADMIN_DB_URL`: required when `TASKIQ_ADMIN_DB_DRIVER=postgres` +#### Postgres and `pg_trgm` (optional) + +Task name search uses a case-insensitive substring match. On postgres it is backed by a +`pg_trgm` GIN index, which taskiq-admin creates on startup. + +This is a performance optimization, not a requirement: if the extension cannot be created, +search keeps returning the same results, it just does a sequential scan over the tasks table. +A warning is logged once at startup when that happens. + +`pg_trgm` ships with the standard `postgresql-contrib` package (included in the official +postgres Docker images) and is a trusted extension since postgres 13, so the database owner +can create it without superuser rights. If the role in `TASKIQ_ADMIN_DB_URL` is neither owner +nor superuser, create it once by hand: + +```sql +CREATE EXTENSION IF NOT EXISTS pg_trgm; +``` + ### Task States + Let's assume we have a task 'do_smth', there are all states it can embrace: -1) `queued` - the task has been sent to the queue without an error -2) `running` - the task is grabbed by a worker and is being processed -3) `success` - the task is fully processed without any errors -4) `failure` - an error occured during the task processing -5) `abandoned` - taskiq-admin sets all 'running' tasks as 'abandoned' if there was a downtime between the time these tasks were in 'running' state and the time of next startup of taskiq-admin + +1. `queued` - the task has been sent to the queue without an error +2. `running` - the task is grabbed by a worker and is being processed +3. `success` - the task is fully processed without any errors +4. `failure` - an error occured during the task processing +5. `abandoned` - taskiq-admin sets all 'running' tasks as 'abandoned' if there was a downtime between the time these tasks were in 'running' state and the time of next startup of taskiq-admin ### Development -1) Run `pnpm install` to install all dependencies -2) Run `pnpm db:push` to create the sqlite database if needed -3) Run `pnpm dev` to run the project + +1. Run `pnpm install` to install all dependencies +2. Run `pnpm db:push` to create the sqlite database if needed +3. Run `pnpm dev` to run the project diff --git a/package.json b/package.json index 6efe7dd..0c45f5e 100644 --- a/package.json +++ b/package.json @@ -21,15 +21,15 @@ "@tailwindcss/vite": "^4.1.17", "@tanstack/vue-table": "^8.21.3", "@vueuse/core": "^14.1.0", - "better-sqlite3": "^12.6.2", + "better-sqlite3": "^12.11.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "dayjs": "^1.11.19", + "dayjs": "^1.11.23", "dotenv": "^17.2.3", "drizzle-orm": "^0.45.1", "lucide-vue-next": "^0.524.0", "nuxt": "^4.2.2", - "pg": "^8.16.3", + "pg": "^8.23.0", "reka-ui": "^2.6.1", "tailwind-merge": "^3.4.0", "tailwindcss": "^4.1.17", @@ -44,7 +44,8 @@ "@iconify-json/radix-icons": "^1.2.5", "@iconify/vue": "^5.0.0", "@nuxt/test-utils": "^3.23.0", - "@types/better-sqlite3": "^7.6.12", + "@types/better-sqlite3": "^7.6.13", + "@types/pg": "^8.23.1", "@vue/test-utils": "^2.4.6", "drizzle-kit": "^0.31.8", "happy-dom": "^18.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 02f4628..3228293 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,10 +10,10 @@ dependencies: version: 3.10.1 '@nuxt/fonts': specifier: 0.12.1 - version: 0.12.1(vite@7.3.1) + version: 0.12.1(vite@7.3.6) '@tailwindcss/vite': specifier: ^4.1.17 - version: 4.1.17(vite@7.3.1) + version: 4.1.17(vite@7.3.6) '@tanstack/vue-table': specifier: ^8.21.3 version: 8.21.3(vue@3.5.27) @@ -21,8 +21,8 @@ dependencies: specifier: ^14.1.0 version: 14.1.0(vue@3.5.27) better-sqlite3: - specifier: ^12.6.2 - version: 12.6.2 + specifier: ^12.11.1 + version: 12.11.1 class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -30,23 +30,23 @@ dependencies: specifier: ^2.1.1 version: 2.1.1 dayjs: - specifier: ^1.11.19 - version: 1.11.19 + specifier: ^1.11.23 + version: 1.11.23 dotenv: specifier: ^17.2.3 version: 17.2.3 drizzle-orm: specifier: ^0.45.1 - version: 0.45.1(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(pg@8.20.0) + version: 0.45.1(@types/better-sqlite3@7.6.13)(@types/pg@8.23.1)(better-sqlite3@12.11.1)(pg@8.23.0) lucide-vue-next: specifier: ^0.524.0 version: 0.524.0(vue@3.5.27) nuxt: specifier: ^4.2.2 - version: 4.2.2(@vue/compiler-sfc@3.5.27)(better-sqlite3@12.6.2)(drizzle-orm@0.45.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1) + version: 4.2.2(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@vue/compiler-sfc@3.5.42)(better-sqlite3@12.11.1)(drizzle-orm@0.45.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.6) pg: - specifier: ^8.16.3 - version: 8.20.0 + specifier: ^8.23.0 + version: 8.23.0 reka-ui: specifier: ^2.6.1 version: 2.6.1(typescript@5.9.3)(vue@3.5.27) @@ -83,8 +83,11 @@ devDependencies: specifier: ^3.23.0 version: 3.23.0(@vue/test-utils@2.4.6)(happy-dom@18.0.1)(playwright-core@1.57.0)(typescript@5.9.3)(vitest@3.2.4) '@types/better-sqlite3': - specifier: ^7.6.12 + specifier: ^7.6.13 version: 7.6.13 + '@types/pg': + specifier: ^8.23.1 + version: 8.23.1 '@vue/test-utils': specifier: ^2.4.6 version: 2.4.6 @@ -275,10 +278,20 @@ packages: resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} + /@babel/helper-string-parser@7.29.7: + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + dev: false + /@babel/helper-validator-identifier@7.28.5: resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} + /@babel/helper-validator-identifier@7.29.7: + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + dev: false + /@babel/helper-validator-option@7.27.1: resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} @@ -306,6 +319,14 @@ packages: dependencies: '@babel/types': 7.28.6 + /@babel/parser@7.29.8: + resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==} + engines: {node: '>=6.0.0'} + hasBin: true + dependencies: + '@babel/types': 7.29.8 + dev: false + /@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.28.6): resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} engines: {node: '>=6.9.0'} @@ -380,6 +401,14 @@ packages: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + /@babel/types@7.29.8: + resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + dev: false + /@bomb.sh/tab@0.0.11(citty@0.1.6): resolution: {integrity: sha512-RSqyreeicYBALcMaNxIUJTBknftXsyW45VRq5gKDNwKroh0Re5SDoWwXZaphb+OTEzVdpm/BA8Uq6y0P+AtVYw==} hasBin: true @@ -443,26 +472,23 @@ packages: resolution: {integrity: sha512-/B8YJGPzaYq1NbsQmwgP8EZqg40NpTw4ZB3suuI0TplbxKHeK94jeaawLmVhCv+YwUnOpiWEz9U6SeThku/8JQ==} dev: false - /@emnapi/core@1.8.1: - resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==} - requiresBuild: true + /@emnapi/core@1.11.3: + resolution: {integrity: sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==} dependencies: - '@emnapi/wasi-threads': 1.1.0 + '@emnapi/wasi-threads': 1.2.3 tslib: 2.8.1 dev: false optional: true - /@emnapi/runtime@1.8.1: - resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} - requiresBuild: true + /@emnapi/runtime@1.11.3: + resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==} dependencies: tslib: 2.8.1 dev: false optional: true - /@emnapi/wasi-threads@1.1.0: - resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} - requiresBuild: true + /@emnapi/wasi-threads@1.2.3: + resolution: {integrity: sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==} dependencies: tslib: 2.8.1 dev: false @@ -509,6 +535,15 @@ packages: dev: false optional: true + /@esbuild/aix-ppc64@0.28.2: + resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + requiresBuild: true + dev: false + optional: true + /@esbuild/android-arm64@0.18.20: resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} engines: {node: '>=12'} @@ -543,6 +578,15 @@ packages: dev: false optional: true + /@esbuild/android-arm64@0.28.2: + resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: false + optional: true + /@esbuild/android-arm@0.18.20: resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} engines: {node: '>=12'} @@ -577,6 +621,15 @@ packages: dev: false optional: true + /@esbuild/android-arm@0.28.2: + resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: false + optional: true + /@esbuild/android-x64@0.18.20: resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} engines: {node: '>=12'} @@ -611,6 +664,15 @@ packages: dev: false optional: true + /@esbuild/android-x64@0.28.2: + resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: false + optional: true + /@esbuild/darwin-arm64@0.18.20: resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} engines: {node: '>=12'} @@ -645,6 +707,15 @@ packages: dev: false optional: true + /@esbuild/darwin-arm64@0.28.2: + resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + /@esbuild/darwin-x64@0.18.20: resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} engines: {node: '>=12'} @@ -679,6 +750,15 @@ packages: dev: false optional: true + /@esbuild/darwin-x64@0.28.2: + resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + /@esbuild/freebsd-arm64@0.18.20: resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} engines: {node: '>=12'} @@ -713,6 +793,15 @@ packages: dev: false optional: true + /@esbuild/freebsd-arm64@0.28.2: + resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: false + optional: true + /@esbuild/freebsd-x64@0.18.20: resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} engines: {node: '>=12'} @@ -747,6 +836,15 @@ packages: dev: false optional: true + /@esbuild/freebsd-x64@0.28.2: + resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: false + optional: true + /@esbuild/linux-arm64@0.18.20: resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} engines: {node: '>=12'} @@ -781,6 +879,15 @@ packages: dev: false optional: true + /@esbuild/linux-arm64@0.28.2: + resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@esbuild/linux-arm@0.18.20: resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} engines: {node: '>=12'} @@ -815,6 +922,15 @@ packages: dev: false optional: true + /@esbuild/linux-arm@0.28.2: + resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@esbuild/linux-ia32@0.18.20: resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} engines: {node: '>=12'} @@ -849,6 +965,15 @@ packages: dev: false optional: true + /@esbuild/linux-ia32@0.28.2: + resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@esbuild/linux-loong64@0.18.20: resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} engines: {node: '>=12'} @@ -883,6 +1008,15 @@ packages: dev: false optional: true + /@esbuild/linux-loong64@0.28.2: + resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@esbuild/linux-mips64el@0.18.20: resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} engines: {node: '>=12'} @@ -917,6 +1051,15 @@ packages: dev: false optional: true + /@esbuild/linux-mips64el@0.28.2: + resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@esbuild/linux-ppc64@0.18.20: resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} engines: {node: '>=12'} @@ -951,6 +1094,15 @@ packages: dev: false optional: true + /@esbuild/linux-ppc64@0.28.2: + resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@esbuild/linux-riscv64@0.18.20: resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} engines: {node: '>=12'} @@ -985,6 +1137,15 @@ packages: dev: false optional: true + /@esbuild/linux-riscv64@0.28.2: + resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@esbuild/linux-s390x@0.18.20: resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} engines: {node: '>=12'} @@ -1019,6 +1180,15 @@ packages: dev: false optional: true + /@esbuild/linux-s390x@0.28.2: + resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@esbuild/linux-x64@0.18.20: resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} engines: {node: '>=12'} @@ -1053,6 +1223,15 @@ packages: dev: false optional: true + /@esbuild/linux-x64@0.28.2: + resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@esbuild/netbsd-arm64@0.25.12: resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} engines: {node: '>=18'} @@ -1078,6 +1257,15 @@ packages: dev: false optional: true + /@esbuild/netbsd-arm64@0.28.2: + resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + requiresBuild: true + dev: false + optional: true + /@esbuild/netbsd-x64@0.18.20: resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} engines: {node: '>=12'} @@ -1112,6 +1300,15 @@ packages: dev: false optional: true + /@esbuild/netbsd-x64@0.28.2: + resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: false + optional: true + /@esbuild/openbsd-arm64@0.25.12: resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} engines: {node: '>=18'} @@ -1137,6 +1334,15 @@ packages: dev: false optional: true + /@esbuild/openbsd-arm64@0.28.2: + resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + requiresBuild: true + dev: false + optional: true + /@esbuild/openbsd-x64@0.18.20: resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} engines: {node: '>=12'} @@ -1171,6 +1377,15 @@ packages: dev: false optional: true + /@esbuild/openbsd-x64@0.28.2: + resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: false + optional: true + /@esbuild/openharmony-arm64@0.25.12: resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} engines: {node: '>=18'} @@ -1196,6 +1411,15 @@ packages: dev: false optional: true + /@esbuild/openharmony-arm64@0.28.2: + resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + requiresBuild: true + dev: false + optional: true + /@esbuild/sunos-x64@0.18.20: resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} engines: {node: '>=12'} @@ -1230,6 +1454,15 @@ packages: dev: false optional: true + /@esbuild/sunos-x64@0.28.2: + resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: false + optional: true + /@esbuild/win32-arm64@0.18.20: resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} engines: {node: '>=12'} @@ -1264,6 +1497,15 @@ packages: dev: false optional: true + /@esbuild/win32-arm64@0.28.2: + resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@esbuild/win32-ia32@0.18.20: resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} engines: {node: '>=12'} @@ -1298,6 +1540,15 @@ packages: dev: false optional: true + /@esbuild/win32-ia32@0.28.2: + resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@esbuild/win32-x64@0.18.20: resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} engines: {node: '>=12'} @@ -1332,6 +1583,15 @@ packages: dev: false optional: true + /@esbuild/win32-x64@0.28.2: + resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@floating-ui/core@1.6.9: resolution: {integrity: sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==} dependencies: @@ -1486,13 +1746,26 @@ packages: - supports-color dev: false - /@napi-rs/wasm-runtime@1.1.1: - resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==} + /@napi-rs/lzma-linux-x64-gnu@1.5.1: + resolution: {integrity: sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==} + engines: {node: ^22.20 || ^24.12 || >=25} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@napi-rs/wasm-runtime@1.2.3(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3): + resolution: {integrity: sha512-UMduMbqO5s5zF2NkNacMT/yK5Y5QiKvWr2+50bzIIxFDwVJ2h49b+oyjaCGPhJxd2/gC2x39EHv/gHVuu36x2Q==} + engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0} requiresBuild: true + peerDependencies: + '@emnapi/core': ^1.7.1 || ^2.0.0-alpha.4 + '@emnapi/runtime': ^1.7.1 || ^2.0.0-alpha.4 dependencies: - '@emnapi/core': 1.8.1 - '@emnapi/runtime': 1.8.1 - '@tybys/wasm-util': 0.10.1 + '@emnapi/core': 1.11.3 + '@emnapi/runtime': 1.11.3 + '@tybys/wasm-util': 0.10.3 dev: false optional: true @@ -1560,14 +1833,14 @@ packages: resolution: {integrity: sha512-GBzP8zOc7CGWyFQS6dv1lQz8VVpz5C2yRszbXufwG/9zhStTIH50EtD87NmWbTMwXDvZLNg8GIpb1UFdH93JCA==} dev: false - /@nuxt/devtools-kit@3.1.1(magicast@0.5.1)(vite@7.3.1): + /@nuxt/devtools-kit@3.1.1(magicast@0.5.1)(vite@7.3.6): resolution: {integrity: sha512-sjiKFeDCOy1SyqezSgyV4rYNfQewC64k/GhOsuJgRF+wR2qr6KTVhO6u2B+csKs74KrMrnJprQBgud7ejvOXAQ==} peerDependencies: vite: '>=6.0' dependencies: '@nuxt/kit': 4.2.1(magicast@0.5.1) execa: 8.0.1 - vite: 7.3.1(jiti@2.6.1)(tsx@4.21.0) + vite: 7.3.6(tsx@4.21.0) transitivePeerDependencies: - magicast dev: false @@ -1586,7 +1859,7 @@ packages: semver: 7.7.3 dev: false - /@nuxt/devtools@3.1.1(vite@7.3.1)(vue@3.5.27): + /@nuxt/devtools@3.1.1(vite@7.3.6)(vue@3.5.27): resolution: {integrity: sha512-UG8oKQqcSyzwBe1l0z24zypmwn6FLW/HQMHK/F/gscUU5LeMHzgBhLPD+cuLlDvwlGAbifexWNMsS/I7n95KlA==} hasBin: true peerDependencies: @@ -1596,10 +1869,10 @@ packages: '@vitejs/devtools': optional: true dependencies: - '@nuxt/devtools-kit': 3.1.1(magicast@0.5.1)(vite@7.3.1) + '@nuxt/devtools-kit': 3.1.1(magicast@0.5.1)(vite@7.3.6) '@nuxt/devtools-wizard': 3.1.1 '@nuxt/kit': 4.2.2(magicast@0.5.1) - '@vue/devtools-core': 8.0.5(vite@7.3.1)(vue@3.5.27) + '@vue/devtools-core': 8.0.5(vite@7.3.6)(vue@3.5.27) '@vue/devtools-kit': 8.0.5 birpc: 2.8.0 consola: 3.4.2 @@ -1624,9 +1897,9 @@ packages: sirv: 3.0.2 structured-clone-es: 1.0.0 tinyglobby: 0.2.15 - vite: 7.3.1(jiti@2.6.1)(tsx@4.21.0) - vite-plugin-inspect: 11.3.3(@nuxt/kit@4.2.2)(vite@7.3.1) - vite-plugin-vue-tracer: 1.1.3(vite@7.3.1)(vue@3.5.27) + vite: 7.3.6(tsx@4.21.0) + vite-plugin-inspect: 11.3.3(@nuxt/kit@4.2.2)(vite@7.3.6) + vite-plugin-vue-tracer: 1.1.3(vite@7.3.6)(vue@3.5.27) which: 5.0.0 ws: 8.18.3 transitivePeerDependencies: @@ -1636,17 +1909,17 @@ packages: - vue dev: false - /@nuxt/fonts@0.12.1(vite@7.3.1): + /@nuxt/fonts@0.12.1(vite@7.3.6): resolution: {integrity: sha512-ALajI/HE+uqqL/PWkWwaSUm1IdpyGPbP3mYGy2U1l26/o4lUZBxjFaduMxaZ85jS5yQeJfCu2eEHANYFjAoujQ==} dependencies: - '@nuxt/devtools-kit': 3.1.1(magicast@0.5.1)(vite@7.3.1) + '@nuxt/devtools-kit': 3.1.1(magicast@0.5.1)(vite@7.3.6) '@nuxt/kit': 4.2.1(magicast@0.5.1) consola: 3.4.2 css-tree: 3.1.0 defu: 6.1.4 esbuild: 0.25.12 fontaine: 0.7.0 - fontless: 0.1.0(vite@7.3.1) + fontless: 0.1.0(vite@7.3.6) h3: 1.15.4 jiti: 2.6.1 magic-regexp: 0.10.0 @@ -1768,7 +2041,7 @@ packages: - magicast dev: false - /@nuxt/nitro-server@4.2.2(better-sqlite3@12.6.2)(drizzle-orm@0.45.1)(nuxt@4.2.2)(typescript@5.9.3): + /@nuxt/nitro-server@4.2.2(better-sqlite3@12.11.1)(drizzle-orm@0.45.1)(nuxt@4.2.2)(typescript@5.9.3): resolution: {integrity: sha512-lDITf4n5bHQ6a5MO7pvkpdQbPdWAUgSvztSHCfui/3ioLZsM2XntlN02ue6GSoh3oV9H4xSB3qGa+qlSjgxN0A==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: @@ -1789,8 +2062,8 @@ packages: impound: 1.0.0 klona: 2.0.6 mocked-exports: 0.1.1 - nitropack: 2.13.1(better-sqlite3@12.6.2)(drizzle-orm@0.45.1) - nuxt: 4.2.2(@vue/compiler-sfc@3.5.27)(better-sqlite3@12.6.2)(drizzle-orm@0.45.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1) + nitropack: 2.13.1(better-sqlite3@12.11.1)(drizzle-orm@0.45.1) + nuxt: 4.2.2(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@vue/compiler-sfc@3.5.42)(better-sqlite3@12.11.1)(drizzle-orm@0.45.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.6) pathe: 2.0.3 pkg-types: 2.3.0 radix3: 1.1.2 @@ -1917,7 +2190,7 @@ packages: fake-indexeddb: 6.2.5 get-port-please: 3.2.0 h3: 1.15.4 - h3-next: /h3@2.0.1-rc.11 + h3-next: /h3@2.0.1-rc.29 happy-dom: 18.0.1 local-pkg: 1.1.2 magic-string: 0.30.21 @@ -1940,6 +2213,7 @@ packages: transitivePeerDependencies: - crossws - magicast + - ocache - typescript dev: true @@ -1972,7 +2246,7 @@ packages: magic-string: 0.30.21 mlly: 1.8.0 mocked-exports: 0.1.1 - nuxt: 4.2.2(@vue/compiler-sfc@3.5.27)(better-sqlite3@12.6.2)(drizzle-orm@0.45.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1) + nuxt: 4.2.2(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@vue/compiler-sfc@3.5.42)(better-sqlite3@12.11.1)(drizzle-orm@0.45.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.6) pathe: 2.0.3 pkg-types: 2.3.0 postcss: 8.5.6 @@ -2124,13 +2398,16 @@ packages: dev: false optional: true - /@oxc-minify/binding-wasm32-wasi@0.102.0: + /@oxc-minify/binding-wasm32-wasi@0.102.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3): resolution: {integrity: sha512-52SepCb9e+8cVisGa9S/F14K8PxW0AnbV1j4KEYi8uwfkUIxeDNKRHVHzPoBXNrr0yxW0EHLn/3i8J7a2YCpWw==} engines: {node: '>=14.0.0'} cpu: [wasm32] requiresBuild: true dependencies: - '@napi-rs/wasm-runtime': 1.1.1 + '@napi-rs/wasm-runtime': 1.2.3(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' dev: false optional: true @@ -2260,13 +2537,16 @@ packages: dev: false optional: true - /@oxc-parser/binding-wasm32-wasi@0.102.0: + /@oxc-parser/binding-wasm32-wasi@0.102.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3): resolution: {integrity: sha512-w6HRyArs1PBb9rDsQSHlooe31buUlUI2iY8sBzp62jZ1tmvaJo9EIVTQlRNDkwJmk9DF9uEyIJ82EkZcCZTs9A==} engines: {node: '>=14.0.0'} cpu: [wasm32] requiresBuild: true dependencies: - '@napi-rs/wasm-runtime': 1.1.1 + '@napi-rs/wasm-runtime': 1.2.3(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' dev: false optional: true @@ -2400,13 +2680,16 @@ packages: dev: false optional: true - /@oxc-transform/binding-wasm32-wasi@0.102.0: + /@oxc-transform/binding-wasm32-wasi@0.102.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3): resolution: {integrity: sha512-M6myOXxHty3L2TJEB1NlJPtQm0c0LmivAxcGv/+DSDadOoB/UnOUbjM8W2Utlh5IYS9ARSOjqHtBiPYLWJ15XA==} engines: {node: '>=14.0.0'} cpu: [wasm32] requiresBuild: true dependencies: - '@napi-rs/wasm-runtime': 1.1.1 + '@napi-rs/wasm-runtime': 1.2.3(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' dev: false optional: true @@ -2753,6 +3036,14 @@ packages: dev: false optional: true + /@rollup/rollup-android-arm-eabi@4.63.0: + resolution: {integrity: sha512-70TeIFezKKy65LgAVyQh+w94/gjWhvPWaLaGGeMEgVrPkQhuj/M5bAYYZzIFUj9Y69oHyTm5Um/R6gcLh4A8JA==} + cpu: [arm] + os: [android] + requiresBuild: true + dev: false + optional: true + /@rollup/rollup-android-arm64@4.53.2: resolution: {integrity: sha512-k8FontTxIE7b0/OGKeSN5B6j25EuppBcWM33Z19JoVT7UTXFSo3D9CdU39wGTeb29NO3XxpMNauh09B+Ibw+9g==} cpu: [arm64] @@ -2769,6 +3060,14 @@ packages: dev: false optional: true + /@rollup/rollup-android-arm64@4.63.0: + resolution: {integrity: sha512-YC86tYIHK6M1IV+wbzO+Bxk8RCBr6ZyWYgWxUCzaZD8mc8rrFoIJDNzDrkHBYRc/wKdrsIXmm6/F7NzrAO+OrA==} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: false + optional: true + /@rollup/rollup-darwin-arm64@4.53.2: resolution: {integrity: sha512-A6s4gJpomNBtJ2yioj8bflM2oogDwzUiMl2yNJ2v9E7++sHrSrsQ29fOfn5DM/iCzpWcebNYEdXpaK4tr2RhfQ==} cpu: [arm64] @@ -2785,6 +3084,14 @@ packages: dev: false optional: true + /@rollup/rollup-darwin-arm64@4.63.0: + resolution: {integrity: sha512-oI+ECtUcli0y0fi4xpW82GdPIXdTkI8G8DSjG2LRuw09fPAGykaWYH/hXxiKuTxiAjiPSTIIuYUqof5Z2hShWw==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + /@rollup/rollup-darwin-x64@4.53.2: resolution: {integrity: sha512-e6XqVmXlHrBlG56obu9gDRPW3O3hLxpwHpLsBJvuI8qqnsrtSZ9ERoWUXtPOkY8c78WghyPHZdmPhHLWNdAGEw==} cpu: [x64] @@ -2801,6 +3108,14 @@ packages: dev: false optional: true + /@rollup/rollup-darwin-x64@4.63.0: + resolution: {integrity: sha512-NwV+1s7TiKrMe4owHyKB/dTLD7ZJD0YEBEhIz+hvav1Cu1GReJjF+rsdNwjzENQeIAbE/CoNiaAc5Vz2h5DPAA==} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + /@rollup/rollup-freebsd-arm64@4.53.2: resolution: {integrity: sha512-v0E9lJW8VsrwPux5Qe5CwmH/CF/2mQs6xU1MF3nmUxmZUCHazCjLgYvToOk+YuuUqLQBio1qkkREhxhc656ViA==} cpu: [arm64] @@ -2817,6 +3132,14 @@ packages: dev: false optional: true + /@rollup/rollup-freebsd-arm64@4.63.0: + resolution: {integrity: sha512-tWtHBTu5gOPK4u4Urtk4qAHW3zZ9rQAmbssO8gp7ELvGTGI3aCiq6NqyTQ0PCIg7KbHJF2UkGDDs77YZGxfjCA==} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: false + optional: true + /@rollup/rollup-freebsd-x64@4.53.2: resolution: {integrity: sha512-ClAmAPx3ZCHtp6ysl4XEhWU69GUB1D+s7G9YjHGhIGCSrsg00nEGRRZHmINYxkdoJehde8VIsDC5t9C0gb6yqA==} cpu: [x64] @@ -2833,6 +3156,14 @@ packages: dev: false optional: true + /@rollup/rollup-freebsd-x64@4.63.0: + resolution: {integrity: sha512-2qPoJiwTvtHQ27NnYvTnsgk8laXWYuVmNESG8WFZBcEPKLfZ3I27qBJarjVRQtwGeYyRfq5ZowHXih9lm2BItw==} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: false + optional: true + /@rollup/rollup-linux-arm-gnueabihf@4.53.2: resolution: {integrity: sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg==} cpu: [arm] @@ -2849,6 +3180,14 @@ packages: dev: false optional: true + /@rollup/rollup-linux-arm-gnueabihf@4.63.0: + resolution: {integrity: sha512-FQwsTRvLNuHoTdICABJQfbPUSEueISGmnpT06tXTMpfprf5NiKLSXKA0A+w45wJnCmZAnzgqBwbt6ARFuyOi5w==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@rollup/rollup-linux-arm-musleabihf@4.53.2: resolution: {integrity: sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q==} cpu: [arm] @@ -2865,6 +3204,14 @@ packages: dev: false optional: true + /@rollup/rollup-linux-arm-musleabihf@4.63.0: + resolution: {integrity: sha512-BBVTXziw8mY1a4ZbWME9tZyfzqXCDPqaC7Z3heQ29p5dkvXzwL0NwelO8zLa8c3RBKvl3YTuSnBgsBhYBtwjIw==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@rollup/rollup-linux-arm64-gnu@4.53.2: resolution: {integrity: sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA==} cpu: [arm64] @@ -2881,6 +3228,14 @@ packages: dev: false optional: true + /@rollup/rollup-linux-arm64-gnu@4.63.0: + resolution: {integrity: sha512-w2Iyy9+RqKwx3d9qWMKsJg0FfRBsY0/pXNv0mCQ3ueRvJI6+QAScfD4nrMlzFLs2HNVW6Ew+mtZfDl9b7Ew5/Q==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@rollup/rollup-linux-arm64-musl@4.53.2: resolution: {integrity: sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ==} cpu: [arm64] @@ -2897,6 +3252,14 @@ packages: dev: false optional: true + /@rollup/rollup-linux-arm64-musl@4.63.0: + resolution: {integrity: sha512-YK++KtrFRHYE0P6/RtYEAy9t8F37znP+K03RrIuLPYOL6SVlObRumf/0OE4V/h63xL9DwkWbNssZfmA9hawuDA==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@rollup/rollup-linux-loong64-gnu@4.53.2: resolution: {integrity: sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ==} cpu: [loong64] @@ -2913,6 +3276,14 @@ packages: dev: false optional: true + /@rollup/rollup-linux-loong64-gnu@4.63.0: + resolution: {integrity: sha512-aBfOG6fP7YkkPmTqPwufRJeFyz7WPpECv9XNbnsk9+vg7rxdih0lbtEel7jcRng4LZrrmU3FfitCFyEj4BWDWg==} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@rollup/rollup-linux-loong64-musl@4.55.2: resolution: {integrity: sha512-qSlWiXnVaS/ceqXNfnoFZh4IiCA0EwvCivivTGbEu1qv2o+WTHpn1zNmCTAoOG5QaVr2/yhCoLScQtc/7RxshA==} cpu: [loong64] @@ -2921,6 +3292,14 @@ packages: dev: false optional: true + /@rollup/rollup-linux-loong64-musl@4.63.0: + resolution: {integrity: sha512-LGaHEOeHNAag9VuS1Crs5DFg4RrU9MPi2nVnNJk9DTePx/B6RRYKVmrIXt2h7YOJlwjaFJ6lwtFDliZxScTLrQ==} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@rollup/rollup-linux-ppc64-gnu@4.53.2: resolution: {integrity: sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g==} cpu: [ppc64] @@ -2937,6 +3316,14 @@ packages: dev: false optional: true + /@rollup/rollup-linux-ppc64-gnu@4.63.0: + resolution: {integrity: sha512-jClvk+J0FC3b7Udvegiw5/4hErbHtmsNsQgENnKXDWtNCJXsJYZH5WURvu7imDOO38xYml24eeh5x3A04ppwCw==} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@rollup/rollup-linux-ppc64-musl@4.55.2: resolution: {integrity: sha512-g+0ZLMook31iWV4PvqKU0i9E78gaZgYpSrYPed/4Bu+nGTgfOPtfs1h11tSSRPXSjC5EzLTjV/1A7L2Vr8pJoQ==} cpu: [ppc64] @@ -2945,6 +3332,14 @@ packages: dev: false optional: true + /@rollup/rollup-linux-ppc64-musl@4.63.0: + resolution: {integrity: sha512-0OJlaGK+8+B777Ql5okIpD7ua5Ro9+VB9Ve0OKa28OQJZ1RbuUBVNHK/e3pr4BROqsyPl1JrPO1ZxJseCNffcA==} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@rollup/rollup-linux-riscv64-gnu@4.53.2: resolution: {integrity: sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA==} cpu: [riscv64] @@ -2961,6 +3356,14 @@ packages: dev: false optional: true + /@rollup/rollup-linux-riscv64-gnu@4.63.0: + resolution: {integrity: sha512-Ygsx+HoNH7afwi1bTIXbnTvVnsO+zurPLSYxybV1hHFVU72OWOCl6v05ql/z0hkpAPx+DK7Kn9Bi7MayCcjLTA==} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@rollup/rollup-linux-riscv64-musl@4.53.2: resolution: {integrity: sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ==} cpu: [riscv64] @@ -2977,6 +3380,14 @@ packages: dev: false optional: true + /@rollup/rollup-linux-riscv64-musl@4.63.0: + resolution: {integrity: sha512-pDQxtMGb+OvG3fLwR2OkZlSd47hW+kWg4BYMG/++sR6RqorQccwPTDsxda5hPwiIeIErAnCF9ma3SAU06bdQtQ==} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@rollup/rollup-linux-s390x-gnu@4.53.2: resolution: {integrity: sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w==} cpu: [s390x] @@ -2993,6 +3404,14 @@ packages: dev: false optional: true + /@rollup/rollup-linux-s390x-gnu@4.63.0: + resolution: {integrity: sha512-0BnUG9mS8I4SSHr3XsxVhuCMEiu+rX61xxZF5vujso4LaiAGFZFxvDjg6Xn6tLPNTUAfuCvQYas4LMQMVsKRSQ==} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@rollup/rollup-linux-x64-gnu@4.53.2: resolution: {integrity: sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw==} cpu: [x64] @@ -3009,6 +3428,14 @@ packages: dev: false optional: true + /@rollup/rollup-linux-x64-gnu@4.63.0: + resolution: {integrity: sha512-Adu/VttB1dpPNW+FEacrZ+xVm9tFty84+RrFzsqlFaPxoJB+9XXyDGtp5dCOoBwGBIEVH0To7lExFXEx0BIF4A==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@rollup/rollup-linux-x64-musl@4.53.2: resolution: {integrity: sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA==} cpu: [x64] @@ -3025,6 +3452,14 @@ packages: dev: false optional: true + /@rollup/rollup-linux-x64-musl@4.63.0: + resolution: {integrity: sha512-NQ3bDvjUbFKmP23671xUlXtKmqVsUBd6M4PQCvbmNtOy06hnQIdKHy8oG/6S3R/S6He1JgPk6A5VT+prAJMYEw==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@rollup/rollup-openbsd-x64@4.55.2: resolution: {integrity: sha512-cT2MmXySMo58ENv8p6/O6wI/h/gLnD3D6JoajwXFZH6X9jz4hARqUhWpGuQhOgLNXscfZYRQMJvZDtWNzMAIDw==} cpu: [x64] @@ -3033,6 +3468,14 @@ packages: dev: false optional: true + /@rollup/rollup-openbsd-x64@4.63.0: + resolution: {integrity: sha512-u2eDAl4+0aFvA13GxlGBtTI3SS3sdgwgtV0HyjZ0QaQVCgNE+jqNGey+GtxWiq+wxr/UycAx/OnfJzApCFamvA==} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: false + optional: true + /@rollup/rollup-openharmony-arm64@4.53.2: resolution: {integrity: sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A==} cpu: [arm64] @@ -3049,6 +3492,14 @@ packages: dev: false optional: true + /@rollup/rollup-openharmony-arm64@4.63.0: + resolution: {integrity: sha512-XvRb5vfW3wAZQ+ZUG21AnHHDKtNcw99eigzEhjr//NZ3u7SoBaPP0seSc7FgP7p1epAEdAoZckMW9WY/+4w70w==} + cpu: [arm64] + os: [openharmony] + requiresBuild: true + dev: false + optional: true + /@rollup/rollup-win32-arm64-msvc@4.53.2: resolution: {integrity: sha512-IlbHFYc/pQCgew/d5fslcy1KEaYVCJ44G8pajugd8VoOEI8ODhtb/j8XMhLpwHCMB3yk2J07ctup10gpw2nyMA==} cpu: [arm64] @@ -3065,6 +3516,14 @@ packages: dev: false optional: true + /@rollup/rollup-win32-arm64-msvc@4.63.0: + resolution: {integrity: sha512-iZPmniy4kNBf5yo2RezbkYNNK5HPbXE9+g+twnbqSng7dtLEJy1SKoxiE/ni4FDacjyuZpEeb9U054N4EoKHYw==} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@rollup/rollup-win32-ia32-msvc@4.53.2: resolution: {integrity: sha512-lNlPEGgdUfSzdCWU176ku/dQRnA7W+Gp8d+cWv73jYrb8uT7HTVVxq62DUYxjbaByuf1Yk0RIIAbDzp+CnOTFg==} cpu: [ia32] @@ -3081,6 +3540,14 @@ packages: dev: false optional: true + /@rollup/rollup-win32-ia32-msvc@4.63.0: + resolution: {integrity: sha512-mFBBd+LF37fnE8JnYUOH+imj0aPFPK30vpar4ehJkgnLj9sZn8ZxiRENmLtgIwxK7TC8klF6N57fxdNBwQoqOA==} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@rollup/rollup-win32-x64-gnu@4.53.2: resolution: {integrity: sha512-S6YojNVrHybQis2lYov1sd+uj7K0Q05NxHcGktuMMdIQ2VixGwAfbJ23NnlvvVV1bdpR2m5MsNBViHJKcA4ADw==} cpu: [x64] @@ -3097,6 +3564,14 @@ packages: dev: false optional: true + /@rollup/rollup-win32-x64-gnu@4.63.0: + resolution: {integrity: sha512-ujeqEY3B+zbGn3Z4Q03cUBG/LGWnBJncVT36WER31LcOsQk9+1dmINKKtvmmfChUvRbK1G0R8OhMWFgHgaZtAw==} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@rollup/rollup-win32-x64-msvc@4.53.2: resolution: {integrity: sha512-k+/Rkcyx//P6fetPoLMb8pBeqJBNGx81uuf7iljX9++yNBVRDQgD04L+SVXmXmh5ZP4/WOp4mWF0kmi06PW2tA==} cpu: [x64] @@ -3113,6 +3588,14 @@ packages: dev: false optional: true + /@rollup/rollup-win32-x64-msvc@4.63.0: + resolution: {integrity: sha512-hncn90N4sOky0L2LKE5oESKLbxCPeVo4eLA2LSMoDzM+879ml4WSr+Rr4DWknNIVVvS1Hirkc9hx02W6YxS8rQ==} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@sindresorhus/is@7.2.0: resolution: {integrity: sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==} engines: {node: '>=18'} @@ -3277,7 +3760,7 @@ packages: '@tailwindcss/oxide-win32-x64-msvc': 4.1.17 dev: false - /@tailwindcss/vite@4.1.17(vite@7.3.1): + /@tailwindcss/vite@4.1.17(vite@7.3.6): resolution: {integrity: sha512-4+9w8ZHOiGnpcGI6z1TVVfWaX/koK7fKeSYF3qlYg2xpBtbteP2ddBxiarL+HVgfSJGeK5RIxRQmKm4rTJJAwA==} peerDependencies: vite: ^5.2.0 || ^6 || ^7 @@ -3285,7 +3768,7 @@ packages: '@tailwindcss/node': 4.1.17 '@tailwindcss/oxide': 4.1.17 tailwindcss: 4.1.17 - vite: 7.3.1(jiti@2.6.1)(tsx@4.21.0) + vite: 7.3.6(tsx@4.21.0) dev: false /@tanstack/table-core@8.21.3: @@ -3316,8 +3799,8 @@ packages: vue: 3.5.27(typescript@5.9.3) dev: false - /@tybys/wasm-util@0.10.1: - resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + /@tybys/wasm-util@0.10.3: + resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} requiresBuild: true dependencies: tslib: 2.8.1 @@ -3342,6 +3825,10 @@ packages: /@types/estree@1.0.8: resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + /@types/estree@1.0.9: + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + dev: false + /@types/node@20.19.1: resolution: {integrity: sha512-jJD50LtlD2dodAEO653i3YF04NWak6jN3ky+Ri3Em3mGR39/glWiboM/IePaRbgwSfqM1TpGXfAg8ohn/4dTgA==} dependencies: @@ -3357,6 +3844,13 @@ packages: resolution: {integrity: sha512-LriObC2+KYZD3FzCrgWGv/qufdUy4eXrxcLgQMfYXgPbLIecKIsVBaQgUPmxSSLcjmYbDTQbMgr6qr6l/eb7Bg==} dev: false + /@types/pg@8.23.1: + resolution: {integrity: sha512-fKVHpikPdg4GKks3JuLEhvwSyvwzF23hnabPy6DD8ljVbC7+6J5dQzdv4arV6jqq57djnMgs1HKBxX4P8aBI3A==} + dependencies: + '@types/node': 22.14.0 + pg-protocol: 1.13.0 + pg-types: 2.2.0 + /@types/resolve@1.20.2: resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} dev: false @@ -3572,12 +4066,29 @@ packages: estree-walker: 2.0.2 source-map-js: 1.2.1 + /@vue/compiler-core@3.5.42: + resolution: {integrity: sha512-2Ye1ilMtKXxl8qZUrQ5j0CdgenFp/HFQmta6rfRyfEsTG69L6Wk+tWuNoHYHMx9E8tF2Slvdg1FuwDvAXdy1LQ==} + dependencies: + '@babel/parser': 7.29.8 + '@vue/shared': 3.5.42 + entities: 7.0.1 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + dev: false + /@vue/compiler-dom@3.5.27: resolution: {integrity: sha512-oAFea8dZgCtVVVTEC7fv3T5CbZW9BxpFzGGxC79xakTr6ooeEqmRuvQydIiDAkglZEAd09LgVf1RoDnL54fu5w==} dependencies: '@vue/compiler-core': 3.5.27 '@vue/shared': 3.5.27 + /@vue/compiler-dom@3.5.42: + resolution: {integrity: sha512-qbhQZEFmycr+ni/qyuccS4sucNN7VAbDfbkvNxWOX2VfgFm90MNs3/UhRNKoPMEIVn0F8gdlYjLPvqxHwHeQOA==} + dependencies: + '@vue/compiler-core': 3.5.42 + '@vue/shared': 3.5.42 + dev: false + /@vue/compiler-sfc@3.5.27: resolution: {integrity: sha512-sHZu9QyDPeDmN/MRoshhggVOWE5WlGFStKFwu8G52swATgSny27hJRWteKDSUUzUH+wp+bmeNbhJnEAel/auUQ==} dependencies: @@ -3591,17 +4102,38 @@ packages: postcss: 8.5.6 source-map-js: 1.2.1 + /@vue/compiler-sfc@3.5.42: + resolution: {integrity: sha512-fkCAFB4okcAANGMThboWnScp/gzWjU0ZSkVnjTIiplmMDq2uq0tIB3j+xVu4rhv5rvOgBySCysudmbMd6xRRqw==} + dependencies: + '@babel/parser': 7.29.8 + '@vue/compiler-core': 3.5.42 + '@vue/compiler-dom': 3.5.42 + '@vue/compiler-ssr': 3.5.42 + '@vue/shared': 3.5.42 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.26 + source-map-js: 1.2.1 + dev: false + /@vue/compiler-ssr@3.5.27: resolution: {integrity: sha512-Sj7h+JHt512fV1cTxKlYhg7qxBvack+BGncSpH+8vnN+KN95iPIcqB5rsbblX40XorP+ilO7VIKlkuu3Xq2vjw==} dependencies: '@vue/compiler-dom': 3.5.27 '@vue/shared': 3.5.27 + /@vue/compiler-ssr@3.5.42: + resolution: {integrity: sha512-xmLk3wLkbizPAiLyomjgFFosf2ys9b5Ghb+oh/k2tnvipNz8OFrQOiTcWCzyK7MpBp9KkyGtfvgfLUivbmuGYA==} + dependencies: + '@vue/compiler-dom': 3.5.42 + '@vue/shared': 3.5.42 + dev: false + /@vue/devtools-api@6.6.4: resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==} dev: false - /@vue/devtools-core@8.0.5(vite@7.3.1)(vue@3.5.27): + /@vue/devtools-core@8.0.5(vite@7.3.6)(vue@3.5.27): resolution: {integrity: sha512-dpCw8nl0GDBuiL9SaY0mtDxoGIEmU38w+TQiYEPOLhW03VDC0lfNMYXS/qhl4I0YlysGp04NLY4UNn6xgD0VIQ==} peerDependencies: vue: ^3.0.0 @@ -3611,7 +4143,7 @@ packages: mitt: 3.0.1 nanoid: 5.1.5 pathe: 2.0.3 - vite-hot-client: 2.1.0(vite@7.3.1) + vite-hot-client: 2.1.0(vite@7.3.6) vue: 3.5.27(typescript@5.9.3) transitivePeerDependencies: - vite @@ -3682,6 +4214,10 @@ packages: /@vue/shared@3.5.27: resolution: {integrity: sha512-dXr/3CgqXsJkZ0n9F3I4elY8wM9jMJpP3pvRG52r6m0tu/MsAFIe6JpXVGeNMd/D9F4hQynWT8Rfuj0bdm9kFQ==} + /@vue/shared@3.5.42: + resolution: {integrity: sha512-2rPxex1jQf4jvl9MOHl6YaXCPcrNqz/FstMOEh3QWY+/OME9nQTvl9WYeCwhW7AFjaR0SnngZGlp/wkR6rkI6g==} + dev: false + /@vue/test-utils@2.4.6: resolution: {integrity: sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==} dependencies: @@ -3916,9 +4452,9 @@ packages: hasBin: true dev: false - /better-sqlite3@12.6.2: - resolution: {integrity: sha512-8VYKM3MjCa9WcaSAI3hzwhmyHVlH8tiGFwf0RlTsZPWJ1I5MkzjiudCo4KC4DxOaL/53A5B1sI/IbldNFDbsKA==} - engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x} + /better-sqlite3@12.11.1: + resolution: {integrity: sha512-dq9AtApgg5PGFtBzPFSBl3HZQjHok5gaQCM6zh2Yk0aSmDCs1CbnVI8/HgASQkNKsWFpseIO9beg5xxpYhbIfA==} + engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x || 26.x} requiresBuild: true dependencies: bindings: 1.5.0 @@ -4397,11 +4933,11 @@ packages: /csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} - /dayjs@1.11.19: - resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==} + /dayjs@1.11.23: + resolution: {integrity: sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==} dev: false - /db0@0.3.4(better-sqlite3@12.6.2)(drizzle-orm@0.45.1): + /db0@0.3.4(better-sqlite3@12.11.1)(drizzle-orm@0.45.1): resolution: {integrity: sha512-RiXXi4WaNzPTHEOu8UPQKMooIbqOEyqA1t7Z6MsdxSCeb8iUC9ko3LcmsLmeUt2SM5bctfArZKkRQggKZz7JNw==} peerDependencies: '@electric-sql/pglite': '*' @@ -4424,8 +4960,8 @@ packages: sqlite3: optional: true dependencies: - better-sqlite3: 12.6.2 - drizzle-orm: 0.45.1(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(pg@8.20.0) + better-sqlite3: 12.11.1 + drizzle-orm: 0.45.1(@types/better-sqlite3@7.6.13)(@types/pg@8.23.1)(better-sqlite3@12.11.1)(pg@8.23.0) dev: false /debug@4.4.1: @@ -4591,7 +5127,7 @@ packages: - supports-color dev: true - /drizzle-orm@0.45.1(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(pg@8.20.0): + /drizzle-orm@0.45.1(@types/better-sqlite3@7.6.13)(@types/pg@8.23.1)(better-sqlite3@12.11.1)(pg@8.23.0): resolution: {integrity: sha512-Te0FOdKIistGNPMq2jscdqngBRfBpC8uMFVwqjf6gtTVJHIQ/dosgV/CLBU2N4ZJBsXL5savCba9b0YJskKdcA==} peerDependencies: '@aws-sdk/client-rds-data': '>=3' @@ -4684,8 +5220,9 @@ packages: optional: true dependencies: '@types/better-sqlite3': 7.6.13 - better-sqlite3: 12.6.2 - pg: 8.20.0 + '@types/pg': 8.23.1 + better-sqlite3: 12.11.1 + pg: 8.23.0 dev: false /duplexer@0.1.2: @@ -4748,6 +5285,11 @@ packages: resolution: {integrity: sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ==} engines: {node: '>=0.12'} + /entities@7.0.1: + resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} + engines: {node: '>=0.12'} + dev: false + /error-stack-parser-es@1.0.5: resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} dev: false @@ -4904,6 +5446,40 @@ packages: '@esbuild/win32-x64': 0.27.2 dev: false + /esbuild@0.28.2: + resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==} + engines: {node: '>=18'} + hasBin: true + requiresBuild: true + 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 + dev: false + /escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -5022,6 +5598,18 @@ packages: dependencies: picomatch: 4.0.3 + /fdir@6.5.0(picomatch@4.0.7): + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + dependencies: + picomatch: 4.0.7 + dev: false + /file-uri-to-path@1.0.0: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} dev: false @@ -5060,7 +5648,7 @@ packages: unicode-trie: 2.0.0 dev: false - /fontless@0.1.0(vite@7.3.1): + /fontless@0.1.0(vite@7.3.6): resolution: {integrity: sha512-KyvRd732HuVd/XP9iEFTb1w8Q01TPSA5GaCJV9HYmPiEs/ZZg/on2YdrQmlKfi9gDGpmN5Bn27Ze/CHqk0vE+w==} engines: {node: '>=18.12.0'} peerDependencies: @@ -5082,7 +5670,7 @@ packages: ufo: 1.6.1 unifont: 0.6.0 unstorage: 1.17.2 - vite: 7.3.1(jiti@2.6.1)(tsx@4.21.0) + vite: 7.3.6(tsx@4.21.0) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -5276,17 +5864,21 @@ packages: uncrypto: 0.1.3 dev: false - /h3@2.0.1-rc.11: - resolution: {integrity: sha512-2myzjCqy32c1As9TjZW9fNZXtLqNedjFSrdFy2AjFBQQ3LzrnGoDdFDYfC0tV2e4vcyfJ2Sfo/F6NQhO2Ly/Mw==} + /h3@2.0.1-rc.29: + resolution: {integrity: sha512-woEUYLvpUa9SzbBGOYn3GfsK/EU7JhoUeNc49B2an6mcO1aqg7P8VvWVUfa5Vkb8FxBh9ZAkU+/rJ5m5pTULUA==} engines: {node: '>=20.11.1'} + hasBin: true peerDependencies: - crossws: ^0.4.1 + crossws: ^0.4.12 + ocache: '>=0.3.0' peerDependenciesMeta: crossws: optional: true + ocache: + optional: true dependencies: - rou3: 0.7.12 - srvx: 0.10.1 + rou3: 0.9.2 + srvx: 0.12.7 dev: true /happy-dom@18.0.1: @@ -5972,6 +6564,12 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + /nanoid@3.3.18: + resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: false + /nanoid@5.1.5: resolution: {integrity: sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==} engines: {node: ^18 || >=20} @@ -5990,7 +6588,7 @@ packages: resolution: {integrity: sha512-h/4nMGsHjZDCYmQVNODIrYACVJ+I9KItbG+0si6W/jSjdA9JbWDoU4LLeMXVcEQGHjttI2tuXqDrbGF7qkUHHg==} dev: false - /nitropack@2.13.1(better-sqlite3@12.6.2)(drizzle-orm@0.45.1): + /nitropack@2.13.1(better-sqlite3@12.11.1)(drizzle-orm@0.45.1): resolution: {integrity: sha512-2dDj89C4wC2uzG7guF3CnyG+zwkZosPEp7FFBGHB3AJo11AywOolWhyQJFHDzve8COvGxJaqscye9wW2IrUsNw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -6019,7 +6617,7 @@ packages: cookie-es: 2.0.0 croner: 9.1.0 crossws: 0.3.5 - db0: 0.3.4(better-sqlite3@12.6.2)(drizzle-orm@0.45.1) + db0: 0.3.4(better-sqlite3@12.11.1)(drizzle-orm@0.45.1) defu: 6.1.4 destr: 2.0.5 dot-prop: 10.1.0 @@ -6186,7 +6784,7 @@ packages: boolbase: 1.0.0 dev: false - /nuxt@4.2.2(@vue/compiler-sfc@3.5.27)(better-sqlite3@12.6.2)(drizzle-orm@0.45.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1): + /nuxt@4.2.2(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@vue/compiler-sfc@3.5.42)(better-sqlite3@12.11.1)(drizzle-orm@0.45.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.6): resolution: {integrity: sha512-n6oYFikgLEb70J4+K19jAzfx4exZcRSRX7yZn09P5qlf2Z59VNOBqNmaZO5ObzvyGUZ308SZfL629/Q2v2FVjw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -6201,9 +6799,9 @@ packages: dependencies: '@dxup/nuxt': 0.2.2 '@nuxt/cli': 3.32.0 - '@nuxt/devtools': 3.1.1(vite@7.3.1)(vue@3.5.27) + '@nuxt/devtools': 3.1.1(vite@7.3.6)(vue@3.5.27) '@nuxt/kit': 4.2.2(magicast@0.5.1) - '@nuxt/nitro-server': 4.2.2(better-sqlite3@12.6.2)(drizzle-orm@0.45.1)(nuxt@4.2.2)(typescript@5.9.3) + '@nuxt/nitro-server': 4.2.2(better-sqlite3@12.11.1)(drizzle-orm@0.45.1)(nuxt@4.2.2)(typescript@5.9.3) '@nuxt/schema': 4.2.2 '@nuxt/telemetry': 2.6.6 '@nuxt/vite-builder': 4.2.2(nuxt@4.2.2)(tsx@4.21.0)(typescript@5.9.3)(vue@3.5.27) @@ -6234,9 +6832,9 @@ packages: ofetch: 1.5.1 ohash: 2.0.11 on-change: 6.0.1 - oxc-minify: 0.102.0 - oxc-parser: 0.102.0 - oxc-transform: 0.102.0 + oxc-minify: 0.102.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) + oxc-parser: 0.102.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) + oxc-transform: 0.102.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) oxc-walker: 0.6.0(oxc-parser@0.102.0) pathe: 2.0.3 perfect-debounce: 2.0.0 @@ -6252,7 +6850,7 @@ packages: unctx: 2.4.1 unimport: 5.5.0 unplugin: 2.3.11 - unplugin-vue-router: 0.19.2(@vue/compiler-sfc@3.5.27)(vue-router@4.6.4)(vue@3.5.27) + unplugin-vue-router: 0.19.2(@vue/compiler-sfc@3.5.42)(vue-router@4.6.4)(vue@3.5.27) untyped: 2.0.0 vue: 3.5.27(typescript@5.9.3) vue-router: 4.6.4(vue@3.5.27) @@ -6267,6 +6865,8 @@ packages: - '@capacitor/preferences' - '@deno/kv' - '@electric-sql/pglite' + - '@emnapi/core' + - '@emnapi/runtime' - '@libsql/client' - '@netlify/blobs' - '@planetscale/database' @@ -6397,7 +6997,7 @@ packages: is-wsl: 2.2.0 dev: false - /oxc-minify@0.102.0: + /oxc-minify@0.102.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3): resolution: {integrity: sha512-FphAHDyTCNepQbiQTSyWFMbNc9zdUmj1WBsoLwvZhWm7rEe/IeIKYKRhy75lWOjwFsi5/i4Qucq43hgs3n2Exw==} engines: {node: ^20.19.0 || >=22.12.0} optionalDependencies: @@ -6413,12 +7013,15 @@ packages: '@oxc-minify/binding-linux-x64-gnu': 0.102.0 '@oxc-minify/binding-linux-x64-musl': 0.102.0 '@oxc-minify/binding-openharmony-arm64': 0.102.0 - '@oxc-minify/binding-wasm32-wasi': 0.102.0 + '@oxc-minify/binding-wasm32-wasi': 0.102.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) '@oxc-minify/binding-win32-arm64-msvc': 0.102.0 '@oxc-minify/binding-win32-x64-msvc': 0.102.0 + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' dev: false - /oxc-parser@0.102.0: + /oxc-parser@0.102.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3): resolution: {integrity: sha512-xMiyHgr2FZsphQ12ZCsXRvSYzmKXCm1ejmyG4GDZIiKOmhyt5iKtWq0klOfFsEQ6jcgbwrUdwcCVYzr1F+h5og==} engines: {node: ^20.19.0 || >=22.12.0} dependencies: @@ -6436,12 +7039,15 @@ packages: '@oxc-parser/binding-linux-x64-gnu': 0.102.0 '@oxc-parser/binding-linux-x64-musl': 0.102.0 '@oxc-parser/binding-openharmony-arm64': 0.102.0 - '@oxc-parser/binding-wasm32-wasi': 0.102.0 + '@oxc-parser/binding-wasm32-wasi': 0.102.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) '@oxc-parser/binding-win32-arm64-msvc': 0.102.0 '@oxc-parser/binding-win32-x64-msvc': 0.102.0 + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' dev: false - /oxc-transform@0.102.0: + /oxc-transform@0.102.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3): resolution: {integrity: sha512-MR5ohiBS6/kvxRpmUZ3LIDTTJBEC4xLAEZXfYr7vrA0eP7WHewQaNQPFDgT4Bee89TdmVQ5ZKrifGwxLjSyHHw==} engines: {node: ^20.19.0 || >=22.12.0} optionalDependencies: @@ -6457,9 +7063,12 @@ packages: '@oxc-transform/binding-linux-x64-gnu': 0.102.0 '@oxc-transform/binding-linux-x64-musl': 0.102.0 '@oxc-transform/binding-openharmony-arm64': 0.102.0 - '@oxc-transform/binding-wasm32-wasi': 0.102.0 + '@oxc-transform/binding-wasm32-wasi': 0.102.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) '@oxc-transform/binding-win32-arm64-msvc': 0.102.0 '@oxc-transform/binding-win32-x64-msvc': 0.102.0 + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' dev: false /oxc-walker@0.6.0(oxc-parser@0.102.0): @@ -6468,7 +7077,7 @@ packages: oxc-parser: '>=0.98.0' dependencies: magic-regexp: 0.10.0 - oxc-parser: 0.102.0 + oxc-parser: 0.102.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) dev: false /package-json-from-dist@1.0.1: @@ -6548,31 +7157,33 @@ packages: /perfect-debounce@2.0.0: resolution: {integrity: sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow==} - /pg-cloudflare@1.3.0: - resolution: {integrity: sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==} + /pg-cloudflare@1.4.0: + resolution: {integrity: sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==} requiresBuild: true dev: false optional: true - /pg-connection-string@2.12.0: - resolution: {integrity: sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==} + /pg-connection-string@2.14.0: + resolution: {integrity: sha512-XwWDGcLRGCXAR8F/AM5bG7Q+A3Wm2s6QeEjlOKZLlH3UYcguiqCWKyWXVag5TLTIjR7oOJUY8kcADaZgWPyLeg==} dev: false /pg-int8@1.0.1: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} engines: {node: '>=4.0.0'} - dev: false - /pg-pool@3.13.0(pg@8.20.0): - resolution: {integrity: sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==} + /pg-pool@3.14.0(pg@8.23.0): + resolution: {integrity: sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==} peerDependencies: pg: '>=8.0' dependencies: - pg: 8.20.0 + pg: 8.23.0 dev: false /pg-protocol@1.13.0: resolution: {integrity: sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==} + + /pg-protocol@1.16.0: + resolution: {integrity: sha512-sILXutLVjCLjcDuOmvhX5e2Z4cS5qG/6Bu3VkpFwdf/633ElGLpEh9bgmuI5I4sqKqkifQiGyiCcx1HdtrK7tg==} dev: false /pg-types@2.2.0: @@ -6584,10 +7195,9 @@ packages: postgres-bytea: 1.0.1 postgres-date: 1.0.7 postgres-interval: 1.2.0 - dev: false - /pg@8.20.0: - resolution: {integrity: sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==} + /pg@8.23.0: + resolution: {integrity: sha512-Ip2EQCngowJLGOfCwkFhPXU7/ljlhn6Rxlmy4XYfL2Y+vyRM59+8uR2xqRWKdYmbXmxCFOAmKxBuSUCdF34qLg==} engines: {node: '>= 16.0.0'} peerDependencies: pg-native: '>=3.0.1' @@ -6595,13 +7205,13 @@ packages: pg-native: optional: true dependencies: - pg-connection-string: 2.12.0 - pg-pool: 3.13.0(pg@8.20.0) - pg-protocol: 1.13.0 + pg-connection-string: 2.14.0 + pg-pool: 3.14.0(pg@8.23.0) + pg-protocol: 1.16.0 pg-types: 2.2.0 pgpass: 1.0.5 optionalDependencies: - pg-cloudflare: 1.3.0 + pg-cloudflare: 1.4.0 dev: false /pgpass@1.0.5: @@ -6622,6 +7232,11 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + /picomatch@4.0.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} + engines: {node: '>=12'} + dev: false + /pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} dependencies: @@ -6938,6 +7553,15 @@ packages: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} dev: false + /postcss@8.5.26: + resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.18 + picocolors: 1.1.1 + source-map-js: 1.2.1 + dev: false + /postcss@8.5.6: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} @@ -6949,24 +7573,20 @@ packages: /postgres-array@2.0.0: resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} engines: {node: '>=4'} - dev: false /postgres-bytea@1.0.1: resolution: {integrity: sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==} engines: {node: '>=0.10.0'} - dev: false /postgres-date@1.0.7: resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} engines: {node: '>=0.10.0'} - dev: false /postgres-interval@1.2.0: resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} engines: {node: '>=0.10.0'} dependencies: xtend: 4.0.2 - dev: false /prebuild-install@7.1.3: resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} @@ -7275,8 +7895,44 @@ packages: fsevents: 2.3.3 dev: false - /rou3@0.7.12: - resolution: {integrity: sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==} + /rollup@4.63.0: + resolution: {integrity: sha512-T5vnZ2y4QqC3/4P+w2+JO+Q/OVdnPsv4XcSYJYMEn0R9/jjl5AgLwO9LAZMzP2lN71O6pypn91rB7lDstUkfrQ==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + dependencies: + '@types/estree': 1.0.9 + optionalDependencies: + '@napi-rs/lzma-linux-x64-gnu': 1.5.1 + '@rollup/rollup-android-arm-eabi': 4.63.0 + '@rollup/rollup-android-arm64': 4.63.0 + '@rollup/rollup-darwin-arm64': 4.63.0 + '@rollup/rollup-darwin-x64': 4.63.0 + '@rollup/rollup-freebsd-arm64': 4.63.0 + '@rollup/rollup-freebsd-x64': 4.63.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.63.0 + '@rollup/rollup-linux-arm-musleabihf': 4.63.0 + '@rollup/rollup-linux-arm64-gnu': 4.63.0 + '@rollup/rollup-linux-arm64-musl': 4.63.0 + '@rollup/rollup-linux-loong64-gnu': 4.63.0 + '@rollup/rollup-linux-loong64-musl': 4.63.0 + '@rollup/rollup-linux-ppc64-gnu': 4.63.0 + '@rollup/rollup-linux-ppc64-musl': 4.63.0 + '@rollup/rollup-linux-riscv64-gnu': 4.63.0 + '@rollup/rollup-linux-riscv64-musl': 4.63.0 + '@rollup/rollup-linux-s390x-gnu': 4.63.0 + '@rollup/rollup-linux-x64-gnu': 4.63.0 + '@rollup/rollup-linux-x64-musl': 4.63.0 + '@rollup/rollup-openbsd-x64': 4.63.0 + '@rollup/rollup-openharmony-arm64': 4.63.0 + '@rollup/rollup-win32-arm64-msvc': 4.63.0 + '@rollup/rollup-win32-ia32-msvc': 4.63.0 + '@rollup/rollup-win32-x64-gnu': 4.63.0 + '@rollup/rollup-win32-x64-msvc': 4.63.0 + fsevents: 2.3.3 + dev: false + + /rou3@0.9.2: + resolution: {integrity: sha512-3SOzvaAg8rkHrXtRjpCvCvbyO5to9oOO27Z/XqHEYXfMRVSw/qMIVdmaOk9W2lcRLtR6dlqTjo9hDeJk70QBYQ==} dev: true /run-applescript@7.0.0: @@ -7471,6 +8127,13 @@ packages: resolution: {integrity: sha512-A//xtfak4eESMWWydSRFUVvCTQbSwivnGCEf8YGPe2eHU0+Z6znfUTCPF0a7oV3sObSOcrXHlL6Bs9vVctfXdg==} engines: {node: '>=20.16.0'} hasBin: true + dev: false + + /srvx@0.12.7: + resolution: {integrity: sha512-PIaq1pDGg3EU2OGSN3pzRfYVu9MJDzLdojlGN6E3lvhAdxqn/lNMJFMA1xGA38jYkBb0V9xw02QEVVMDb0PExA==} + engines: {node: '>=20.16.0'} + hasBin: true + dev: true /stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -7711,6 +8374,14 @@ packages: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 + /tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + dependencies: + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 + dev: false + /tinypool@1.1.1: resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} engines: {node: ^18.0.0 || >=20.0.0} @@ -7922,7 +8593,7 @@ packages: picomatch: 4.0.3 dev: false - /unplugin-vue-router@0.19.2(@vue/compiler-sfc@3.5.27)(vue-router@4.6.4)(vue@3.5.27): + /unplugin-vue-router@0.19.2(@vue/compiler-sfc@3.5.42)(vue-router@4.6.4)(vue@3.5.27): resolution: {integrity: sha512-u5dgLBarxE5cyDK/hzJGfpCTLIAyiTXGlo85COuD4Nssj6G7NxS+i9mhCWz/1p/ud1eMwdcUbTXehQe41jYZUA==} peerDependencies: '@vue/compiler-sfc': ^3.5.17 @@ -7933,7 +8604,7 @@ packages: dependencies: '@babel/generator': 7.28.6 '@vue-macros/common': 3.1.2(vue@3.5.27) - '@vue/compiler-sfc': 3.5.27 + '@vue/compiler-sfc': 3.5.42 '@vue/language-core': 3.2.2 ast-walker-scope: 0.8.3 chokidar: 5.0.0 @@ -8109,7 +8780,7 @@ packages: dependencies: anymatch: 3.1.3 chokidar: 5.0.0 - db0: 0.3.4(better-sqlite3@12.6.2)(drizzle-orm@0.45.1) + db0: 0.3.4(better-sqlite3@12.11.1)(drizzle-orm@0.45.1) destr: 2.0.5 h3: 1.15.5 ioredis: 5.9.2 @@ -8168,22 +8839,22 @@ packages: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} dev: false - /vite-dev-rpc@1.1.0(vite@7.3.1): + /vite-dev-rpc@1.1.0(vite@7.3.6): resolution: {integrity: sha512-pKXZlgoXGoE8sEKiKJSng4hI1sQ4wi5YT24FCrwrLt6opmkjlqPPVmiPWWJn8M8byMxRGzp1CrFuqQs4M/Z39A==} peerDependencies: vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.1 || ^7.0.0-0 dependencies: birpc: 2.8.0 - vite: 7.3.1(jiti@2.6.1)(tsx@4.21.0) - vite-hot-client: 2.1.0(vite@7.3.1) + vite: 7.3.6(tsx@4.21.0) + vite-hot-client: 2.1.0(vite@7.3.6) dev: false - /vite-hot-client@2.1.0(vite@7.3.1): + /vite-hot-client@2.1.0(vite@7.3.6): resolution: {integrity: sha512-7SpgZmU7R+dDnSmvXE1mfDtnHLHQSisdySVR7lO8ceAXvM0otZeuQQ6C8LrS5d/aYyP/QZ0hI0L+dIPrm4YlFQ==} peerDependencies: vite: ^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 dependencies: - vite: 7.3.1(jiti@2.6.1)(tsx@4.21.0) + vite: 7.3.6(tsx@4.21.0) dev: false /vite-node@3.2.4(tsx@4.21.0): @@ -8284,7 +8955,7 @@ packages: vscode-uri: 3.1.0 dev: false - /vite-plugin-inspect@11.3.3(@nuxt/kit@4.2.2)(vite@7.3.1): + /vite-plugin-inspect@11.3.3(@nuxt/kit@4.2.2)(vite@7.3.6): resolution: {integrity: sha512-u2eV5La99oHoYPHE6UvbwgEqKKOQGz86wMg40CCosP6q8BkB6e5xPneZfYagK4ojPJSj5anHCrnvC20DpwVdRA==} engines: {node: '>=14'} peerDependencies: @@ -8303,13 +8974,13 @@ packages: perfect-debounce: 2.0.0 sirv: 3.0.2 unplugin-utils: 0.3.1 - vite: 7.3.1(jiti@2.6.1)(tsx@4.21.0) - vite-dev-rpc: 1.1.0(vite@7.3.1) + vite: 7.3.6(tsx@4.21.0) + vite-dev-rpc: 1.1.0(vite@7.3.6) transitivePeerDependencies: - supports-color dev: false - /vite-plugin-vue-tracer@1.1.3(vite@7.3.1)(vue@3.5.27): + /vite-plugin-vue-tracer@1.1.3(vite@7.3.6)(vue@3.5.27): resolution: {integrity: sha512-fM7hfHELZvbPnSn8EKZwHfzxm5EfYFQIclz8rwcNXfodNbRkwNvh0AGMtaBfMxQ9HC5KVa3KitwHnmE4ezDemw==} peerDependencies: vite: ^6.0.0 || ^7.0.0 @@ -8320,7 +8991,7 @@ packages: magic-string: 0.30.21 pathe: 2.0.3 source-map-js: 1.2.1 - vite: 7.3.1(jiti@2.6.1)(tsx@4.21.0) + vite: 7.3.6(tsx@4.21.0) vue: 3.5.27(typescript@5.9.3) dev: false @@ -8427,6 +9098,57 @@ packages: fsevents: 2.3.3 dev: false + /vite@7.3.6(tsx@4.21.0): + resolution: {integrity: sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + dependencies: + esbuild: 0.28.2 + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 + postcss: 8.5.26 + rollup: 4.63.0 + tinyglobby: 0.2.17 + tsx: 4.21.0 + optionalDependencies: + fsevents: 2.3.3 + dev: false + /vitest-environment-nuxt@1.0.1(@vue/test-utils@2.4.6)(happy-dom@18.0.1)(playwright-core@1.57.0)(typescript@5.9.3)(vitest@3.2.4): resolution: {integrity: sha512-eBCwtIQriXW5/M49FjqNKfnlJYlG2LWMSNFsRVKomc8CaMqmhQPBS5LZ9DlgYL9T8xIVsiA6RZn2lk7vxov3Ow==} dependencies: @@ -8442,6 +9164,7 @@ packages: - happy-dom - jsdom - magicast + - ocache - playwright-core - typescript - vitest @@ -8570,7 +9293,7 @@ packages: nuxt: optional: true dependencies: - nuxt: 4.2.2(@vue/compiler-sfc@3.5.27)(better-sqlite3@12.6.2)(drizzle-orm@0.45.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1) + nuxt: 4.2.2(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@vue/compiler-sfc@3.5.42)(better-sqlite3@12.11.1)(drizzle-orm@0.45.1)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.6) dev: false /vue@3.5.27(typescript@5.9.3): @@ -8674,7 +9397,6 @@ packages: /xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} - dev: false /y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} diff --git a/server/repositories/tasks.ts b/server/repositories/tasks.ts index 8c2d44c..5a2b80c 100644 --- a/server/repositories/tasks.ts +++ b/server/repositories/tasks.ts @@ -1,8 +1,25 @@ import { db } from '../../shared/db' import { tasksTable } from '../../shared/db/schema' +import { DB_TABLE_NAMES } from '../../shared/db/constants' +import { envVariables } from '../../shared/env' import { takeUniqueOrThrow, utcNow } from '../../shared/utils' import { TaskCreate, TaskState } from '../../shared/types' -import { count, eq, desc, like, and, asc, gte, lte } from 'drizzle-orm' +import { count, eq, desc, and, asc, gte, lte, sql, like } from 'drizzle-orm' + +const escapeLikePattern = (value: string) => + value.replace(/[\\%_]/g, (character) => `\\${character}`) + +const buildNameCondition = (name: string) => { + if (envVariables.dbDriver === 'postgres') { + const pattern = `%${escapeLikePattern(name.toLowerCase())}%` + return like(tasksTable.name, pattern) + } + // wrap user input as an FTS5 phrase query (escape embedded double quotes by doubling) + const ftsQuery = `"${name.replace(/"/g, '""')}"` + return sql`rowid IN (SELECT rowid FROM ${sql.identifier( + DB_TABLE_NAMES.tasksFts + )} WHERE name MATCH ${ftsQuery})` +} class TasksRepository { async getAll({ @@ -28,7 +45,7 @@ class TasksRepository { }) { const whereConditions = [] if (name) { - whereConditions.push(like(tasksTable.name, `%${name.toLowerCase()}%`)) + whereConditions.push(buildNameCondition(name)) } if (state) { whereConditions.push(eq(tasksTable.state, state)) diff --git a/shared/db/constants.ts b/shared/db/constants.ts index 867a799..0df35ce 100644 --- a/shared/db/constants.ts +++ b/shared/db/constants.ts @@ -1,6 +1,7 @@ export const DB_TABLE_NAMES = { tasks: 'taskiq_admin_tasks', - settings: 'taskiq_admin_settings' + settings: 'taskiq_admin_settings', + tasksFts: 'taskiq_admin_tasks_fts' } as const export const DB_INDEX_NAMES = { @@ -10,5 +11,16 @@ export const DB_INDEX_NAMES = { tasksFinishedAt: 'idx_taskiq_admin_tasks__finished_at', tasksExecutionTime: 'idx_taskiq_admin_tasks__execution_time', tasksName: 'idx_taskiq_admin_tasks__name', - tasksWorker: 'idx_taskiq_admin_tasks__worker' + tasksWorker: 'idx_taskiq_admin_tasks__worker', + tasksStateQueuedAt: 'idx_taskiq_admin_tasks__state_queued_at', + tasksStateStartedAt: 'idx_taskiq_admin_tasks__state_started_at', + tasksStateExecutionTime: 'idx_taskiq_admin_tasks__state_execution_time', + tasksNameFts: 'idx_taskiq_admin_tasks__name_fts', + tasksNameTrgm: 'idx_taskiq_admin_tasks__name_trgm' +} as const + +export const DB_TRIGGER_NAMES = { + tasksFtsInsert: 'trg_taskiq_admin_tasks_fts_ai', + tasksFtsDelete: 'trg_taskiq_admin_tasks_fts_ad', + tasksFtsUpdate: 'trg_taskiq_admin_tasks_fts_au' } as const diff --git a/shared/db/index.ts b/shared/db/index.ts index 4d5bb5f..e201297 100644 --- a/shared/db/index.ts +++ b/shared/db/index.ts @@ -51,8 +51,20 @@ const executeStatement = async (statement: string) => { } export const initializeDatabase = async () => { + const loggedWarnings = new Set() for (const statement of dbInitializationStatements[envVariables.dbDriver]) { - await executeStatement(statement) + if (typeof statement === 'string') { + await executeStatement(statement) + continue + } + try { + await executeStatement(statement.sql) + } catch { + if (!loggedWarnings.has(statement.warning)) { + loggedWarnings.add(statement.warning) + console.warn(`[taskiq-admin] ${statement.warning}`) + } + } } } diff --git a/shared/db/init.ts b/shared/db/init.ts index 197d85a..ad7e0a8 100644 --- a/shared/db/init.ts +++ b/shared/db/init.ts @@ -1,30 +1,66 @@ import type { DBDriver } from '../env' -import { DB_INDEX_NAMES, DB_TABLE_NAMES } from './constants' +import { DB_INDEX_NAMES, DB_TABLE_NAMES, DB_TRIGGER_NAMES } from './constants' -export const dbInitializationStatements: Record = { +// `optional` statements are allowed to fail without aborting initialization +// (e.g. CREATE EXTENSION requires privileges the connected role may not have). +// `warning` explains the degradation to the operator; identical warnings are logged once. +export type DBInitStatement = + | string + | { sql: string; optional: true; warning: string } + +const PG_TRGM_WARNING = [ + 'the pg_trgm extension is not available, so task name search is not indexed', + 'and falls back to a sequential scan (results stay correct).', + 'To enable the index: install the postgresql-contrib package and let the', + 'role in TASKIQ_ADMIN_DB_URL run "CREATE EXTENSION pg_trgm" (database owner', + 'or superuser), or create the extension once by hand.' +].join(' ') + +export const dbInitializationStatements: Record = { sqlite: [ 'PRAGMA journal_mode = WAL', 'PRAGMA synchronous = NORMAL', 'PRAGMA journal_size_limit = 6144000', `CREATE TABLE IF NOT EXISTS \`${DB_TABLE_NAMES.settings}\` (\`key\` text PRIMARY KEY NOT NULL, \`value\` text)`, `CREATE TABLE IF NOT EXISTS \`${DB_TABLE_NAMES.tasks}\` (\`id\` text PRIMARY KEY NOT NULL, \`name\` text NOT NULL, \`state\` text NOT NULL, \`error\` text, \`worker\` text, \`execution_time\` real, \`queued_at\` integer NOT NULL, \`started_at\` integer, \`finished_at\` integer, \`args\` text, \`kwargs\` text, \`return_value\` text)`, - `CREATE INDEX IF NOT EXISTS \`${DB_INDEX_NAMES.tasksState}\` ON \`${DB_TABLE_NAMES.tasks}\` (\`state\`)`, `CREATE INDEX IF NOT EXISTS \`${DB_INDEX_NAMES.tasksQueuedAt}\` ON \`${DB_TABLE_NAMES.tasks}\` (\`queued_at\`)`, `CREATE INDEX IF NOT EXISTS \`${DB_INDEX_NAMES.tasksStartedAt}\` ON \`${DB_TABLE_NAMES.tasks}\` (\`started_at\`)`, `CREATE INDEX IF NOT EXISTS \`${DB_INDEX_NAMES.tasksFinishedAt}\` ON \`${DB_TABLE_NAMES.tasks}\` (\`finished_at\`)`, `CREATE INDEX IF NOT EXISTS \`${DB_INDEX_NAMES.tasksExecutionTime}\` ON \`${DB_TABLE_NAMES.tasks}\` (\`execution_time\`)`, - `CREATE INDEX IF NOT EXISTS \`${DB_INDEX_NAMES.tasksName}\` ON \`${DB_TABLE_NAMES.tasks}\` (name COLLATE NOCASE)`, - `CREATE INDEX IF NOT EXISTS \`${DB_INDEX_NAMES.tasksWorker}\` ON \`${DB_TABLE_NAMES.tasks}\` (worker COLLATE NOCASE)` + `CREATE INDEX IF NOT EXISTS \`${DB_INDEX_NAMES.tasksWorker}\` ON \`${DB_TABLE_NAMES.tasks}\` (worker COLLATE NOCASE)`, + `CREATE INDEX IF NOT EXISTS \`${DB_INDEX_NAMES.tasksStateQueuedAt}\` ON \`${DB_TABLE_NAMES.tasks}\` (\`state\`, \`queued_at\`)`, + `CREATE INDEX IF NOT EXISTS \`${DB_INDEX_NAMES.tasksStateStartedAt}\` ON \`${DB_TABLE_NAMES.tasks}\` (\`state\`, \`started_at\`)`, + `CREATE INDEX IF NOT EXISTS \`${DB_INDEX_NAMES.tasksStateExecutionTime}\` ON \`${DB_TABLE_NAMES.tasks}\` (\`state\`, \`execution_time\`)`, + // FTS5 external-content table over name with trigram tokenizer (substring match, preserves LIKE %x% semantics) + `CREATE VIRTUAL TABLE IF NOT EXISTS \`${DB_TABLE_NAMES.tasksFts}\` USING fts5(name, content='${DB_TABLE_NAMES.tasks}', content_rowid='rowid', tokenize='trigram')`, + `CREATE TRIGGER IF NOT EXISTS \`${DB_TRIGGER_NAMES.tasksFtsInsert}\` AFTER INSERT ON \`${DB_TABLE_NAMES.tasks}\` BEGIN INSERT INTO \`${DB_TABLE_NAMES.tasksFts}\`(rowid, name) VALUES (new.rowid, new.name); END`, + `CREATE TRIGGER IF NOT EXISTS \`${DB_TRIGGER_NAMES.tasksFtsDelete}\` AFTER DELETE ON \`${DB_TABLE_NAMES.tasks}\` BEGIN INSERT INTO \`${DB_TABLE_NAMES.tasksFts}\`(\`${DB_TABLE_NAMES.tasksFts}\`, rowid, name) VALUES('delete', old.rowid, old.name); END`, + `CREATE TRIGGER IF NOT EXISTS \`${DB_TRIGGER_NAMES.tasksFtsUpdate}\` AFTER UPDATE ON \`${DB_TABLE_NAMES.tasks}\` BEGIN INSERT INTO \`${DB_TABLE_NAMES.tasksFts}\`(\`${DB_TABLE_NAMES.tasksFts}\`, rowid, name) VALUES('delete', old.rowid, old.name); INSERT INTO \`${DB_TABLE_NAMES.tasksFts}\`(rowid, name) VALUES (new.rowid, new.name); END`, + // rebuild FTS index from existing rows (no-op when table empty; cheap on subsequent runs only if needed) + `INSERT INTO \`${DB_TABLE_NAMES.tasksFts}\`(\`${DB_TABLE_NAMES.tasksFts}\`) VALUES('rebuild')` ], postgres: [ - `CREATE TABLE IF NOT EXISTS \"${DB_TABLE_NAMES.settings}\" (\"key\" text PRIMARY KEY, \"value\" text)`, - `CREATE TABLE IF NOT EXISTS \"${DB_TABLE_NAMES.tasks}\" (\"id\" text PRIMARY KEY, \"name\" text NOT NULL, \"state\" text NOT NULL, \"error\" text, \"worker\" text, \"execution_time\" real, \"queued_at\" timestamp with time zone NOT NULL, \"started_at\" timestamp with time zone, \"finished_at\" timestamp with time zone, \"args\" jsonb, \"kwargs\" jsonb, \"return_value\" jsonb)`, - `CREATE INDEX IF NOT EXISTS \"${DB_INDEX_NAMES.tasksState}\" ON \"${DB_TABLE_NAMES.tasks}\" (\"state\")`, - `CREATE INDEX IF NOT EXISTS \"${DB_INDEX_NAMES.tasksQueuedAt}\" ON \"${DB_TABLE_NAMES.tasks}\" (\"queued_at\")`, - `CREATE INDEX IF NOT EXISTS \"${DB_INDEX_NAMES.tasksStartedAt}\" ON \"${DB_TABLE_NAMES.tasks}\" (\"started_at\")`, - `CREATE INDEX IF NOT EXISTS \"${DB_INDEX_NAMES.tasksFinishedAt}\" ON \"${DB_TABLE_NAMES.tasks}\" (\"finished_at\")`, - `CREATE INDEX IF NOT EXISTS \"${DB_INDEX_NAMES.tasksExecutionTime}\" ON \"${DB_TABLE_NAMES.tasks}\" (\"execution_time\")`, - `CREATE INDEX IF NOT EXISTS \"${DB_INDEX_NAMES.tasksName}\" ON \"${DB_TABLE_NAMES.tasks}\" (\"name\")`, - `CREATE INDEX IF NOT EXISTS \"${DB_INDEX_NAMES.tasksWorker}\" ON \"${DB_TABLE_NAMES.tasks}\" (\"worker\")` + `CREATE TABLE IF NOT EXISTS "${DB_TABLE_NAMES.settings}" ("key" text PRIMARY KEY, "value" text)`, + `CREATE TABLE IF NOT EXISTS "${DB_TABLE_NAMES.tasks}" ("id" text PRIMARY KEY, "name" text NOT NULL, "state" text NOT NULL, "error" text, "worker" text, "execution_time" real, "queued_at" timestamp with time zone NOT NULL, "started_at" timestamp with time zone, "finished_at" timestamp with time zone, "args" jsonb, "kwargs" jsonb, "return_value" jsonb)`, + `CREATE INDEX IF NOT EXISTS "${DB_INDEX_NAMES.tasksQueuedAt}" ON "${DB_TABLE_NAMES.tasks}" ("queued_at")`, + `CREATE INDEX IF NOT EXISTS "${DB_INDEX_NAMES.tasksStartedAt}" ON "${DB_TABLE_NAMES.tasks}" ("started_at")`, + `CREATE INDEX IF NOT EXISTS "${DB_INDEX_NAMES.tasksFinishedAt}" ON "${DB_TABLE_NAMES.tasks}" ("finished_at")`, + `CREATE INDEX IF NOT EXISTS "${DB_INDEX_NAMES.tasksExecutionTime}" ON "${DB_TABLE_NAMES.tasks}" ("execution_time")`, + `CREATE INDEX IF NOT EXISTS "${DB_INDEX_NAMES.tasksWorker}" ON "${DB_TABLE_NAMES.tasks}" ("worker")`, + `CREATE INDEX IF NOT EXISTS "${DB_INDEX_NAMES.tasksStateQueuedAt}" ON "${DB_TABLE_NAMES.tasks}" ("state", "queued_at")`, + `CREATE INDEX IF NOT EXISTS "${DB_INDEX_NAMES.tasksStateStartedAt}" ON "${DB_TABLE_NAMES.tasks}" ("state", "started_at")`, + `CREATE INDEX IF NOT EXISTS "${DB_INDEX_NAMES.tasksStateExecutionTime}" ON "${DB_TABLE_NAMES.tasks}" ("state", "execution_time")`, + // both statements are optional: without pg_trgm the LIKE search still returns + // correct results, it just falls back to a sequential scan. + { + sql: 'CREATE EXTENSION IF NOT EXISTS pg_trgm', + optional: true, + warning: PG_TRGM_WARNING + }, + { + sql: `CREATE INDEX IF NOT EXISTS "${DB_INDEX_NAMES.tasksNameTrgm}" ON "${DB_TABLE_NAMES.tasks}" USING gin (lower("name") gin_trgm_ops)`, + optional: true, + warning: PG_TRGM_WARNING + } ] } diff --git a/shared/db/schema.postgres.ts b/shared/db/schema.postgres.ts index 91c5f2d..3bee48a 100644 --- a/shared/db/schema.postgres.ts +++ b/shared/db/schema.postgres.ts @@ -1,4 +1,4 @@ -import { type InferSelectModel } from 'drizzle-orm' +import { sql, type InferSelectModel } from 'drizzle-orm' import { index, jsonb, @@ -18,7 +18,10 @@ export const tasksTable = pgTable( error: text(), worker: text(), executionTime: real('execution_time'), - queuedAt: timestamp('queued_at', { withTimezone: true, mode: 'date' }).notNull(), + queuedAt: timestamp('queued_at', { + withTimezone: true, + mode: 'date' + }).notNull(), startedAt: timestamp('started_at', { withTimezone: true, mode: 'date' }), finishedAt: timestamp('finished_at', { withTimezone: true, mode: 'date' }), args: jsonb().$type>(), @@ -28,13 +31,18 @@ export const tasksTable = pgTable( }>() }, (t) => [ - index(DB_INDEX_NAMES.tasksState).on(t.state), index(DB_INDEX_NAMES.tasksQueuedAt).on(t.queuedAt), index(DB_INDEX_NAMES.tasksStartedAt).on(t.startedAt), index(DB_INDEX_NAMES.tasksFinishedAt).on(t.finishedAt), index(DB_INDEX_NAMES.tasksExecutionTime).on(t.executionTime), - index(DB_INDEX_NAMES.tasksName).on(t.name), - index(DB_INDEX_NAMES.tasksWorker).on(t.worker) + index(DB_INDEX_NAMES.tasksWorker).on(t.worker), + index(DB_INDEX_NAMES.tasksStateQueuedAt).on(t.state, t.queuedAt), + index(DB_INDEX_NAMES.tasksStateStartedAt).on(t.state, t.startedAt), + index(DB_INDEX_NAMES.tasksStateExecutionTime).on(t.state, t.executionTime), + index(DB_INDEX_NAMES.tasksNameTrgm).using( + 'gin', + sql`lower(${t.name}) gin_trgm_ops` + ) ] ) diff --git a/shared/db/schema.sqlite.ts b/shared/db/schema.sqlite.ts index 4e2c9cb..8e6dca3 100644 --- a/shared/db/schema.sqlite.ts +++ b/shared/db/schema.sqlite.ts @@ -23,13 +23,14 @@ export const tasksTable = sqliteTable( }>() }, (t) => [ - index(DB_INDEX_NAMES.tasksState).on(t.state), index(DB_INDEX_NAMES.tasksQueuedAt).on(t.queuedAt), index(DB_INDEX_NAMES.tasksStartedAt).on(t.startedAt), index(DB_INDEX_NAMES.tasksFinishedAt).on(t.finishedAt), index(DB_INDEX_NAMES.tasksExecutionTime).on(t.executionTime), - index(DB_INDEX_NAMES.tasksName).on(sql`name COLLATE NOCASE`), - index(DB_INDEX_NAMES.tasksWorker).on(sql`worker COLLATE NOCASE`) + index(DB_INDEX_NAMES.tasksWorker).on(sql`worker COLLATE NOCASE`), + index(DB_INDEX_NAMES.tasksStateQueuedAt).on(t.state, t.queuedAt), + index(DB_INDEX_NAMES.tasksStateStartedAt).on(t.state, t.startedAt), + index(DB_INDEX_NAMES.tasksStateExecutionTime).on(t.state, t.executionTime) ] ) diff --git a/tests/db-init.spec.ts b/tests/db-init.spec.ts new file mode 100644 index 0000000..9736e3d --- /dev/null +++ b/tests/db-init.spec.ts @@ -0,0 +1,52 @@ +// In-process sqlite checks for the initialization statements themselves: +// they must be re-runnable, and the FTS triggers must stay in sync with the +// tasks table (a stale FTS index silently returns wrong search results). +import { beforeAll, expect, test } from 'vitest' +import { DB_TABLE_NAMES } from '../shared/db/constants' + +process.env.TASKIQ_ADMIN_DB_DRIVER = 'sqlite' +process.env.TASKIQ_ADMIN_DB_FILE_PATH = ':memory:' +process.env.TASKIQ_ADMIN_BACKUP_FILE_PATH = ':memory:' +process.env.TASKIQ_ADMIN_API_TOKEN = 'supersecret' + +const { db, initializeDatabase } = await import('../shared/db') +const { tasksRepository } = await import('../server/repositories/tasks') +const { sql } = await import('drizzle-orm') + +const ftsCount = () => + db.get( + sql`SELECT count(*) AS c FROM ${sql.identifier(DB_TABLE_NAMES.tasksFts)}` + ).c as number + +const tasksCount = () => + db.get(sql`SELECT count(*) AS c FROM ${sql.identifier(DB_TABLE_NAMES.tasks)}`) + .c as number + +const createTask = (id: string, name: string) => + tasksRepository.create({ + id, + name, + state: 'queued', + queuedAt: new Date('2025-01-01T10:00:00Z') + } as any) + +beforeAll(async () => { + await initializeDatabase() +}) + +test('initialization statements are re-runnable', async () => { + await expect(initializeDatabase()).resolves.not.toThrow() + await expect(initializeDatabase()).resolves.not.toThrow() +}) + +test('fts index stays in sync on insert, update and delete', async () => { + await createTask('sync-1', 'myapp.tasks.send_email') + await createTask('sync-2', 'myapp.tasks.other') + expect(ftsCount()).toBe(tasksCount()) + + await tasksRepository.update('sync-2', { state: 'success' }) + expect(ftsCount()).toBe(tasksCount()) + + await db.delete((await import('../shared/db/schema')).tasksTable) + expect(ftsCount()).toBe(0) +}) diff --git a/tests/helpers/search-fixtures.ts b/tests/helpers/search-fixtures.ts new file mode 100644 index 0000000..5cafff3 --- /dev/null +++ b/tests/helpers/search-fixtures.ts @@ -0,0 +1,61 @@ +// Shared task-name search fixtures, run against every db driver so a driver +// specific search implementation cannot silently diverge. +export const SEARCH_TASK_NAMES = [ + 'myapp.tasks.send_email', + 'myapp.tasks.SEND_sms', + 'other.pipeline.run', + 'weird%_name', + 'quote"name' +] as const + +export const SEARCH_CASES: { + title: string + query: string + expected: string[] +}[] = [ + { + title: 'matches a substring shorter than three characters', + query: 'se', + expected: ['myapp.tasks.SEND_sms', 'myapp.tasks.send_email'] + }, + { + title: 'matches a substring in the middle of a name', + query: 'send', + expected: ['myapp.tasks.SEND_sms', 'myapp.tasks.send_email'] + }, + { + title: 'is case insensitive', + query: 'SEND', + expected: ['myapp.tasks.SEND_sms', 'myapp.tasks.send_email'] + }, + { + title: 'matches a name part containing an underscore', + query: 'send_email', + expected: ['myapp.tasks.send_email'] + }, + { + title: 'matches a full dotted task name', + query: 'myapp.tasks.send_email', + expected: ['myapp.tasks.send_email'] + }, + { + title: 'treats % and _ as literal characters, not wildcards', + query: 'rd%_n', + expected: ['weird%_name'] + }, + { + title: 'does not let % act as a wildcard inside the query', + query: 'p%run', + expected: [] + }, + { + title: 'handles a double quote in the query', + query: 'quote"na', + expected: ['quote"name'] + }, + { + title: 'returns nothing for an unknown name', + query: 'nope', + expected: [] + } +] diff --git a/tests/helpers/search-suite.ts b/tests/helpers/search-suite.ts new file mode 100644 index 0000000..b52d764 --- /dev/null +++ b/tests/helpers/search-suite.ts @@ -0,0 +1,75 @@ +import { $fetch } from '@nuxt/test-utils' +import { beforeAll, describe, expect, test } from 'vitest' +import { SEARCH_CASES, SEARCH_TASK_NAMES } from './search-fixtures' + +const token = { 'access-token': 'supersecret' } + +type TasksResponse = { + count: number + tasks: { id: string; name: string; state: string }[] +} + +const fetchTasks = (query: Record) => + $fetch('/api/tasks', { + query: { limit: 100, offset: 0, ...query } + }) + +const seedTasks = async (idPrefix: string) => { + for (const [index, taskName] of SEARCH_TASK_NAMES.entries()) { + await $fetch(`/api/tasks/${idPrefix}-${index}/queued`, { + method: 'POST', + headers: token, + body: { + taskName, + args: [], + kwargs: {}, + worker: 'w', + queuedAt: new Date('2025-01-01T10:00:00Z').toISOString() + } + }) + } +} + +export const registerSearchTests = (driver: 'sqlite' | 'postgres') => { + describe(`task name search (${driver})`, () => { + beforeAll(async () => { + await seedTasks('search') + }) + + for (const { title, query, expected } of SEARCH_CASES) { + test(`${title}: ${JSON.stringify(query)}`, async () => { + const result = await fetchTasks({ search: query }) + expect(result.tasks.map((task) => task.name).sort()).toEqual( + [...expected].sort() + ) + }) + } + + test('count matches the number of returned rows', async () => { + const result = await fetchTasks({ search: 'send' }) + expect(result.count).toBe(result.tasks.length) + }) + + test('count reflects the filter, not the whole table', async () => { + const all = await fetchTasks({}) + const filtered = await fetchTasks({ search: 'send' }) + expect(all.count).toBe(SEARCH_TASK_NAMES.length) + expect(filtered.count).toBe(2) + }) + + test('name search combines with the state filter', async () => { + const matching = await fetchTasks({ search: 'send', state: 'queued' }) + expect(matching.count).toBe(2) + + const nonMatching = await fetchTasks({ search: 'send', state: 'success' }) + expect(nonMatching.count).toBe(0) + expect(nonMatching.tasks).toEqual([]) + }) + + test('pagination applies to the filtered set', async () => { + const firstPage = await fetchTasks({ search: 'send', limit: 1 }) + expect(firstPage.tasks).toHaveLength(1) + expect(firstPage.count).toBe(2) + }) + }) +} diff --git a/tests/task-flow.nuxt.spec.ts b/tests/task-flow.nuxt.spec.ts index c281093..c6a149a 100644 --- a/tests/task-flow.nuxt.spec.ts +++ b/tests/task-flow.nuxt.spec.ts @@ -1,4 +1,3 @@ -// tests/task-flow.nuxt.spec.ts import { setup, $fetch } from '@nuxt/test-utils' import { resolve } from 'path' import { expect, test } from 'vitest' diff --git a/tests/task-search.nuxt.spec.ts b/tests/task-search.nuxt.spec.ts new file mode 100644 index 0000000..26daa06 --- /dev/null +++ b/tests/task-search.nuxt.spec.ts @@ -0,0 +1,17 @@ +import { setup } from '@nuxt/test-utils' +import { resolve } from 'path' +import { registerSearchTests } from './helpers/search-suite' + +await setup({ + rootDir: resolve(__dirname, '../..'), + server: true, + env: { + NODE_ENV: 'test', + TASKIQ_ADMIN_DB_DRIVER: 'sqlite', + TASKIQ_ADMIN_DB_FILE_PATH: ':memory:', + TASKIQ_ADMIN_BACKUP_FILE_PATH: ':memory:', + TASKIQ_ADMIN_API_TOKEN: 'supersecret' + } +}) + +registerSearchTests('sqlite') diff --git a/tests/task-search.postgres.nuxt.spec.ts b/tests/task-search.postgres.nuxt.spec.ts new file mode 100644 index 0000000..0d1486c --- /dev/null +++ b/tests/task-search.postgres.nuxt.spec.ts @@ -0,0 +1,36 @@ +// Runs the shared search suite against postgres. +// Skipped unless TASKIQ_ADMIN_TEST_DB_URL points at a disposable database, +// because the suite truncates the tasks table. +import { setup } from '@nuxt/test-utils' +import { resolve } from 'path' +import { describe, test } from 'vitest' +import { registerSearchTests } from './helpers/search-suite' + +const dbUrl = process.env.TASKIQ_ADMIN_TEST_DB_URL + +if (!dbUrl) { + describe.skip('task name search (postgres)', () => { + test('needs TASKIQ_ADMIN_TEST_DB_URL', () => {}) + }) +} else { + process.env.TASKIQ_ADMIN_DB_DRIVER = 'postgres' + process.env.TASKIQ_ADMIN_DB_URL = dbUrl + process.env.TASKIQ_ADMIN_API_TOKEN = 'supersecret' + + const { resetDatabaseForTests } = await import('../shared/db') + + await setup({ + rootDir: resolve(__dirname, '../..'), + server: true, + env: { + NODE_ENV: 'test', + TASKIQ_ADMIN_DB_DRIVER: 'postgres', + TASKIQ_ADMIN_DB_URL: dbUrl, + TASKIQ_ADMIN_API_TOKEN: 'supersecret' + } + }) + + await resetDatabaseForTests() + + registerSearchTests('postgres') +}