diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..bd2a760 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,44 @@ +name: CI + +on: + push: + branches: + - '**' # Matches every branch + pull_request: + branches: + - master + +jobs: + install-lint-and-test: + timeout-minutes: 3 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: corepack enable + - uses: actions/setup-node@v4 + with: + node-version: 22 + - name: Install Bun + uses: oven-sh/setup-bun@v2 + - name: Install dependencies + run: npnpm i + + - name: Typecheck + run: npm run test:types + + - name: Lint + run: npm run lint + + - name: Test + run: npm run test:ci + + - name: Coverage + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload test results to Codecov + if: ${{ !cancelled() }} + uses: codecov/test-results-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..345abca --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Mac OSX +.DS_Store + + + +# Nuxt dev/build outputs +.data + +# Node dependencies +node_modules + +# Logs +logs +*.log + +# Misc +.DS_Store +.fleet +.idea diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..43365b2 --- /dev/null +++ b/biome.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.0/schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignoreUnknown": false, + "ignore": [] + }, + "formatter": { + "enabled": true, + "indentStyle": "tab" + }, + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "javascript": { + "formatter": { + "quoteStyle": "double" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..bf62fec --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "drizzle-schema-checker", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "pnpm run vitest" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@biomejs/biome": "^1.9.0", + "db0": "^0.1.4", + "tsup": "^8.2.4", + "vitest": "^2.1.1" + }, + "dependencies": { + "@libsql/client": "^0.11.0", + "better-sqlite3": "^11.3.0", + "consola": "^3.2.3", + "drizzle-orm": "^0.33.0", + "zod": "^3.23.8" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..2d68453 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,2580 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@libsql/client': + specifier: ^0.11.0 + version: 0.11.0 + better-sqlite3: + specifier: ^11.3.0 + version: 11.3.0 + consola: + specifier: ^3.2.3 + version: 3.2.3 + drizzle-orm: + specifier: ^0.33.0 + version: 0.33.0(@libsql/client@0.11.0)(better-sqlite3@11.3.0) + zod: + specifier: ^3.23.8 + version: 3.23.8 + devDependencies: + '@biomejs/biome': + specifier: ^1.9.0 + version: 1.9.0 + db0: + specifier: ^0.1.4 + version: 0.1.4(@libsql/client@0.11.0)(better-sqlite3@11.3.0)(drizzle-orm@0.33.0(@libsql/client@0.11.0)(better-sqlite3@11.3.0)) + tsup: + specifier: ^8.2.4 + version: 8.2.4(postcss@8.4.47) + vitest: + specifier: ^2.1.1 + version: 2.1.1(@types/node@22.5.5) + +packages: + + '@biomejs/biome@1.9.0': + resolution: {integrity: sha512-NlWh2F1wbxB3O/wE+aohGL0BziTS6e+6+dyFvpdeqLsbQZY7EsiklFb9W5Xs41U4vEmY7ANgdNp+oVDij6sQdA==} + engines: {node: '>=14.21.3'} + hasBin: true + + '@biomejs/cli-darwin-arm64@1.9.0': + resolution: {integrity: sha512-2w9v/NRtYSmodx5QWQ49OGcyGKSECdWKbzc7n532Iq5sBhkKk996fd19icT6BuL54f01KFKRCRibAW+A2rg1Kw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + + '@biomejs/cli-darwin-x64@1.9.0': + resolution: {integrity: sha512-fBVt8jJQi0zX0SJ1C+tdzUbRpuX/07sgtBXEhunWRkPjdi6W/2S1sYHQ1wKn4OKiRAKfHM2Cf2FNO7hQvY61dA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + + '@biomejs/cli-linux-arm64-musl@1.9.0': + resolution: {integrity: sha512-Jy84mZ4vcppdmWMgQWOCfd8qIVC/vHmlaS5gy7GXkdWlBKSQ56YxEXTU58MHTbZ16LwJQpK2IulqRCC/rqWLBA==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-arm64@1.9.0': + resolution: {integrity: sha512-l8U2lcqsl9yKPP5WUdIrKH//C1pWyM2cSUfcTBn6GSvXmsSjBNEdGSdM4Wfne777Oe/9ONaD1Ga53U2HksHHLw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-x64-musl@1.9.0': + resolution: {integrity: sha512-N3enoFoIrkB6qJWyYfTiYmFdB1R/Mrij1dd1xBHqxxCKZY9GRkEswRX3F1Uqzo5T+9Iu8nAQobDqI/ygicYy/Q==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-linux-x64@1.9.0': + resolution: {integrity: sha512-8jAzjrrJTj510pwq4aVs7ZKkOvEy1D+nzl9DKvrPh4TOyUw5Ie+0EDwXGE2RAkCKHkGNOQBZ78WtIdsATgz5sA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-win32-arm64@1.9.0': + resolution: {integrity: sha512-AIjwJTGfdWGMRluSQ9pDB29nzce077dfHh0/HMqzztKzgD3spyuo2R9VoaFpbR0hLHPWEH6g6OxxDO7hfkXNkQ==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + + '@biomejs/cli-win32-x64@1.9.0': + resolution: {integrity: sha512-4/4wTjNSoyNkm1SzcUaStDx46baX1VJRXtUoeEHjX9LfedR5N3qwZz5KfrRUnCd2fl5bmXK1CwMqKBkoF6zEiA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.23.1': + resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.23.1': + resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.23.1': + resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.23.1': + resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.23.1': + resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.23.1': + resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.23.1': + resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.23.1': + resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.23.1': + resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.23.1': + resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.23.1': + resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.23.1': + resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.23.1': + resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.23.1': + resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.23.1': + resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.23.1': + resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.23.1': + resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.23.1': + resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.23.1': + resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.23.1': + resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.23.1': + resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.23.1': + resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.23.1': + resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.23.1': + resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@jridgewell/gen-mapping@0.3.5': + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@libsql/client@0.11.0': + resolution: {integrity: sha512-IkMxBFI43JXBRf4K57m6zeoa8M6u0FFEI11vHh4/ObTCZFk20wJNO9hRa1Zq1BeVDqABxMRf02HPAr02pC06bQ==} + + '@libsql/core@0.11.0': + resolution: {integrity: sha512-ksMxodOCXoU2tiQtlY7DzOLZ0xq8EWbXlmBk+56/8U7e7QMDrYmdt+XhOcCr4DAO5HSD9kMPqS4iVFBKGcWbBA==} + + '@libsql/darwin-arm64@0.4.5': + resolution: {integrity: sha512-xLdnn0NrgSk6OMi716FFs/27Hs33jtSd2fkKi/72Ey/qBtPWcB1BMurDQekzi0yAcfQTjGqIz7tpOibyjiEPyQ==} + cpu: [arm64] + os: [darwin] + + '@libsql/darwin-x64@0.4.5': + resolution: {integrity: sha512-rZsEWj0H7oCqd5Y2pe0RzKmuQXC2OB1RbnFy4CvjeAjT6MP6mFp+Vx9mTCAUuJMhuoSVMsFPUJRpAQznl9E3Tg==} + cpu: [x64] + os: [darwin] + + '@libsql/hrana-client@0.6.2': + resolution: {integrity: sha512-MWxgD7mXLNf9FXXiM0bc90wCjZSpErWKr5mGza7ERy2FJNNMXd7JIOv+DepBA1FQTIfI8TFO4/QDYgaQC0goNw==} + + '@libsql/isomorphic-fetch@0.2.5': + resolution: {integrity: sha512-8s/B2TClEHms2yb+JGpsVRTPBfy1ih/Pq6h6gvyaNcYnMVJvgQRY7wAa8U2nD0dppbCuDU5evTNMEhrQ17ZKKg==} + engines: {node: '>=18.0.0'} + + '@libsql/isomorphic-ws@0.1.5': + resolution: {integrity: sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==} + + '@libsql/linux-arm64-gnu@0.4.5': + resolution: {integrity: sha512-VR09iu6KWGJ6fauCn59u/jJ9OA+/A2yQ0dr2HDN2zkRueLC6D2oGYt4gPfLZPFKf+WJpVMtIhNfd+Ru9MMaFkA==} + cpu: [arm64] + os: [linux] + + '@libsql/linux-arm64-musl@0.4.5': + resolution: {integrity: sha512-74hvD5ej4rBshhxFGNYU16a3m8B/NjIPvhlZ/flG1Oeydfo6AuUXSSNFi+H5+zi9/uWuzyz5TLVeQcraoUV10A==} + cpu: [arm64] + os: [linux] + + '@libsql/linux-x64-gnu@0.4.5': + resolution: {integrity: sha512-gb5WObGO3+rbuG8h9font1N02iF+zgYAgY0wNa8BNiZ5A9UolZKFxiqGFS7eHaAYfemHJKKTT+aAt3X2p5TibA==} + cpu: [x64] + os: [linux] + + '@libsql/linux-x64-musl@0.4.5': + resolution: {integrity: sha512-JfyE6OVC5X4Nr4cFF77VhB1o+hBRxAqYT9YdeqnWdAQSYc/ASi5HnRALLAQEsGacFPZZ32pixfraQmPE3iJFfw==} + cpu: [x64] + os: [linux] + + '@libsql/win32-x64-msvc@0.4.5': + resolution: {integrity: sha512-57GGurNJhOhq3XIopLdGnCoQ4kQAcmbmzzFoC4tpvDE/KSbwZ/13zqJWhQA41nMGk/PKM1XKfKmbIybKx1+eqA==} + cpu: [x64] + os: [win32] + + '@neon-rs/load@0.0.4': + resolution: {integrity: sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@rollup/rollup-android-arm-eabi@4.21.3': + resolution: {integrity: sha512-MmKSfaB9GX+zXl6E8z4koOr/xU63AMVleLEa64v7R0QF/ZloMs5vcD1sHgM64GXXS1csaJutG+ddtzcueI/BLg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.21.3': + resolution: {integrity: sha512-zrt8ecH07PE3sB4jPOggweBjJMzI1JG5xI2DIsUbkA+7K+Gkjys6eV7i9pOenNSDJH3eOr/jLb/PzqtmdwDq5g==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.21.3': + resolution: {integrity: sha512-P0UxIOrKNBFTQaXTxOH4RxuEBVCgEA5UTNV6Yz7z9QHnUJ7eLX9reOd/NYMO3+XZO2cco19mXTxDMXxit4R/eQ==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.21.3': + resolution: {integrity: sha512-L1M0vKGO5ASKntqtsFEjTq/fD91vAqnzeaF6sfNAy55aD+Hi2pBI5DKwCO+UNDQHWsDViJLqshxOahXyLSh3EA==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-linux-arm-gnueabihf@4.21.3': + resolution: {integrity: sha512-btVgIsCjuYFKUjopPoWiDqmoUXQDiW2A4C3Mtmp5vACm7/GnyuprqIDPNczeyR5W8rTXEbkmrJux7cJmD99D2g==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.21.3': + resolution: {integrity: sha512-zmjbSphplZlau6ZTkxd3+NMtE4UKVy7U4aVFMmHcgO5CUbw17ZP6QCgyxhzGaU/wFFdTfiojjbLG3/0p9HhAqA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.21.3': + resolution: {integrity: sha512-nSZfcZtAnQPRZmUkUQwZq2OjQciR6tEoJaZVFvLHsj0MF6QhNMg0fQ6mUOsiCUpTqxTx0/O6gX0V/nYc7LrgPw==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.21.3': + resolution: {integrity: sha512-MnvSPGO8KJXIMGlQDYfvYS3IosFN2rKsvxRpPO2l2cum+Z3exiExLwVU+GExL96pn8IP+GdH8Tz70EpBhO0sIQ==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.21.3': + resolution: {integrity: sha512-+W+p/9QNDr2vE2AXU0qIy0qQE75E8RTwTwgqS2G5CRQ11vzq0tbnfBd6brWhS9bCRjAjepJe2fvvkvS3dno+iw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.21.3': + resolution: {integrity: sha512-yXH6K6KfqGXaxHrtr+Uoy+JpNlUlI46BKVyonGiaD74ravdnF9BUNC+vV+SIuB96hUMGShhKV693rF9QDfO6nQ==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.21.3': + resolution: {integrity: sha512-R8cwY9wcnApN/KDYWTH4gV/ypvy9yZUHlbJvfaiXSB48JO3KpwSpjOGqO4jnGkLDSk1hgjYkTbTt6Q7uvPf8eg==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.21.3': + resolution: {integrity: sha512-kZPbX/NOPh0vhS5sI+dR8L1bU2cSO9FgxwM8r7wHzGydzfSjLRCFAT87GR5U9scj2rhzN3JPYVC7NoBbl4FZ0g==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.21.3': + resolution: {integrity: sha512-S0Yq+xA1VEH66uiMNhijsWAafffydd2X5b77eLHfRmfLsRSpbiAWiRHV6DEpz6aOToPsgid7TI9rGd6zB1rhbg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.21.3': + resolution: {integrity: sha512-9isNzeL34yquCPyerog+IMCNxKR8XYmGd0tHSV+OVx0TmE0aJOo9uw4fZfUuk2qxobP5sug6vNdZR6u7Mw7Q+Q==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.21.3': + resolution: {integrity: sha512-nMIdKnfZfzn1Vsk+RuOvl43ONTZXoAPUUxgcU0tXooqg4YrAqzfKzVenqqk2g5efWh46/D28cKFrOzDSW28gTA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.21.3': + resolution: {integrity: sha512-fOvu7PCQjAj4eWDEuD8Xz5gpzFqXzGlxHZozHP4b9Jxv9APtdxL6STqztDzMLuRXEc4UpXGGhx029Xgm91QBeA==} + cpu: [x64] + os: [win32] + + '@types/estree@1.0.5': + resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + + '@types/node@22.5.5': + resolution: {integrity: sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==} + + '@types/ws@8.5.12': + resolution: {integrity: sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==} + + '@vitest/expect@2.1.1': + resolution: {integrity: sha512-YeueunS0HiHiQxk+KEOnq/QMzlUuOzbU1Go+PgAsHvvv3tUkJPm9xWt+6ITNTlzsMXUjmgm5T+U7KBPK2qQV6w==} + + '@vitest/mocker@2.1.1': + resolution: {integrity: sha512-LNN5VwOEdJqCmJ/2XJBywB11DLlkbY0ooDJW3uRX5cZyYCrc4PI/ePX0iQhE3BiEGiQmK4GE7Q/PqCkkaiPnrA==} + peerDependencies: + '@vitest/spy': 2.1.1 + msw: ^2.3.5 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@2.1.1': + resolution: {integrity: sha512-SjxPFOtuINDUW8/UkElJYQSFtnWX7tMksSGW0vfjxMneFqxVr8YJ979QpMbDW7g+BIiq88RAGDjf7en6rvLPPQ==} + + '@vitest/runner@2.1.1': + resolution: {integrity: sha512-uTPuY6PWOYitIkLPidaY5L3t0JJITdGTSwBtwMjKzo5O6RCOEncz9PUN+0pDidX8kTHYjO0EwUIvhlGpnGpxmA==} + + '@vitest/snapshot@2.1.1': + resolution: {integrity: sha512-BnSku1WFy7r4mm96ha2FzN99AZJgpZOWrAhtQfoxjUU5YMRpq1zmHRq7a5K9/NjqonebO7iVDla+VvZS8BOWMw==} + + '@vitest/spy@2.1.1': + resolution: {integrity: sha512-ZM39BnZ9t/xZ/nF4UwRH5il0Sw93QnZXd9NAZGRpIgj0yvVwPpLd702s/Cx955rGaMlyBQkZJ2Ir7qyY48VZ+g==} + + '@vitest/utils@2.1.1': + resolution: {integrity: sha512-Y6Q9TsI+qJ2CC0ZKj6VBb+T8UPz593N113nnUykqwANqhgf3QkZeHFlusgKLTqrnVHbj/XDKZcDHol+dxVT+rQ==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.1.0: + resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + better-sqlite3@11.3.0: + resolution: {integrity: sha512-iHt9j8NPYF3oKCNOO5ZI4JwThjt3Z6J6XrcwG85VNMVzv1ByqrHWv5VILEbCMFWDsoHhXvQ7oC8vgRXFAKgl9w==} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + bundle-require@5.0.0: + resolution: {integrity: sha512-GuziW3fSSmopcx4KRymQEJVbZUfqlCqcq7dvs6TYwKRZiegK/2buMxQTPs6MGlNv50wms1699qYO54R8XfRX4w==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + esbuild: '>=0.18' + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + chai@5.1.1: + resolution: {integrity: sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==} + engines: {node: '>=12'} + + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + consola@3.2.3: + resolution: {integrity: sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==} + engines: {node: ^14.18.0 || >=16.10.0} + + cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + + data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} + + db0@0.1.4: + resolution: {integrity: sha512-Ft6eCwONYxlwLjBXSJxw0t0RYtA5gW9mq8JfBXn9TtC0nDPlqePAhpv9v4g9aONBi6JI1OXHTKKkUYGd+BOrCA==} + peerDependencies: + '@libsql/client': ^0.5.2 + better-sqlite3: ^9.4.3 + drizzle-orm: ^0.29.4 + peerDependenciesMeta: + '@libsql/client': + optional: true + better-sqlite3: + optional: true + drizzle-orm: + optional: true + + debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + + detect-libc@2.0.2: + resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==} + engines: {node: '>=8'} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + drizzle-orm@0.33.0: + resolution: {integrity: sha512-SHy72R2Rdkz0LEq0PSG/IdvnT3nGiWuRk+2tXZQ90GVq/XQhpCzu/EFT3V2rox+w8MlkBQxifF8pCStNYnERfA==} + peerDependencies: + '@aws-sdk/client-rds-data': '>=3' + '@cloudflare/workers-types': '>=3' + '@electric-sql/pglite': '>=0.1.1' + '@libsql/client': '*' + '@neondatabase/serverless': '>=0.1' + '@op-engineering/op-sqlite': '>=2' + '@opentelemetry/api': ^1.4.1 + '@planetscale/database': '>=1' + '@prisma/client': '*' + '@tidbcloud/serverless': '*' + '@types/better-sqlite3': '*' + '@types/pg': '*' + '@types/react': '>=18' + '@types/sql.js': '*' + '@vercel/postgres': '>=0.8.0' + '@xata.io/client': '*' + better-sqlite3: '>=7' + bun-types: '*' + expo-sqlite: '>=13.2.0' + knex: '*' + kysely: '*' + mysql2: '>=2' + pg: '>=8' + postgres: '>=3' + prisma: '*' + react: '>=18' + sql.js: '>=1' + sqlite3: '>=5' + peerDependenciesMeta: + '@aws-sdk/client-rds-data': + optional: true + '@cloudflare/workers-types': + optional: true + '@electric-sql/pglite': + optional: true + '@libsql/client': + optional: true + '@neondatabase/serverless': + optional: true + '@op-engineering/op-sqlite': + optional: true + '@opentelemetry/api': + optional: true + '@planetscale/database': + optional: true + '@prisma/client': + optional: true + '@tidbcloud/serverless': + optional: true + '@types/better-sqlite3': + optional: true + '@types/pg': + optional: true + '@types/react': + optional: true + '@types/sql.js': + optional: true + '@vercel/postgres': + optional: true + '@xata.io/client': + optional: true + better-sqlite3: + optional: true + bun-types: + optional: true + expo-sqlite: + optional: true + knex: + optional: true + kysely: + optional: true + mysql2: + optional: true + pg: + optional: true + postgres: + optional: true + prisma: + optional: true + react: + optional: true + sql.js: + optional: true + sqlite3: + optional: true + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + end-of-stream@1.4.4: + resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + esbuild@0.23.1: + resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} + engines: {node: '>=18'} + hasBin: true + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + + fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + + fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + + fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + + file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + foreground-child@3.3.0: + resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} + engines: {node: '>=14'} + + formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + get-func-name@2.0.2: + resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + + js-base64@3.7.7: + resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==} + + libsql@0.4.5: + resolution: {integrity: sha512-sorTJV6PNt94Wap27Sai5gtVLIea4Otb2LUiAUyr3p6BPOScGMKGt5F1b5X/XgkNtcsDKeX5qfeBDj+PdShclQ==} + os: [darwin, linux, win32] + + lilconfig@3.1.2: + resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + load-tsconfig@0.2.5: + resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + lodash.sortby@4.7.0: + resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} + + loupe@3.1.1: + resolution: {integrity: sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + magic-string@0.30.11: + resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + napi-build-utils@1.0.2: + resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} + + node-abi@3.67.0: + resolution: {integrity: sha512-bLn/fU/ALVBE9wj+p4Y21ZJWYFjUXLXPi/IewyLZkx3ApxKDNBWCKdReeKOtD8dWpOdDCeMyLh6ZewzcLsG2Nw==} + engines: {node: '>=10'} + + node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + + node-fetch@3.3.2: + resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + package-json-from-dist@1.0.0: + resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + + pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} + + picocolors@1.1.0: + resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + engines: {node: '>= 6'} + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + + postcss@8.4.47: + resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} + engines: {node: ^10 || ^12 || >=14} + + prebuild-install@7.1.2: + resolution: {integrity: sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==} + engines: {node: '>=10'} + hasBin: true + + promise-limit@2.7.0: + resolution: {integrity: sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==} + + pump@3.0.2: + resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rollup@4.21.3: + resolution: {integrity: sha512-7sqRtBNnEbcBtMeRVc6VRsJMmpI+JU1z9VTvW8D4gXIYQFz0aLcsE6rRkyghZkLfEgUZgVvOG7A5CVz/VW5GIA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.8.0-beta.0: + resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} + engines: {node: '>= 8'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.7.0: + resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + + sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + tar-fs@2.1.1: + resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} + + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.0: + resolution: {integrity: sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==} + + tinypool@1.0.1: + resolution: {integrity: sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tr46@1.0.1: + resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + tsup@8.2.4: + resolution: {integrity: sha512-akpCPePnBnC/CXgRrcy72ZSntgIEUa1jN0oJbbvpALWKNOz1B7aM+UVDWGRGIO/T/PZugAESWDJUAb5FD48o8Q==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + '@microsoft/api-extractor': ^7.36.0 + '@swc/core': ^1 + postcss: ^8.4.12 + typescript: '>=4.5.0' + peerDependenciesMeta: + '@microsoft/api-extractor': + optional: true + '@swc/core': + optional: true + postcss: + optional: true + typescript: + optional: true + + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + vite-node@2.1.1: + resolution: {integrity: sha512-N/mGckI1suG/5wQI35XeR9rsMsPqKXzq1CdUndzVstBj/HvyxxGctwnK6WX43NGt5L3Z5tcRf83g4TITKJhPrA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + + vite@5.4.5: + resolution: {integrity: sha512-pXqR0qtb2bTwLkev4SE3r4abCNioP3GkjvIDLlzziPpXtHgiJIjuKl+1GN6ESOT3wMjG3JTeARopj2SwYaHTOA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vitest@2.1.1: + resolution: {integrity: sha512-97We7/VC0e9X5zBVkvt7SGQMGrRtn3KtySFQG5fpaMlS+l62eeXRQO633AYhSTC3z7IMebnPPNjGXVGNRFlxBA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.1.1 + '@vitest/ui': 2.1.1 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + web-streams-polyfill@3.3.3: + resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} + engines: {node: '>= 8'} + + webidl-conversions@4.0.2: + resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} + + whatwg-url@7.1.0: + resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + zod@3.23.8: + resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} + +snapshots: + + '@biomejs/biome@1.9.0': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 1.9.0 + '@biomejs/cli-darwin-x64': 1.9.0 + '@biomejs/cli-linux-arm64': 1.9.0 + '@biomejs/cli-linux-arm64-musl': 1.9.0 + '@biomejs/cli-linux-x64': 1.9.0 + '@biomejs/cli-linux-x64-musl': 1.9.0 + '@biomejs/cli-win32-arm64': 1.9.0 + '@biomejs/cli-win32-x64': 1.9.0 + + '@biomejs/cli-darwin-arm64@1.9.0': + optional: true + + '@biomejs/cli-darwin-x64@1.9.0': + optional: true + + '@biomejs/cli-linux-arm64-musl@1.9.0': + optional: true + + '@biomejs/cli-linux-arm64@1.9.0': + optional: true + + '@biomejs/cli-linux-x64-musl@1.9.0': + optional: true + + '@biomejs/cli-linux-x64@1.9.0': + optional: true + + '@biomejs/cli-win32-arm64@1.9.0': + optional: true + + '@biomejs/cli-win32-x64@1.9.0': + optional: true + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/aix-ppc64@0.23.1': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.23.1': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-arm@0.23.1': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/android-x64@0.23.1': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.23.1': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.23.1': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.23.1': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.23.1': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.23.1': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-arm@0.23.1': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.23.1': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.23.1': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.23.1': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.23.1': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.23.1': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.23.1': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/linux-x64@0.23.1': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.23.1': + optional: true + + '@esbuild/openbsd-arm64@0.23.1': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.23.1': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.23.1': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.23.1': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.23.1': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@esbuild/win32-x64@0.23.1': + optional: true + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@jridgewell/gen-mapping@0.3.5': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@libsql/client@0.11.0': + dependencies: + '@libsql/core': 0.11.0 + '@libsql/hrana-client': 0.6.2 + js-base64: 3.7.7 + libsql: 0.4.5 + promise-limit: 2.7.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@libsql/core@0.11.0': + dependencies: + js-base64: 3.7.7 + + '@libsql/darwin-arm64@0.4.5': + optional: true + + '@libsql/darwin-x64@0.4.5': + optional: true + + '@libsql/hrana-client@0.6.2': + dependencies: + '@libsql/isomorphic-fetch': 0.2.5 + '@libsql/isomorphic-ws': 0.1.5 + js-base64: 3.7.7 + node-fetch: 3.3.2 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@libsql/isomorphic-fetch@0.2.5': {} + + '@libsql/isomorphic-ws@0.1.5': + dependencies: + '@types/ws': 8.5.12 + ws: 8.18.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@libsql/linux-arm64-gnu@0.4.5': + optional: true + + '@libsql/linux-arm64-musl@0.4.5': + optional: true + + '@libsql/linux-x64-gnu@0.4.5': + optional: true + + '@libsql/linux-x64-musl@0.4.5': + optional: true + + '@libsql/win32-x64-msvc@0.4.5': + optional: true + + '@neon-rs/load@0.0.4': {} + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.17.1 + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@rollup/rollup-android-arm-eabi@4.21.3': + optional: true + + '@rollup/rollup-android-arm64@4.21.3': + optional: true + + '@rollup/rollup-darwin-arm64@4.21.3': + optional: true + + '@rollup/rollup-darwin-x64@4.21.3': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.21.3': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.21.3': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.21.3': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.21.3': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.21.3': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.21.3': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.21.3': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.21.3': + optional: true + + '@rollup/rollup-linux-x64-musl@4.21.3': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.21.3': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.21.3': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.21.3': + optional: true + + '@types/estree@1.0.5': {} + + '@types/node@22.5.5': + dependencies: + undici-types: 6.19.8 + + '@types/ws@8.5.12': + dependencies: + '@types/node': 22.5.5 + + '@vitest/expect@2.1.1': + dependencies: + '@vitest/spy': 2.1.1 + '@vitest/utils': 2.1.1 + chai: 5.1.1 + tinyrainbow: 1.2.0 + + '@vitest/mocker@2.1.1(@vitest/spy@2.1.1)(vite@5.4.5(@types/node@22.5.5))': + dependencies: + '@vitest/spy': 2.1.1 + estree-walker: 3.0.3 + magic-string: 0.30.11 + optionalDependencies: + vite: 5.4.5(@types/node@22.5.5) + + '@vitest/pretty-format@2.1.1': + dependencies: + tinyrainbow: 1.2.0 + + '@vitest/runner@2.1.1': + dependencies: + '@vitest/utils': 2.1.1 + pathe: 1.1.2 + + '@vitest/snapshot@2.1.1': + dependencies: + '@vitest/pretty-format': 2.1.1 + magic-string: 0.30.11 + pathe: 1.1.2 + + '@vitest/spy@2.1.1': + dependencies: + tinyspy: 3.0.2 + + '@vitest/utils@2.1.1': + dependencies: + '@vitest/pretty-format': 2.1.1 + loupe: 3.1.1 + tinyrainbow: 1.2.0 + + ansi-regex@5.0.1: {} + + ansi-regex@6.1.0: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.1: {} + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + array-union@2.1.0: {} + + assertion-error@2.0.1: {} + + balanced-match@1.0.2: {} + + base64-js@1.5.1: {} + + better-sqlite3@11.3.0: + dependencies: + bindings: 1.5.0 + prebuild-install: 7.1.2 + + binary-extensions@2.3.0: {} + + bindings@1.5.0: + dependencies: + file-uri-to-path: 1.0.0 + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + bundle-require@5.0.0(esbuild@0.23.1): + dependencies: + esbuild: 0.23.1 + load-tsconfig: 0.2.5 + + cac@6.7.14: {} + + chai@5.1.1: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.1 + pathval: 2.0.0 + + check-error@2.1.1: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + chownr@1.1.4: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + commander@4.1.1: {} + + consola@3.2.3: {} + + cross-spawn@7.0.3: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + data-uri-to-buffer@4.0.1: {} + + db0@0.1.4(@libsql/client@0.11.0)(better-sqlite3@11.3.0)(drizzle-orm@0.33.0(@libsql/client@0.11.0)(better-sqlite3@11.3.0)): + optionalDependencies: + '@libsql/client': 0.11.0 + better-sqlite3: 11.3.0 + drizzle-orm: 0.33.0(@libsql/client@0.11.0)(better-sqlite3@11.3.0) + + debug@4.3.7: + dependencies: + ms: 2.1.3 + + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + + deep-eql@5.0.2: {} + + deep-extend@0.6.0: {} + + detect-libc@2.0.2: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + drizzle-orm@0.33.0(@libsql/client@0.11.0)(better-sqlite3@11.3.0): + optionalDependencies: + '@libsql/client': 0.11.0 + better-sqlite3: 11.3.0 + + eastasianwidth@0.2.0: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + end-of-stream@1.4.4: + dependencies: + once: 1.4.0 + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + esbuild@0.23.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.23.1 + '@esbuild/android-arm': 0.23.1 + '@esbuild/android-arm64': 0.23.1 + '@esbuild/android-x64': 0.23.1 + '@esbuild/darwin-arm64': 0.23.1 + '@esbuild/darwin-x64': 0.23.1 + '@esbuild/freebsd-arm64': 0.23.1 + '@esbuild/freebsd-x64': 0.23.1 + '@esbuild/linux-arm': 0.23.1 + '@esbuild/linux-arm64': 0.23.1 + '@esbuild/linux-ia32': 0.23.1 + '@esbuild/linux-loong64': 0.23.1 + '@esbuild/linux-mips64el': 0.23.1 + '@esbuild/linux-ppc64': 0.23.1 + '@esbuild/linux-riscv64': 0.23.1 + '@esbuild/linux-s390x': 0.23.1 + '@esbuild/linux-x64': 0.23.1 + '@esbuild/netbsd-x64': 0.23.1 + '@esbuild/openbsd-arm64': 0.23.1 + '@esbuild/openbsd-x64': 0.23.1 + '@esbuild/sunos-x64': 0.23.1 + '@esbuild/win32-arm64': 0.23.1 + '@esbuild/win32-ia32': 0.23.1 + '@esbuild/win32-x64': 0.23.1 + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.5 + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.3 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + expand-template@2.0.3: {} + + fast-glob@3.3.2: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fastq@1.17.1: + dependencies: + reusify: 1.0.4 + + fetch-blob@3.2.0: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.3.3 + + file-uri-to-path@1.0.0: {} + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + foreground-child@3.3.0: + dependencies: + cross-spawn: 7.0.3 + signal-exit: 4.1.0 + + formdata-polyfill@4.0.10: + dependencies: + fetch-blob: 3.2.0 + + fs-constants@1.0.0: {} + + fsevents@2.3.3: + optional: true + + get-func-name@2.0.2: {} + + get-stream@6.0.1: {} + + github-from-package@0.0.0: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob@10.4.5: + dependencies: + foreground-child: 3.3.0 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.0 + path-scurry: 1.11.1 + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.2 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + human-signals@2.1.0: {} + + ieee754@1.2.1: {} + + ignore@5.3.2: {} + + inherits@2.0.4: {} + + ini@1.3.8: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-stream@2.0.1: {} + + isexe@2.0.0: {} + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + joycon@3.1.1: {} + + js-base64@3.7.7: {} + + libsql@0.4.5: + dependencies: + '@neon-rs/load': 0.0.4 + detect-libc: 2.0.2 + optionalDependencies: + '@libsql/darwin-arm64': 0.4.5 + '@libsql/darwin-x64': 0.4.5 + '@libsql/linux-arm64-gnu': 0.4.5 + '@libsql/linux-arm64-musl': 0.4.5 + '@libsql/linux-x64-gnu': 0.4.5 + '@libsql/linux-x64-musl': 0.4.5 + '@libsql/win32-x64-msvc': 0.4.5 + + lilconfig@3.1.2: {} + + lines-and-columns@1.2.4: {} + + load-tsconfig@0.2.5: {} + + lodash.sortby@4.7.0: {} + + loupe@3.1.1: + dependencies: + get-func-name: 2.0.2 + + lru-cache@10.4.3: {} + + magic-string@0.30.11: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mimic-fn@2.1.0: {} + + mimic-response@3.1.0: {} + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + + minimist@1.2.8: {} + + minipass@7.1.2: {} + + mkdirp-classic@0.5.3: {} + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.7: {} + + napi-build-utils@1.0.2: {} + + node-abi@3.67.0: + dependencies: + semver: 7.6.3 + + node-domexception@1.0.0: {} + + node-fetch@3.3.2: + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + + normalize-path@3.0.0: {} + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + object-assign@4.1.1: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + package-json-from-dist@1.0.0: {} + + path-key@3.1.1: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + path-type@4.0.0: {} + + pathe@1.1.2: {} + + pathval@2.0.0: {} + + picocolors@1.1.0: {} + + picomatch@2.3.1: {} + + pirates@4.0.6: {} + + postcss-load-config@6.0.1(postcss@8.4.47): + dependencies: + lilconfig: 3.1.2 + optionalDependencies: + postcss: 8.4.47 + + postcss@8.4.47: + dependencies: + nanoid: 3.3.7 + picocolors: 1.1.0 + source-map-js: 1.2.1 + + prebuild-install@7.1.2: + dependencies: + detect-libc: 2.0.2 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 1.0.2 + node-abi: 3.67.0 + pump: 3.0.2 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.1 + tunnel-agent: 0.6.0 + + promise-limit@2.7.0: {} + + pump@3.0.2: + dependencies: + end-of-stream: 1.4.4 + once: 1.4.0 + + punycode@2.3.1: {} + + queue-microtask@1.2.3: {} + + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + resolve-from@5.0.0: {} + + reusify@1.0.4: {} + + rollup@4.21.3: + dependencies: + '@types/estree': 1.0.5 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.21.3 + '@rollup/rollup-android-arm64': 4.21.3 + '@rollup/rollup-darwin-arm64': 4.21.3 + '@rollup/rollup-darwin-x64': 4.21.3 + '@rollup/rollup-linux-arm-gnueabihf': 4.21.3 + '@rollup/rollup-linux-arm-musleabihf': 4.21.3 + '@rollup/rollup-linux-arm64-gnu': 4.21.3 + '@rollup/rollup-linux-arm64-musl': 4.21.3 + '@rollup/rollup-linux-powerpc64le-gnu': 4.21.3 + '@rollup/rollup-linux-riscv64-gnu': 4.21.3 + '@rollup/rollup-linux-s390x-gnu': 4.21.3 + '@rollup/rollup-linux-x64-gnu': 4.21.3 + '@rollup/rollup-linux-x64-musl': 4.21.3 + '@rollup/rollup-win32-arm64-msvc': 4.21.3 + '@rollup/rollup-win32-ia32-msvc': 4.21.3 + '@rollup/rollup-win32-x64-msvc': 4.21.3 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-buffer@5.2.1: {} + + semver@7.6.3: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + siginfo@2.0.0: {} + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + simple-concat@1.0.1: {} + + simple-get@4.0.1: + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + + slash@3.0.0: {} + + source-map-js@1.2.1: {} + + source-map@0.8.0-beta.0: + dependencies: + whatwg-url: 7.1.0 + + stackback@0.0.2: {} + + std-env@3.7.0: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.1.0 + + strip-final-newline@2.0.0: {} + + strip-json-comments@2.0.1: {} + + sucrase@3.35.0: + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + commander: 4.1.1 + glob: 10.4.5 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.6 + ts-interface-checker: 0.1.13 + + tar-fs@2.1.1: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.2 + tar-stream: 2.2.0 + + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.4 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + tinybench@2.9.0: {} + + tinyexec@0.3.0: {} + + tinypool@1.0.1: {} + + tinyrainbow@1.2.0: {} + + tinyspy@3.0.2: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tr46@1.0.1: + dependencies: + punycode: 2.3.1 + + tree-kill@1.2.2: {} + + ts-interface-checker@0.1.13: {} + + tsup@8.2.4(postcss@8.4.47): + dependencies: + bundle-require: 5.0.0(esbuild@0.23.1) + cac: 6.7.14 + chokidar: 3.6.0 + consola: 3.2.3 + debug: 4.3.7 + esbuild: 0.23.1 + execa: 5.1.1 + globby: 11.1.0 + joycon: 3.1.1 + picocolors: 1.1.0 + postcss-load-config: 6.0.1(postcss@8.4.47) + resolve-from: 5.0.0 + rollup: 4.21.3 + source-map: 0.8.0-beta.0 + sucrase: 3.35.0 + tree-kill: 1.2.2 + optionalDependencies: + postcss: 8.4.47 + transitivePeerDependencies: + - jiti + - supports-color + - tsx + - yaml + + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.1 + + undici-types@6.19.8: {} + + util-deprecate@1.0.2: {} + + vite-node@2.1.1(@types/node@22.5.5): + dependencies: + cac: 6.7.14 + debug: 4.3.7 + pathe: 1.1.2 + vite: 5.4.5(@types/node@22.5.5) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + vite@5.4.5(@types/node@22.5.5): + dependencies: + esbuild: 0.21.5 + postcss: 8.4.47 + rollup: 4.21.3 + optionalDependencies: + '@types/node': 22.5.5 + fsevents: 2.3.3 + + vitest@2.1.1(@types/node@22.5.5): + dependencies: + '@vitest/expect': 2.1.1 + '@vitest/mocker': 2.1.1(@vitest/spy@2.1.1)(vite@5.4.5(@types/node@22.5.5)) + '@vitest/pretty-format': 2.1.1 + '@vitest/runner': 2.1.1 + '@vitest/snapshot': 2.1.1 + '@vitest/spy': 2.1.1 + '@vitest/utils': 2.1.1 + chai: 5.1.1 + debug: 4.3.7 + magic-string: 0.30.11 + pathe: 1.1.2 + std-env: 3.7.0 + tinybench: 2.9.0 + tinyexec: 0.3.0 + tinypool: 1.0.1 + tinyrainbow: 1.2.0 + vite: 5.4.5(@types/node@22.5.5) + vite-node: 2.1.1(@types/node@22.5.5) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.5.5 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + web-streams-polyfill@3.3.3: {} + + webidl-conversions@4.0.2: {} + + whatwg-url@7.1.0: + dependencies: + lodash.sortby: 4.7.0 + tr46: 1.0.1 + webidl-conversions: 4.0.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + + wrappy@1.0.2: {} + + ws@8.18.0: {} + + zod@3.23.8: {} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..1723be7 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,98 @@ +import type { ConnectorName, Database } from "db0"; +import z from "zod"; +import consola from "consola"; +import { SqliteTableChecker } from "./lib/sqlite-table-checker"; + +export type supportedConnectors = Extract; +const CONNECTOR_NAME = ["sqlite", "libsql", "bun-sqlite"] as const satisfies supportedConnectors[]; + +const DatabaseSchema = z.object({ + exec: z.function(), + prepare: z.function(), + sql: z.function(), +}); + +const TableNamesSchema = z.object({ + users: z.string().min(1), + sessions: z.string().min(1), +}); + +export interface tableNames { + users: string + sessions: string + oauthAccounts: string +} + +export function checkDatabaseValidity( + db: unknown, + tableNames: unknown, +): Database { + if (!db) { + throw new Error("No database to check, please provide one"); + } + + if (!tableNames) { + throw new Error( + "No tableNames provided for SlipAuth: { users: string, sessions: string, oauthAccounts: string }", + ); + } + + const { success: tableNamesSuccess } = TableNamesSchema.safeParse(tableNames); + + if (!tableNamesSuccess) { + throw new Error( + "tableNames provided for SlipAuth are incorrect, { users: string, sessions: string }", + ); + } + + const { data: validatedDatabase, success: databaseValidity } + = DatabaseSchema.safeParse(db); + if (!databaseValidity) { + throw new Error( + "The provided database is not a valid db0 database, see https://github.com/unjs/db0", + ); + } + + return validatedDatabase as Database; +} + +export async function checkDbAndTables( + _database: Database, + connectorType: supportedConnectors, + tableNames: tableNames, +): Promise { + const database = checkDatabaseValidity(_database, tableNames); + + let tableChecker: SqliteTableChecker; + + switch (connectorType) { + case "sqlite": + tableChecker = new SqliteTableChecker(database, tableNames); + break; + case "libsql": + tableChecker = new SqliteTableChecker(database, tableNames); + break; + case "bun-sqlite": + tableChecker = new SqliteTableChecker(database, tableNames); + break; + default: + throw new Error( + `Invalid enum value. Expected ${CONNECTOR_NAME.map(name => `'${name}'`).join(" | ")}, received '${connectorType}'`, + ); + } + + const isUserTableOk = await tableChecker.checkUserTable(); + consola.success(`Table "${tableNames.users}" exists and has a valid schema`); + + const isSessionTableOk = await tableChecker.checkSessionTable(); + consola.success( + `Table "${tableNames.sessions}" exists and has a valid schema`, + ); + + const isOauthTableOk = await tableChecker.checkOauthAccountTable(); + consola.success( + `Table "${tableNames.oauthAccounts}" exists and has a valid schema`, + ); + + return isUserTableOk && isSessionTableOk && isOauthTableOk; +} diff --git a/src/lib/schema.ts b/src/lib/schema.ts new file mode 100644 index 0000000..cbcc068 --- /dev/null +++ b/src/lib/schema.ts @@ -0,0 +1,38 @@ +import { sqliteTable, text, integer, primaryKey } from "drizzle-orm/sqlite-core"; +import type { tableNames } from ".."; +import { sql } from "drizzle-orm"; + +const datesColumns = { + created_at: integer("created_at", { mode: "timestamp" }).notNull().default(sql`CURRENT_TIMESTAMP`), + update_at: integer("updated_at", { mode: "timestamp" }).notNull().default(sql`CURRENT_TIMESTAMP`), +}; + +export const getUsersTableSchema = (tableNames: tableNames) => sqliteTable(tableNames.users, { + id: text("id").primaryKey().notNull(), + password: text("password"), + email: text("email").notNull().unique(), + ...datesColumns, +}); + +export const getSessionsTableSchema = (tableNames: tableNames) => sqliteTable(tableNames.sessions, { + id: text("id").primaryKey().notNull(), + expires_at: integer("expires_at").notNull(), + ip: text("ip"), + ua: text("ua"), + user_id: text("user_id") + .references(() => getUsersTableSchema(tableNames).id) + .notNull(), + ...datesColumns, +}); + +// https://lucia-auth.com/guides/oauth/multiple-providers +export const getOAuthAccountsTableSchema = (tableNames: tableNames) => sqliteTable(tableNames.oauthAccounts, { + provider_id: text("provider_id").notNull(), + provider_user_id: text("provider_user_id").notNull(), + user_id: text("user_id") + .references(() => getUsersTableSchema(tableNames).id) + .notNull(), + ...datesColumns, +}, slipAuthOAuthAccounts => ({ + pk: primaryKey(slipAuthOAuthAccounts.provider_id, slipAuthOAuthAccounts.provider_user_id), +})); diff --git a/src/lib/sqlite-table-checker.ts b/src/lib/sqlite-table-checker.ts new file mode 100644 index 0000000..af6beb5 --- /dev/null +++ b/src/lib/sqlite-table-checker.ts @@ -0,0 +1,150 @@ +import { type PrimaryKey, getTableConfig, type SQLiteColumn, type ForeignKey } from "drizzle-orm/sqlite-core"; +import { getTableName } from "drizzle-orm"; +import type { Database } from "db0"; +import { z } from "zod"; +import { TableChecker } from "./table-checker"; +import { getUsersTableSchema, getOAuthAccountsTableSchema, getSessionsTableSchema } from "./schema"; + +// #region HELPERS +const sqliteTableInfoRowSchema = z.object({ + cid: z.number(), + name: z.string(), + type: z.string(), + notnull: z.number(), + dflt_value: z.any(), + pk: z.number(), +}); + +const sqliteDrizzleColumnTypeMapping = { + SQLiteText: "TEXT", + SQLiteInteger: "INTEGER", + SQLiteTimestamp: "TIMESTAMP", +}; +function getSQLiteColumType(drizzleColumnType: string) { + return sqliteDrizzleColumnTypeMapping[drizzleColumnType as keyof typeof sqliteDrizzleColumnTypeMapping] || drizzleColumnType; +} + +function createSQLiteTableExistSchema(tableName: string) { + return z + .array(sqliteTableInfoRowSchema) + .min(1, `${tableName} table for SLIP does not exist`); +} + +const findColumnInSQLiteTableInfo = (source: T[], columnName: string) => { + return source.find(columnFromSQLite => columnFromSQLite.name === columnName); +}; +const findColumnInSQLiteTableForeignKeys = (source: T[], columnName: string) => { + return source.find(columnFromSQLite => columnFromSQLite.from === columnName); +}; +// #endregion + +async function validateDabaseWithSchema(db: Database, tableName: string, drizzleTableInfos: { columns: SQLiteColumn[], primaryKeys: PrimaryKey[], foreignKeys: ForeignKey[] }): Promise { + const maybeTableInfo = await db.prepare(`PRAGMA table_info(${tableName})`).all(); + const { success, error, data: tableInfo } = createSQLiteTableExistSchema(tableName).safeParse(maybeTableInfo); + + if (!success) { + throw new Error(error.errors[0].message); + }; + + // Check if all columns from schema exist in SQLite table + for (const columnFromSchema of drizzleTableInfos.columns) { + const correspondingColumn = findColumnInSQLiteTableInfo(tableInfo, columnFromSchema.name); + + if (!correspondingColumn) { + return `${tableName} table must contain a column with name "${columnFromSchema.name}"`; + } + + if (correspondingColumn.type !== getSQLiteColumType(columnFromSchema.columnType)) { + return `${tableName} table must contain a column "${columnFromSchema.name}" with type "${getSQLiteColumType(columnFromSchema.columnType)}"`; + } + + const primaryKeysColumnsNames = drizzleTableInfos.primaryKeys.at(0)?.columns.map(col => col.name); + if ((columnFromSchema.primary || primaryKeysColumnsNames?.includes(columnFromSchema.name)) && correspondingColumn.pk < 1) { + return `${tableName} table must contain a column "${columnFromSchema.name}" as primary key`; + } + + if (columnFromSchema.notNull && correspondingColumn.notnull !== 1) { + return `${tableName} table must contain a column "${columnFromSchema.name}" not nullable`; + } + + const indexesInTableSQLite = (await db + .prepare(`PRAGMA INDEX_LIST(${tableName})`) + .all()) as Array<{ name: string, origin: string, unique: number }>; + + const uniqueIndexesSQLite = await Promise.all(indexesInTableSQLite.filter((indexData) => { + return indexData.origin === "u" && indexData.unique === 1; + }).map((uniqueIndex) => { + return db + .prepare(`PRAGMA index_info(${uniqueIndex.name})`) + .all() as Promise>; + })); + + if (columnFromSchema.isUnique && uniqueIndexesSQLite.find(uniqueIndex => columnFromSchema.name === uniqueIndex.at(0)?.name) === undefined) { + return `${tableName} table must contain a column "${columnFromSchema.name}" unique`; + } + + const defaultData = columnFromSchema.default ? columnFromSchema.default as { queryChunks?: Array<{ value?: string[] }> } : null; + const defaultValue = defaultData?.queryChunks?.at(0)?.value?.at(0); + + if (Boolean(columnFromSchema.hasDefault && defaultValue) && (!correspondingColumn.dflt_value || correspondingColumn.dflt_value !== defaultValue)) { + return `${tableName} table must contain a column with name "${columnFromSchema.name}" with default value of ${defaultValue}`; + } + } + + const foreignKeysTable = drizzleTableInfos.foreignKeys; + const foreignKeysSQLite = (await db + .prepare(`PRAGMA foreign_key_list(${tableName})`) + .all()) as Array<{ table: string, from: string, to: string, name: string }>; + + for (const foreignKeyData of foreignKeysTable) { + const reference = foreignKeyData.reference(); + + for (const foreignKeyColumn of reference.columns) { + const fcorrespondingColumn = findColumnInSQLiteTableForeignKeys(foreignKeysSQLite, foreignKeyColumn.name); + + if (!fcorrespondingColumn) { + return `${tableName} table should have a foreign key "${foreignKeyColumn.name}"`; + } + + const targetTableName = getTableName(reference.foreignTable); + const targetColumnName = reference.foreignColumns[0].name; + if (fcorrespondingColumn.table !== targetTableName || fcorrespondingColumn.to !== targetColumnName) { + return `foreign key "${fcorrespondingColumn.from}" in ${tableName} table should target "${targetColumnName}" column from the "${targetTableName}" table`; + } + } + } + + return null; +} + +export class SqliteTableChecker extends TableChecker { + override async checkUserTable() { + const error = await validateDabaseWithSchema(this.dbClient, this.tableNames.users, getTableConfig(getUsersTableSchema(this.tableNames))); + + if (error) { + throw new Error(error); + } + + return true; + } + + override async checkSessionTable() { + const error = await validateDabaseWithSchema(this.dbClient, this.tableNames.sessions, getTableConfig(getSessionsTableSchema(this.tableNames))); + + if (error) { + throw new Error(error); + } + + return true; + } + + override async checkOauthAccountTable() { + const error = await validateDabaseWithSchema(this.dbClient, this.tableNames.oauthAccounts, getTableConfig(getOAuthAccountsTableSchema(this.tableNames))); + + if (error) { + throw new Error(error); + } + + return true; + } +} diff --git a/src/lib/table-checker.ts b/src/lib/table-checker.ts new file mode 100644 index 0000000..c519218 --- /dev/null +++ b/src/lib/table-checker.ts @@ -0,0 +1,24 @@ +import type { Database } from "db0"; +import type { tableNames } from ".."; + +export class TableChecker { + dbClient: Database; + tableNames: tableNames; + + constructor(dbClient: Database, tableNames: tableNames) { + this.dbClient = dbClient; + this.tableNames = tableNames; + } + + async checkUserTable(): Promise { + throw new Error("checkUserTable not implemented"); + } + + async checkSessionTable(): Promise { + throw new Error("checkSessionTable not implemented"); + } + + async checkOauthAccountTable(): Promise { + throw new Error("checkOauthAccountTable not implemented"); + } +} diff --git a/tests/bun-sqlite.test.bun b/tests/bun-sqlite.test.bun new file mode 100644 index 0000000..374ce26 --- /dev/null +++ b/tests/bun-sqlite.test.bun @@ -0,0 +1,432 @@ +import { describe, it, expect, beforeEach } from "bun:test"; +import bunSqlite from "db0/connectors/bun-sqlite"; +import { createDatabase } from "db0"; +import { checkDbAndTables } from "../../src/runtime/database"; + +function testFunction() { + return checkDbAndTables(db, "bun-sqlite", { + users: "slip_users", + sessions: "slip_sessions", + oauthAccounts: "slip_oauth_accounts", + }); +} + +const db = createDatabase(bunSqlite({ +})); + +beforeEach(async () => { + await db.sql`DROP TABLE IF EXISTS slip_users`; + await db.sql`DROP TABLE IF EXISTS slip_sessions`; + await db.sql`DROP TABLE IF EXISTS slip_oauth_accounts`; +}); + +describe("sqlite connector", () => { + describe("users table", () => { + describe("id field", () => { + it("should throw an error when users table does not exist in database", async () => { + await expect(testFunction()).rejects.toThrowError("slip_users table for SLIP does not exist"); + }); + + it("should throw an error when users table does not have an id field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("notid" TEXT PRIMARY KEY)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column with name \"id\"", + ); + }); + + it("should throw an error when users table does not have an id field as primary key", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column \"id\" as primary key", + ); + }); + + it("should throw an error when users table does not have an id field with type of text", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" INTEGER PRIMARY KEY)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column \"id\" with type \"TEXT\"", + ); + }); + + it("should throw an error when users table does not have a not nullable id field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT PRIMARY KEY, "email" TEXT)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column \"id\" not nullable", + ); + }); + }); + + describe("password field", () => { + it("should throw an error when users table does not have a password field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "email" TEXT NOT NULL UNIQUE)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column with name \"password\"", + ); + }); + + it("should throw an error when users table does not have an email field with type of text", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "password" INTEGER)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column \"password\" with type \"TEXT\"", + ); + }); + }); + + describe("email field", () => { + it("should throw an error when users table does not have an email field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "password" TEXT)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column with name \"email\"", + ); + }); + + it("should throw an error when users table does not have an email field with type of text", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "password" TEXT, "email" INTEGER)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column \"email\" with type \"TEXT\"", + ); + }); + + it("should throw an error when users table does not have an not nullable email field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "password" TEXT, "email" TEXT)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column \"email\" not nullable", + ); + }); + + it("should throw an error when users table does not have a unique email field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "password" TEXT, "email" TEXT NOT NULL)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column \"email\" unique", + ); + }); + }); + + describe("created_at field", () => { + it("should throw an error when users table does not have an created_at field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "email" TEXT NOT NULL UNIQUE, "password" TEXT)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column with name \"created_at\"", + ); + }); + + it("should throw an error when users table does not have an created_at field with type of text", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "email" TEXT NOT NULL UNIQUE, "password" TEXT, "created_at" INTEGER)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column \"created_at\" with type \"TIMESTAMP\"", + ); + }); + + it("should throw an error when users table does not have an not nullable created_at field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "email" TEXT NOT NULL UNIQUE, "password" TEXT, "created_at" TIMESTAMP)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column \"created_at\" not nullable", + ); + }); + + it("should throw an error when users table does not have a default value of CURRENT_TIMESTAMP at created_at field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "email" TEXT NOT NULL UNIQUE, "password" TEXT, "created_at" TIMESTAMP NOT NULL)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column with name \"created_at\" with default value of CURRENT_TIMESTAMP", + ); + }); + }); + + describe("updated_at field", () => { + it("should throw an error when users table does not have an updated_at field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "email" TEXT NOT NULL UNIQUE, "password" TEXT, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column with name \"updated_at\"", + ); + }); + + it("should throw an error when users table does not have an updated_at field with type of text", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "email" TEXT NOT NULL UNIQUE, "password" TEXT, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" INTEGER)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column \"updated_at\" with type \"TIMESTAMP\"", + ); + }); + + it("should throw an error when users table does not have an not nullable updated_at field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "email" TEXT NOT NULL UNIQUE, "password" TEXT, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column \"updated_at\" not nullable", + ); + }); + + it("should throw an error when users table does not have a default value of CURRENT_TIMESTAMP at updated_at field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "email" TEXT NOT NULL UNIQUE, "password" TEXT, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP NOT NULL)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column with name \"updated_at\" with default value of CURRENT_TIMESTAMP", + ); + }); + }); + }); + + describe("sessions table", () => { + const validUsersTableSetup = () => + db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "email" TEXT NOT NULL UNIQUE, "password" TEXT, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP)`; + + beforeEach(async () => { + await validUsersTableSetup(); + }); + + describe("id field", () => { + it("should throw an error when sessions table does not exist in database", async () => { + await expect(testFunction()).rejects.toThrowError("slip_sessions table for SLIP does not exist"); + }); + + it("should throw an error when sessions table does not have an id field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("notid" TEXT)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table must contain a column with name \"id\"", + ); + }); + + it("should throw an error when sessions table does not have an id field as primary key", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table must contain a column \"id\" as primary key", + ); + }); + + it("should throw an error when sessions table does not have an id field with type of text", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" INTEGER PRIMARY KEY)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table must contain a column \"id\" with type \"TEXT\"", + ); + }); + + it("should throw an error when sessions table does not have a not nullable id field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT PRIMARY KEY)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table must contain a column \"id\" not nullable", + ); + }); + }); + + describe("expires_at field", () => { + it("should throw an error when sessions table does not have an expires_at field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table must contain a column with name \"expires_at\"", + ); + }); + + it("should throw an error when sessions table does not have an expires_at field with type of number", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY, "expires_at" DATE)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table must contain a column \"expires_at\" with type \"INTEGER\"", + ); + }); + + it("should throw an error when sessions table does not have an not nullable expires_at field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY, "expires_at" INTEGER)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table must contain a column \"expires_at\" not nullable", + ); + }); + }); + + describe("ip field", () => { + it("should throw an error when sessions table does not have an ip field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY, "expires_at" INTEGER NOT NULL)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table must contain a column with name \"ip\"", + ); + }); + + it("should throw an error when sessions table does not have an ip field with type of text", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY, "expires_at" INTEGER NOT NULL, "ip" INTEGER)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table must contain a column \"ip\" with type \"TEXT\"", + ); + }); + }); + + describe("ua field", () => { + it("should throw an error when sessions table does not have an ua field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY, "expires_at" INTEGER NOT NULL, "ip" TEXT)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table must contain a column with name \"ua\"", + ); + }); + + it("should throw an error when sessions table does not have an ua field with type of text", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY, "expires_at" INTEGER NOT NULL, "ip" TEXT, "ua" INTEGER)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table must contain a column \"ua\" with type \"TEXT\"", + ); + }); + }); + + describe("user_id field", () => { + it("should throw an error when sessions table does not have a user_id foreign key", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY, "expires_at" INTEGER NOT NULL, "user_id" TEXT NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "ip" TEXT, "ua" TEXT)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table should have a foreign key \"user_id\"", + ); + }); + + it("should throw an error when sessions table does not have an user_id field with type of text", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY, "expires_at" INTEGER NOT NULL, "ip" TEXT, "ua" TEXT, "user_id" BLOB)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table must contain a column \"user_id\" with type \"TEXT\"", + ); + }); + + it("should throw an error when sessions table does not have an not nullable user_id field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY, "expires_at" INTEGER NOT NULL, "ip" TEXT, "ua" TEXT, "user_id" TEXT)`; + + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table must contain a column \"user_id\" not nullable", + ); + }); + + it("should throw an error when sessions table does not have a user_id foreign key", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY, "expires_at" INTEGER NOT NULL, "user_id" TEXT NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "ip" TEXT, "ua" TEXT)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table should have a foreign key \"user_id\"", + ); + }); + + it("should throw an error when sessions table does not have a user_id foreign key to user table", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS othertable ("id" TEXT)`; + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY, "expires_at" INTEGER NOT NULL, "user_id" TEXT NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "ip" TEXT, "ua" TEXT, FOREIGN KEY (user_id) REFERENCES othertable(id))`; + await expect(testFunction()).rejects.toThrowError( + "foreign key \"user_id\" in slip_sessions table should target \"id\" column from the \"slip_users\" table", + ); + }); + + it("should throw an error when sessions table does not have a user_id foreign key to user table \"id\" column", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY, "expires_at" INTEGER NOT NULL, "user_id" TEXT NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "ip" TEXT, "ua" TEXT, FOREIGN KEY (user_id) REFERENCES slip_users(email))`; + await expect(testFunction()).rejects.toThrowError( + "foreign key \"user_id\" in slip_sessions table should target \"id\" column from the \"slip_users\" table", + ); + }); + }); + }); + + describe("slip_oauth_accounts table", () => { + const validUsersTableSetup = () => + db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "email" TEXT NOT NULL UNIQUE, "password" TEXT, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP)`; + const validSessionsTableSetup = () => + db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY, "expires_at" INTEGER NOT NULL, "user_id" TEXT NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "ip" TEXT, "ua" TEXT, FOREIGN KEY (user_id) REFERENCES slip_users(id))`; + + beforeEach(async () => { + await validUsersTableSetup(); + await validSessionsTableSetup(); + }); + + describe("provider_id field", () => { + it("should throw an error when oauth table does not exist in database", async () => { + await expect(testFunction()).rejects.toThrowError( + "slip_oauth_accounts table for SLIP does not exist", + ); + }); + + it("should throw an error when oauth table does not have an provider_id field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("notid" TEXT)`; + await expect(testFunction()).rejects.toThrowError( + "slip_oauth_accounts table must contain a column with name \"provider_id\"", + ); + }); + + it("should throw an error when oauth table does not have an provider_id field as primary key", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("provider_id" TEXT)`; + await expect(testFunction()).rejects.toThrowError( + "slip_oauth_accounts table must contain a column \"provider_id\" as primary key", + ); + }); + + it("should throw an error when oauth table does not have an provider_id field with type of text", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("provider_id" INTEGER PRIMARY KEY)`; + await expect(testFunction()).rejects.toThrowError( + "slip_oauth_accounts table must contain a column \"provider_id\" with type \"TEXT\"", + ); + }); + + it("should throw an error when oauth table does not have a not nullable provider_id field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("provider_id" TEXT PRIMARY KEY)`; + await expect(testFunction()).rejects.toThrowError( + "slip_oauth_accounts table must contain a column \"provider_id\" not nullable", + ); + }); + }); + + describe("provider_user_id field", () => { + it("should throw an error when oauth table does not have an provider_user_id field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("provider_id" TEXT NOT NULL PRIMARY KEY)`; + await expect(testFunction()).rejects.toThrowError( + "slip_oauth_accounts table must contain a column with name \"provider_user_id\"", + ); + }); + + it("should throw an error when oauth table does not have an provider_user_id field with type of text", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("provider_id" TEXT NOT NULL PRIMARY KEY, "provider_user_id" INTEGER)`; + await expect(testFunction()).rejects.toThrowError( + "slip_oauth_accounts table must contain a column \"provider_user_id\" with type \"TEXT\"", + ); + }); + + it("should throw an error when oauth table does not have an provider_user_id field as primary key", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("provider_id" TEXT NOT NULL, "provider_user_id" TEXT, PRIMARY KEY (provider_id))`; + await expect(testFunction()).rejects.toThrowError( + "slip_oauth_accounts table must contain a column \"provider_user_id\" as primary key", + ); + }); + + it("should throw an error when oauth table does not have a not nullable provider_user_id field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("provider_id" TEXT NOT NULL, "provider_user_id" TEXT, PRIMARY KEY (provider_id, provider_user_id))`; + await expect(testFunction()).rejects.toThrowError( + "slip_oauth_accounts table must contain a column \"provider_user_id\" not nullable", + ); + }); + }); + + describe("user_id field", () => { + it("should throw an error when slip_oauth_accounts table does not have a user_id foreign key", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("provider_id" TEXT NOT NULL, "provider_user_id" TEXT NOT NULL, "user_id" TEXT NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (provider_id, provider_user_id))`; + await expect(testFunction()).rejects.toThrowError( + "slip_oauth_accounts table should have a foreign key \"user_id\"", + ); + }); + + it("should throw an error when slip_oauth_accounts table does not have an user_id field with type of text", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("provider_id" TEXT NOT NULL, "provider_user_id" TEXT NOT NULL, "user_id" BLOB, PRIMARY KEY (provider_id, provider_user_id))`; + await expect(testFunction()).rejects.toThrowError( + "slip_oauth_accounts table must contain a column \"user_id\" with type \"TEXT\"", + ); + }); + + it("should throw an error when slip_oauth_accounts table does not have an not nullable user_id field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("provider_id" TEXT NOT NULL, "provider_user_id" TEXT NOT NULL, "user_id" TEXT, PRIMARY KEY (provider_id, provider_user_id))`; + await expect(testFunction()).rejects.toThrowError( + "slip_oauth_accounts table must contain a column \"user_id\" not nullable", + ); + }); + + it("should throw an error when slip_oauth_accounts table does not have a user_id foreign key", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("provider_id" TEXT NOT NULL, "provider_user_id" TEXT NOT NULL, "user_id" TEXT NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (provider_id, provider_user_id))`; + await expect(testFunction()).rejects.toThrowError( + "slip_oauth_accounts table should have a foreign key \"user_id\"", + ); + }); + + it("should throw an error when slip_oauth_accounts table does not have a user_id foreign key to user table", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS othertable ("id" TEXT)`; + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("provider_id" TEXT NOT NULL, "provider_user_id" TEXT NOT NULL, "user_id" TEXT NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES othertable(id), PRIMARY KEY (provider_id, provider_user_id))`; + await expect(testFunction()).rejects.toThrowError( + "foreign key \"user_id\" in slip_oauth_accounts table should target \"id\" column from the \"slip_users\" table", + ); + }); + + it("should throw an error when slip_oauth_accounts table does not have a user_id foreign key to user table \"id\" column", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("provider_id" TEXT NOT NULL, "provider_user_id" TEXT NOT NULL, "user_id" TEXT NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES slip_users(email), PRIMARY KEY (provider_id, provider_user_id))`; + await expect(testFunction()).rejects.toThrowError( + "foreign key \"user_id\" in slip_oauth_accounts table should target \"id\" column from the \"slip_users\" table", + ); + }); + }); + }); +}); diff --git a/tests/database.test.ts b/tests/database.test.ts new file mode 100644 index 0000000..b07b985 --- /dev/null +++ b/tests/database.test.ts @@ -0,0 +1,79 @@ +import { describe, it, expect } from "vitest"; +import sqlite from "db0/connectors/better-sqlite3"; +import { createDatabase } from "db0"; +import { checkDatabaseValidity, checkDbAndTables } from "../../src/runtime/database"; + +describe("checkDatabaseValidity", () => { + it("should throw an error when no arguments are provided", () => { + // @ts-expect-error testing no db + expect(() => checkDatabaseValidity()).toThrow( + "No database to check, please provide one", + ); + }); + + it("should throw an error when an invalid database is provided", () => { + const invalidDatabase = {}; + expect(() => + checkDatabaseValidity(invalidDatabase, { + users: "slip_users", + sessions: "slip_sessions", + }), + ).toThrowError( + "The provided database is not a valid db0 database, see https://github.com/unjs/db0", + ); + }); + + it("should throw an error when tableNames are missing", () => { + // @ts-expect-error testing no table names + expect(() => checkDatabaseValidity({})).toThrowError( + "No tableNames provided for SlipAuth: { users: string, sessions: string, oauthAccounts: string }", + ); + }); + + it("should throw an error when tableNames are missing users table", () => { + expect(() => + checkDatabaseValidity({}, { sessions: "slip_sessions" }), + ).toThrowError( + "tableNames provided for SlipAuth are incorrect, { users: string, sessions: string }", + ); + }); + + it("should throw an error when tableNames are missing sessions table", () => { + expect(() => + checkDatabaseValidity({}, { users: "users_sessions" }), + ).toThrowError( + "tableNames provided for SlipAuth are incorrect, { users: string, sessions: string }", + ); + }); + + it("should throw an error when tableNames are from an incorrect type", () => { + expect(() => checkDatabaseValidity({}, new Date())).toThrowError( + "tableNames provided for SlipAuth are incorrect, { users: string, sessions: string }", + ); + }); + + it("should throw an error when tableNames have an empty value for a valid key", () => { + expect(() => + checkDatabaseValidity({}, { users: "users_sessions", sessions: "" }), + ).toThrowError( + "tableNames provided for SlipAuth are incorrect, { users: string, sessions: string }", + ); + }); +}); + +describe("checkAndCreateDb", () => { + it("should throw an error when unsupported connector are provided", async () => { + const db = createDatabase(sqlite({ + name: "database.test", + })); + await expect( + // @ts-expect-error testing unsupported connector + checkDbAndTables(db, "notsupported", { + users: "slip_users", + sessions: "slip_sessions", + }), + ).rejects.toThrowError( + "Invalid enum value. Expected 'sqlite' | 'libsql' | 'bun-sqlite', received 'notsupported'", + ); + }); +}); diff --git a/tests/libsql.test.ts b/tests/libsql.test.ts new file mode 100644 index 0000000..1e270c0 --- /dev/null +++ b/tests/libsql.test.ts @@ -0,0 +1,433 @@ +import { describe, it, expect, beforeEach } from "vitest"; +import libSql from "db0/connectors/libsql/node"; +import { createDatabase } from "db0"; +import { checkDbAndTables } from "../../src/runtime/database"; + +function testFunction() { + return checkDbAndTables(db, "libsql", { + users: "slip_users", + sessions: "slip_sessions", + oauthAccounts: "slip_oauth_accounts", + }); +} + +const db = createDatabase(libSql({ + url: "file:.data/libsql.test.db" }, +)); + +beforeEach(async () => { + await db.sql`DROP TABLE IF EXISTS slip_users`; + await db.sql`DROP TABLE IF EXISTS slip_sessions`; + await db.sql`DROP TABLE IF EXISTS slip_oauth_accounts`; +}); + +describe("sqlite connector", () => { + describe("users table", () => { + describe("id field", () => { + it("should throw an error when users table does not exist in database", async () => { + await expect(testFunction()).rejects.toThrowError("slip_users table for SLIP does not exist"); + }); + + it("should throw an error when users table does not have an id field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("notid" TEXT PRIMARY KEY)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column with name \"id\"", + ); + }); + + it("should throw an error when users table does not have an id field as primary key", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column \"id\" as primary key", + ); + }); + + it("should throw an error when users table does not have an id field with type of text", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" INTEGER PRIMARY KEY)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column \"id\" with type \"TEXT\"", + ); + }); + + it("should throw an error when users table does not have a not nullable id field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT PRIMARY KEY, "email" TEXT)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column \"id\" not nullable", + ); + }); + }); + + describe("password field", () => { + it("should throw an error when users table does not have a password field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "email" TEXT NOT NULL UNIQUE)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column with name \"password\"", + ); + }); + + it("should throw an error when users table does not have an email field with type of text", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "password" INTEGER)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column \"password\" with type \"TEXT\"", + ); + }); + }); + + describe("email field", () => { + it("should throw an error when users table does not have an email field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "password" TEXT)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column with name \"email\"", + ); + }); + + it("should throw an error when users table does not have an email field with type of text", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "password" TEXT, "email" INTEGER)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column \"email\" with type \"TEXT\"", + ); + }); + + it("should throw an error when users table does not have an not nullable email field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "password" TEXT, "email" TEXT)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column \"email\" not nullable", + ); + }); + + it("should throw an error when users table does not have a unique email field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "password" TEXT, "email" TEXT NOT NULL)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column \"email\" unique", + ); + }); + }); + + describe("created_at field", () => { + it("should throw an error when users table does not have an created_at field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "email" TEXT NOT NULL UNIQUE, "password" TEXT)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column with name \"created_at\"", + ); + }); + + it("should throw an error when users table does not have an created_at field with type of text", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "email" TEXT NOT NULL UNIQUE, "password" TEXT, "created_at" INTEGER)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column \"created_at\" with type \"TIMESTAMP\"", + ); + }); + + it("should throw an error when users table does not have an not nullable created_at field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "email" TEXT NOT NULL UNIQUE, "password" TEXT, "created_at" TIMESTAMP)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column \"created_at\" not nullable", + ); + }); + + it("should throw an error when users table does not have a default value of CURRENT_TIMESTAMP at created_at field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "email" TEXT NOT NULL UNIQUE, "password" TEXT, "created_at" TIMESTAMP NOT NULL)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column with name \"created_at\" with default value of CURRENT_TIMESTAMP", + ); + }); + }); + + describe("updated_at field", () => { + it("should throw an error when users table does not have an updated_at field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "email" TEXT NOT NULL UNIQUE, "password" TEXT, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column with name \"updated_at\"", + ); + }); + + it("should throw an error when users table does not have an updated_at field with type of text", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "email" TEXT NOT NULL UNIQUE, "password" TEXT, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" INTEGER)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column \"updated_at\" with type \"TIMESTAMP\"", + ); + }); + + it("should throw an error when users table does not have an not nullable updated_at field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "email" TEXT NOT NULL UNIQUE, "password" TEXT, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column \"updated_at\" not nullable", + ); + }); + + it("should throw an error when users table does not have a default value of CURRENT_TIMESTAMP at updated_at field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "email" TEXT NOT NULL UNIQUE, "password" TEXT, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP NOT NULL)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column with name \"updated_at\" with default value of CURRENT_TIMESTAMP", + ); + }); + }); + }); + + describe("sessions table", () => { + const validUsersTableSetup = () => + db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "email" TEXT NOT NULL UNIQUE, "password" TEXT, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP)`; + + beforeEach(async () => { + await validUsersTableSetup(); + }); + + describe("id field", () => { + it("should throw an error when sessions table does not exist in database", async () => { + await expect(testFunction()).rejects.toThrowError("slip_sessions table for SLIP does not exist"); + }); + + it("should throw an error when sessions table does not have an id field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("notid" TEXT)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table must contain a column with name \"id\"", + ); + }); + + it("should throw an error when sessions table does not have an id field as primary key", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table must contain a column \"id\" as primary key", + ); + }); + + it("should throw an error when sessions table does not have an id field with type of text", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" INTEGER PRIMARY KEY)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table must contain a column \"id\" with type \"TEXT\"", + ); + }); + + it("should throw an error when sessions table does not have a not nullable id field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT PRIMARY KEY)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table must contain a column \"id\" not nullable", + ); + }); + }); + + describe("expires_at field", () => { + it("should throw an error when sessions table does not have an expires_at field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table must contain a column with name \"expires_at\"", + ); + }); + + it("should throw an error when sessions table does not have an expires_at field with type of number", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY, "expires_at" DATE)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table must contain a column \"expires_at\" with type \"INTEGER\"", + ); + }); + + it("should throw an error when sessions table does not have an not nullable expires_at field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY, "expires_at" INTEGER)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table must contain a column \"expires_at\" not nullable", + ); + }); + }); + + describe("ip field", () => { + it("should throw an error when sessions table does not have an ip field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY, "expires_at" INTEGER NOT NULL)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table must contain a column with name \"ip\"", + ); + }); + + it("should throw an error when sessions table does not have an ip field with type of text", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY, "expires_at" INTEGER NOT NULL, "ip" INTEGER)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table must contain a column \"ip\" with type \"TEXT\"", + ); + }); + }); + + describe("ua field", () => { + it("should throw an error when sessions table does not have an ua field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY, "expires_at" INTEGER NOT NULL, "ip" TEXT)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table must contain a column with name \"ua\"", + ); + }); + + it("should throw an error when sessions table does not have an ua field with type of text", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY, "expires_at" INTEGER NOT NULL, "ip" TEXT, "ua" INTEGER)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table must contain a column \"ua\" with type \"TEXT\"", + ); + }); + }); + + describe("user_id field", () => { + it("should throw an error when sessions table does not have a user_id foreign key", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY, "expires_at" INTEGER NOT NULL, "user_id" TEXT NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "ip" TEXT, "ua" TEXT)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table should have a foreign key \"user_id\"", + ); + }); + + it("should throw an error when sessions table does not have an user_id field with type of text", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY, "expires_at" INTEGER NOT NULL, "ip" TEXT, "ua" TEXT, "user_id" BLOB)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table must contain a column \"user_id\" with type \"TEXT\"", + ); + }); + + it("should throw an error when sessions table does not have an not nullable user_id field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY, "expires_at" INTEGER NOT NULL, "ip" TEXT, "ua" TEXT, "user_id" TEXT)`; + + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table must contain a column \"user_id\" not nullable", + ); + }); + + it("should throw an error when sessions table does not have a user_id foreign key", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY, "expires_at" INTEGER NOT NULL, "user_id" TEXT NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "ip" TEXT, "ua" TEXT)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table should have a foreign key \"user_id\"", + ); + }); + + it("should throw an error when sessions table does not have a user_id foreign key to user table", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS othertable ("id" TEXT)`; + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY, "expires_at" INTEGER NOT NULL, "user_id" TEXT NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "ip" TEXT, "ua" TEXT, FOREIGN KEY (user_id) REFERENCES othertable(id))`; + await expect(testFunction()).rejects.toThrowError( + "foreign key \"user_id\" in slip_sessions table should target \"id\" column from the \"slip_users\" table", + ); + }); + + it("should throw an error when sessions table does not have a user_id foreign key to user table \"id\" column", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY, "expires_at" INTEGER NOT NULL, "user_id" TEXT NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "ip" TEXT, "ua" TEXT, FOREIGN KEY (user_id) REFERENCES slip_users(email))`; + await expect(testFunction()).rejects.toThrowError( + "foreign key \"user_id\" in slip_sessions table should target \"id\" column from the \"slip_users\" table", + ); + }); + }); + }); + + describe("slip_oauth_accounts table", () => { + const validUsersTableSetup = () => + db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "email" TEXT NOT NULL UNIQUE, "password" TEXT, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP)`; + const validSessionsTableSetup = () => + db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY, "expires_at" INTEGER NOT NULL, "user_id" TEXT NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "ip" TEXT, "ua" TEXT, FOREIGN KEY (user_id) REFERENCES slip_users(id))`; + + beforeEach(async () => { + await validUsersTableSetup(); + await validSessionsTableSetup(); + }); + + describe("provider_id field", () => { + it("should throw an error when oauth table does not exist in database", async () => { + await expect(testFunction()).rejects.toThrowError( + "slip_oauth_accounts table for SLIP does not exist", + ); + }); + + it("should throw an error when oauth table does not have an provider_id field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("notid" TEXT)`; + await expect(testFunction()).rejects.toThrowError( + "slip_oauth_accounts table must contain a column with name \"provider_id\"", + ); + }); + + it("should throw an error when oauth table does not have an provider_id field as primary key", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("provider_id" TEXT)`; + await expect(testFunction()).rejects.toThrowError( + "slip_oauth_accounts table must contain a column \"provider_id\" as primary key", + ); + }); + + it("should throw an error when oauth table does not have an provider_id field with type of text", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("provider_id" INTEGER PRIMARY KEY)`; + await expect(testFunction()).rejects.toThrowError( + "slip_oauth_accounts table must contain a column \"provider_id\" with type \"TEXT\"", + ); + }); + + it("should throw an error when oauth table does not have a not nullable provider_id field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("provider_id" TEXT PRIMARY KEY)`; + await expect(testFunction()).rejects.toThrowError( + "slip_oauth_accounts table must contain a column \"provider_id\" not nullable", + ); + }); + }); + + describe("provider_user_id field", () => { + it("should throw an error when oauth table does not have an provider_user_id field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("provider_id" TEXT NOT NULL PRIMARY KEY)`; + await expect(testFunction()).rejects.toThrowError( + "slip_oauth_accounts table must contain a column with name \"provider_user_id\"", + ); + }); + + it("should throw an error when oauth table does not have an provider_user_id field with type of text", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("provider_id" TEXT NOT NULL PRIMARY KEY, "provider_user_id" INTEGER)`; + await expect(testFunction()).rejects.toThrowError( + "slip_oauth_accounts table must contain a column \"provider_user_id\" with type \"TEXT\"", + ); + }); + + it("should throw an error when oauth table does not have an provider_user_id field as primary key", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("provider_id" TEXT NOT NULL, "provider_user_id" TEXT, PRIMARY KEY (provider_id))`; + await expect(testFunction()).rejects.toThrowError( + "slip_oauth_accounts table must contain a column \"provider_user_id\" as primary key", + ); + }); + + it("should throw an error when oauth table does not have a not nullable provider_user_id field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("provider_id" TEXT NOT NULL, "provider_user_id" TEXT, PRIMARY KEY (provider_id, provider_user_id))`; + await expect(testFunction()).rejects.toThrowError( + "slip_oauth_accounts table must contain a column \"provider_user_id\" not nullable", + ); + }); + }); + + describe("user_id field", () => { + it("should throw an error when slip_oauth_accounts table does not have a user_id foreign key", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("provider_id" TEXT NOT NULL, "provider_user_id" TEXT NOT NULL, "user_id" TEXT NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (provider_id, provider_user_id))`; + await expect(testFunction()).rejects.toThrowError( + "slip_oauth_accounts table should have a foreign key \"user_id\"", + ); + }); + + it("should throw an error when slip_oauth_accounts table does not have an user_id field with type of text", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("provider_id" TEXT NOT NULL, "provider_user_id" TEXT NOT NULL, "user_id" BLOB, PRIMARY KEY (provider_id, provider_user_id))`; + await expect(testFunction()).rejects.toThrowError( + "slip_oauth_accounts table must contain a column \"user_id\" with type \"TEXT\"", + ); + }); + + it("should throw an error when slip_oauth_accounts table does not have an not nullable user_id field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("provider_id" TEXT NOT NULL, "provider_user_id" TEXT NOT NULL, "user_id" TEXT, PRIMARY KEY (provider_id, provider_user_id))`; + await expect(testFunction()).rejects.toThrowError( + "slip_oauth_accounts table must contain a column \"user_id\" not nullable", + ); + }); + + it("should throw an error when slip_oauth_accounts table does not have a user_id foreign key", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("provider_id" TEXT NOT NULL, "provider_user_id" TEXT NOT NULL, "user_id" TEXT NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (provider_id, provider_user_id))`; + await expect(testFunction()).rejects.toThrowError( + "slip_oauth_accounts table should have a foreign key \"user_id\"", + ); + }); + + it("should throw an error when slip_oauth_accounts table does not have a user_id foreign key to user table", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS othertable ("id" TEXT)`; + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("provider_id" TEXT NOT NULL, "provider_user_id" TEXT NOT NULL, "user_id" TEXT NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES othertable(id), PRIMARY KEY (provider_id, provider_user_id))`; + await expect(testFunction()).rejects.toThrowError( + "foreign key \"user_id\" in slip_oauth_accounts table should target \"id\" column from the \"slip_users\" table", + ); + }); + + it("should throw an error when slip_oauth_accounts table does not have a user_id foreign key to user table \"id\" column", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("provider_id" TEXT NOT NULL, "provider_user_id" TEXT NOT NULL, "user_id" TEXT NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES slip_users(email), PRIMARY KEY (provider_id, provider_user_id))`; + await expect(testFunction()).rejects.toThrowError( + "foreign key \"user_id\" in slip_oauth_accounts table should target \"id\" column from the \"slip_users\" table", + ); + }); + }); + }); +}); diff --git a/tests/sqlite.test.ts b/tests/sqlite.test.ts new file mode 100644 index 0000000..b3a1298 --- /dev/null +++ b/tests/sqlite.test.ts @@ -0,0 +1,433 @@ +import { describe, it, expect, beforeEach } from "vitest"; +import sqlite from "db0/connectors/better-sqlite3"; +import { createDatabase } from "db0"; +import { checkDbAndTables } from "../../src/runtime/database"; + +function testFunction() { + return checkDbAndTables(db, "sqlite", { + users: "slip_users", + sessions: "slip_sessions", + oauthAccounts: "slip_oauth_accounts", + }); +} + +const db = createDatabase(sqlite({ + name: "sqlite.test", +})); + +beforeEach(async () => { + await db.sql`DROP TABLE IF EXISTS slip_users`; + await db.sql`DROP TABLE IF EXISTS slip_sessions`; + await db.sql`DROP TABLE IF EXISTS slip_oauth_accounts`; +}); + +describe("sqlite connector", () => { + describe("users table", () => { + describe("id field", () => { + it("should throw an error when users table does not exist in database", async () => { + await expect(testFunction()).rejects.toThrowError("slip_users table for SLIP does not exist"); + }); + + it("should throw an error when users table does not have an id field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("notid" TEXT PRIMARY KEY)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column with name \"id\"", + ); + }); + + it("should throw an error when users table does not have an id field as primary key", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column \"id\" as primary key", + ); + }); + + it("should throw an error when users table does not have an id field with type of text", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" INTEGER PRIMARY KEY)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column \"id\" with type \"TEXT\"", + ); + }); + + it("should throw an error when users table does not have a not nullable id field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT PRIMARY KEY, "email" TEXT)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column \"id\" not nullable", + ); + }); + }); + + describe("password field", () => { + it("should throw an error when users table does not have a password field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "email" TEXT NOT NULL UNIQUE)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column with name \"password\"", + ); + }); + + it("should throw an error when users table does not have an email field with type of text", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "password" INTEGER)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column \"password\" with type \"TEXT\"", + ); + }); + }); + + describe("email field", () => { + it("should throw an error when users table does not have an email field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "password" TEXT)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column with name \"email\"", + ); + }); + + it("should throw an error when users table does not have an email field with type of text", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "password" TEXT, "email" INTEGER)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column \"email\" with type \"TEXT\"", + ); + }); + + it("should throw an error when users table does not have an not nullable email field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "password" TEXT, "email" TEXT)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column \"email\" not nullable", + ); + }); + + it("should throw an error when users table does not have a unique email field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "password" TEXT, "email" TEXT NOT NULL)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column \"email\" unique", + ); + }); + }); + + describe("created_at field", () => { + it("should throw an error when users table does not have an created_at field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "email" TEXT NOT NULL UNIQUE, "password" TEXT)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column with name \"created_at\"", + ); + }); + + it("should throw an error when users table does not have an created_at field with type of text", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "email" TEXT NOT NULL UNIQUE, "password" TEXT, "created_at" INTEGER)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column \"created_at\" with type \"TIMESTAMP\"", + ); + }); + + it("should throw an error when users table does not have an not nullable created_at field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "email" TEXT NOT NULL UNIQUE, "password" TEXT, "created_at" TIMESTAMP)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column \"created_at\" not nullable", + ); + }); + + it("should throw an error when users table does not have a default value of CURRENT_TIMESTAMP at created_at field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "email" TEXT NOT NULL UNIQUE, "password" TEXT, "created_at" TIMESTAMP NOT NULL)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column with name \"created_at\" with default value of CURRENT_TIMESTAMP", + ); + }); + }); + + describe("updated_at field", () => { + it("should throw an error when users table does not have an updated_at field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "email" TEXT NOT NULL UNIQUE, "password" TEXT, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column with name \"updated_at\"", + ); + }); + + it("should throw an error when users table does not have an updated_at field with type of text", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "email" TEXT NOT NULL UNIQUE, "password" TEXT, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" INTEGER)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column \"updated_at\" with type \"TIMESTAMP\"", + ); + }); + + it("should throw an error when users table does not have an not nullable updated_at field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "email" TEXT NOT NULL UNIQUE, "password" TEXT, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column \"updated_at\" not nullable", + ); + }); + + it("should throw an error when users table does not have a default value of CURRENT_TIMESTAMP at updated_at field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "email" TEXT NOT NULL UNIQUE, "password" TEXT, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP NOT NULL)`; + await expect(testFunction()).rejects.toThrowError( + "slip_users table must contain a column with name \"updated_at\" with default value of CURRENT_TIMESTAMP", + ); + }); + }); + }); + + describe("sessions table", () => { + const validUsersTableSetup = () => + db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "email" TEXT NOT NULL UNIQUE, "password" TEXT, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP)`; + + beforeEach(async () => { + await validUsersTableSetup(); + }); + + describe("id field", () => { + it("should throw an error when sessions table does not exist in database", async () => { + await expect(testFunction()).rejects.toThrowError("slip_sessions table for SLIP does not exist"); + }); + + it("should throw an error when sessions table does not have an id field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("notid" TEXT)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table must contain a column with name \"id\"", + ); + }); + + it("should throw an error when sessions table does not have an id field as primary key", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table must contain a column \"id\" as primary key", + ); + }); + + it("should throw an error when sessions table does not have an id field with type of text", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" INTEGER PRIMARY KEY)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table must contain a column \"id\" with type \"TEXT\"", + ); + }); + + it("should throw an error when sessions table does not have a not nullable id field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT PRIMARY KEY)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table must contain a column \"id\" not nullable", + ); + }); + }); + + describe("expires_at field", () => { + it("should throw an error when sessions table does not have an expires_at field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table must contain a column with name \"expires_at\"", + ); + }); + + it("should throw an error when sessions table does not have an expires_at field with type of number", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY, "expires_at" DATE)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table must contain a column \"expires_at\" with type \"INTEGER\"", + ); + }); + + it("should throw an error when sessions table does not have an not nullable expires_at field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY, "expires_at" INTEGER)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table must contain a column \"expires_at\" not nullable", + ); + }); + }); + + describe("ip field", () => { + it("should throw an error when sessions table does not have an ip field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY, "expires_at" INTEGER NOT NULL)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table must contain a column with name \"ip\"", + ); + }); + + it("should throw an error when sessions table does not have an ip field with type of text", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY, "expires_at" INTEGER NOT NULL, "ip" INTEGER)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table must contain a column \"ip\" with type \"TEXT\"", + ); + }); + }); + + describe("ua field", () => { + it("should throw an error when sessions table does not have an ua field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY, "expires_at" INTEGER NOT NULL, "ip" TEXT)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table must contain a column with name \"ua\"", + ); + }); + + it("should throw an error when sessions table does not have an ua field with type of text", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY, "expires_at" INTEGER NOT NULL, "ip" TEXT, "ua" INTEGER)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table must contain a column \"ua\" with type \"TEXT\"", + ); + }); + }); + + describe("user_id field", () => { + it("should throw an error when sessions table does not have a user_id foreign key", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY, "expires_at" INTEGER NOT NULL, "user_id" TEXT NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "ip" TEXT, "ua" TEXT)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table should have a foreign key \"user_id\"", + ); + }); + + it("should throw an error when sessions table does not have an user_id field with type of text", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY, "expires_at" INTEGER NOT NULL, "ip" TEXT, "ua" TEXT, "user_id" BLOB)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table must contain a column \"user_id\" with type \"TEXT\"", + ); + }); + + it("should throw an error when sessions table does not have an not nullable user_id field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY, "expires_at" INTEGER NOT NULL, "ip" TEXT, "ua" TEXT, "user_id" TEXT)`; + + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table must contain a column \"user_id\" not nullable", + ); + }); + + it("should throw an error when sessions table does not have a user_id foreign key", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY, "expires_at" INTEGER NOT NULL, "user_id" TEXT NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "ip" TEXT, "ua" TEXT)`; + await expect(testFunction()).rejects.toThrowError( + "slip_sessions table should have a foreign key \"user_id\"", + ); + }); + + it("should throw an error when sessions table does not have a user_id foreign key to user table", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS othertable ("id" TEXT)`; + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY, "expires_at" INTEGER NOT NULL, "user_id" TEXT NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "ip" TEXT, "ua" TEXT, FOREIGN KEY (user_id) REFERENCES othertable(id))`; + await expect(testFunction()).rejects.toThrowError( + "foreign key \"user_id\" in slip_sessions table should target \"id\" column from the \"slip_users\" table", + ); + }); + + it("should throw an error when sessions table does not have a user_id foreign key to user table \"id\" column", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY, "expires_at" INTEGER NOT NULL, "user_id" TEXT NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "ip" TEXT, "ua" TEXT, FOREIGN KEY (user_id) REFERENCES slip_users(email))`; + await expect(testFunction()).rejects.toThrowError( + "foreign key \"user_id\" in slip_sessions table should target \"id\" column from the \"slip_users\" table", + ); + }); + }); + }); + + describe("slip_oauth_accounts table", () => { + const validUsersTableSetup = () => + db.sql`CREATE TABLE IF NOT EXISTS slip_users ("id" TEXT NOT NULL PRIMARY KEY, "email" TEXT NOT NULL UNIQUE, "password" TEXT, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP)`; + const validSessionsTableSetup = () => + db.sql`CREATE TABLE IF NOT EXISTS slip_sessions ("id" TEXT NOT NULL PRIMARY KEY, "expires_at" INTEGER NOT NULL, "user_id" TEXT NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "ip" TEXT, "ua" TEXT, FOREIGN KEY (user_id) REFERENCES slip_users(id))`; + + beforeEach(async () => { + await validUsersTableSetup(); + await validSessionsTableSetup(); + }); + + describe("provider_id field", () => { + it("should throw an error when oauth table does not exist in database", async () => { + await expect(testFunction()).rejects.toThrowError( + "slip_oauth_accounts table for SLIP does not exist", + ); + }); + + it("should throw an error when oauth table does not have an provider_id field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("notid" TEXT)`; + await expect(testFunction()).rejects.toThrowError( + "slip_oauth_accounts table must contain a column with name \"provider_id\"", + ); + }); + + it("should throw an error when oauth table does not have an provider_id field as primary key", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("provider_id" TEXT)`; + await expect(testFunction()).rejects.toThrowError( + "slip_oauth_accounts table must contain a column \"provider_id\" as primary key", + ); + }); + + it("should throw an error when oauth table does not have an provider_id field with type of text", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("provider_id" INTEGER PRIMARY KEY)`; + await expect(testFunction()).rejects.toThrowError( + "slip_oauth_accounts table must contain a column \"provider_id\" with type \"TEXT\"", + ); + }); + + it("should throw an error when oauth table does not have a not nullable provider_id field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("provider_id" TEXT PRIMARY KEY)`; + await expect(testFunction()).rejects.toThrowError( + "slip_oauth_accounts table must contain a column \"provider_id\" not nullable", + ); + }); + }); + + describe("provider_user_id field", () => { + it("should throw an error when oauth table does not have an provider_user_id field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("provider_id" TEXT NOT NULL PRIMARY KEY)`; + await expect(testFunction()).rejects.toThrowError( + "slip_oauth_accounts table must contain a column with name \"provider_user_id\"", + ); + }); + + it("should throw an error when oauth table does not have an provider_user_id field with type of text", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("provider_id" TEXT NOT NULL PRIMARY KEY, "provider_user_id" INTEGER)`; + await expect(testFunction()).rejects.toThrowError( + "slip_oauth_accounts table must contain a column \"provider_user_id\" with type \"TEXT\"", + ); + }); + + it("should throw an error when oauth table does not have an provider_user_id field as primary key", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("provider_id" TEXT NOT NULL, "provider_user_id" TEXT, PRIMARY KEY (provider_id))`; + await expect(testFunction()).rejects.toThrowError( + "slip_oauth_accounts table must contain a column \"provider_user_id\" as primary key", + ); + }); + + it("should throw an error when oauth table does not have a not nullable provider_user_id field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("provider_id" TEXT NOT NULL, "provider_user_id" TEXT, PRIMARY KEY (provider_id, provider_user_id))`; + await expect(testFunction()).rejects.toThrowError( + "slip_oauth_accounts table must contain a column \"provider_user_id\" not nullable", + ); + }); + }); + + describe("user_id field", () => { + it("should throw an error when slip_oauth_accounts table does not have a user_id foreign key", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("provider_id" TEXT NOT NULL, "provider_user_id" TEXT NOT NULL, "user_id" TEXT NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (provider_id, provider_user_id))`; + await expect(testFunction()).rejects.toThrowError( + "slip_oauth_accounts table should have a foreign key \"user_id\"", + ); + }); + + it("should throw an error when slip_oauth_accounts table does not have an user_id field with type of text", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("provider_id" TEXT NOT NULL, "provider_user_id" TEXT NOT NULL, "user_id" BLOB, PRIMARY KEY (provider_id, provider_user_id))`; + await expect(testFunction()).rejects.toThrowError( + "slip_oauth_accounts table must contain a column \"user_id\" with type \"TEXT\"", + ); + }); + + it("should throw an error when slip_oauth_accounts table does not have an not nullable user_id field", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("provider_id" TEXT NOT NULL, "provider_user_id" TEXT NOT NULL, "user_id" TEXT, PRIMARY KEY (provider_id, provider_user_id))`; + await expect(testFunction()).rejects.toThrowError( + "slip_oauth_accounts table must contain a column \"user_id\" not nullable", + ); + }); + + it("should throw an error when slip_oauth_accounts table does not have a user_id foreign key", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("provider_id" TEXT NOT NULL, "provider_user_id" TEXT NOT NULL, "user_id" TEXT NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (provider_id, provider_user_id))`; + await expect(testFunction()).rejects.toThrowError( + "slip_oauth_accounts table should have a foreign key \"user_id\"", + ); + }); + + it("should throw an error when slip_oauth_accounts table does not have a user_id foreign key to user table", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS othertable ("id" TEXT)`; + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("provider_id" TEXT NOT NULL, "provider_user_id" TEXT NOT NULL, "user_id" TEXT NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES othertable(id), PRIMARY KEY (provider_id, provider_user_id))`; + await expect(testFunction()).rejects.toThrowError( + "foreign key \"user_id\" in slip_oauth_accounts table should target \"id\" column from the \"slip_users\" table", + ); + }); + + it("should throw an error when slip_oauth_accounts table does not have a user_id foreign key to user table \"id\" column", async () => { + await db.sql`CREATE TABLE IF NOT EXISTS slip_oauth_accounts ("provider_id" TEXT NOT NULL, "provider_user_id" TEXT NOT NULL, "user_id" TEXT NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES slip_users(email), PRIMARY KEY (provider_id, provider_user_id))`; + await expect(testFunction()).rejects.toThrowError( + "foreign key \"user_id\" in slip_oauth_accounts table should target \"id\" column from the \"slip_users\" table", + ); + }); + }); + }); +}); diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..1ed4c78 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + outputFile: { + junit: "./junit.xml", + }, + }, +});