├── .dev.vars.example ├── .env.example ├── .gitignore ├── LICENSE ├── README.md ├── biome.json ├── bun.lock ├── docs ├── drizzle.md ├── lemonsqueezy.md ├── ratelimits.md └── tests.md ├── drizzle.config.ts ├── drizzle ├── 0000_crazy_zuras.sql └── meta │ ├── 0000_snapshot.json │ └── _journal.json ├── package.json ├── scripts └── setup.ts ├── src ├── api.ts ├── auth.ts ├── db │ └── schema.ts ├── index.tsx ├── middleware │ └── rateLimit.ts ├── payment │ ├── lemonsqueezy.ts │ └── types.ts ├── ui │ └── landing.tsx └── utils │ ├── cipher.ts │ └── key.ts ├── test └── index.spec.ts ├── tsconfig.json ├── vitest.config.mts ├── worker-configuration.d.ts └── wrangler.jsonc /.dev.vars.example: -------------------------------------------------------------------------------- 1 | AUTH_GITHUB_ID= 2 | AUTH_GITHUB_SECRET= 3 | BETTER_AUTH_URL=http://localhost:8787 4 | SECRET= 5 | LEMONSQUEEZY_CHECKOUT_LINK= -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | DATABASE_ID= 2 | ACCOUNT_ID= 3 | TOKEN= 4 | NODE_ENV=development -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # prod 2 | dist/ 3 | bun.lock 4 | 5 | # dev 6 | .yarn/ 7 | !.yarn/releases 8 | .vscode/* 9 | !.vscode/launch.json 10 | !.vscode/*.code-snippets 11 | .idea/workspace.xml 12 | .idea/usage.statistics.xml 13 | .idea/shelf 14 | 15 | # deps 16 | node_modules/ 17 | .wrangler 18 | 19 | # env 20 | .env 21 | .env.production 22 | .dev.vars 23 | 24 | # logs 25 | logs/ 26 | *.log 27 | npm-debug.log* 28 | yarn-debug.log* 29 | yarn-error.log* 30 | pnpm-debug.log* 31 | lerna-debug.log* 32 | 33 | # misc 34 | .DS_Store 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Dhravya Shah 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Backend API kit 2 | 3 | Easily create scalable, monetisable backend APIs with Hono + Cloudflare workers 4 | 5 | Why? 6 | While buildling [supermemory](https://api.supermemory.ai), [markdowner](https://md.dhr.wtf), [reranker](https://reranker.dhr.wtf), i found myself solving all the same problems again and again - there's a lot of starter kits for frontend / full stack stuff, but almost none if you just want to build and monetise a small API. 7 | 8 | ## Features 9 | - API Key provisioning 10 | - Rate Limiting 11 | - Authentication with [better-auth](https://better-auth.com) (both session based and Bearer token based) 12 | - Database with [Drizzle](https://orm.drizzle.team) + [Cloudflare D1](https://developers.cloudflare.com/d1) 13 | - Subscriptions with [Lemonsqueezy](https://www.lemonsqueezy.com) 14 | - Landing page for API 15 | - Linting with [Biome](https://biomejs.dev) 16 | - Integration tests with [Vitest](https://vitest.dev) 17 | - and more... 18 | 19 | ## Getting set up 20 | 21 | ### Quick Setup 22 | 23 | 1. Clone & set up the repo 24 | ```bash 25 | git clone https://github.com/dhravya/backend-api-kit.git 26 | cd backend-api-kit 27 | npm i -g bun 28 | bun install 29 | ``` 30 | 31 | 2. Run the setup script 32 | ```bash 33 | bun run setup 34 | ``` 35 | 36 | The setup script will: 37 | - Create a D1 database and configure it in `wrangler.jsonc` 38 | - Set up environment variables in `.dev.vars` 39 | - Run database migrations 40 | - Optionally deploy the worker 41 | 42 | [See guide for setting up lemonsqueezy](docs/lemonsqueezy.md) 43 | 44 | [How to make changes to database schema](docs/drizzle.md) 45 | 46 | [Writing and running tests](docs/tests.md) 47 | 48 | [Changing ratelimits](docs/ratelimits.md) 49 | 50 | If the quick setup script fails, you can [manually set up](#manual-setup) the database and environment variables. 51 | 52 | 53 | ## Development 54 | 55 | Start the development server: 56 | ```bash 57 | bun run dev 58 | ``` 59 | 60 | Now, write your business logic inside [api](src/api.ts) 61 | 62 | Congrats! You're all set for shipping your backend API! 63 | 64 | 65 | ### Manual Setup 66 | 67 | 1. Clone & set up the repo 68 | ```bash 69 | git clone https://github.com/dhravya/backend-api-kit.git 70 | cd backend-api-kit 71 | npm i -g bun 72 | bun install 73 | ``` 74 | 75 | 2. Create a D1 database 76 | ```bash 77 | bunx wrangler d1 create 78 | ``` 79 | 80 | 3. Update `wrangler.jsonc` with your database configuration 81 | ```jsonc 82 | { 83 | // ... other config 84 | "d1_databases": [ 85 | { 86 | "binding": "DATABASE", 87 | "database_name": "", 88 | "database_id": "", 89 | "migrations_dir": "./drizzle" 90 | } 91 | ] 92 | } 93 | ``` 94 | 95 | 4. Set up environment variables 96 | - Copy `.dev.vars.example` to `.dev.vars` 97 | - Fill in the required environment variables: 98 | - `AUTH_GITHUB_ID` and `AUTH_GITHUB_SECRET`: GitHub OAuth credentials 99 | - `BETTER_AUTH_URL`: Your auth URL (default: http://localhost:8787) 100 | - `SECRET`: A secure random string 101 | - `LEMONSQUEEZY_CHECKOUT_LINK`: Your Lemonsqueezy checkout link 102 | 103 | 5. Run database migrations 104 | ```bash 105 | bunx drizzle-kit generate --name setup 106 | bunx wrangler d1 migrations apply 107 | bunx wrangler d1 migrations apply --remote 108 | ``` 109 | 110 | 6. Deploy (optional) 111 | ```bash 112 | bunx wrangler deploy 113 | ``` 114 | 115 | ## License 116 | 117 | This project is open-sourced under the MIT License - see the [LICENSE](LICENSE) file for details. 118 | 119 | ## Contributing 120 | 121 | Contributions are welcome! Please feel free to submit a PR. 122 | 123 | These are some things that contributions would be helpful for: 124 | - Adding or improving documentation 125 | - Adding or improving tests 126 | - More configurations (for eg, adding more auth providers, payment providers, database providers, etc) 127 | - Usage based pricing 128 | 129 | ## Acknowledgements 130 | 131 | This project is built thanks to these open source projects 🙏: 132 | 133 | - [better-auth](https://better-auth.com) for authentication 134 | - [Drizzle](https://orm.drizzle.team) for database 135 | - [Hono](https://hono.dev) for the web framework 136 | - [Vitest](https://vitest.dev) for testing 137 | - [Biome](https://biomejs.dev) for linting 138 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "vcs": { 4 | "enabled": false, 5 | "clientKind": "git", 6 | "useIgnoreFile": false 7 | }, 8 | "files": { 9 | "ignoreUnknown": false, 10 | "ignore": ["drizzle/", "node_modules/", ".wrangler/"] 11 | }, 12 | "formatter": { 13 | "enabled": true, 14 | "indentStyle": "tab" 15 | }, 16 | "organizeImports": { 17 | "enabled": true 18 | }, 19 | "linter": { 20 | "enabled": true, 21 | "rules": { 22 | "recommended": true 23 | } 24 | }, 25 | "javascript": { 26 | "formatter": { 27 | "quoteStyle": "double" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /bun.lock: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1, 3 | "workspaces": { 4 | "": { 5 | "name": "backend-api-kit", 6 | "dependencies": { 7 | "better-auth": "^1.1.14", 8 | "bun": "^1.2.0", 9 | "dotenv": "^16.4.7", 10 | "drizzle-orm": "^0.38.4", 11 | "hono": "^4.6.18", 12 | "nanoid": "^5.0.9", 13 | }, 14 | "devDependencies": { 15 | "@biomejs/biome": "1.9.4", 16 | "@clack/prompts": "^0.9.1", 17 | "@cloudflare/vitest-pool-workers": "0.6.4", 18 | "@cloudflare/workers-types": "^4.20250121.0", 19 | "@types/bun": "^1.2.0", 20 | "better-sqlite3": "^11.8.1", 21 | "child_process": "^1.0.2", 22 | "drizzle-kit": "^0.30.2", 23 | "vitest": "2.1.8", 24 | "wrangler": "^3.105.0", 25 | }, 26 | }, 27 | }, 28 | "packages": { 29 | "@better-auth/utils": ["@better-auth/utils@0.2.3", "", { "dependencies": { "uncrypto": "^0.1.3" } }, "sha512-Ap1GaSmo6JYhJhxJOpUB0HobkKPTNzfta+bLV89HfpyCAHN7p8ntCrmNFHNAVD0F6v0mywFVEUg1FUhNCc81Rw=="], 30 | 31 | "@better-fetch/fetch": ["@better-fetch/fetch@1.1.12", "", {}, "sha512-B3bfloI/2UBQWIATRN6qmlORrvx3Mp0kkNjmXLv0b+DtbtR+pP4/I5kQA/rDUv+OReLywCCldf6co4LdDmh8JA=="], 32 | 33 | "@biomejs/biome": ["@biomejs/biome@1.9.4", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "1.9.4", "@biomejs/cli-darwin-x64": "1.9.4", "@biomejs/cli-linux-arm64": "1.9.4", "@biomejs/cli-linux-arm64-musl": "1.9.4", "@biomejs/cli-linux-x64": "1.9.4", "@biomejs/cli-linux-x64-musl": "1.9.4", "@biomejs/cli-win32-arm64": "1.9.4", "@biomejs/cli-win32-x64": "1.9.4" }, "bin": { "biome": "bin/biome" } }, "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog=="], 34 | 35 | "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@1.9.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw=="], 36 | 37 | "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@1.9.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg=="], 38 | 39 | "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@1.9.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g=="], 40 | 41 | "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@1.9.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA=="], 42 | 43 | "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@1.9.4", "", { "os": "linux", "cpu": "x64" }, "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg=="], 44 | 45 | "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@1.9.4", "", { "os": "linux", "cpu": "x64" }, "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg=="], 46 | 47 | "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@1.9.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg=="], 48 | 49 | "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@1.9.4", "", { "os": "win32", "cpu": "x64" }, "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA=="], 50 | 51 | "@clack/core": ["@clack/core@0.4.1", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-Pxhij4UXg8KSr7rPek6Zowm+5M22rbd2g1nfojHJkxp5YkFqiZ2+YLEM/XGVIzvGOcM0nqjIFxrpDwWRZYWYjA=="], 52 | 53 | "@clack/prompts": ["@clack/prompts@0.9.1", "", { "dependencies": { "@clack/core": "0.4.1", "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-JIpyaboYZeWYlyP0H+OoPPxd6nqueG/CmN6ixBiNFsIDHREevjIf0n0Ohh5gr5C8pEDknzgvz+pIJ8dMhzWIeg=="], 54 | 55 | "@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.3.4", "", { "dependencies": { "mime": "^3.0.0" } }, "sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q=="], 56 | 57 | "@cloudflare/vitest-pool-workers": ["@cloudflare/vitest-pool-workers@0.6.4", "", { "dependencies": { "birpc": "0.2.14", "cjs-module-lexer": "^1.2.3", "devalue": "^4.3.0", "esbuild": "0.17.19", "miniflare": "3.20241230.2", "semver": "^7.5.1", "wrangler": "3.103.2", "zod": "^3.22.3" }, "peerDependencies": { "@vitest/runner": "2.0.x - 2.1.x", "@vitest/snapshot": "2.0.x - 2.1.x", "vitest": "2.0.x - 2.1.x" } }, "sha512-/S875Em7P3jRZASOmE2sCu7lLnYlGXKNkreo71M+SyYb+Vlh2SD8LqTmDydYDe2aRLQtyCcAZWJUzs7ymeDZ3Q=="], 58 | 59 | "@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20241230.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-BZHLg4bbhNQoaY1Uan81O3FV/zcmWueC55juhnaI7NAobiQth9RppadPNpxNAmS9fK2mR5z8xrwMQSQrHmztyQ=="], 60 | 61 | "@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20241230.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-lllxycj7EzYoJ0VOJh8M3palUgoonVrILnzGrgsworgWlIpgjfXGS7b41tEGCw6AxSxL9prmTIGtfSPUvn/rjg=="], 62 | 63 | "@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20241230.0", "", { "os": "linux", "cpu": "x64" }, "sha512-Y3mHcW0KghOmWdNZyHYpEOG4Ba/ga8tht5vj1a+WXfagEjMO8Y98XhZUlCaYa9yB7Wh5jVcK5LM2jlO/BLgqpA=="], 64 | 65 | "@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20241230.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-IAjhsWPlHzhhkJ6I49sDG6XfMnhPvv0szKGXxTWQK/IWMrbGdHm4RSfNKBSoLQm67jGMIzbmcrX9UIkms27Y1g=="], 66 | 67 | "@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20241230.0", "", { "os": "win32", "cpu": "x64" }, "sha512-y5SPIk9iOb2gz+yWtHxoeMnjPnkYQswiCJ480oHC6zexnJLlKTpcmBCjDH1nWCT4pQi8F25gaH8thgElf4NvXQ=="], 68 | 69 | "@cloudflare/workers-types": ["@cloudflare/workers-types@4.20250121.0", "", {}, "sha512-2bBosmudcwvUOKzuCL/Jum18LDh3QoU0QnTNMXIgcVwuq3LaNzyZnOW14bFXPhLU/84ZjNO3zO5R/U11Zgag2Q=="], 70 | 71 | "@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="], 72 | 73 | "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="], 74 | 75 | "@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="], 76 | 77 | "@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="], 78 | 79 | "@esbuild-plugins/node-globals-polyfill": ["@esbuild-plugins/node-globals-polyfill@0.2.3", "", { "peerDependencies": { "esbuild": "*" } }, "sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw=="], 80 | 81 | "@esbuild-plugins/node-modules-polyfill": ["@esbuild-plugins/node-modules-polyfill@0.2.2", "", { "dependencies": { "escape-string-regexp": "^4.0.0", "rollup-plugin-node-polyfills": "^0.2.1" }, "peerDependencies": { "esbuild": "*" } }, "sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA=="], 82 | 83 | "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.19.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA=="], 84 | 85 | "@esbuild/android-arm": ["@esbuild/android-arm@0.17.19", "", { "os": "android", "cpu": "arm" }, "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A=="], 86 | 87 | "@esbuild/android-arm64": ["@esbuild/android-arm64@0.17.19", "", { "os": "android", "cpu": "arm64" }, "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA=="], 88 | 89 | "@esbuild/android-x64": ["@esbuild/android-x64@0.17.19", "", { "os": "android", "cpu": "x64" }, "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww=="], 90 | 91 | "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.17.19", "", { "os": "darwin", "cpu": "arm64" }, "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg=="], 92 | 93 | "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.17.19", "", { "os": "darwin", "cpu": "x64" }, "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw=="], 94 | 95 | "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.17.19", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ=="], 96 | 97 | "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.17.19", "", { "os": "freebsd", "cpu": "x64" }, "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ=="], 98 | 99 | "@esbuild/linux-arm": ["@esbuild/linux-arm@0.17.19", "", { "os": "linux", "cpu": "arm" }, "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA=="], 100 | 101 | "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.17.19", "", { "os": "linux", "cpu": "arm64" }, "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg=="], 102 | 103 | "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.17.19", "", { "os": "linux", "cpu": "ia32" }, "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ=="], 104 | 105 | "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.17.19", "", { "os": "linux", "cpu": "none" }, "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ=="], 106 | 107 | "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.17.19", "", { "os": "linux", "cpu": "none" }, "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A=="], 108 | 109 | "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.17.19", "", { "os": "linux", "cpu": "ppc64" }, "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg=="], 110 | 111 | "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.17.19", "", { "os": "linux", "cpu": "none" }, "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA=="], 112 | 113 | "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.17.19", "", { "os": "linux", "cpu": "s390x" }, "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q=="], 114 | 115 | "@esbuild/linux-x64": ["@esbuild/linux-x64@0.17.19", "", { "os": "linux", "cpu": "x64" }, "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw=="], 116 | 117 | "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.17.19", "", { "os": "none", "cpu": "x64" }, "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q=="], 118 | 119 | "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.17.19", "", { "os": "openbsd", "cpu": "x64" }, "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g=="], 120 | 121 | "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.17.19", "", { "os": "sunos", "cpu": "x64" }, "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg=="], 122 | 123 | "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.17.19", "", { "os": "win32", "cpu": "arm64" }, "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag=="], 124 | 125 | "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.17.19", "", { "os": "win32", "cpu": "ia32" }, "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw=="], 126 | 127 | "@esbuild/win32-x64": ["@esbuild/win32-x64@0.17.19", "", { "os": "win32", "cpu": "x64" }, "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA=="], 128 | 129 | "@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="], 130 | 131 | "@hexagon/base64": ["@hexagon/base64@1.1.28", "", {}, "sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw=="], 132 | 133 | "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], 134 | 135 | "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], 136 | 137 | "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="], 138 | 139 | "@levischuck/tiny-cbor": ["@levischuck/tiny-cbor@0.2.2", "", {}, "sha512-f5CnPw997Y2GQ8FAvtuVVC19FX8mwNNC+1XJcIi16n/LTJifKO6QBgGLgN3YEmqtGMk17SKSuoWES3imJVxAVw=="], 140 | 141 | "@noble/ciphers": ["@noble/ciphers@0.6.0", "", {}, "sha512-mIbq/R9QXk5/cTfESb1OKtyFnk7oc1Om/8onA1158K9/OZUQFDEVy55jVTato+xmp3XX6F6Qh0zz0Nc1AxAlRQ=="], 142 | 143 | "@noble/hashes": ["@noble/hashes@1.7.1", "", {}, "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ=="], 144 | 145 | "@oven/bun-darwin-aarch64": ["@oven/bun-darwin-aarch64@1.2.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-51gx0Q7MJshZn554VEHj599/TVW+IDcYITWDzxeRyBN96vrbnYT9sGT58tCvN/yVrJBcTH83aHgjsOefM/egrQ=="], 146 | 147 | "@oven/bun-darwin-x64": ["@oven/bun-darwin-x64@1.2.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-LxbuMT5h5IgBbBpCzUCz9JlAIRcVy9tLJ54s+X6H8qpaDNf4TL+bmZQEog/VZMHdyFVQignSiTI6zPkPnoF+Kw=="], 148 | 149 | "@oven/bun-darwin-x64-baseline": ["@oven/bun-darwin-x64-baseline@1.2.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZgtE37T6ogQjp25JZRYbxy4Iziw7+THEnm2Mb+SyjimLSmU10d0fgYsPrT5ZIWfsMh42IXPgUFksG7IY7CmR/A=="], 150 | 151 | "@oven/bun-linux-aarch64": ["@oven/bun-linux-aarch64@1.2.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-1N10z7tQE1CwrSHuhdQFxl1vUd86VKqt1258a++m9EUwZ+uQMYnoevKyDc2TiwJF4yXG1AHc1STz7biQ1q/wNA=="], 152 | 153 | "@oven/bun-linux-aarch64-musl": ["@oven/bun-linux-aarch64-musl@1.2.0", "", { "os": "linux", "cpu": "none" }, "sha512-sXfkddLwsOF6x2YfDkmbfuwV5BgaI53G1nh2/cq7iLf1HhdWDhbmB4i9Dw68Gy3tXTrezOOQqZ5+un5OOOXXxg=="], 154 | 155 | "@oven/bun-linux-x64": ["@oven/bun-linux-x64@1.2.0", "", { "os": "linux", "cpu": "x64" }, "sha512-QGXdeY0zkTSMYqIJ1crDF8gvRx0JeRYjd/nmfpxnuxyVNjUUVZHaQKMHvGGptPmkzvhEvCYnWmh2wmCFQmCZBw=="], 156 | 157 | "@oven/bun-linux-x64-baseline": ["@oven/bun-linux-x64-baseline@1.2.0", "", { "os": "linux", "cpu": "x64" }, "sha512-eXxEbZxUO02EAofgMW1SCiMlGgS0F8NSvF7xTgSAxl8WYYDAR9GVu37DIwGokXBKBuysgdZuBUb7IpWzB+n6IQ=="], 158 | 159 | "@oven/bun-linux-x64-musl": ["@oven/bun-linux-x64-musl@1.2.0", "", { "os": "linux", "cpu": "x64" }, "sha512-pJjVzEiS9HXuWy4LdbmiOLUmU1l2pUAWbbd43OocJRYvZL54cZpE+x0yUyEb38vd+jkHQu0ty8jHMyl+fBNUZA=="], 160 | 161 | "@oven/bun-linux-x64-musl-baseline": ["@oven/bun-linux-x64-musl-baseline@1.2.0", "", { "os": "linux", "cpu": "x64" }, "sha512-/M53/WHnhKjgFxQXljapXMnQvkdZ9EzK9LuWjZ0Kw3WCx9n2v7MS2803vjpiVwUxoDYpq1+wSyXIaEloZQ/R5w=="], 162 | 163 | "@oven/bun-windows-x64": ["@oven/bun-windows-x64@1.2.0", "", { "os": "win32", "cpu": "x64" }, "sha512-inCrG8tQJ9Sz5N8uVoDwoR+QFDLbOcfAkGtjEvnn6djeyDx8PbO8IErhbrpkb+10vtGDFWaOr0XVS7kuSu08Fw=="], 164 | 165 | "@oven/bun-windows-x64-baseline": ["@oven/bun-windows-x64-baseline@1.2.0", "", { "os": "win32", "cpu": "x64" }, "sha512-A+U0PGh0twPTbUNwK2BZcFR8fWDumWOAiTReTWTK0wOIcQIy9DCTzKhxBffYNwLhf8BxLkJpoEag7wL1AWiejg=="], 166 | 167 | "@peculiar/asn1-android": ["@peculiar/asn1-android@2.3.15", "", { "dependencies": { "@peculiar/asn1-schema": "^2.3.15", "asn1js": "^3.0.5", "tslib": "^2.8.1" } }, "sha512-8U2TIj59cRlSXTX2d0mzUKP7whfWGFMzTeC3qPgAbccXFrPNZLaDhpNEdG5U2QZ/tBv/IHlCJ8s+KYXpJeop6w=="], 168 | 169 | "@peculiar/asn1-ecc": ["@peculiar/asn1-ecc@2.3.15", "", { "dependencies": { "@peculiar/asn1-schema": "^2.3.15", "@peculiar/asn1-x509": "^2.3.15", "asn1js": "^3.0.5", "tslib": "^2.8.1" } }, "sha512-/HtR91dvgog7z/WhCVdxZJ/jitJuIu8iTqiyWVgRE9Ac5imt2sT/E4obqIVGKQw7PIy+X6i8lVBoT6wC73XUgA=="], 170 | 171 | "@peculiar/asn1-rsa": ["@peculiar/asn1-rsa@2.3.15", "", { "dependencies": { "@peculiar/asn1-schema": "^2.3.15", "@peculiar/asn1-x509": "^2.3.15", "asn1js": "^3.0.5", "tslib": "^2.8.1" } }, "sha512-p6hsanvPhexRtYSOHihLvUUgrJ8y0FtOM97N5UEpC+VifFYyZa0iZ5cXjTkZoDwxJ/TTJ1IJo3HVTB2JJTpXvg=="], 172 | 173 | "@peculiar/asn1-schema": ["@peculiar/asn1-schema@2.3.15", "", { "dependencies": { "asn1js": "^3.0.5", "pvtsutils": "^1.3.6", "tslib": "^2.8.1" } }, "sha512-QPeD8UA8axQREpgR5UTAfu2mqQmm97oUqahDtNdBcfj3qAnoXzFdQW+aNf/tD2WVXF8Fhmftxoj0eMIT++gX2w=="], 174 | 175 | "@peculiar/asn1-x509": ["@peculiar/asn1-x509@2.3.15", "", { "dependencies": { "@peculiar/asn1-schema": "^2.3.15", "asn1js": "^3.0.5", "pvtsutils": "^1.3.6", "tslib": "^2.8.1" } }, "sha512-0dK5xqTqSLaxv1FHXIcd4Q/BZNuopg+u1l23hT9rOmQ1g4dNtw0g/RnEi+TboB0gOwGtrWn269v27cMgchFIIg=="], 176 | 177 | "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.32.0", "", { "os": "android", "cpu": "arm" }, "sha512-G2fUQQANtBPsNwiVFg4zKiPQyjVKZCUdQUol53R8E71J7AsheRMV/Yv/nB8giOcOVqP7//eB5xPqieBYZe9bGg=="], 178 | 179 | "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-qhFwQ+ljoymC+j5lXRv8DlaJYY/+8vyvYmVx074zrLsu5ZGWYsJNLjPPVJJjhZQpyAKUGPydOq9hRLLNvh1s3A=="], 180 | 181 | "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-44n/X3lAlWsEY6vF8CzgCx+LQaoqWGN7TzUfbJDiTIOjJm4+L2Yq+r5a8ytQRGyPqgJDs3Rgyo8eVL7n9iW6AQ=="], 182 | 183 | "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-F9ct0+ZX5Np6+ZDztxiGCIvlCaW87HBdHcozUfsHnj1WCUTBUubAoanhHUfnUHZABlElyRikI0mgcw/qdEm2VQ=="], 184 | 185 | "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.32.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-JpsGxLBB2EFXBsTLHfkZDsXSpSmKD3VxXCgBQtlPcuAqB8TlqtLcbeMhxXQkCDv1avgwNjF8uEIbq5p+Cee0PA=="], 186 | 187 | "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-wegiyBT6rawdpvnD9lmbOpx5Sph+yVZKHbhnSP9MqUEDX08G4UzMU+D87jrazGE7lRSyTRs6NEYHtzfkJ3FjjQ=="], 188 | 189 | "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-3pA7xecItbgOs1A5H58dDvOUEboG5UfpTq3WzAdF54acBbUM+olDJAPkgj1GRJ4ZqE12DZ9/hNS2QZk166v92A=="], 190 | 191 | "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-Y7XUZEVISGyge51QbYyYAEHwpGgmRrAxQXO3siyYo2kmaj72USSG8LtlQQgAtlGfxYiOwu+2BdbPjzEpcOpRmQ=="], 192 | 193 | "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-r7/OTF5MqeBrZo5omPXcTnjvv1GsrdH8a8RerARvDFiDwFpDVDnJyByYM/nX+mvks8XXsgPUxkwe/ltaX2VH7w=="], 194 | 195 | "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-HJbifC9vex9NqnlodV2BHVFNuzKL5OnsV2dvTw6e1dpZKkNjPG6WUq+nhEYV6Hv2Bv++BXkwcyoGlXnPrjAKXw=="], 196 | 197 | "@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.32.0", "", { "os": "linux", "cpu": "none" }, "sha512-VAEzZTD63YglFlWwRj3taofmkV1V3xhebDXffon7msNz4b14xKsz7utO6F8F4cqt8K/ktTl9rm88yryvDpsfOw=="], 198 | 199 | "@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.32.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-Sts5DST1jXAc9YH/iik1C9QRsLcCoOScf3dfbY5i4kH9RJpKxiTBXqm7qU5O6zTXBTEZry69bGszr3SMgYmMcQ=="], 200 | 201 | "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.32.0", "", { "os": "linux", "cpu": "none" }, "sha512-qhlXeV9AqxIyY9/R1h1hBD6eMvQCO34ZmdYvry/K+/MBs6d1nRFLm6BOiITLVI+nFAAB9kUB6sdJRKyVHXnqZw=="], 202 | 203 | "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.32.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-8ZGN7ExnV0qjXa155Rsfi6H8M4iBBwNLBM9lcVS+4NcSzOFaNqmt7djlox8pN1lWrRPMRRQ8NeDlozIGx3Omsw=="], 204 | 205 | "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-VDzNHtLLI5s7xd/VubyS10mq6TxvZBp+4NRWoW+Hi3tgV05RtVm4qK99+dClwTN1McA6PHwob6DEJ6PlXbY83A=="], 206 | 207 | "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-qcb9qYDlkxz9DxJo7SDhWxTWV1gFuwznjbTiov289pASxlfGbaOD54mgbs9+z94VwrXtKTu+2RqwlSTbiOqxGg=="], 208 | 209 | "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-pFDdotFDMXW2AXVbfdUEfidPAk/OtwE/Hd4eYMTNVVaCQ6Yl8et0meDaKNL63L44Haxv4UExpv9ydSf3aSayDg=="], 210 | 211 | "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.32.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-/TG7WfrCAjeRNDvI4+0AAMoHxea/USWhAzf9PVDFHbcqrQ7hMMKp4jZIy4VEjk72AAfN5k4TiSMRXRKf/0akSw=="], 212 | 213 | "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-5hqO5S3PTEO2E5VjCePxv40gIgyS2KvO7E7/vvC/NbIW4SIRamkMr1hqj+5Y67fbBWv/bQLB6KelBQmXlyCjWA=="], 214 | 215 | "@simplewebauthn/browser": ["@simplewebauthn/browser@13.1.0", "", {}, "sha512-WuHZ/PYvyPJ9nxSzgHtOEjogBhwJfC8xzYkPC+rR/+8chl/ft4ngjiK8kSU5HtRJfczupyOh33b25TjYbvwAcg=="], 216 | 217 | "@simplewebauthn/server": ["@simplewebauthn/server@13.1.0", "", { "dependencies": { "@hexagon/base64": "^1.1.27", "@levischuck/tiny-cbor": "^0.2.2", "@peculiar/asn1-android": "^2.3.10", "@peculiar/asn1-ecc": "^2.3.8", "@peculiar/asn1-rsa": "^2.3.8", "@peculiar/asn1-schema": "^2.3.8", "@peculiar/asn1-x509": "^2.3.8" } }, "sha512-vGz9sgzoN0c0IySnpdG0TG0B71GM75basrlTpXHDAw8tQ/JUH5O73x6s3UXzDWHlv7d2E4Hy4aX5ugoHkW4udg=="], 218 | 219 | "@types/bun": ["@types/bun@1.2.0", "", { "dependencies": { "bun-types": "1.2.0" } }, "sha512-5N1JqdahfpBlAv4wy6svEYcd/YfO2GNrbL95JOmFx8nkE6dbK4R0oSE5SpBA4vBRqgrOUAXF8Dpiz+gi7r80SA=="], 220 | 221 | "@types/estree": ["@types/estree@1.0.6", "", {}, "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="], 222 | 223 | "@types/node": ["@types/node@22.10.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-Ir6hwgsKyNESl/gLOcEz3krR4CBGgliDqBQ2ma4wIhEx0w+xnoeTq3tdrNw15kU3SxogDjOgv9sqdtLW8mIHaw=="], 224 | 225 | "@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="], 226 | 227 | "@vitest/expect": ["@vitest/expect@2.1.8", "", { "dependencies": { "@vitest/spy": "2.1.8", "@vitest/utils": "2.1.8", "chai": "^5.1.2", "tinyrainbow": "^1.2.0" } }, "sha512-8ytZ/fFHq2g4PJVAtDX57mayemKgDR6X3Oa2Foro+EygiOJHUXhCqBAAKQYYajZpFoIfvBCF1j6R6IYRSIUFuw=="], 228 | 229 | "@vitest/mocker": ["@vitest/mocker@2.1.8", "", { "dependencies": { "@vitest/spy": "2.1.8", "estree-walker": "^3.0.3", "magic-string": "^0.30.12" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-7guJ/47I6uqfttp33mgo6ga5Gr1VnL58rcqYKyShoRK9ebu8T5Rs6HN3s1NABiBeVTdWNrwUMcHH54uXZBN4zA=="], 230 | 231 | "@vitest/pretty-format": ["@vitest/pretty-format@2.1.8", "", { "dependencies": { "tinyrainbow": "^1.2.0" } }, "sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ=="], 232 | 233 | "@vitest/runner": ["@vitest/runner@2.1.8", "", { "dependencies": { "@vitest/utils": "2.1.8", "pathe": "^1.1.2" } }, "sha512-17ub8vQstRnRlIU5k50bG+QOMLHRhYPAna5tw8tYbj+jzjcspnwnwtPtiOlkuKC4+ixDPTuLZiqiWWQ2PSXHVg=="], 234 | 235 | "@vitest/snapshot": ["@vitest/snapshot@2.1.8", "", { "dependencies": { "@vitest/pretty-format": "2.1.8", "magic-string": "^0.30.12", "pathe": "^1.1.2" } }, "sha512-20T7xRFbmnkfcmgVEz+z3AU/3b0cEzZOt/zmnvZEctg64/QZbSDJEVm9fLnnlSi74KibmRsO9/Qabi+t0vCRPg=="], 236 | 237 | "@vitest/spy": ["@vitest/spy@2.1.8", "", { "dependencies": { "tinyspy": "^3.0.2" } }, "sha512-5swjf2q95gXeYPevtW0BLk6H8+bPlMb4Vw/9Em4hFxDcaOxS+e0LOX4yqNxoHzMR2akEB2xfpnWUzkZokmgWDg=="], 238 | 239 | "@vitest/utils": ["@vitest/utils@2.1.8", "", { "dependencies": { "@vitest/pretty-format": "2.1.8", "loupe": "^3.1.2", "tinyrainbow": "^1.2.0" } }, "sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA=="], 240 | 241 | "acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="], 242 | 243 | "acorn-walk": ["acorn-walk@8.3.4", "", { "dependencies": { "acorn": "^8.11.0" } }, "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g=="], 244 | 245 | "as-table": ["as-table@1.0.55", "", { "dependencies": { "printable-characters": "^1.0.42" } }, "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ=="], 246 | 247 | "asn1js": ["asn1js@3.0.5", "", { "dependencies": { "pvtsutils": "^1.3.2", "pvutils": "^1.1.3", "tslib": "^2.4.0" } }, "sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ=="], 248 | 249 | "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], 250 | 251 | "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], 252 | 253 | "better-auth": ["better-auth@1.1.14", "", { "dependencies": { "@better-auth/utils": "0.2.3", "@better-fetch/fetch": "1.1.12", "@noble/ciphers": "^0.6.0", "@noble/hashes": "^1.6.1", "@simplewebauthn/browser": "^13.0.0", "@simplewebauthn/server": "^13.0.0", "better-call": "0.3.3", "defu": "^6.1.4", "jose": "^5.9.6", "kysely": "^0.27.4", "nanostores": "^0.11.3", "zod": "^3.24.1" } }, "sha512-DnGHPxRLIQFGyqvPPXbudiD3sI+04BU0hiNnnoQaXg//JZSxdfBs/YbNx3ccVWz6aFt/XL6WtrxXDyUPCC1qrQ=="], 254 | 255 | "better-call": ["better-call@0.3.3", "", { "dependencies": { "@better-fetch/fetch": "^1.1.4", "rou3": "^0.5.1", "uncrypto": "^0.1.3", "zod": "^3.24.1" } }, "sha512-N4lDVm0NGmFfDJ0XMQ4O83Zm/3dPlvIQdxvwvgSLSkjFX5PM4GUYSVAuxNzXN27QZMHDkrJTWUqxBrm4tPC3eA=="], 256 | 257 | "better-sqlite3": ["better-sqlite3@11.8.1", "", { "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" } }, "sha512-9BxNaBkblMjhJW8sMRZxnxVTRgbRmssZW0Oxc1MPBTfiR+WW21e2Mk4qu8CzrcZb1LwPCnFsfDEzq+SNcBU8eg=="], 258 | 259 | "bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="], 260 | 261 | "birpc": ["birpc@0.2.14", "", {}, "sha512-37FHE8rqsYM5JEKCnXFyHpBCzvgHEExwVVTq+nUmloInU7l8ezD1TpOhKpS8oe1DTYFqEK27rFZVKG43oTqXRA=="], 262 | 263 | "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], 264 | 265 | "blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="], 266 | 267 | "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], 268 | 269 | "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], 270 | 271 | "bun": ["bun@1.2.0", "", { "optionalDependencies": { "@oven/bun-darwin-aarch64": "1.2.0", "@oven/bun-darwin-x64": "1.2.0", "@oven/bun-darwin-x64-baseline": "1.2.0", "@oven/bun-linux-aarch64": "1.2.0", "@oven/bun-linux-aarch64-musl": "1.2.0", "@oven/bun-linux-x64": "1.2.0", "@oven/bun-linux-x64-baseline": "1.2.0", "@oven/bun-linux-x64-musl": "1.2.0", "@oven/bun-linux-x64-musl-baseline": "1.2.0", "@oven/bun-windows-x64": "1.2.0", "@oven/bun-windows-x64-baseline": "1.2.0" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ], "bin": { "bun": "bin/bun.exe", "bunx": "bin/bun.exe" } }, "sha512-QUt9418MMPgylSsfOjGABV0aodlVZmQ6goCn/o0tf6jVKkPFgaMw643iv+Uo5uqEKtECaDCbCmssqofq1nVgvw=="], 272 | 273 | "bun-types": ["bun-types@1.2.0", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-KEaJxyZfbV/c4eyG0vyehDpYmBGreNiQbZIqvVHJwZ4BmeuWlNZ7EAzMN2Zcd7ailmS/tGVW0BgYbGf+lGEpWw=="], 274 | 275 | "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], 276 | 277 | "capnp-ts": ["capnp-ts@0.7.0", "", { "dependencies": { "debug": "^4.3.1", "tslib": "^2.2.0" } }, "sha512-XKxXAC3HVPv7r674zP0VC3RTXz+/JKhfyw94ljvF80yynK6VkTnqE3jMuN8b3dUVmmc43TjyxjW4KTsmB3c86g=="], 278 | 279 | "chai": ["chai@5.1.2", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw=="], 280 | 281 | "check-error": ["check-error@2.1.1", "", {}, "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw=="], 282 | 283 | "child_process": ["child_process@1.0.2", "", {}, "sha512-Wmza/JzL0SiWz7kl6MhIKT5ceIlnFPJX+lwUGj7Clhy5MMldsSoJR0+uvRzOS5Kv45Mq7t1PoE8TsOA9bzvb6g=="], 284 | 285 | "chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], 286 | 287 | "cjs-module-lexer": ["cjs-module-lexer@1.4.1", "", {}, "sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA=="], 288 | 289 | "confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], 290 | 291 | "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], 292 | 293 | "data-uri-to-buffer": ["data-uri-to-buffer@2.0.2", "", {}, "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA=="], 294 | 295 | "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], 296 | 297 | "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], 298 | 299 | "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="], 300 | 301 | "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], 302 | 303 | "defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], 304 | 305 | "detect-libc": ["detect-libc@2.0.3", "", {}, "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw=="], 306 | 307 | "devalue": ["devalue@4.3.3", "", {}, "sha512-UH8EL6H2ifcY8TbD2QsxwCC/pr5xSwPvv85LrLXVihmHVC3T3YqTCIwnR5ak0yO1KYqlxrPVOA/JVZJYPy2ATg=="], 308 | 309 | "dotenv": ["dotenv@16.4.7", "", {}, "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ=="], 310 | 311 | "drizzle-kit": ["drizzle-kit@0.30.2", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.19.7", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-vhdLrxWA32WNVF77NabpSnX7pQBornx64VDQDmKddRonOB2Xe/yY4glQ7rECoa+ogqcQNo7VblLUbeBK6Zn9Ow=="], 312 | 313 | "drizzle-orm": ["drizzle-orm@0.38.4", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1", "@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": ">=14.0.0", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "react": ">=18", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/react", "@types/sql.js", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "knex", "kysely", "mysql2", "pg", "postgres", "react", "sql.js", "sqlite3"] }, "sha512-s7/5BpLKO+WJRHspvpqTydxFob8i1vo2rEx4pY6TGY7QSMuUfWUuzaY0DIpXCkgHOo37BaFC+SJQb99dDUXT3Q=="], 314 | 315 | "end-of-stream": ["end-of-stream@1.4.4", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q=="], 316 | 317 | "es-module-lexer": ["es-module-lexer@1.6.0", "", {}, "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ=="], 318 | 319 | "esbuild": ["esbuild@0.17.19", "", { "optionalDependencies": { "@esbuild/android-arm": "0.17.19", "@esbuild/android-arm64": "0.17.19", "@esbuild/android-x64": "0.17.19", "@esbuild/darwin-arm64": "0.17.19", "@esbuild/darwin-x64": "0.17.19", "@esbuild/freebsd-arm64": "0.17.19", "@esbuild/freebsd-x64": "0.17.19", "@esbuild/linux-arm": "0.17.19", "@esbuild/linux-arm64": "0.17.19", "@esbuild/linux-ia32": "0.17.19", "@esbuild/linux-loong64": "0.17.19", "@esbuild/linux-mips64el": "0.17.19", "@esbuild/linux-ppc64": "0.17.19", "@esbuild/linux-riscv64": "0.17.19", "@esbuild/linux-s390x": "0.17.19", "@esbuild/linux-x64": "0.17.19", "@esbuild/netbsd-x64": "0.17.19", "@esbuild/openbsd-x64": "0.17.19", "@esbuild/sunos-x64": "0.17.19", "@esbuild/win32-arm64": "0.17.19", "@esbuild/win32-ia32": "0.17.19", "@esbuild/win32-x64": "0.17.19" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw=="], 320 | 321 | "esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="], 322 | 323 | "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], 324 | 325 | "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], 326 | 327 | "exit-hook": ["exit-hook@2.2.1", "", {}, "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw=="], 328 | 329 | "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="], 330 | 331 | "expect-type": ["expect-type@1.1.0", "", {}, "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA=="], 332 | 333 | "file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="], 334 | 335 | "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], 336 | 337 | "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], 338 | 339 | "get-source": ["get-source@2.0.12", "", { "dependencies": { "data-uri-to-buffer": "^2.0.0", "source-map": "^0.6.1" } }, "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w=="], 340 | 341 | "get-tsconfig": ["get-tsconfig@4.10.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A=="], 342 | 343 | "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], 344 | 345 | "glob-to-regexp": ["glob-to-regexp@0.4.1", "", {}, "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="], 346 | 347 | "hono": ["hono@4.6.18", "", {}, "sha512-Fu7hEPvdwtPG8LqSAiPn8p8HjD+PDxrP/HVnwRBnwtVKOn5zDDKsw0ma2Xt58oq97Rlp3t/mRNADEV/Ym6cDng=="], 348 | 349 | "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], 350 | 351 | "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], 352 | 353 | "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], 354 | 355 | "jose": ["jose@5.9.6", "", {}, "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ=="], 356 | 357 | "kysely": ["kysely@0.27.5", "", {}, "sha512-s7hZHcQeSNKpzCkHRm8yA+0JPLjncSWnjb+2TIElwS2JAqYr+Kv3Ess+9KFfJS0C1xcQ1i9NkNHpWwCYpHMWsA=="], 358 | 359 | "loupe": ["loupe@3.1.2", "", {}, "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg=="], 360 | 361 | "magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="], 362 | 363 | "mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="], 364 | 365 | "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], 366 | 367 | "miniflare": ["miniflare@3.20241230.2", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "^8.8.0", "acorn-walk": "^8.2.0", "capnp-ts": "^0.7.0", "exit-hook": "^2.2.1", "glob-to-regexp": "^0.4.1", "stoppable": "^1.1.0", "undici": "^5.28.4", "workerd": "1.20241230.0", "ws": "^8.18.0", "youch": "^3.2.2", "zod": "^3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-gFC3IaUKrLGdtA6y6PLpC/QE5YAjB5ITCfBZHkosRyFZ9ApaCHKcHRvrEFMc/R19QxxtHD+G3tExEHp7MmtsYQ=="], 368 | 369 | "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], 370 | 371 | "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="], 372 | 373 | "mlly": ["mlly@1.7.4", "", { "dependencies": { "acorn": "^8.14.0", "pathe": "^2.0.1", "pkg-types": "^1.3.0", "ufo": "^1.5.4" } }, "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw=="], 374 | 375 | "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], 376 | 377 | "mustache": ["mustache@4.2.0", "", { "bin": { "mustache": "bin/mustache" } }, "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ=="], 378 | 379 | "nanoid": ["nanoid@5.0.9", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-Aooyr6MXU6HpvvWXKoVoXwKMs/KyVakWwg7xQfv5/S/RIgJMy0Ifa45H9qqYy7pTCszrHzP21Uk4PZq2HpEM8Q=="], 380 | 381 | "nanostores": ["nanostores@0.11.3", "", {}, "sha512-TUes3xKIX33re4QzdxwZ6tdbodjmn3tWXCEc1uokiEmo14sI1EaGYNs2k3bU2pyyGNmBqFGAVl6jAGWd06AVIg=="], 382 | 383 | "napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="], 384 | 385 | "node-abi": ["node-abi@3.73.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-z8iYzQGBu35ZkTQ9mtR8RqugJZ9RCLn8fv3d7LsgDBzOijGQP3RdKTX4LA7LXw03ZhU5z0l4xfhIMgSES31+cg=="], 386 | 387 | "ohash": ["ohash@1.1.4", "", {}, "sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g=="], 388 | 389 | "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], 390 | 391 | "path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="], 392 | 393 | "pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="], 394 | 395 | "pathval": ["pathval@2.0.0", "", {}, "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA=="], 396 | 397 | "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], 398 | 399 | "pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], 400 | 401 | "postcss": ["postcss@8.5.1", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ=="], 402 | 403 | "prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="], 404 | 405 | "printable-characters": ["printable-characters@1.0.42", "", {}, "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ=="], 406 | 407 | "pump": ["pump@3.0.2", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw=="], 408 | 409 | "pvtsutils": ["pvtsutils@1.3.6", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg=="], 410 | 411 | "pvutils": ["pvutils@1.1.3", "", {}, "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ=="], 412 | 413 | "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], 414 | 415 | "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], 416 | 417 | "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], 418 | 419 | "rollup": ["rollup@4.32.0", "", { "dependencies": { "@types/estree": "1.0.6" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.32.0", "@rollup/rollup-android-arm64": "4.32.0", "@rollup/rollup-darwin-arm64": "4.32.0", "@rollup/rollup-darwin-x64": "4.32.0", "@rollup/rollup-freebsd-arm64": "4.32.0", "@rollup/rollup-freebsd-x64": "4.32.0", "@rollup/rollup-linux-arm-gnueabihf": "4.32.0", "@rollup/rollup-linux-arm-musleabihf": "4.32.0", "@rollup/rollup-linux-arm64-gnu": "4.32.0", "@rollup/rollup-linux-arm64-musl": "4.32.0", "@rollup/rollup-linux-loongarch64-gnu": "4.32.0", "@rollup/rollup-linux-powerpc64le-gnu": "4.32.0", "@rollup/rollup-linux-riscv64-gnu": "4.32.0", "@rollup/rollup-linux-s390x-gnu": "4.32.0", "@rollup/rollup-linux-x64-gnu": "4.32.0", "@rollup/rollup-linux-x64-musl": "4.32.0", "@rollup/rollup-win32-arm64-msvc": "4.32.0", "@rollup/rollup-win32-ia32-msvc": "4.32.0", "@rollup/rollup-win32-x64-msvc": "4.32.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-JmrhfQR31Q4AuNBjjAX4s+a/Pu/Q8Q9iwjWBsjRH1q52SPFE2NqRMK6fUZKKnvKO6id+h7JIRf0oYsph53eATg=="], 420 | 421 | "rollup-plugin-inject": ["rollup-plugin-inject@3.0.2", "", { "dependencies": { "estree-walker": "^0.6.1", "magic-string": "^0.25.3", "rollup-pluginutils": "^2.8.1" } }, "sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w=="], 422 | 423 | "rollup-plugin-node-polyfills": ["rollup-plugin-node-polyfills@0.2.1", "", { "dependencies": { "rollup-plugin-inject": "^3.0.0" } }, "sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA=="], 424 | 425 | "rollup-pluginutils": ["rollup-pluginutils@2.8.2", "", { "dependencies": { "estree-walker": "^0.6.1" } }, "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ=="], 426 | 427 | "rou3": ["rou3@0.5.1", "", {}, "sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ=="], 428 | 429 | "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], 430 | 431 | "semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="], 432 | 433 | "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], 434 | 435 | "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="], 436 | 437 | "simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="], 438 | 439 | "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], 440 | 441 | "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], 442 | 443 | "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], 444 | 445 | "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], 446 | 447 | "sourcemap-codec": ["sourcemap-codec@1.4.8", "", {}, "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="], 448 | 449 | "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], 450 | 451 | "stacktracey": ["stacktracey@2.1.8", "", { "dependencies": { "as-table": "^1.0.36", "get-source": "^2.0.12" } }, "sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw=="], 452 | 453 | "std-env": ["std-env@3.8.0", "", {}, "sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w=="], 454 | 455 | "stoppable": ["stoppable@1.1.0", "", {}, "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw=="], 456 | 457 | "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], 458 | 459 | "strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], 460 | 461 | "tar-fs": ["tar-fs@2.1.2", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA=="], 462 | 463 | "tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], 464 | 465 | "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], 466 | 467 | "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], 468 | 469 | "tinypool": ["tinypool@1.0.2", "", {}, "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA=="], 470 | 471 | "tinyrainbow": ["tinyrainbow@1.2.0", "", {}, "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ=="], 472 | 473 | "tinyspy": ["tinyspy@3.0.2", "", {}, "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q=="], 474 | 475 | "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], 476 | 477 | "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], 478 | 479 | "ufo": ["ufo@1.5.4", "", {}, "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ=="], 480 | 481 | "uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="], 482 | 483 | "undici": ["undici@5.28.5", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-zICwjrDrcrUE0pyyJc1I2QzBkLM8FINsgOrt6WjA+BgajVq9Nxu2PbFFXUrAggLfDXlZGZBVZYw7WNV5KiBiBA=="], 484 | 485 | "undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], 486 | 487 | "unenv": ["unenv@2.0.0-rc.0", "", { "dependencies": { "defu": "^6.1.4", "mlly": "^1.7.4", "ohash": "^1.1.4", "pathe": "^1.1.2", "ufo": "^1.5.4" } }, "sha512-H0kl2w8jFL/FAk0xvjVing4bS3jd//mbg1QChDnn58l9Sc5RtduaKmLAL8n+eBw5jJo8ZjYV7CrEGage5LAOZQ=="], 488 | 489 | "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], 490 | 491 | "vite": ["vite@5.4.14", "", { "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", "rollup": "^4.20.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA=="], 492 | 493 | "vite-node": ["vite-node@2.1.8", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.3.7", "es-module-lexer": "^1.5.4", "pathe": "^1.1.2", "vite": "^5.0.0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-uPAwSr57kYjAUux+8E2j0q0Fxpn8M9VoyfGiRI8Kfktz9NcYMCenwY5RnZxnF1WTu3TGiYipirIzacLL3VVGFg=="], 494 | 495 | "vitest": ["vitest@2.1.8", "", { "dependencies": { "@vitest/expect": "2.1.8", "@vitest/mocker": "2.1.8", "@vitest/pretty-format": "^2.1.8", "@vitest/runner": "2.1.8", "@vitest/snapshot": "2.1.8", "@vitest/spy": "2.1.8", "@vitest/utils": "2.1.8", "chai": "^5.1.2", "debug": "^4.3.7", "expect-type": "^1.1.0", "magic-string": "^0.30.12", "pathe": "^1.1.2", "std-env": "^3.8.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.1", "tinypool": "^1.0.1", "tinyrainbow": "^1.2.0", "vite": "^5.0.0", "vite-node": "2.1.8", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", "@vitest/browser": "2.1.8", "@vitest/ui": "2.1.8", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-1vBKTZskHw/aosXqQUlVWWlGUxSJR8YtiyZDJAFeW2kPAeX6S3Sool0mjspO+kXLuxVWlEDDowBAeqeAQefqLQ=="], 496 | 497 | "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], 498 | 499 | "workerd": ["workerd@1.20241230.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20241230.0", "@cloudflare/workerd-darwin-arm64": "1.20241230.0", "@cloudflare/workerd-linux-64": "1.20241230.0", "@cloudflare/workerd-linux-arm64": "1.20241230.0", "@cloudflare/workerd-windows-64": "1.20241230.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-EgixXP0JGXGq6J9lz17TKIZtfNDUvJNG+cl9paPMfZuYWT920fFpBx+K04YmnbQRLnglsivF1GT9pxh1yrlWhg=="], 500 | 501 | "wrangler": ["wrangler@3.105.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.3.4", "@esbuild-plugins/node-globals-polyfill": "0.2.3", "@esbuild-plugins/node-modules-polyfill": "0.2.2", "blake3-wasm": "2.1.5", "esbuild": "0.17.19", "miniflare": "3.20241230.2", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.0", "workerd": "1.20241230.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20241230.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-NX10iuUXtgiVRG9YJ7dwwEUuhQ38hu4stcxMWq4dbKCnfcOj7fLFh+HwaWudqOr1jDCPrnSOQVkgfAfG3ZH9Lw=="], 502 | 503 | "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], 504 | 505 | "ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="], 506 | 507 | "youch": ["youch@3.3.4", "", { "dependencies": { "cookie": "^0.7.1", "mustache": "^4.2.0", "stacktracey": "^2.1.8" } }, "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg=="], 508 | 509 | "zod": ["zod@3.24.1", "", {}, "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A=="], 510 | 511 | "@cloudflare/vitest-pool-workers/wrangler": ["wrangler@3.103.2", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.3.4", "@esbuild-plugins/node-globals-polyfill": "0.2.3", "@esbuild-plugins/node-modules-polyfill": "0.2.2", "blake3-wasm": "2.1.5", "esbuild": "0.17.19", "miniflare": "3.20241230.2", "path-to-regexp": "6.3.0", "unenv": "npm:unenv-nightly@2.0.0-20250109-100802-88ad671", "workerd": "1.20241230.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20241230.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-eYcnubPhPBU1QMZYTam+vfCLpaQx+x1EWA6nFbLhid1eqNDAk1dNwNlbo+ZryrOHDEX3XlOxn2Z3Fx8vVv3hKw=="], 512 | 513 | "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], 514 | 515 | "drizzle-kit/esbuild": ["esbuild@0.19.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.19.12", "@esbuild/android-arm": "0.19.12", "@esbuild/android-arm64": "0.19.12", "@esbuild/android-x64": "0.19.12", "@esbuild/darwin-arm64": "0.19.12", "@esbuild/darwin-x64": "0.19.12", "@esbuild/freebsd-arm64": "0.19.12", "@esbuild/freebsd-x64": "0.19.12", "@esbuild/linux-arm": "0.19.12", "@esbuild/linux-arm64": "0.19.12", "@esbuild/linux-ia32": "0.19.12", "@esbuild/linux-loong64": "0.19.12", "@esbuild/linux-mips64el": "0.19.12", "@esbuild/linux-ppc64": "0.19.12", "@esbuild/linux-riscv64": "0.19.12", "@esbuild/linux-s390x": "0.19.12", "@esbuild/linux-x64": "0.19.12", "@esbuild/netbsd-x64": "0.19.12", "@esbuild/openbsd-x64": "0.19.12", "@esbuild/sunos-x64": "0.19.12", "@esbuild/win32-arm64": "0.19.12", "@esbuild/win32-ia32": "0.19.12", "@esbuild/win32-x64": "0.19.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg=="], 516 | 517 | "mlly/pathe": ["pathe@2.0.2", "", {}, "sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w=="], 518 | 519 | "pkg-types/pathe": ["pathe@2.0.2", "", {}, "sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w=="], 520 | 521 | "postcss/nanoid": ["nanoid@3.3.8", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w=="], 522 | 523 | "rollup-plugin-inject/estree-walker": ["estree-walker@0.6.1", "", {}, "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w=="], 524 | 525 | "rollup-plugin-inject/magic-string": ["magic-string@0.25.9", "", { "dependencies": { "sourcemap-codec": "^1.4.8" } }, "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ=="], 526 | 527 | "rollup-pluginutils/estree-walker": ["estree-walker@0.6.1", "", {}, "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w=="], 528 | 529 | "vite/esbuild": ["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" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="], 530 | 531 | "@cloudflare/vitest-pool-workers/wrangler/unenv": ["unenv-nightly@2.0.0-20250109-100802-88ad671", "", { "dependencies": { "defu": "^6.1.4", "mlly": "^1.7.3", "ohash": "^1.1.4", "pathe": "^1.1.2", "ufo": "^1.5.4" } }, "sha512-Uij6gODNNNNsNBoDlnaMvZI99I6YlVJLRfYH8AOLMlbFrW7k2w872v9VLuIdch2vF8QBeSC4EftIh5sG4ibzdA=="], 532 | 533 | "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], 534 | 535 | "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], 536 | 537 | "@esbuild-kit/core-utils/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.18.20", "", { "os": "android", "cpu": "x64" }, "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg=="], 538 | 539 | "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.18.20", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA=="], 540 | 541 | "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.18.20", "", { "os": "darwin", "cpu": "x64" }, "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ=="], 542 | 543 | "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.18.20", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw=="], 544 | 545 | "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.18.20", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ=="], 546 | 547 | "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.18.20", "", { "os": "linux", "cpu": "arm" }, "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg=="], 548 | 549 | "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.18.20", "", { "os": "linux", "cpu": "arm64" }, "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA=="], 550 | 551 | "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.18.20", "", { "os": "linux", "cpu": "ia32" }, "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA=="], 552 | 553 | "@esbuild-kit/core-utils/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg=="], 554 | 555 | "@esbuild-kit/core-utils/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ=="], 556 | 557 | "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.18.20", "", { "os": "linux", "cpu": "ppc64" }, "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA=="], 558 | 559 | "@esbuild-kit/core-utils/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A=="], 560 | 561 | "@esbuild-kit/core-utils/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.18.20", "", { "os": "linux", "cpu": "s390x" }, "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ=="], 562 | 563 | "@esbuild-kit/core-utils/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.18.20", "", { "os": "linux", "cpu": "x64" }, "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w=="], 564 | 565 | "@esbuild-kit/core-utils/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.18.20", "", { "os": "none", "cpu": "x64" }, "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A=="], 566 | 567 | "@esbuild-kit/core-utils/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.18.20", "", { "os": "openbsd", "cpu": "x64" }, "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg=="], 568 | 569 | "@esbuild-kit/core-utils/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.18.20", "", { "os": "sunos", "cpu": "x64" }, "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ=="], 570 | 571 | "@esbuild-kit/core-utils/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.18.20", "", { "os": "win32", "cpu": "arm64" }, "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg=="], 572 | 573 | "@esbuild-kit/core-utils/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.18.20", "", { "os": "win32", "cpu": "ia32" }, "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g=="], 574 | 575 | "@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="], 576 | 577 | "drizzle-kit/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.19.12", "", { "os": "android", "cpu": "arm" }, "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w=="], 578 | 579 | "drizzle-kit/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.19.12", "", { "os": "android", "cpu": "arm64" }, "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA=="], 580 | 581 | "drizzle-kit/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.19.12", "", { "os": "android", "cpu": "x64" }, "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew=="], 582 | 583 | "drizzle-kit/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.19.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g=="], 584 | 585 | "drizzle-kit/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.19.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A=="], 586 | 587 | "drizzle-kit/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.19.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA=="], 588 | 589 | "drizzle-kit/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.19.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg=="], 590 | 591 | "drizzle-kit/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.19.12", "", { "os": "linux", "cpu": "arm" }, "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w=="], 592 | 593 | "drizzle-kit/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.19.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA=="], 594 | 595 | "drizzle-kit/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.19.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA=="], 596 | 597 | "drizzle-kit/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA=="], 598 | 599 | "drizzle-kit/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w=="], 600 | 601 | "drizzle-kit/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.19.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg=="], 602 | 603 | "drizzle-kit/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg=="], 604 | 605 | "drizzle-kit/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.19.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg=="], 606 | 607 | "drizzle-kit/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.19.12", "", { "os": "linux", "cpu": "x64" }, "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg=="], 608 | 609 | "drizzle-kit/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.19.12", "", { "os": "none", "cpu": "x64" }, "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA=="], 610 | 611 | "drizzle-kit/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.19.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw=="], 612 | 613 | "drizzle-kit/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.19.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA=="], 614 | 615 | "drizzle-kit/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.19.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A=="], 616 | 617 | "drizzle-kit/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.19.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ=="], 618 | 619 | "drizzle-kit/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.19.12", "", { "os": "win32", "cpu": "x64" }, "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA=="], 620 | 621 | "vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.21.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="], 622 | 623 | "vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.21.5", "", { "os": "android", "cpu": "arm" }, "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg=="], 624 | 625 | "vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.21.5", "", { "os": "android", "cpu": "arm64" }, "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A=="], 626 | 627 | "vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.21.5", "", { "os": "android", "cpu": "x64" }, "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA=="], 628 | 629 | "vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.21.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ=="], 630 | 631 | "vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.21.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw=="], 632 | 633 | "vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.21.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g=="], 634 | 635 | "vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.21.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ=="], 636 | 637 | "vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.21.5", "", { "os": "linux", "cpu": "arm" }, "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA=="], 638 | 639 | "vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.21.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q=="], 640 | 641 | "vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.21.5", "", { "os": "linux", "cpu": "ia32" }, "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg=="], 642 | 643 | "vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg=="], 644 | 645 | "vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg=="], 646 | 647 | "vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.21.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w=="], 648 | 649 | "vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA=="], 650 | 651 | "vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.21.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A=="], 652 | 653 | "vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.21.5", "", { "os": "linux", "cpu": "x64" }, "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ=="], 654 | 655 | "vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.21.5", "", { "os": "none", "cpu": "x64" }, "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg=="], 656 | 657 | "vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.21.5", "", { "os": "openbsd", "cpu": "x64" }, "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow=="], 658 | 659 | "vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.21.5", "", { "os": "sunos", "cpu": "x64" }, "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg=="], 660 | 661 | "vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.21.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A=="], 662 | 663 | "vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.21.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA=="], 664 | 665 | "vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.21.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw=="], 666 | } 667 | } 668 | -------------------------------------------------------------------------------- /docs/drizzle.md: -------------------------------------------------------------------------------- 1 | ## How to make changes to database schema 2 | 3 | 1. Make changes to the schema in `src/db/schema.ts` 4 | 2. Run `npx drizzle-kit generate` to generate the migration 5 | 3. Run `npx drizzle-kit migrate` to apply the migration 6 | 4. Run `npx drizzle-kit push` to push the migration to the database 7 | 8 | For making changes to the prod database, you will need to change the NODE_ENV in `.env` to `production` and run `npx drizzle-kit migrate` or push the migration. 9 | 10 | To learn more about Drizzle, see the [Drizzle docs](https://orm.drizzle.team/docs/introduction) -------------------------------------------------------------------------------- /docs/lemonsqueezy.md: -------------------------------------------------------------------------------- 1 | ## Setting up lemonsqueezy store and webhooks 2 | 3 | 1. [Create a store](https://docs.lemonsqueezy.com/guides/getting-started) on lemonsqueezy 4 | 2. Make sure you set up **Subscriptions** in the store 5 | 6 | ![Setting up subscriptions](https://imagedelivery.net/_Zs8NCbSWCQ8-iurXrWjBg/d997516e-bd3e-46e9-f962-15c331d85100/public) 7 | 8 | 3. Go to Settings -> Webhooks and set up a webhook, select *all events* 9 | 10 | ![Setting up webhook](https://imagedelivery.net/_Zs8NCbSWCQ8-iurXrWjBg/cd1eabd0-9981-4dfe-8ce9-2748cca76c00/public) 11 | 12 | 4. Make sure that the secret is the same as the one in your `.dev.vars` file 13 | 14 | 5. Copy the store URL and paste it in the `.env` file as `LEMONSQUEEZY_STORE_LINK` 15 | 16 | ... and you're done! -------------------------------------------------------------------------------- /docs/ratelimits.md: -------------------------------------------------------------------------------- 1 | ## Changing ratelimits 2 | 3 | Ratelimits are set in [`src/middleware/rateLimit.ts`](/src/middleware/rateLimit.ts) 4 | 5 | To edit them, simply change the `getTierLimit` function in the ratelimit middleware. 6 | 7 | ```ts 8 | export const getTierLimit = async (user: User) => { 9 | if (!user?.subscriptionId) { 10 | return { limit: 100, window: 60 * 60 }; // Free tier 11 | } 12 | return { limit: 1000, window: 60 * 60 }; // Paid tier 13 | }; 14 | ``` 15 | 16 | All the `window` values are in seconds. 17 | 18 | We are using D1 with custom logic to ratelimit the requests. The schema can be found here - [`src/db/schema.ts`](/src/db/schema.ts) 19 | 20 | ```ts 21 | export const rateLimit = sqliteTable("rateLimit", { 22 | id: text("id").primaryKey(), 23 | userId: text("userId").notNull().references(() => user.id), 24 | endpoint: text("endpoint").notNull(), 25 | count: integer("count").notNull().default(0), 26 | resetAt: integer("resetAt", { mode: "timestamp" }).notNull(), 27 | createdAt: integer("createdAt", { mode: "timestamp" }).notNull(), 28 | updatedAt: integer("updatedAt", { mode: "timestamp" }).notNull() 29 | }); 30 | ``` -------------------------------------------------------------------------------- /docs/tests.md: -------------------------------------------------------------------------------- 1 | ## Writing and running tests 2 | 3 | To run tests, use `npm run test` 4 | 5 | Tests are made using the hono RPC functions, for better type safety and error handling. 6 | 7 | [Hono testing guide](https://hono.dev/docs/guides/testing) 8 | [Hono testing client](https://hono.dev/docs/helpers/testing) 9 | [Vitest docs](https://vitest.dev/guide/) 10 | [Using Vitest-pool-workers](https://www.npmjs.com/package/@cloudflare/vitest-pool-workers) -------------------------------------------------------------------------------- /drizzle.config.ts: -------------------------------------------------------------------------------- 1 | import 'dotenv/config'; 2 | import { defineConfig } from 'drizzle-kit'; 3 | import { execSync } from 'child_process'; 4 | 5 | declare global { 6 | interface Process { 7 | env: { 8 | DATABASE_ID: string; 9 | ACCOUNT_ID: string; 10 | TOKEN: string; 11 | NODE_ENV: string; 12 | } 13 | } 14 | 15 | var process: Process; 16 | } 17 | 18 | const getSqlitePath = () => { 19 | try { 20 | return execSync('find .wrangler/state/v3/d1/miniflare-D1DatabaseObject -type f -name "*.sqlite" -print -quit', { encoding: 'utf-8' }).trim(); 21 | } catch (e) { 22 | console.error('Failed to find SQLite database file'); 23 | return ''; 24 | } 25 | }; 26 | 27 | const cloudflareConfig = defineConfig({ 28 | out: './drizzle', 29 | schema: './src/db/schema.ts', 30 | dialect: 'sqlite', 31 | driver: "d1-http", 32 | dbCredentials: { 33 | accountId: process.env.ACCOUNT_ID, 34 | databaseId: process.env.DATABASE_ID, 35 | token: process.env.TOKEN, 36 | }, 37 | }); 38 | 39 | const localConfig = defineConfig({ 40 | out: './drizzle', 41 | schema: './src/db/schema.ts', 42 | dialect: 'sqlite', 43 | dbCredentials: { 44 | url: `file:${getSqlitePath()}`, 45 | }, 46 | }); 47 | 48 | const config = process.env.NODE_ENV === "production" ? cloudflareConfig : localConfig; 49 | console.log(`Using ${process.env.NODE_ENV === "production" ? "cloudflare" : "local"} config`); 50 | export default config; -------------------------------------------------------------------------------- /drizzle/0000_crazy_zuras.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `account` ( 2 | `id` text PRIMARY KEY NOT NULL, 3 | `userId` text NOT NULL, 4 | `accountId` text NOT NULL, 5 | `providerId` text NOT NULL, 6 | `accessToken` text, 7 | `refreshToken` text, 8 | `accessTokenExpiresAt` integer, 9 | `refreshTokenExpiresAt` integer, 10 | `scope` text, 11 | `idToken` text, 12 | `password` text, 13 | `createdAt` integer NOT NULL, 14 | `updatedAt` integer NOT NULL, 15 | FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action 16 | ); 17 | --> statement-breakpoint 18 | CREATE TABLE `rateLimit` ( 19 | `id` text PRIMARY KEY NOT NULL, 20 | `userId` text NOT NULL, 21 | `endpoint` text NOT NULL, 22 | `count` integer DEFAULT 0 NOT NULL, 23 | `resetAt` integer NOT NULL, 24 | `createdAt` integer NOT NULL, 25 | `updatedAt` integer NOT NULL, 26 | FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action 27 | ); 28 | --> statement-breakpoint 29 | CREATE TABLE `session` ( 30 | `id` text PRIMARY KEY NOT NULL, 31 | `userId` text NOT NULL, 32 | `token` text NOT NULL, 33 | `expiresAt` integer NOT NULL, 34 | `ipAddress` text, 35 | `userAgent` text, 36 | `createdAt` integer NOT NULL, 37 | `updatedAt` integer NOT NULL, 38 | FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action 39 | ); 40 | --> statement-breakpoint 41 | CREATE TABLE `user` ( 42 | `id` text PRIMARY KEY NOT NULL, 43 | `name` text NOT NULL, 44 | `email` text NOT NULL, 45 | `emailVerified` integer DEFAULT false NOT NULL, 46 | `image` text, 47 | `createdAt` integer NOT NULL, 48 | `updatedAt` integer NOT NULL, 49 | `subscriptionId` text, 50 | `lastKeyGeneratedAt` integer 51 | ); 52 | --> statement-breakpoint 53 | CREATE UNIQUE INDEX `user_email_unique` ON `user` (`email`);--> statement-breakpoint 54 | CREATE TABLE `verification` ( 55 | `id` text PRIMARY KEY NOT NULL, 56 | `identifier` text NOT NULL, 57 | `value` text NOT NULL, 58 | `expiresAt` integer NOT NULL, 59 | `createdAt` integer NOT NULL, 60 | `updatedAt` integer NOT NULL 61 | ); 62 | -------------------------------------------------------------------------------- /drizzle/meta/0000_snapshot.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "6", 3 | "dialect": "sqlite", 4 | "id": "15bcb89c-5ea5-4756-a34f-39d3ab222176", 5 | "prevId": "00000000-0000-0000-0000-000000000000", 6 | "tables": { 7 | "account": { 8 | "name": "account", 9 | "columns": { 10 | "id": { 11 | "name": "id", 12 | "type": "text", 13 | "primaryKey": true, 14 | "notNull": true, 15 | "autoincrement": false 16 | }, 17 | "userId": { 18 | "name": "userId", 19 | "type": "text", 20 | "primaryKey": false, 21 | "notNull": true, 22 | "autoincrement": false 23 | }, 24 | "accountId": { 25 | "name": "accountId", 26 | "type": "text", 27 | "primaryKey": false, 28 | "notNull": true, 29 | "autoincrement": false 30 | }, 31 | "providerId": { 32 | "name": "providerId", 33 | "type": "text", 34 | "primaryKey": false, 35 | "notNull": true, 36 | "autoincrement": false 37 | }, 38 | "accessToken": { 39 | "name": "accessToken", 40 | "type": "text", 41 | "primaryKey": false, 42 | "notNull": false, 43 | "autoincrement": false 44 | }, 45 | "refreshToken": { 46 | "name": "refreshToken", 47 | "type": "text", 48 | "primaryKey": false, 49 | "notNull": false, 50 | "autoincrement": false 51 | }, 52 | "accessTokenExpiresAt": { 53 | "name": "accessTokenExpiresAt", 54 | "type": "integer", 55 | "primaryKey": false, 56 | "notNull": false, 57 | "autoincrement": false 58 | }, 59 | "refreshTokenExpiresAt": { 60 | "name": "refreshTokenExpiresAt", 61 | "type": "integer", 62 | "primaryKey": false, 63 | "notNull": false, 64 | "autoincrement": false 65 | }, 66 | "scope": { 67 | "name": "scope", 68 | "type": "text", 69 | "primaryKey": false, 70 | "notNull": false, 71 | "autoincrement": false 72 | }, 73 | "idToken": { 74 | "name": "idToken", 75 | "type": "text", 76 | "primaryKey": false, 77 | "notNull": false, 78 | "autoincrement": false 79 | }, 80 | "password": { 81 | "name": "password", 82 | "type": "text", 83 | "primaryKey": false, 84 | "notNull": false, 85 | "autoincrement": false 86 | }, 87 | "createdAt": { 88 | "name": "createdAt", 89 | "type": "integer", 90 | "primaryKey": false, 91 | "notNull": true, 92 | "autoincrement": false 93 | }, 94 | "updatedAt": { 95 | "name": "updatedAt", 96 | "type": "integer", 97 | "primaryKey": false, 98 | "notNull": true, 99 | "autoincrement": false 100 | } 101 | }, 102 | "indexes": {}, 103 | "foreignKeys": { 104 | "account_userId_user_id_fk": { 105 | "name": "account_userId_user_id_fk", 106 | "tableFrom": "account", 107 | "tableTo": "user", 108 | "columnsFrom": [ 109 | "userId" 110 | ], 111 | "columnsTo": [ 112 | "id" 113 | ], 114 | "onDelete": "no action", 115 | "onUpdate": "no action" 116 | } 117 | }, 118 | "compositePrimaryKeys": {}, 119 | "uniqueConstraints": {}, 120 | "checkConstraints": {} 121 | }, 122 | "rateLimit": { 123 | "name": "rateLimit", 124 | "columns": { 125 | "id": { 126 | "name": "id", 127 | "type": "text", 128 | "primaryKey": true, 129 | "notNull": true, 130 | "autoincrement": false 131 | }, 132 | "userId": { 133 | "name": "userId", 134 | "type": "text", 135 | "primaryKey": false, 136 | "notNull": true, 137 | "autoincrement": false 138 | }, 139 | "endpoint": { 140 | "name": "endpoint", 141 | "type": "text", 142 | "primaryKey": false, 143 | "notNull": true, 144 | "autoincrement": false 145 | }, 146 | "count": { 147 | "name": "count", 148 | "type": "integer", 149 | "primaryKey": false, 150 | "notNull": true, 151 | "autoincrement": false, 152 | "default": 0 153 | }, 154 | "resetAt": { 155 | "name": "resetAt", 156 | "type": "integer", 157 | "primaryKey": false, 158 | "notNull": true, 159 | "autoincrement": false 160 | }, 161 | "createdAt": { 162 | "name": "createdAt", 163 | "type": "integer", 164 | "primaryKey": false, 165 | "notNull": true, 166 | "autoincrement": false 167 | }, 168 | "updatedAt": { 169 | "name": "updatedAt", 170 | "type": "integer", 171 | "primaryKey": false, 172 | "notNull": true, 173 | "autoincrement": false 174 | } 175 | }, 176 | "indexes": {}, 177 | "foreignKeys": { 178 | "rateLimit_userId_user_id_fk": { 179 | "name": "rateLimit_userId_user_id_fk", 180 | "tableFrom": "rateLimit", 181 | "tableTo": "user", 182 | "columnsFrom": [ 183 | "userId" 184 | ], 185 | "columnsTo": [ 186 | "id" 187 | ], 188 | "onDelete": "no action", 189 | "onUpdate": "no action" 190 | } 191 | }, 192 | "compositePrimaryKeys": {}, 193 | "uniqueConstraints": {}, 194 | "checkConstraints": {} 195 | }, 196 | "session": { 197 | "name": "session", 198 | "columns": { 199 | "id": { 200 | "name": "id", 201 | "type": "text", 202 | "primaryKey": true, 203 | "notNull": true, 204 | "autoincrement": false 205 | }, 206 | "userId": { 207 | "name": "userId", 208 | "type": "text", 209 | "primaryKey": false, 210 | "notNull": true, 211 | "autoincrement": false 212 | }, 213 | "token": { 214 | "name": "token", 215 | "type": "text", 216 | "primaryKey": false, 217 | "notNull": true, 218 | "autoincrement": false 219 | }, 220 | "expiresAt": { 221 | "name": "expiresAt", 222 | "type": "integer", 223 | "primaryKey": false, 224 | "notNull": true, 225 | "autoincrement": false 226 | }, 227 | "ipAddress": { 228 | "name": "ipAddress", 229 | "type": "text", 230 | "primaryKey": false, 231 | "notNull": false, 232 | "autoincrement": false 233 | }, 234 | "userAgent": { 235 | "name": "userAgent", 236 | "type": "text", 237 | "primaryKey": false, 238 | "notNull": false, 239 | "autoincrement": false 240 | }, 241 | "createdAt": { 242 | "name": "createdAt", 243 | "type": "integer", 244 | "primaryKey": false, 245 | "notNull": true, 246 | "autoincrement": false 247 | }, 248 | "updatedAt": { 249 | "name": "updatedAt", 250 | "type": "integer", 251 | "primaryKey": false, 252 | "notNull": true, 253 | "autoincrement": false 254 | } 255 | }, 256 | "indexes": {}, 257 | "foreignKeys": { 258 | "session_userId_user_id_fk": { 259 | "name": "session_userId_user_id_fk", 260 | "tableFrom": "session", 261 | "tableTo": "user", 262 | "columnsFrom": [ 263 | "userId" 264 | ], 265 | "columnsTo": [ 266 | "id" 267 | ], 268 | "onDelete": "no action", 269 | "onUpdate": "no action" 270 | } 271 | }, 272 | "compositePrimaryKeys": {}, 273 | "uniqueConstraints": {}, 274 | "checkConstraints": {} 275 | }, 276 | "user": { 277 | "name": "user", 278 | "columns": { 279 | "id": { 280 | "name": "id", 281 | "type": "text", 282 | "primaryKey": true, 283 | "notNull": true, 284 | "autoincrement": false 285 | }, 286 | "name": { 287 | "name": "name", 288 | "type": "text", 289 | "primaryKey": false, 290 | "notNull": true, 291 | "autoincrement": false 292 | }, 293 | "email": { 294 | "name": "email", 295 | "type": "text", 296 | "primaryKey": false, 297 | "notNull": true, 298 | "autoincrement": false 299 | }, 300 | "emailVerified": { 301 | "name": "emailVerified", 302 | "type": "integer", 303 | "primaryKey": false, 304 | "notNull": true, 305 | "autoincrement": false, 306 | "default": false 307 | }, 308 | "image": { 309 | "name": "image", 310 | "type": "text", 311 | "primaryKey": false, 312 | "notNull": false, 313 | "autoincrement": false 314 | }, 315 | "createdAt": { 316 | "name": "createdAt", 317 | "type": "integer", 318 | "primaryKey": false, 319 | "notNull": true, 320 | "autoincrement": false 321 | }, 322 | "updatedAt": { 323 | "name": "updatedAt", 324 | "type": "integer", 325 | "primaryKey": false, 326 | "notNull": true, 327 | "autoincrement": false 328 | }, 329 | "subscriptionId": { 330 | "name": "subscriptionId", 331 | "type": "text", 332 | "primaryKey": false, 333 | "notNull": false, 334 | "autoincrement": false 335 | }, 336 | "lastKeyGeneratedAt": { 337 | "name": "lastKeyGeneratedAt", 338 | "type": "integer", 339 | "primaryKey": false, 340 | "notNull": false, 341 | "autoincrement": false 342 | } 343 | }, 344 | "indexes": { 345 | "user_email_unique": { 346 | "name": "user_email_unique", 347 | "columns": [ 348 | "email" 349 | ], 350 | "isUnique": true 351 | } 352 | }, 353 | "foreignKeys": {}, 354 | "compositePrimaryKeys": {}, 355 | "uniqueConstraints": {}, 356 | "checkConstraints": {} 357 | }, 358 | "verification": { 359 | "name": "verification", 360 | "columns": { 361 | "id": { 362 | "name": "id", 363 | "type": "text", 364 | "primaryKey": true, 365 | "notNull": true, 366 | "autoincrement": false 367 | }, 368 | "identifier": { 369 | "name": "identifier", 370 | "type": "text", 371 | "primaryKey": false, 372 | "notNull": true, 373 | "autoincrement": false 374 | }, 375 | "value": { 376 | "name": "value", 377 | "type": "text", 378 | "primaryKey": false, 379 | "notNull": true, 380 | "autoincrement": false 381 | }, 382 | "expiresAt": { 383 | "name": "expiresAt", 384 | "type": "integer", 385 | "primaryKey": false, 386 | "notNull": true, 387 | "autoincrement": false 388 | }, 389 | "createdAt": { 390 | "name": "createdAt", 391 | "type": "integer", 392 | "primaryKey": false, 393 | "notNull": true, 394 | "autoincrement": false 395 | }, 396 | "updatedAt": { 397 | "name": "updatedAt", 398 | "type": "integer", 399 | "primaryKey": false, 400 | "notNull": true, 401 | "autoincrement": false 402 | } 403 | }, 404 | "indexes": {}, 405 | "foreignKeys": {}, 406 | "compositePrimaryKeys": {}, 407 | "uniqueConstraints": {}, 408 | "checkConstraints": {} 409 | } 410 | }, 411 | "views": {}, 412 | "enums": {}, 413 | "_meta": { 414 | "schemas": {}, 415 | "tables": {}, 416 | "columns": {} 417 | }, 418 | "internal": { 419 | "indexes": {} 420 | } 421 | } -------------------------------------------------------------------------------- /drizzle/meta/_journal.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "7", 3 | "dialect": "sqlite", 4 | "entries": [ 5 | { 6 | "idx": 0, 7 | "version": "6", 8 | "when": 1737744579019, 9 | "tag": "0000_crazy_zuras", 10 | "breakpoints": true 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend-api-kit", 3 | "scripts": { 4 | "dev": "wrangler dev", 5 | "deploy": "wrangler deploy --minify", 6 | "cf-typegen": "wrangler types --env-interface CloudflareBindings", 7 | "setup": "bun scripts/setup.ts", 8 | "lint": "biome lint --write ." 9 | }, 10 | "dependencies": { 11 | "better-auth": "^1.1.14", 12 | "drizzle-orm": "^0.38.4", 13 | "hono": "^4.6.18" 14 | }, 15 | "devDependencies": { 16 | "@biomejs/biome": "1.9.4", 17 | "@clack/prompts": "^0.9.1", 18 | "@cloudflare/vitest-pool-workers": "0.6.4", 19 | "@cloudflare/workers-types": "^4.20250121.0", 20 | "@types/bun": "^1.2.0", 21 | "better-sqlite3": "^11.8.1", 22 | "child_process": "^1.0.2", 23 | "drizzle-kit": "^0.30.2", 24 | "vitest": "2.1.8", 25 | "wrangler": "^3.105.0", 26 | "bun": "^1.2.0", 27 | "dotenv": "^16.4.7" 28 | } 29 | } -------------------------------------------------------------------------------- /scripts/setup.ts: -------------------------------------------------------------------------------- 1 | import { execSync, spawnSync } from "node:child_process"; 2 | import crypto from "node:crypto"; 3 | import { default as fs } from "node:fs"; 4 | import os from "node:os"; 5 | import { default as path } from "node:path"; 6 | import { cancel, intro, outro, select, spinner, text } from "@clack/prompts"; 7 | 8 | 9 | interface CommandError { 10 | stdout?: string; 11 | stderr?: string; 12 | } 13 | 14 | // Function to get current working directory 15 | function getCurrentDirectory(): string { 16 | return execSync('pwd', { encoding: 'utf-8' }).trim(); 17 | } 18 | 19 | // Function to execute shell commands 20 | function executeCommand(command: string): string | { error: true; message: string } { 21 | console.log(`\x1b[33m${command}\x1b[0m`); 22 | try { 23 | return execSync(command, { encoding: "utf-8" }); 24 | } catch (error) { 25 | const err = error as CommandError; 26 | return { error: true, message: err.stdout || err.stderr || "Unknown error" }; 27 | } 28 | } 29 | 30 | // Function to prompt user for input without readline-sync 31 | async function prompt(message: string, defaultValue: string): Promise { 32 | return (await text({ 33 | message: `${message} (${defaultValue}):`, 34 | placeholder: defaultValue, 35 | defaultValue, 36 | })) as string; 37 | } 38 | 39 | // Function to extract account IDs from `wrangler whoami` output 40 | function extractAccountDetails(output: string): { name: string; id: string }[] { 41 | const lines = output.split("\n"); 42 | const accountDetails: { name: string; id: string }[] = []; 43 | 44 | for (const line of lines) { 45 | const isValidLine = 46 | line.trim().startsWith("│ ") && line.trim().endsWith(" │"); 47 | 48 | if (isValidLine) { 49 | const regex = /\b[a-f0-9]{32}\b/g; 50 | const matches = line.match(regex); 51 | 52 | if (matches && matches.length === 1) { 53 | const accountName = line.split("│ ")[1]?.trim(); 54 | const accountId = matches[0].replace("│ ", "").replace(" │", ""); 55 | if (accountName === undefined || accountId === undefined) { 56 | console.error( 57 | "\x1b[31mError extracting account details from wrangler whoami output.\x1b[0m", 58 | ); 59 | cancel("Operation cancelled."); 60 | throw new Error("Failed to extract account details"); 61 | } 62 | accountDetails.push({ name: accountName, id: accountId }); 63 | } 64 | } 65 | } 66 | 67 | return accountDetails; 68 | } 69 | 70 | // Function to prompt for account ID if there are multiple accounts 71 | async function promptForAccountId( 72 | accounts: { name: string; id: string }[], 73 | ): Promise { 74 | if (accounts.length === 1) { 75 | if (!accounts[0]) { 76 | console.error( 77 | "\x1b[31mNo accounts found. Please run `wrangler login`.\x1b[0m", 78 | ); 79 | cancel("Operation cancelled."); 80 | throw new Error("No accounts found"); 81 | } 82 | if (!accounts[0].id) { 83 | console.error( 84 | "\x1b[31mNo accounts found. Please run `wrangler login`.\x1b[0m", 85 | ); 86 | cancel("Operation cancelled."); 87 | throw new Error("No account ID found"); 88 | } 89 | return accounts[0].id; 90 | } 91 | 92 | if (accounts.length > 1) { 93 | const options = accounts.map((account) => ({ 94 | value: account.id, 95 | label: account.name, 96 | })); 97 | const selectedAccountId = await select({ 98 | message: "Select an account to use:", 99 | options, 100 | }); 101 | 102 | if (!selectedAccountId) { 103 | throw new Error("No account selected"); 104 | } 105 | 106 | return selectedAccountId as string; 107 | } 108 | 109 | console.error( 110 | "\x1b[31mNo accounts found. Please run `wrangler login`.\x1b[0m", 111 | ); 112 | cancel("Operation cancelled."); 113 | throw new Error("No accounts found"); 114 | } 115 | 116 | let dbName: string; 117 | 118 | interface WranglerConfig { 119 | name?: string; 120 | main?: string; 121 | compatibility_date?: string; 122 | compatibility_flags?: string[]; 123 | d1_databases?: Array<{ 124 | binding: string; 125 | database_name: string; 126 | database_id: string; 127 | migrations_dir: string; 128 | }>; 129 | } 130 | 131 | // Function to create database and update wrangler.jsonc 132 | async function createDatabaseAndConfigure() { 133 | intro(`Let's set up your database...`); 134 | const defaultDBName = `${path.basename(getCurrentDirectory())}-db`; 135 | dbName = await prompt("Enter the name of your database", defaultDBName); 136 | 137 | let databaseID: string | undefined; 138 | 139 | const wranglerJsoncPath = path.join(__dirname, "..", "wrangler.jsonc"); 140 | let wranglerConfig: WranglerConfig; 141 | 142 | try { 143 | const wranglerJsoncContent = fs.readFileSync(wranglerJsoncPath, "utf-8"); 144 | wranglerConfig = JSON.parse(wranglerJsoncContent); 145 | } catch (error) { 146 | console.error("\x1b[31mError reading wrangler.jsonc:", error, "\x1b[0m"); 147 | cancel("Operation cancelled."); 148 | throw error; 149 | } 150 | 151 | // Run command to create a new database 152 | const creationOutput = executeCommand(`bunx wrangler d1 create ${dbName}`); 153 | 154 | if (creationOutput === undefined || typeof creationOutput !== "string") { 155 | console.log( 156 | "\x1b[33mDatabase creation failed, maybe you have already created a database with that name. I'll try to find the database ID for you.\x1b[0m", 157 | ); 158 | const dbInfoOutput = executeCommand(`bunx wrangler d1 info ${dbName}`); 159 | if (typeof dbInfoOutput === "string") { 160 | const getInfo = dbInfoOutput.match( 161 | /│ [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12} │/i, 162 | ); 163 | if (getInfo && getInfo.length === 1) { 164 | console.log( 165 | "\x1b[33mFound it! The database ID is: ", 166 | getInfo[0], 167 | "\x1b[0m", 168 | ); 169 | databaseID = getInfo[0].replace("│ ", "").replace(" │", ""); 170 | } 171 | } 172 | 173 | if (!databaseID) { 174 | console.error( 175 | "\x1b[31mSomething went wrong when initialising the database. Please try again.\x1b[0m", 176 | ); 177 | cancel("Operation cancelled."); 178 | throw new Error("Failed to get database ID"); 179 | } 180 | } else { 181 | // Extract database ID from the output 182 | const matchResult = creationOutput.match( 183 | /database_id = "(.*)"/, 184 | ); 185 | if (matchResult?.[1]) { 186 | databaseID = matchResult[1]; 187 | } else { 188 | console.error("Failed to extract database ID from the output."); 189 | cancel("Operation cancelled."); 190 | throw new Error("Failed to extract database ID"); 191 | } 192 | } 193 | 194 | // Update wrangler.jsonc with database configuration 195 | wranglerConfig.d1_databases = [ 196 | { 197 | binding: "USERS_DATABASE", 198 | database_name: dbName, 199 | database_id: databaseID, 200 | migrations_dir: "./drizzle", 201 | }, 202 | ]; 203 | 204 | try { 205 | const updatedJsonc = JSON.stringify(wranglerConfig, null, 2); 206 | fs.writeFileSync(wranglerJsoncPath, updatedJsonc); 207 | console.log( 208 | "\x1b[33mDatabase configuration updated in wrangler.jsonc\x1b[0m", 209 | ); 210 | } catch (error) { 211 | console.error("\x1b[31mError updating wrangler.jsonc:", error, "\x1b[0m"); 212 | cancel("Operation cancelled."); 213 | throw error; 214 | } 215 | 216 | outro("Database configuration completed."); 217 | } 218 | 219 | // Function to prompt for environment variables 220 | async function promptForEnvVars() { 221 | intro("Setting up environment variables..."); 222 | 223 | const devVarsPath = path.join(__dirname, "..", ".dev.vars"); 224 | const devVarsExamplePath = path.join(__dirname, "..", ".dev.vars.example"); 225 | 226 | if (!fs.existsSync(devVarsPath)) { 227 | console.log("\x1b[33mNow, let's set up your environment variables.\x1b[0m"); 228 | 229 | const vars = { 230 | AUTH_GITHUB_ID: await prompt("Enter your GitHub Client ID (enter to skip)", ""), 231 | AUTH_GITHUB_SECRET: await prompt("Enter your GitHub Client Secret (enter to skip)", ""), 232 | BETTER_AUTH_URL: await prompt("Enter your Better Auth URL", "http://localhost:8787"), 233 | SECRET: generateSecureRandomString(32), 234 | LEMONSQUEEZY_CHECKOUT_LINK: await prompt("Enter your Lemonsqueezy Checkout Link (enter to skip)", ""), 235 | }; 236 | 237 | try { 238 | const envContent = Object.entries(vars) 239 | .map(([key, value]) => `${key}=${value}`) 240 | .join("\n"); 241 | fs.writeFileSync(devVarsPath, `${envContent}\n`); 242 | console.log("\x1b[33m.dev.vars file created with environment variables.\x1b[0m"); 243 | } catch (error) { 244 | console.error("\x1b[31mError creating .dev.vars file:", error, "\x1b[0m"); 245 | cancel("Operation cancelled."); 246 | throw error; 247 | } 248 | } else { 249 | console.log("\x1b[31m.dev.vars file already exists. Skipping creation.\x1b[0m"); 250 | } 251 | 252 | outro("Environment variables setup completed."); 253 | } 254 | 255 | // Function to generate secure random string 256 | function generateSecureRandomString(length: number): string { 257 | return crypto 258 | .randomBytes(Math.ceil(length / 2)) 259 | .toString("hex") 260 | .slice(0, length); 261 | } 262 | 263 | // Function to run database migrations 264 | async function runDatabaseMigrations(dbName: string) { 265 | const setupMigrationSpinner = spinner(); 266 | setupMigrationSpinner.start("Generating setup migration..."); 267 | executeCommand("bunx drizzle-kit generate --name setup"); 268 | setupMigrationSpinner.stop("Setup migration generated."); 269 | 270 | const localMigrationSpinner = spinner(); 271 | localMigrationSpinner.start("Running local database migrations..."); 272 | executeCommand(`bunx wrangler d1 migrations apply ${dbName}`); 273 | localMigrationSpinner.stop("Local database migrations completed."); 274 | 275 | const remoteMigrationSpinner = spinner(); 276 | remoteMigrationSpinner.start("Running remote database migrations..."); 277 | executeCommand(`bunx wrangler d1 migrations apply ${dbName} --remote`); 278 | remoteMigrationSpinner.stop("Remote database migrations completed."); 279 | } 280 | 281 | // Function to deploy worker 282 | async function deployWorker() { 283 | const shouldDeploy = await select({ 284 | message: "Would you like to deploy the worker now?", 285 | options: [ 286 | { value: "yes", label: "Yes" }, 287 | { value: "no", label: "No" }, 288 | ], 289 | }); 290 | 291 | if (shouldDeploy === "yes") { 292 | console.log("\x1b[33mDeploying worker...\x1b[0m"); 293 | executeCommand("bunx wrangler deploy"); 294 | console.log("\x1b[32mWorker deployed successfully!\x1b[0m"); 295 | } 296 | } 297 | 298 | function setEnvironmentVariable(name: string, value: string): never { 299 | const platform = os.platform(); 300 | const command = platform === "win32" 301 | ? `set ${name}=${value}` // Windows Command Prompt 302 | : `export ${name}=${value}`; // Unix-like shells 303 | 304 | console.log( 305 | `\x1b[33mPlease run this command: ${command} and then rerun the setup script.\x1b[0m`, 306 | ); 307 | throw new Error("Environment variable needs to be set"); 308 | } 309 | 310 | async function main() { 311 | try { 312 | const whoamiOutput = executeCommand("wrangler whoami"); 313 | if (whoamiOutput === undefined || typeof whoamiOutput !== "string") { 314 | console.error( 315 | "\x1b[31mError running wrangler whoami. Please run `wrangler login`.\x1b[0m", 316 | ); 317 | cancel("Operation cancelled."); 318 | throw new Error("Failed to run wrangler whoami"); 319 | } 320 | 321 | try { 322 | await createDatabaseAndConfigure(); 323 | } catch (error) { 324 | console.error("\x1b[31mError:", error, "\x1b[0m"); 325 | const accountIds = extractAccountDetails(whoamiOutput); 326 | const accountId = await promptForAccountId(accountIds); 327 | setEnvironmentVariable("CLOUDFLARE_ACCOUNT_ID", accountId); 328 | } 329 | 330 | await promptForEnvVars(); 331 | await runDatabaseMigrations(dbName); 332 | await deployWorker(); 333 | 334 | console.log("\x1b[32mSetup completed successfully!\x1b[0m"); 335 | console.log("\x1b[33mYou can now run 'bun run dev' to start the development server.\x1b[0m"); 336 | } catch (error) { 337 | console.error("\x1b[31mError:", error, "\x1b[0m"); 338 | cancel("Operation cancelled."); 339 | throw error; 340 | } 341 | } 342 | 343 | main().catch(() => { 344 | // Exit with error code 345 | // @ts-expect-error 346 | process.exit(1) 347 | }); 348 | -------------------------------------------------------------------------------- /src/api.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from "hono"; 2 | import { ratelimiter } from "./middleware/rateLimit"; 3 | import type { User, Session } from "./db/schema"; 4 | 5 | export const apiRouter = new Hono<{ 6 | Bindings: Env; 7 | Variables: { 8 | user: User; 9 | session: Session; 10 | }; 11 | }>() 12 | .use(ratelimiter) 13 | .get("/", (c) => { 14 | const user = c.get("user") 15 | if (!user?.subscriptionId) { 16 | return c.json({ 17 | error: "Unauthorized, please buy a subscription" 18 | }, 401) 19 | } 20 | return c.json({ 21 | message: "Hello World" 22 | }) 23 | }) -------------------------------------------------------------------------------- /src/auth.ts: -------------------------------------------------------------------------------- 1 | import { betterAuth } from "better-auth"; 2 | import type { User, Session } from "./db/schema"; 3 | import { drizzle } from "drizzle-orm/d1"; 4 | import { drizzleAdapter } from "better-auth/adapters/drizzle"; 5 | import * as schema from "./db/schema"; 6 | import { createMiddleware } from "hono/factory"; 7 | import { Hono } from "hono"; 8 | import { generateKey, decryptKey } from "./utils/key"; 9 | import { and, eq } from "drizzle-orm"; 10 | 11 | const app = new Hono<{ 12 | Bindings: Env; 13 | Variables: { 14 | user: User; 15 | session: Session; 16 | }; 17 | }>(); 18 | 19 | export const db = (env: Env) => drizzle(env.USERS_DATABASE); 20 | 21 | export const auth = (env: Env) => 22 | betterAuth({ 23 | database: drizzleAdapter(drizzle(env.USERS_DATABASE), { 24 | provider: "sqlite", 25 | schema: { 26 | account: schema.account, 27 | session: schema.session, 28 | user: schema.user, 29 | verification: schema.verification, 30 | }, 31 | }), 32 | secret: env.SECRET, 33 | socialProviders: { 34 | github: { 35 | clientId: env.AUTH_GITHUB_ID, 36 | clientSecret: env.AUTH_GITHUB_SECRET, 37 | redirectURI: `${env.BETTER_AUTH_URL}/api/auth/callback/github`, 38 | }, 39 | }, 40 | }); 41 | 42 | export const authMiddleware = createMiddleware(async (c, next) => { 43 | // Check for bearer token 44 | const authHeader = c.req.header("Authorization"); 45 | if (authHeader?.startsWith("Bearer ")) { 46 | const token = authHeader.substring(7); 47 | try { 48 | const [userId, lastKeyGeneratedAtTimestamp] = await decryptKey(token, c.env.SECRET); 49 | const user = await db(c.env) 50 | .select() 51 | .from(schema.user) 52 | .where(eq(schema.user.id, userId)) 53 | .get(); 54 | 55 | if (user) { 56 | if (!user.lastKeyGeneratedAt || user.lastKeyGeneratedAt === null) { 57 | // Update user with current timestamp if no lastKeyGeneratedAt 58 | const now = new Date(); 59 | await db(c.env) 60 | .update(schema.user) 61 | .set({ lastKeyGeneratedAt: now }) 62 | .where(eq(schema.user.id, userId)) 63 | .run(); 64 | user.lastKeyGeneratedAt = now; 65 | } 66 | 67 | // Convert both timestamps to numbers for comparison 68 | const storedTimestamp = user.lastKeyGeneratedAt.getTime(); 69 | const providedTimestamp = Number(lastKeyGeneratedAtTimestamp); 70 | 71 | if (storedTimestamp === providedTimestamp) { 72 | c.set("user", user); 73 | c.set("session", null); 74 | await next(); 75 | return; 76 | } 77 | } 78 | } catch (e) { 79 | console.error("API Key validation failed:", e); 80 | return c.json({ error: "Invalid API key" }, 401); 81 | } 82 | 83 | // If we reach here, the API key was invalid 84 | return c.json({ error: "Invalid API key" }, 401); 85 | } 86 | 87 | // Fall back to session-based auth 88 | const session = await auth(c.env).api.getSession({ 89 | headers: c.req.raw.headers, 90 | }); 91 | 92 | if (session?.user) { 93 | const user = await db(c.env) 94 | .select() 95 | .from(schema.user) 96 | .where(eq(schema.user.id, session.user.id)) 97 | .get(); 98 | 99 | if (user && (!user.lastKeyGeneratedAt || user.lastKeyGeneratedAt === null)) { 100 | // Update user with current timestamp if no lastKeyGeneratedAt 101 | const now = new Date(); 102 | await db(c.env) 103 | .update(schema.user) 104 | .set({ lastKeyGeneratedAt: now }) 105 | .where(eq(schema.user.id, user.id)) 106 | .run(); 107 | user.lastKeyGeneratedAt = now; 108 | } 109 | 110 | c.set("session", session.session || null); 111 | c.set("user", user || null); 112 | } 113 | await next(); 114 | }); 115 | 116 | export const authRouter = app 117 | .all("/api/auth/*", (c) => { 118 | const authHandler = auth(c.env).handler; 119 | return authHandler(c.req.raw); 120 | }) 121 | .get("/signout", async (c) => { 122 | await auth(c.env).api.signOut({ 123 | headers: c.req.raw.headers, 124 | }); 125 | return c.redirect("/"); 126 | }) 127 | .get("/signin", async (c) => { 128 | const signinUrl = await auth(c.env).api.signInSocial({ 129 | body: { 130 | provider: "github", 131 | callbackURL: "/", 132 | }, 133 | }); 134 | 135 | if (!signinUrl || !signinUrl.url) { 136 | return c.text("Failed to sign in", 500); 137 | } 138 | 139 | return c.redirect(signinUrl.url); 140 | }) 141 | .post("/api/auth/token", async (c) => { 142 | const user = c.get("user"); 143 | if (!user) { 144 | return c.json({ error: "Unauthorized" }, 401); 145 | } 146 | 147 | const lastKeyGeneratedAt = new Date().getTime(); 148 | const token = await generateKey(user.id, String(lastKeyGeneratedAt), c.env.SECRET); 149 | 150 | return c.json({ token }); 151 | }); 152 | -------------------------------------------------------------------------------- /src/db/schema.ts: -------------------------------------------------------------------------------- 1 | import { sql } from "drizzle-orm"; 2 | import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; 3 | 4 | export const user = sqliteTable("user", { 5 | id: text("id").primaryKey(), 6 | name: text("name").notNull(), 7 | email: text("email").notNull().unique(), 8 | emailVerified: integer("emailVerified", { mode: "boolean" }).notNull().default(false), 9 | image: text("image"), 10 | createdAt: integer("createdAt", { mode: "timestamp" }).notNull(), 11 | updatedAt: integer("updatedAt", { mode: "timestamp" }).notNull(), 12 | subscriptionId: text("subscriptionId"), 13 | lastKeyGeneratedAt: integer("lastKeyGeneratedAt", { mode: "timestamp" }), 14 | }); 15 | 16 | export const session = sqliteTable("session", { 17 | id: text("id").primaryKey(), 18 | userId: text("userId").notNull().references(() => user.id), 19 | token: text("token").notNull(), 20 | expiresAt: integer("expiresAt", { mode: "timestamp" }).notNull(), 21 | ipAddress: text("ipAddress"), 22 | userAgent: text("userAgent"), 23 | createdAt: integer("createdAt", { mode: "timestamp" }).notNull(), 24 | updatedAt: integer("updatedAt", { mode: "timestamp" }).notNull() 25 | }); 26 | 27 | export const account = sqliteTable("account", { 28 | id: text("id").primaryKey(), 29 | userId: text("userId").notNull().references(() => user.id), 30 | accountId: text("accountId").notNull(), 31 | providerId: text("providerId").notNull(), 32 | accessToken: text("accessToken"), 33 | refreshToken: text("refreshToken"), 34 | accessTokenExpiresAt: integer("accessTokenExpiresAt", { mode: "timestamp" }), 35 | refreshTokenExpiresAt: integer("refreshTokenExpiresAt", { mode: "timestamp" }), 36 | scope: text("scope"), 37 | idToken: text("idToken"), 38 | password: text("password"), 39 | createdAt: integer("createdAt", { mode: "timestamp" }).notNull(), 40 | updatedAt: integer("updatedAt", { mode: "timestamp" }).notNull() 41 | }); 42 | 43 | export const verification = sqliteTable("verification", { 44 | id: text("id").primaryKey(), 45 | identifier: text("identifier").notNull(), 46 | value: text("value").notNull(), 47 | expiresAt: integer("expiresAt", { mode: "timestamp" }).notNull(), 48 | createdAt: integer("createdAt", { mode: "timestamp" }).notNull(), 49 | updatedAt: integer("updatedAt", { mode: "timestamp" }).notNull() 50 | }); 51 | 52 | export const rateLimit = sqliteTable("rateLimit", { 53 | id: text("id").primaryKey(), 54 | userId: text("userId").notNull().references(() => user.id), 55 | endpoint: text("endpoint").notNull(), 56 | count: integer("count").notNull().default(0), 57 | resetAt: integer("resetAt", { mode: "timestamp" }).notNull(), 58 | createdAt: integer("createdAt", { mode: "timestamp" }).notNull(), 59 | updatedAt: integer("updatedAt", { mode: "timestamp" }).notNull() 60 | }); 61 | 62 | export type User = typeof user.$inferSelect; 63 | export type Session = typeof session.$inferSelect; 64 | export type Account = typeof account.$inferSelect; 65 | export type Verification = typeof verification.$inferSelect; 66 | export type RateLimit = typeof rateLimit.$inferSelect; 67 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import { Hono } from "hono"; 2 | import { authMiddleware, authRouter } from "./auth"; 3 | import type { User, Session } from "./db/schema"; 4 | import { paymentRouter } from "./payment/lemonsqueezy"; 5 | import { apiRouter } from "./api"; 6 | import { generateKey } from "./utils/key"; 7 | import { Landing } from "./ui/landing"; 8 | 9 | const app = new Hono<{ 10 | Bindings: Env; 11 | Variables: { 12 | user: User; 13 | session: Session; 14 | }; 15 | }>() 16 | .use(authMiddleware) 17 | // main (signup) route 18 | .route("/", authRouter) 19 | // webhook handler 20 | .route("/", paymentRouter) 21 | // api routes 22 | .route("/api", apiRouter) 23 | .get("/", async (c) => { 24 | const user = c.get("user"); 25 | 26 | const apiKey = await generateKey( 27 | user?.id, 28 | String(user?.lastKeyGeneratedAt?.getTime()), 29 | c.env.SECRET 30 | ); 31 | 32 | const subscriptionLink = `${c.env.LEMONSQUEEZY_CHECKOUT_LINK}?checkout[email]=${user?.email}`; 33 | const subscriptionStatus = user?.subscriptionId 34 | ? "Premium" 35 | : `Free • Upgrade`; 36 | 37 | return c.html(); 38 | }); 39 | 40 | export default app; 41 | -------------------------------------------------------------------------------- /src/middleware/rateLimit.ts: -------------------------------------------------------------------------------- 1 | import type { Context, MiddlewareHandler, Next } from "hono"; 2 | import { HTTPException } from "hono/http-exception"; 3 | import { db } from "../auth"; 4 | import { rateLimit, user } from "../db/schema"; 5 | import { and, eq, gt } from "drizzle-orm"; 6 | import type { User, Session } from "../db/schema"; 7 | 8 | export interface RateLimitConfig { 9 | // Requests per window 10 | limit: number; 11 | // Window size in seconds 12 | window: number; 13 | } 14 | 15 | export const ratelimiter = async (c: Context<{ 16 | Bindings: Env, 17 | Variables: { 18 | user: User, 19 | session: Session 20 | } 21 | }>, next: Next) => { 22 | const rateLimiter = createRateLimiter( 23 | async (user) => await getTierLimit(user) 24 | ); 25 | return rateLimiter(c, next); 26 | } 27 | 28 | export const getTierLimit = async (user: User) => { 29 | if (!user?.subscriptionId) { 30 | return { limit: 100, window: 60 * 60 }; // Free tier 31 | } 32 | return { limit: 1000, window: 60 * 60 }; // Paid tier 33 | }; 34 | 35 | // Default tier limits: You can disable this if you want. 36 | const DEFAULT_LIMIT: RateLimitConfig = { limit: 10, window: 60 * 60 } 37 | 38 | export const createRateLimiter = ( 39 | getTierLimit: (user: User) => Promise 40 | ): MiddlewareHandler<{ 41 | Bindings: Env; 42 | Variables: { 43 | user: User, 44 | session: Session 45 | }; 46 | }> => { 47 | return async (c, next) => { 48 | const user = c.get("user"); 49 | if (!user || c.req.path === "/") { 50 | await next(); 51 | return; 52 | } 53 | 54 | const endpoint = new URL(c.req.url).pathname; 55 | const now = new Date(); 56 | 57 | // Get user's tier limit 58 | const tierLimit = (await getTierLimit(user)) ?? DEFAULT_LIMIT; 59 | 60 | // Check existing rate limit 61 | const existing = await db(c.env) 62 | .select() 63 | .from(rateLimit) 64 | .where( 65 | and( 66 | eq(rateLimit.userId, user.id), 67 | eq(rateLimit.endpoint, endpoint), 68 | gt(rateLimit.resetAt, now) 69 | ) 70 | ) 71 | .get(); 72 | 73 | if (!existing) { 74 | // Create new rate limit entry 75 | const resetAt = new Date(now.getTime() + tierLimit.window * 1000); 76 | await db(c.env) 77 | .insert(rateLimit) 78 | .values({ 79 | id: crypto.randomUUID(), 80 | userId: user.id, 81 | endpoint, 82 | count: 1, 83 | resetAt, 84 | createdAt: now, 85 | updatedAt: now, 86 | }); 87 | } else if (existing.count >= tierLimit.limit) { 88 | // Rate limit exceeded 89 | throw new HTTPException(429, { 90 | message: `Rate limit exceeded ${JSON.stringify(tierLimit)}, existing: ${JSON.stringify(existing)}`, 91 | }); 92 | } else { 93 | // Update count 94 | await db(c.env) 95 | .update(rateLimit) 96 | .set({ 97 | count: existing.count + 1, 98 | updatedAt: now, 99 | }) 100 | .where(eq(rateLimit.id, existing.id)); 101 | } 102 | 103 | // Set rate limit headers 104 | if (existing) { 105 | c.header("X-RateLimit-Limit", tierLimit.limit.toString()); 106 | c.header("X-RateLimit-Remaining", (tierLimit.limit - existing.count - 1).toString()); 107 | c.header( 108 | "X-RateLimit-Reset", 109 | Math.floor(existing.resetAt.getTime() / 1000).toString() 110 | ); 111 | } 112 | 113 | await next(); 114 | }; 115 | }; -------------------------------------------------------------------------------- /src/payment/lemonsqueezy.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from "hono" 2 | import { user } from "../db/schema" 3 | import { db } from "../auth" 4 | import { eq } from "drizzle-orm" 5 | 6 | import type{ Subscription, WebhookPayload, DiscriminatedWebhookPayload } from "./types" 7 | import type { User, Session } from "../db/schema"; 8 | 9 | export const paymentRouter = new Hono<{ Bindings: Env, Variables: { 10 | user: User, 11 | session: Session 12 | } }>() 13 | 14 | const SUPPORTED_EVENTS = [ 15 | 'subscription_created', 16 | 'subscription_updated', 17 | 'subscription_cancelled', 18 | 'subscription_expired' 19 | ] 20 | 21 | const verifySignature = async (secret: string, signature: string, body: string) => { 22 | const encoder = new TextEncoder(); 23 | 24 | const key = await crypto.subtle.importKey( 25 | 'raw', 26 | encoder.encode(secret), 27 | { name: 'HMAC', hash: 'SHA-256' }, 28 | false, 29 | ['sign', 'verify'] 30 | ); 31 | 32 | const hmac = await crypto.subtle.sign( 33 | 'HMAC', 34 | key, 35 | encoder.encode(body) 36 | ); 37 | 38 | const expectedSignature = Array.from(new Uint8Array(hmac)) 39 | .map(b => b.toString(16).padStart(2, '0')) 40 | .join(''); 41 | 42 | if (signature !== expectedSignature) { 43 | return false 44 | } 45 | 46 | return true 47 | } 48 | 49 | const updateUserSubscription = async (env: Env, userId: string, subscriptionId: string | null) => { 50 | await db(env).update(user) 51 | .set({ subscriptionId }) 52 | .where(eq(user.id, userId)) 53 | } 54 | 55 | paymentRouter.post("/webhook", async (c) => { 56 | if (c.req.method !== "POST") { 57 | return c.json({ error: "Method not allowed" }, 405) 58 | } 59 | 60 | const secret = c.env.SECRET 61 | const signature = c.req.header("x-signature") 62 | 63 | 64 | if (!signature || !secret) { 65 | return c.json({ error: "Unauthorized" }, 401) 66 | } 67 | 68 | const body = await c.req.text() 69 | 70 | const isValid = await verifySignature(secret, signature, body) 71 | 72 | if (!isValid) { 73 | return c.json({ error: "Unauthorized" }, 401) 74 | } 75 | 76 | const payload = JSON.parse(body) as DiscriminatedWebhookPayload<{email: string}> 77 | const { event_name: eventName } = payload.meta 78 | 79 | 80 | if (!SUPPORTED_EVENTS.includes(eventName)) { 81 | return c.json({ error: "Event not supported" }, 400) 82 | } 83 | 84 | if (!payload.data.attributes.user_email) { 85 | return c.json({ error: "Email not found" }, 400) 86 | } 87 | 88 | const users = await db(c.env) 89 | .select() 90 | .from(user) 91 | .where(eq(user.email, payload.data.attributes.user_email)) 92 | 93 | if (!users.length) { 94 | return c.json({ error: "User not found" }, 404) 95 | } 96 | 97 | const isSubscriptionActive = ['subscription_created', 'subscription_updated'].includes(eventName) 98 | await updateUserSubscription( 99 | c.env, 100 | users[0].id, 101 | isSubscriptionActive ? payload.data.id : null 102 | ) 103 | 104 | console.log('Webhook processed successfully') 105 | return c.json({ message: "Webhook received" }, 200) 106 | }) -------------------------------------------------------------------------------- /src/payment/types.ts: -------------------------------------------------------------------------------- 1 | // Source: https://github.com/remorses/lemonsqueezy-webhooks 2 | 3 | type SubscriptionEventNames = 4 | | 'subscription_created' 5 | | 'subscription_cancelled' 6 | | 'subscription_resumed' 7 | | 'subscription_updated' 8 | | 'subscription_expired' 9 | | 'subscription_paused' 10 | | 'subscription_unpaused' 11 | 12 | type SubscriptionInvoiceEventNames = 13 | | 'subscription_payment_success' 14 | | 'subscription_payment_failed' 15 | | 'subscription_payment_recovered' 16 | 17 | type OrderEventNames = 'order_created' | 'order_refunded' 18 | 19 | type LicenseKeyEventNames = 'license_key_created' 20 | 21 | export type WebhookPayload = { 22 | meta: { 23 | event_name: 24 | | SubscriptionEventNames 25 | | SubscriptionInvoiceEventNames 26 | | OrderEventNames 27 | | LicenseKeyEventNames 28 | custom_data?: CustomData 29 | } 30 | data: Subscription | SubscriptionInvoice | Order | LicenseKey 31 | } 32 | 33 | // augmented type to make TypeScript discriminated unions work: https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#discriminating-unions 34 | export type DiscriminatedWebhookPayload = 35 | | { 36 | event_name: SubscriptionEventNames 37 | meta: { 38 | event_name: SubscriptionEventNames 39 | 40 | custom_data: CustomData 41 | } 42 | data: Subscription 43 | } 44 | // | { 45 | // event_name: SubscriptionInvoiceEventNames 46 | // meta: { 47 | // event_name: SubscriptionInvoiceEventNames 48 | // custom_data: CustomData 49 | // } 50 | // data: SubscriptionInvoice 51 | // } 52 | // | { 53 | // event_name: OrderEventNames 54 | // meta: { event_name: OrderEventNames; custom_data: CustomData } 55 | // data: Order 56 | // } 57 | // | { 58 | // event_name: LicenseKeyEventNames 59 | // meta: { event_name: LicenseKeyEventNames; custom_data: CustomData } 60 | // data: LicenseKey 61 | // } 62 | 63 | export type EventName = WebhookPayload['meta']['event_name'] 64 | 65 | export type SubscriptionInvoice = { 66 | type: 'subscription-invoices' 67 | id: string 68 | attributes: { 69 | store_id: number 70 | subscription_id: number 71 | billing_reason: string 72 | card_brand: string 73 | card_last_four: string 74 | currency: string 75 | currency_rate: string 76 | subtotal: number 77 | discount_total: number 78 | tax: number 79 | total: number 80 | subtotal_usd: number 81 | discount_total_usd: number 82 | tax_usd: number 83 | total_usd: number 84 | status: string 85 | status_formatted: string 86 | refunded: number 87 | refunded_at: string | null 88 | subtotal_formatted: string 89 | discount_total_formatted: string 90 | tax_formatted: string 91 | total_formatted: string 92 | urls: { 93 | invoice_url: string 94 | } 95 | created_at: string 96 | updated_at: string 97 | test_mode: boolean 98 | } 99 | relationships: { 100 | store: { 101 | links: { 102 | related: string 103 | self: string 104 | } 105 | } 106 | subscription: { 107 | links: { 108 | related: string 109 | self: string 110 | } 111 | } 112 | } 113 | links: { 114 | self: string 115 | } 116 | } 117 | 118 | export type Subscription = { 119 | type: 'subscriptions' 120 | id: string 121 | attributes: { 122 | store_id: number 123 | order_id: number 124 | customer_id: number 125 | order_item_id: number 126 | product_id: number 127 | variant_id: number 128 | product_name: string 129 | variant_name: string 130 | user_name: string 131 | user_email: string 132 | status: SubscriptionStatus 133 | status_formatted: string 134 | pause: null 135 | cancelled: boolean 136 | trial_ends_at: string | null 137 | billing_anchor: number 138 | urls: { 139 | update_payment_method: string 140 | } 141 | renews_at: string 142 | /** 143 | * If the subscription has as status of cancelled or expired, this will be an ISO-8601 formatted date-time string indicating when the subscription expires (or expired). For all other status values, this will be null. 144 | */ 145 | ends_at: string | null 146 | created_at: string 147 | updated_at: string 148 | test_mode: boolean 149 | } 150 | } 151 | 152 | export type Order = { 153 | type: 'orders' 154 | id: string 155 | attributes: { 156 | store_id: number 157 | identifier: string 158 | customer_id: number 159 | order_number: number 160 | user_name: string 161 | user_email: string 162 | currency: string 163 | currency_rate: string 164 | subtotal: number 165 | discount_total: number 166 | tax: number 167 | total: number 168 | subtotal_usd: number 169 | discount_total_usd: number 170 | tax_usd: number 171 | total_usd: number 172 | tax_name: string 173 | tax_rate: string 174 | status: string 175 | status_formatted: string 176 | refunded: number 177 | refunded_at: string | null 178 | subtotal_formatted: string 179 | discount_total_formatted: string 180 | tax_formatted: string 181 | total_formatted: string 182 | first_order_item: { 183 | id: number 184 | order_id: number 185 | product_id: number 186 | variant_id: number 187 | product_name: string 188 | variant_name: string 189 | price: number 190 | created_at: string 191 | updated_at: string 192 | test_mode: boolean 193 | } 194 | created_at: string 195 | updated_at: string 196 | } 197 | } 198 | 199 | export type LicenseKey = { 200 | type: 'license-keys' 201 | id: string 202 | attributes: { 203 | store_id: number 204 | order_id: number 205 | order_item_id: number 206 | product_id: number 207 | user_name: string 208 | user_email: string 209 | key: string 210 | key_short: string 211 | activation_limit: number 212 | instances_count: number 213 | disabled: number 214 | status: string 215 | status_formatted: string 216 | expires_at: string | null 217 | created_at: string 218 | updated_at: string 219 | } 220 | } 221 | 222 | type SubscriptionStatus = 223 | | 'on_trial' 224 | | 'active' 225 | | 'paused' 226 | | 'past_due' 227 | | 'unpaid' 228 | | 'cancelled' 229 | | 'expired' -------------------------------------------------------------------------------- /src/ui/landing.tsx: -------------------------------------------------------------------------------- 1 | import type { FC } from "hono/jsx"; 2 | import type { User } from "../db/schema"; 3 | 4 | const styles = { 5 | container: { 6 | padding: "40px", 7 | fontFamily: "system-ui, sans-serif", 8 | maxWidth: "800px", 9 | margin: "0 auto" 10 | }, 11 | title: { 12 | color: "#f97316", 13 | fontSize: "2.5rem", 14 | fontWeight: "bold", 15 | marginBottom: "20px" 16 | }, 17 | subtitle: { 18 | fontSize: "1.2rem", 19 | marginBottom: "20px" 20 | }, 21 | featureList: { 22 | listStyle: "none", 23 | padding: 0 24 | }, 25 | featureItem: { 26 | marginBottom: "10px", 27 | display: "flex", 28 | alignItems: "center" 29 | }, 30 | checkmark: { 31 | color: "#f97316", 32 | marginRight: "10px" 33 | }, 34 | link: { 35 | textDecoration: "none", 36 | fontWeight: "semibold" 37 | }, 38 | button: { 39 | color: "#f97316", 40 | textDecoration: "none", 41 | fontWeight: "bold", 42 | fontSize: "1.1rem", 43 | padding: "10px 20px", 44 | border: "2px solid #f97316", 45 | borderRadius: "6px", 46 | display: "inline-block" 47 | }, 48 | apiKeyBox: { 49 | fontFamily: "monospace", 50 | padding: "16px", 51 | background: "#fff7ed", 52 | borderRadius: "8px", 53 | marginBottom: "20px", 54 | border: "2px solid #f97316" 55 | }, 56 | githubIcon: { 57 | width: "20px", 58 | height: "20px", 59 | marginRight: "8px", 60 | verticalAlign: "middle" 61 | } 62 | }; 63 | 64 | const features = [ 65 | { text: "API Keys" }, 66 | { text: "Rate Limiting" }, 67 | { text: "Authentication with", link: { text: "better-auth", url: "https://www.better-auth.com/" } }, 68 | { text: "Database with", link: { text: "Drizzle", url: "https://drizzle.team" }, extra: { text: "D1", url: "https://developers.cloudflare.com/d1" } }, 69 | { text: "Subscriptions with", link: { text: "Lemonsqueezy", url: "https://lemonsqueezy.com" } }, 70 | { text: "", link: { text: "Integration tests", url: "https://vitest.dev" } } 71 | ]; 72 | 73 | export const Landing: FC<{ 74 | user?: User; 75 | apiKey?: string; 76 | subscriptionLink?: string; 77 | }> = ({ user, apiKey, subscriptionLink }) => { 78 | if (!user) { 79 | return ( 80 |
81 |

⚡️ Backend API Kit

82 |

83 | Easily create scalable, monetisable backend APIs with Hono + Cloudflare workers 84 |

85 |
86 |

Features:

87 |
    88 | {features.map((feature, i) => ( 89 |
  • 90 | 91 | {feature.text} 92 | {feature.link && ( 93 | <> {feature.link.text} 94 | )} 95 | {feature.extra && ( 96 | <> + {feature.extra.text} 97 | )} 98 |
  • 99 | ))} 100 |
101 |
102 | 120 |
121 | ); 122 | } 123 | 124 | return ( 125 |
126 |

Backend API Kit

127 |
128 | Welcome {user.name}! 129 |
130 |
131 | {user.email} • {user.subscriptionId ? ( 132 | Premium 133 | ) : ( 134 | <> 135 | Free Upgrade  136 | 137 | )} 138 |
139 | {apiKey && ( 140 |
141 |
Your API Key:
142 |
{apiKey}
143 |
144 | )} 145 |
146 | Sign out 147 |
148 |
149 | ); 150 | }; 151 | -------------------------------------------------------------------------------- /src/utils/cipher.ts: -------------------------------------------------------------------------------- 1 | async function encrypt(data: string, key: string): Promise { 2 | try { 3 | const encoder = new TextEncoder(); 4 | const encodedData = encoder.encode(data); 5 | 6 | // Hash the key to ensure it's exactly 16 bytes (128 bits) 7 | const keyHash = await crypto.subtle.digest("SHA-256", encoder.encode(key)); 8 | const keyBytes = new Uint8Array(keyHash).slice(0, 16); 9 | 10 | const baseForIv = encoder.encode(data + key); 11 | const ivHash = await crypto.subtle.digest("SHA-256", baseForIv); 12 | const iv = new Uint8Array(ivHash).slice(0, 12); 13 | 14 | const cryptoKey = await crypto.subtle.importKey( 15 | "raw", 16 | keyBytes, 17 | { name: "AES-GCM", length: 128 }, // Changed to 128 bit key 18 | false, 19 | ["encrypt", "decrypt"], 20 | ); 21 | 22 | const encrypted = await crypto.subtle.encrypt( 23 | { name: "AES-GCM", iv: new Uint8Array(iv).buffer as ArrayBuffer }, 24 | cryptoKey, 25 | encodedData, 26 | ); 27 | 28 | const combined = new Uint8Array([...iv, ...new Uint8Array(encrypted)]); 29 | 30 | // Convert to base64 safely 31 | const base64 = Buffer.from(combined).toString("base64"); 32 | 33 | // Make URL-safe 34 | return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); 35 | } catch (err) { 36 | console.error("Encryption error:", err); 37 | throw err; 38 | } 39 | } 40 | 41 | async function decrypt(encryptedData: string, key: string): Promise { 42 | try { 43 | // Restore base64 padding and convert URL-safe chars 44 | const base64 = encryptedData 45 | .replace(/-/g, "+") 46 | .replace(/_/g, "/") 47 | .padEnd( 48 | encryptedData.length + ((4 - (encryptedData.length % 4)) % 4), 49 | "=", 50 | ); 51 | 52 | // Use Buffer for safer base64 decoding 53 | const combined = Buffer.from(base64, "base64"); 54 | const combinedArray = new Uint8Array(combined); 55 | 56 | // Extract the IV that was used for encryption 57 | const iv = combinedArray.slice(0, 12); 58 | const encrypted = combinedArray.slice(12); 59 | 60 | // Hash the key to ensure it's exactly 16 bytes (128 bits) 61 | const keyHash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(key)); 62 | const keyBytes = new Uint8Array(keyHash).slice(0, 16); 63 | 64 | // Import the same key used for encryption 65 | const cryptoKey = await crypto.subtle.importKey( 66 | "raw", 67 | keyBytes, 68 | { name: "AES-GCM", length: 128 }, // Changed to 128 bit key 69 | false, 70 | ["encrypt", "decrypt"], 71 | ); 72 | 73 | // Use the extracted IV and key to decrypt 74 | const decrypted = await crypto.subtle.decrypt( 75 | { name: "AES-GCM", iv: new Uint8Array(iv).buffer as ArrayBuffer }, 76 | cryptoKey, 77 | encrypted.buffer as ArrayBuffer, 78 | ); 79 | 80 | return new TextDecoder().decode(decrypted); 81 | } catch (err) { 82 | console.error("Decryption error:", err); 83 | throw err; 84 | } 85 | } 86 | 87 | export { encrypt, decrypt }; 88 | -------------------------------------------------------------------------------- /src/utils/key.ts: -------------------------------------------------------------------------------- 1 | import { decrypt, encrypt } from "./cipher" 2 | 3 | export const generateKey = async (userId: string, lastKeyGeneratedAt: string, secret: string) => { 4 | return await encrypt(`${userId}--${lastKeyGeneratedAt}`, secret) 5 | } 6 | 7 | export const decryptKey = async (key: string, secret: string) => { 8 | const decrypted = await decrypt(key, secret) 9 | 10 | return decrypted.split("--") 11 | } -------------------------------------------------------------------------------- /test/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { env } from 'cloudflare:test'; 2 | import { describe, it, expect } from 'vitest'; 3 | import app from "../src" 4 | import { testClient } from 'hono/testing' 5 | 6 | describe('Authentication on /api routes', () => { 7 | it('should return 401 on protected routes', async () => { 8 | const client = testClient(app, env) 9 | const res = await client.api.$get() 10 | 11 | expect(res.status).toBe(401) 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "moduleResolution": "Bundler", 6 | "strict": true, 7 | "skipLibCheck": true, 8 | "lib": [ 9 | "ESNext" 10 | ], 11 | "types": [ 12 | "@cloudflare/workers-types/2023-07-01","node", "@cloudflare/vitest-pool-workers" 13 | ], 14 | "jsx": "react-jsx", 15 | "jsxImportSource": "hono/jsx" 16 | }, 17 | } -------------------------------------------------------------------------------- /vitest.config.mts: -------------------------------------------------------------------------------- 1 | import { defineWorkersConfig } from '@cloudflare/vitest-pool-workers/config'; 2 | 3 | export default defineWorkersConfig({ 4 | test: { 5 | poolOptions: { 6 | workers: { 7 | wrangler: { configPath: './wrangler.jsonc' }, 8 | }, 9 | }, 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /worker-configuration.d.ts: -------------------------------------------------------------------------------- 1 | interface Env { 2 | USERS_DATABASE: D1Database; 3 | BETTER_AUTH_URL: string; 4 | SECRET: string; 5 | AUTH_GITHUB_ID: string; 6 | AUTH_GITHUB_SECRET: string; 7 | LEMONSQUEEZY_CHECKOUT_LINK: string 8 | } -------------------------------------------------------------------------------- /wrangler.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "node_modules/wrangler/config-schema.json", 3 | "name": "backend-api-kit", 4 | "main": "src/index.tsx", 5 | "compatibility_date": "2025-01-21", 6 | "compatibility_flags": ["nodejs_compat"], 7 | "observability": { 8 | "enabled": true 9 | }, 10 | "placement": { 11 | "mode": "smart" 12 | }, 13 | "d1_databases": [ 14 | { 15 | "binding": "USERS_DATABASE", 16 | "database_name": "backend-api-kit", 17 | "database_id": "05a34dd0-b2df-40e1-9e3f-daec23f471d9", 18 | "preview_database_id": "05a34dd0-b2df-40e1-9e3f-daec23f471d9" 19 | } 20 | ] 21 | } --------------------------------------------------------------------------------