the universal sandbox runtime for agents and humans. pocketenv.io
sandbox openclaw agent claude-code vercel-sandbox deno-sandbox cloudflare-sandbox atproto sprites daytona
7
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add sandbox XRPC API, auth, DB & web client

Add generated lexicons/types and XRPC handlers for io.pocketenv.sandbox.
Implement OAuth client, session/state stores, and an SQLite KV driver.
Add DB schema updates and drizzle migrations, pkl/tooling scripts
(pkl eval, sandbox, seed), and web client API, hooks and sandbox pages.

+7429 -398
+1
.gitignore
··· 1 + node_modules/
+14
apps/api/.env.example
··· 1 + POSTGRES_URL=postgresql://postgres:mysecretpassword@localhost:5434/pocketenv?sslmode=disable 2 + 3 + JWT_SECRET= 4 + COOKIE_SECRET= 5 + FRONTEND_URL="http://localhost:5173" 6 + 7 + DB_PATH="./atproto.sqlite" 8 + KV_DB_PATH="./pocketenv-kv.sqlite" 9 + 10 + PRIVATE_KEY_1='YOUR-PRIVATE-KEY-1' 11 + 12 + PRIVATE_KEY_2='YOUR-PRIVATE-KEY-2' 13 + 14 + PRIVATE_KEY_3='YOUR-PRIVATE-KEY-3'
+4
apps/api/.gitignore
··· 32 32 33 33 # Finder (MacOS) folder config 34 34 .DS_Store 35 + 36 + *.sqlite 37 + *.sqlite-shm 38 + *.sqlite-wal
+34
apps/api/biome.json
··· 1 + { 2 + "$schema": "https://biomejs.dev/schemas/2.2.3/schema.json", 3 + "vcs": { 4 + "enabled": false, 5 + "clientKind": "git", 6 + "useIgnoreFile": false 7 + }, 8 + "files": { 9 + "ignoreUnknown": false 10 + }, 11 + "formatter": { 12 + "enabled": true, 13 + "indentStyle": "space" 14 + }, 15 + "linter": { 16 + "enabled": true, 17 + "rules": { 18 + "recommended": true 19 + } 20 + }, 21 + "javascript": { 22 + "formatter": { 23 + "quoteStyle": "double" 24 + } 25 + }, 26 + "assist": { 27 + "enabled": true, 28 + "actions": { 29 + "source": { 30 + "organizeImports": "on" 31 + } 32 + } 33 + } 34 + }
+124 -16
apps/api/bun.lock
··· 18 18 "@hono/node-server": "^1.19.9", 19 19 "@tsndr/cloudflare-worker-jwt": "^3.2.1", 20 20 "@types/node": "^25.2.3", 21 + "@types/prompts": "^2.4.9", 21 22 "@types/ramda": "^0.31.1", 22 23 "better-sqlite3": "^12.6.2", 23 24 "chalk": "^5.6.2", ··· 27 28 "dotenv": "^17.2.4", 28 29 "drizzle-orm": "^0.45.1", 29 30 "effect": "^3.19.16", 31 + "envalid": "^8.1.1", 30 32 "express": "^5.2.1", 31 33 "hono": "^4.11.9", 34 + "ioredis": "^5.9.3", 35 + "jsonwebtoken": "^9.0.3", 36 + "kysely": "^0.28.11", 32 37 "libsodium-wrappers": "^0.8.2", 38 + "morgan": "^1.10.1", 33 39 "pg": "^8.18.0", 40 + "prompts": "^2.4.2", 34 41 "ramda": "^0.32.0", 35 - "unstorage": "^1.14.4", 42 + "redis": "^5.10.0", 43 + "redlock": "^5.0.0-beta.2", 44 + "unstorage": "^1.17.4", 36 45 "zod": "^4.3.6", 46 + "zx": "^8.8.5", 37 47 }, 38 48 "devDependencies": { 39 - "@biomejs/biome": "^2.3.14", 49 + "@biomejs/biome": "^2.3.15", 50 + "@types/better-sqlite3": "^7.6.13", 51 + "@types/cors": "^2.8.19", 40 52 "@types/express": "^5.0.6", 53 + "@types/jsonwebtoken": "^9.0.10", 54 + "@types/morgan": "^1.9.10", 55 + "@types/pg": "^8.16.0", 41 56 "drizzle-kit": "^0.31.9", 42 57 "tsx": "^4.21.0", 43 - }, 44 - "peerDependencies": { 45 - "typescript": "^5", 46 58 }, 47 59 }, 48 60 }, ··· 111 123 112 124 "@atproto/xrpc-server": ["@atproto/xrpc-server@0.7.19", "", { "dependencies": { "@atproto/common": "^0.4.11", "@atproto/crypto": "^0.4.4", "@atproto/lexicon": "^0.4.11", "@atproto/xrpc": "^0.7.0", "cbor-x": "^1.5.1", "express": "^4.17.2", "http-errors": "^2.0.0", "mime-types": "^2.1.35", "rate-limiter-flexible": "^2.4.1", "uint8arrays": "3.0.0", "ws": "^8.12.0", "zod": "^3.23.8" } }, "sha512-YSCl/tU2NDykgDYslFSOYCr96esUgDwncFiADKL59/fyIFPLoT0qY8Uq/budpxUh0qPzjow4HHgVWESOaOpUmA=="], 113 125 114 - "@biomejs/biome": ["@biomejs/biome@2.3.14", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.14", "@biomejs/cli-darwin-x64": "2.3.14", "@biomejs/cli-linux-arm64": "2.3.14", "@biomejs/cli-linux-arm64-musl": "2.3.14", "@biomejs/cli-linux-x64": "2.3.14", "@biomejs/cli-linux-x64-musl": "2.3.14", "@biomejs/cli-win32-arm64": "2.3.14", "@biomejs/cli-win32-x64": "2.3.14" }, "bin": { "biome": "bin/biome" } }, "sha512-QMT6QviX0WqXJCaiqVMiBUCr5WRQ1iFSjvOLoTk6auKukJMvnMzWucXpwZB0e8F00/1/BsS9DzcKgWH+CLqVuA=="], 126 + "@biomejs/biome": ["@biomejs/biome@2.3.15", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.15", "@biomejs/cli-darwin-x64": "2.3.15", "@biomejs/cli-linux-arm64": "2.3.15", "@biomejs/cli-linux-arm64-musl": "2.3.15", "@biomejs/cli-linux-x64": "2.3.15", "@biomejs/cli-linux-x64-musl": "2.3.15", "@biomejs/cli-win32-arm64": "2.3.15", "@biomejs/cli-win32-x64": "2.3.15" }, "bin": { "biome": "bin/biome" } }, "sha512-u+jlPBAU2B45LDkjjNNYpc1PvqrM/co4loNommS9/sl9oSxsAQKsNZejYuUztvToB5oXi1tN/e62iNd6ESiY3g=="], 115 127 116 - "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.14", "", { "os": "darwin", "cpu": "arm64" }, "sha512-UJGPpvWJMkLxSRtpCAKfKh41Q4JJXisvxZL8ChN1eNW3m/WlPFJ6EFDCE7YfUb4XS8ZFi3C1dFpxUJ0Ety5n+A=="], 128 + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.15", "", { "os": "darwin", "cpu": "arm64" }, "sha512-SDCdrJ4COim1r8SNHg19oqT50JfkI/xGZHSyC6mGzMfKrpNe/217Eq6y98XhNTc0vGWDjznSDNXdUc6Kg24jbw=="], 117 129 118 - "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.3.14", "", { "os": "darwin", "cpu": "x64" }, "sha512-PNkLNQG6RLo8lG7QoWe/hhnMxJIt1tEimoXpGQjwS/dkdNiKBLPv4RpeQl8o3s1OKI3ZOR5XPiYtmbGGHAOnLA=="], 130 + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.3.15", "", { "os": "darwin", "cpu": "x64" }, "sha512-RkyeSosBtn3C3Un8zQnl9upX0Qbq4E3QmBa0qjpOh1MebRbHhNlRC16jk8HdTe/9ym5zlfnpbb8cKXzW+vlTxw=="], 119 131 120 - "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.3.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-KT67FKfzIw6DNnUNdYlBg+eU24Go3n75GWK6NwU4+yJmDYFe9i/MjiI+U/iEzKvo0g7G7MZqoyrhIYuND2w8QQ=="], 132 + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.3.15", "", { "os": "linux", "cpu": "arm64" }, "sha512-FN83KxrdVWANOn5tDmW6UBC0grojchbGmcEz6JkRs2YY6DY63sTZhwkQ56x6YtKhDVV1Unz7FJexy8o7KwuIhg=="], 121 133 122 - "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.3.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-LInRbXhYujtL3sH2TMCH/UBwJZsoGwfQjBrMfl84CD4hL/41C/EU5mldqf1yoFpsI0iPWuU83U+nB2TUUypWeg=="], 134 + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.3.15", "", { "os": "linux", "cpu": "arm64" }, "sha512-SSSIj2yMkFdSkXqASzIBdjySBXOe65RJlhKEDlri7MN19RC4cpez+C0kEwPrhXOTgJbwQR9QH1F4+VnHkC35pg=="], 123 135 124 - "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.3.14", "", { "os": "linux", "cpu": "x64" }, "sha512-ZsZzQsl9U+wxFrGGS4f6UxREUlgHwmEfu1IrXlgNFrNnd5Th6lIJr8KmSzu/+meSa9f4rzFrbEW9LBBA6ScoMA=="], 136 + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.3.15", "", { "os": "linux", "cpu": "x64" }, "sha512-T8n9p8aiIKOrAD7SwC7opiBM1LYGrE5G3OQRXWgbeo/merBk8m+uxJ1nOXMPzfYyFLfPlKF92QS06KN1UW+Zbg=="], 125 137 126 - "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.3.14", "", { "os": "linux", "cpu": "x64" }, "sha512-KQU7EkbBBuHPW3/rAcoiVmhlPtDSGOGRPv9js7qJVpYTzjQmVR+C9Rfcz+ti8YCH+zT1J52tuBybtP4IodjxZQ=="], 138 + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.3.15", "", { "os": "linux", "cpu": "x64" }, "sha512-dbjPzTh+ijmmNwojFYbQNMFp332019ZDioBYAMMJj5Ux9d8MkM+u+J68SBJGVwVeSHMYj+T9504CoxEzQxrdNw=="], 127 139 128 - "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.3.14", "", { "os": "win32", "cpu": "arm64" }, "sha512-+IKYkj/pUBbnRf1G1+RlyA3LWiDgra1xpS7H2g4BuOzzRbRB+hmlw0yFsLprHhbbt7jUzbzAbAjK/Pn0FDnh1A=="], 140 + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.3.15", "", { "os": "win32", "cpu": "arm64" }, "sha512-puMuenu/2brQdgqtQ7geNwQlNVxiABKEZJhMRX6AGWcmrMO8EObMXniFQywy2b81qmC+q+SDvlOpspNwz0WiOA=="], 129 141 130 - "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.14", "", { "os": "win32", "cpu": "x64" }, "sha512-oizCjdyQ3WJEswpb3Chdngeat56rIdSYK12JI3iI11Mt5T5EXcZ7WLuowzEaFPNJ3zmOQFliMN8QY1Pi+qsfdQ=="], 142 + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.15", "", { "os": "win32", "cpu": "x64" }, "sha512-kDZr/hgg+igo5Emi0LcjlgfkoGZtgIpJKhnvKTRmMBv6FF/3SDyEV4khBwqNebZIyMZTzvpca9sQNSXJ39pI2A=="], 131 143 132 144 "@cbor-extract/cbor-extract-darwin-arm64": ["@cbor-extract/cbor-extract-darwin-arm64@2.2.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-P7swiOAdF7aSi0H+tHtHtr6zrpF3aAq/W9FXx5HektRvLTM2O89xCyXF3pk7pLc7QpaY7AoaE8UowVf9QBdh3w=="], 133 145 ··· 201 213 202 214 "@hono/node-server": ["@hono/node-server@1.19.9", "", { "peerDependencies": { "hono": "^4" } }, "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw=="], 203 215 216 + "@ioredis/commands": ["@ioredis/commands@1.5.0", "", {}, "sha512-eUgLqrMf8nJkZxT24JvVRrQya1vZkQh8BBeYNwGDqa5I0VUi8ACx7uFvAaLxintokpTenkK6DASvo/bvNbBGow=="], 217 + 204 218 "@ipld/dag-cbor": ["@ipld/dag-cbor@7.0.3", "", { "dependencies": { "cborg": "^1.6.0", "multiformats": "^9.5.4" } }, "sha512-1VVh2huHsuohdXC1bGJNE8WR72slZ9XE2T3wbBBq31dm7ZBatmKLLxrB+XAqafxfRFjv08RZmj/W/ZqaM13AuA=="], 205 219 206 220 "@noble/curves": ["@noble/curves@1.9.7", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw=="], ··· 213 227 214 228 "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], 215 229 230 + "@redis/bloom": ["@redis/bloom@5.10.0", "", { "peerDependencies": { "@redis/client": "^5.10.0" } }, "sha512-doIF37ob+l47n0rkpRNgU8n4iacBlKM9xLiP1LtTZTvz8TloJB8qx/MgvhMhKdYG+CvCY2aPBnN2706izFn/4A=="], 231 + 232 + "@redis/client": ["@redis/client@5.10.0", "", { "dependencies": { "cluster-key-slot": "1.1.2" } }, "sha512-JXmM4XCoso6C75Mr3lhKA3eNxSzkYi3nCzxDIKY+YOszYsJjuKbFgVtguVPbLMOttN4iu2fXoc2BGhdnYhIOxA=="], 233 + 234 + "@redis/json": ["@redis/json@5.10.0", "", { "peerDependencies": { "@redis/client": "^5.10.0" } }, "sha512-B2G8XlOmTPUuZtD44EMGbtoepQG34RCDXLZbjrtON1Djet0t5Ri7/YPXvL9aomXqP8lLTreaprtyLKF4tmXEEA=="], 235 + 236 + "@redis/search": ["@redis/search@5.10.0", "", { "peerDependencies": { "@redis/client": "^5.10.0" } }, "sha512-3SVcPswoSfp2HnmWbAGUzlbUPn7fOohVu2weUQ0S+EMiQi8jwjL+aN2p6V3TI65eNfVsJ8vyPvqWklm6H6esmg=="], 237 + 238 + "@redis/time-series": ["@redis/time-series@5.10.0", "", { "peerDependencies": { "@redis/client": "^5.10.0" } }, "sha512-cPkpddXH5kc/SdRhF0YG0qtjL+noqFT0AcHbQ6axhsPsO7iqPi1cjxgdkE9TNeKiBUUdCaU1DbqkR/LzbzPBhg=="], 239 + 216 240 "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], 217 241 218 242 "@ts-morph/common": ["@ts-morph/common@0.17.0", "", { "dependencies": { "fast-glob": "^3.2.11", "minimatch": "^5.1.0", "mkdirp": "^1.0.4", "path-browserify": "^1.0.1" } }, "sha512-RMSSvSfs9kb0VzkvQ2NWobwnj7TxCA9vI/IjR9bDHqgAyVbu2T0DN4wiKVqomyDWqO7dPr/tErSfq7urQ1Q37g=="], 219 243 220 244 "@tsndr/cloudflare-worker-jwt": ["@tsndr/cloudflare-worker-jwt@3.2.1", "", {}, "sha512-1AfDEgu7DTbU0sXDSOKh4D5t7+RJ0dIRRaDmoX3LFeW0AxQdunuqTQ7AVGwNJqOnxrbxiruyvX01cf9HHqEvXQ=="], 221 245 246 + "@types/better-sqlite3": ["@types/better-sqlite3@7.6.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA=="], 247 + 222 248 "@types/body-parser": ["@types/body-parser@1.19.6", "", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g=="], 223 249 224 250 "@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="], 251 + 252 + "@types/cors": ["@types/cors@2.8.19", "", { "dependencies": { "@types/node": "*" } }, "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg=="], 225 253 226 254 "@types/express": ["@types/express@5.0.6", "", { "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", "@types/serve-static": "^2" } }, "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA=="], 227 255 228 256 "@types/express-serve-static-core": ["@types/express-serve-static-core@5.1.1", "", { "dependencies": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*", "@types/send": "*" } }, "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A=="], 229 257 230 258 "@types/http-errors": ["@types/http-errors@2.0.5", "", {}, "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg=="], 259 + 260 + "@types/jsonwebtoken": ["@types/jsonwebtoken@9.0.10", "", { "dependencies": { "@types/ms": "*", "@types/node": "*" } }, "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA=="], 261 + 262 + "@types/morgan": ["@types/morgan@1.9.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-sS4A1zheMvsADRVfT0lYbJ4S9lmsey8Zo2F7cnbYjWHP67Q0AwMYuuzLlkIM2N8gAbb9cubhIVFwcIN2XyYCkA=="], 263 + 264 + "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], 231 265 232 266 "@types/node": ["@types/node@25.2.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ=="], 233 267 268 + "@types/pg": ["@types/pg@8.16.0", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-RmhMd/wD+CF8Dfo+cVIy3RR5cl8CyfXQ0tGgW6XBL8L4LM/UTEbNXYRbLwU6w+CgrKBNbrQWt4FUtTfaU5jSYQ=="], 269 + 270 + "@types/prompts": ["@types/prompts@2.4.9", "", { "dependencies": { "@types/node": "*", "kleur": "^3.0.3" } }, "sha512-qTxFi6Buiu8+50/+3DGIWLHM6QuWsEKugJnnP6iv2Mc4ncxE4A/OJkjuVOA+5X0X1S/nq5VJRa8Lu+nwcvbrKA=="], 271 + 234 272 "@types/qs": ["@types/qs@6.14.0", "", {}, "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ=="], 235 273 236 274 "@types/ramda": ["@types/ramda@0.31.1", "", { "dependencies": { "types-ramda": "^0.31.0" } }, "sha512-Vt6sFXnuRpzaEj+yeutA0q3bcAsK7wdPuASIzR9LXqL4gJPyFw8im9qchlbp4ltuf3kDEIRmPJTD/Fkg60dn7g=="], ··· 259 297 260 298 "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], 261 299 300 + "basic-auth": ["basic-auth@2.0.1", "", { "dependencies": { "safe-buffer": "5.1.2" } }, "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg=="], 301 + 262 302 "better-sqlite3": ["better-sqlite3@12.6.2", "", { "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" } }, "sha512-8VYKM3MjCa9WcaSAI3hzwhmyHVlH8tiGFwf0RlTsZPWJ1I5MkzjiudCo4KC4DxOaL/53A5B1sI/IbldNFDbsKA=="], 263 303 264 304 "bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="], ··· 272 312 "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], 273 313 274 314 "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], 315 + 316 + "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], 275 317 276 318 "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], 277 319 ··· 293 335 294 336 "chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], 295 337 338 + "cluster-key-slot": ["cluster-key-slot@1.1.2", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="], 339 + 296 340 "code-block-writer": ["code-block-writer@11.0.3", "", {}, "sha512-NiujjUFB4SwScJq2bwbYUtXbZhBSlY6vYzm++3Q6oC+U+injTqfPYFK8wS9COOmb2lueqp0ZRB4nK1VYeHgNyw=="], 297 341 298 342 "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], ··· 329 373 330 374 "defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], 331 375 376 + "denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="], 377 + 332 378 "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], 333 379 334 380 "destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="], ··· 345 391 346 392 "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], 347 393 394 + "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], 395 + 348 396 "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], 349 397 350 398 "effect": ["effect@3.19.16", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-7+XC3vGrbAhCHd8LTFHvnZjRpZKZ8YHRZqJTkpNoxcJ2mCyNs2SwI+6VkV/ij8Y3YW7wfBN4EbU06/F5+m/wkQ=="], ··· 352 400 "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], 353 401 354 402 "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], 403 + 404 + "envalid": ["envalid@8.1.1", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-vOUfHxAFFvkBjbVQbBfgnCO9d3GcNfMMTtVfgqSU2rQGMFEVqWy9GBuoSfHnwGu7EqR0/GeukQcL3KjFBaga9w=="], 355 405 356 406 "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], 357 407 ··· 433 483 434 484 "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], 435 485 486 + "ioredis": ["ioredis@5.9.3", "", { "dependencies": { "@ioredis/commands": "1.5.0", "cluster-key-slot": "^1.1.0", "debug": "^4.3.4", "denque": "^2.1.0", "lodash.defaults": "^4.2.0", "lodash.isarguments": "^3.1.0", "redis-errors": "^1.2.0", "redis-parser": "^3.0.0", "standard-as-callback": "^2.1.0" } }, "sha512-VI5tMCdeoxZWU5vjHWsiE/Su76JGhBvWF1MJnV9ZtGltHk9BmD48oDq8Tj8haZ85aceXZMxLNDQZRVo5QKNgXA=="], 487 + 436 488 "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], 437 489 438 490 "iron-webcrypto": ["iron-webcrypto@1.2.1", "", {}, "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg=="], ··· 449 501 450 502 "jose": ["jose@5.10.0", "", {}, "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg=="], 451 503 504 + "jsonwebtoken": ["jsonwebtoken@9.0.3", "", { "dependencies": { "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g=="], 505 + 506 + "jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="], 507 + 508 + "jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="], 509 + 510 + "kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], 511 + 512 + "kysely": ["kysely@0.28.11", "", {}, "sha512-zpGIFg0HuoC893rIjYX1BETkVWdDnzTzF5e0kWXJFg5lE0k1/LfNWBejrcnOFu8Q2Rfq/hTDTU7XLUM8QOrpzg=="], 513 + 452 514 "libsodium": ["libsodium@0.8.2", "", {}, "sha512-TsnGYMoZtpweT+kR+lOv5TVsnJ/9U0FZOsLFzFOMWmxqOAYXjX3fsrPAW+i1LthgDKXJnI9A8dWEanT1tnJKIw=="], 453 515 454 516 "libsodium-wrappers": ["libsodium-wrappers@0.8.2", "", { "dependencies": { "libsodium": "^0.8.0" } }, "sha512-VFLmfxkxo+U9q60tjcnSomQBRx2UzlRjKWJqvB4K1pUqsMQg4cu3QXA2nrcsj9A1qRsnJBbi2Ozx1hsiDoCkhw=="], 455 517 518 + "lodash.defaults": ["lodash.defaults@4.2.0", "", {}, "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="], 519 + 520 + "lodash.includes": ["lodash.includes@4.3.0", "", {}, "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="], 521 + 522 + "lodash.isarguments": ["lodash.isarguments@3.1.0", "", {}, "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg=="], 523 + 524 + "lodash.isboolean": ["lodash.isboolean@3.0.3", "", {}, "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="], 525 + 526 + "lodash.isinteger": ["lodash.isinteger@4.0.4", "", {}, "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="], 527 + 528 + "lodash.isnumber": ["lodash.isnumber@3.0.3", "", {}, "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="], 529 + 530 + "lodash.isplainobject": ["lodash.isplainobject@4.0.6", "", {}, "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="], 531 + 532 + "lodash.isstring": ["lodash.isstring@4.0.1", "", {}, "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="], 533 + 534 + "lodash.once": ["lodash.once@4.1.1", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="], 535 + 456 536 "lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="], 457 537 458 538 "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], ··· 482 562 "mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="], 483 563 484 564 "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="], 565 + 566 + "morgan": ["morgan@1.10.1", "", { "dependencies": { "basic-auth": "~2.0.1", "debug": "2.6.9", "depd": "~2.0.0", "on-finished": "~2.3.0", "on-headers": "~1.1.0" } }, "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A=="], 485 567 486 568 "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], 487 569 ··· 493 575 494 576 "node-abi": ["node-abi@3.87.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ=="], 495 577 578 + "node-abort-controller": ["node-abort-controller@3.1.1", "", {}, "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ=="], 579 + 496 580 "node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="], 497 581 498 582 "node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.1.1", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-test": "build-test.js", "node-gyp-build-optional-packages-optional": "optional.js" } }, "sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw=="], ··· 510 594 "on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="], 511 595 512 596 "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], 597 + 598 + "on-headers": ["on-headers@1.1.0", "", {}, "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A=="], 513 599 514 600 "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], 515 601 ··· 565 651 566 652 "process-warning": ["process-warning@3.0.0", "", {}, "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ=="], 567 653 654 + "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="], 655 + 568 656 "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], 569 657 570 658 "pump": ["pump@3.0.3", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="], ··· 595 683 596 684 "real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="], 597 685 686 + "redis": ["redis@5.10.0", "", { "dependencies": { "@redis/bloom": "5.10.0", "@redis/client": "5.10.0", "@redis/json": "5.10.0", "@redis/search": "5.10.0", "@redis/time-series": "5.10.0" } }, "sha512-0/Y+7IEiTgVGPrLFKy8oAEArSyEJkU0zvgV5xyi9NzNQ+SLZmyFbUsWIbgPcd4UdUh00opXGKlXJwMmsis5Byw=="], 687 + 688 + "redis-errors": ["redis-errors@1.2.0", "", {}, "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w=="], 689 + 690 + "redis-parser": ["redis-parser@3.0.0", "", { "dependencies": { "redis-errors": "^1.0.0" } }, "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A=="], 691 + 692 + "redlock": ["redlock@5.0.0-beta.2", "", { "dependencies": { "node-abort-controller": "^3.0.1" } }, "sha512-2RDWXg5jgRptDrB1w9O/JgSZC0j7y4SlaXnor93H/UJm/QyDiFgBKNtrh0TI6oCXqYSaSoXxFh6Sd3VtYfhRXw=="], 693 + 598 694 "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], 599 695 600 696 "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], ··· 629 725 630 726 "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=="], 631 727 728 + "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], 729 + 632 730 "sonic-boom": ["sonic-boom@3.8.1", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg=="], 633 731 634 732 "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], ··· 636 734 "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=="], 637 735 638 736 "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], 737 + 738 + "standard-as-callback": ["standard-as-callback@2.1.0", "", {}, "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="], 639 739 640 740 "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], 641 741 ··· 671 771 672 772 "types-ramda": ["types-ramda@0.31.0", "", { "dependencies": { "ts-toolbelt": "^9.6.0" } }, "sha512-vaoC35CRC3xvL8Z6HkshDbi6KWM1ezK0LHN0YyxXWUn9HKzBNg/T3xSGlJZjCYspnOD3jE7bcizsp0bUXZDxnQ=="], 673 773 674 - "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], 675 - 676 774 "ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="], 677 775 678 776 "uint8arrays": ["uint8arrays@3.0.0", "", { "dependencies": { "multiformats": "^9.4.2" } }, "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA=="], ··· 706 804 "yesno": ["yesno@0.4.0", "", {}, "sha512-tdBxmHvbXPBKYIg81bMCB7bVeDmHkRzk5rVJyYYXurwKkHq/MCd8rz4HSJUP7hW0H2NlXiq8IFiWvYKEHhlotA=="], 707 805 708 806 "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], 807 + 808 + "zx": ["zx@8.8.5", "", { "bin": { "zx": "build/cli.js" } }, "sha512-SNgDF5L0gfN7FwVOdEFguY3orU5AkfFZm9B5YSHog/UDHv+lvmd82ZAsOenOkQixigwH2+yyH198AwNdKhj+RA=="], 709 809 710 810 "@atproto-labs/did-resolver/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], 711 811 ··· 769 869 770 870 "accepts/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], 771 871 872 + "basic-auth/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], 873 + 772 874 "bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], 773 875 774 876 "bl/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=="], 775 877 776 878 "express/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], 879 + 880 + "morgan/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], 881 + 882 + "morgan/on-finished": ["on-finished@2.3.0", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww=="], 777 883 778 884 "router/path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="], 779 885 ··· 868 974 "accepts/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], 869 975 870 976 "express/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], 977 + 978 + "morgan/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], 871 979 872 980 "send/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], 873 981
+11
apps/api/drizzle.config.ts
··· 1 + import "dotenv/config"; 2 + import { defineConfig } from "drizzle-kit"; 3 + import { env } from "./src/lib/env"; 4 + 5 + export default defineConfig({ 6 + dialect: "postgresql", 7 + schema: "./src/schema", 8 + dbCredentials: { 9 + url: env.POSTGRES_URL, 10 + }, 11 + });
+60
apps/api/lexicons/actor/profile.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.actor.profile", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "A declaration of a Bluesky account profile.", 8 + "key": "literal:self", 9 + "record": { 10 + "type": "object", 11 + "properties": { 12 + "displayName": { 13 + "type": "string", 14 + "maxGraphemes": 64, 15 + "maxLength": 640 16 + }, 17 + "description": { 18 + "type": "string", 19 + "description": "Free-form profile description text.", 20 + "maxGraphemes": 256, 21 + "maxLength": 2560 22 + }, 23 + "avatar": { 24 + "type": "blob", 25 + "description": "Small image to be displayed next to posts from account. AKA, 'profile picture'", 26 + "accept": [ 27 + "image/png", 28 + "image/jpeg" 29 + ], 30 + "maxSize": 1000000 31 + }, 32 + "banner": { 33 + "type": "blob", 34 + "description": "Larger horizontal image to display behind profile view.", 35 + "accept": [ 36 + "image/png", 37 + "image/jpeg" 38 + ], 39 + "maxSize": 10000000 40 + }, 41 + "labels": { 42 + "type": "union", 43 + "description": "Self-label values, specific to the Bluesky application, on the overall account.", 44 + "refs": [ 45 + "com.atproto.label.defs#selfLabels" 46 + ] 47 + }, 48 + "joinedViaStarterPack": { 49 + "type": "ref", 50 + "ref": "com.atproto.repo.strongRef" 51 + }, 52 + "createdAt": { 53 + "type": "string", 54 + "format": "datetime" 55 + } 56 + } 57 + } 58 + } 59 + } 60 + }
+33
apps/api/lexicons/publicKey.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "io.pocketenv.publicKey", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "key": "tid", 8 + "record": { 9 + "type": "object", 10 + "required": [ 11 + "name", 12 + "key", 13 + "createdAt" 14 + ], 15 + "properties": { 16 + "name": { 17 + "type": "string", 18 + "description": "Name of the public key", 19 + "maxLength": 255 20 + }, 21 + "key": { 22 + "type": "string", 23 + "description": "The public key value, e.g. an SSH public key string." 24 + }, 25 + "createdAt": { 26 + "type": "string", 27 + "format": "datetime" 28 + } 29 + } 30 + } 31 + } 32 + } 33 + }
+29
apps/api/lexicons/sandbox/claimSandbox.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "io.pocketenv.sandbox.claimSandbox", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Claim a sandbox by id", 8 + "parameters": { 9 + "type": "params", 10 + "required": [ 11 + "id" 12 + ], 13 + "properties": { 14 + "id": { 15 + "type": "string", 16 + "description": "The sandbox ID." 17 + } 18 + } 19 + }, 20 + "output": { 21 + "encoding": "application/json", 22 + "schema": { 23 + "type": "ref", 24 + "ref": "io.pocketenv.sandbox.defs#sandboxViewBasic" 25 + } 26 + } 27 + } 28 + } 29 + }
+85
apps/api/lexicons/sandbox/createSandbox.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "io.pocketenv.sandbox.createSandbox", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Create a sandbox", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": [ 13 + "base" 14 + ], 15 + "properties": { 16 + "base": { 17 + "type": "string", 18 + "description": "The base sandbox URI to clone from, e.g. a template or an existing sandbox.", 19 + "format": "at-uri" 20 + }, 21 + "name": { 22 + "type": "string", 23 + "description": "The name of the sandbox", 24 + "minLength": 1 25 + }, 26 + "description": { 27 + "type": "string", 28 + "description": "A description for the sandbox" 29 + }, 30 + "topics": { 31 + "type": "array", 32 + "description": "A list of topics/tags to associate with the sandbox", 33 + "items": { 34 + "type": "string", 35 + "maxLength": 50 36 + } 37 + }, 38 + "repo": { 39 + "type": "string", 40 + "description": "A git repository URL to clone into the sandbox, e.g. a GitHub/Tangled repo.", 41 + "format": "uri" 42 + }, 43 + "vcpus": { 44 + "type": "integer", 45 + "description": "The number of virtual CPUs to allocate for the sandbox", 46 + "minimum": 1 47 + }, 48 + "memory": { 49 + "type": "integer", 50 + "description": "The amount of memory (in GB) to allocate for the sandbox", 51 + "minimum": 1 52 + }, 53 + "disk": { 54 + "type": "integer", 55 + "description": "The amount of disk space (in GB) to allocate for the sandbox", 56 + "minimum": 3 57 + }, 58 + "readme": { 59 + "type": "string", 60 + "description": "A URI to a README for the sandbox.", 61 + "format": "uri" 62 + }, 63 + "secrets": { 64 + "type": "ref", 65 + "description": "A list of secrets to add to the sandbox", 66 + "ref": "io.pocketenv.sandbox.defs#secrets" 67 + }, 68 + "envs": { 69 + "type": "ref", 70 + "description": "A list of environment variables to add to the sandbox", 71 + "ref": "io.pocketenv.sandbox.defs#envs" 72 + } 73 + } 74 + } 75 + }, 76 + "output": { 77 + "encoding": "application/json", 78 + "schema": { 79 + "type": "ref", 80 + "ref": "io.pocketenv.sandbox.defs#sandboxViewBasic" 81 + } 82 + } 83 + } 84 + } 85 + }
+115
apps/api/lexicons/sandbox/defs.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "io.pocketenv.sandbox.defs", 4 + "defs": { 5 + "sandboxViewBasic": { 6 + "type": "object", 7 + "properties": { 8 + "name": { 9 + "type": "string", 10 + "description": "Name of the sandbox", 11 + "maxLength": 50 12 + }, 13 + "provider": { 14 + "type": "string", 15 + "description": "The provider of the sandbox, e.g. 'daytona', 'vercel', 'cloudflare', etc.", 16 + "maxLength": 50 17 + }, 18 + "description": { 19 + "type": "string", 20 + "maxGraphemes": 300, 21 + "maxLength": 3000 22 + }, 23 + "website": { 24 + "type": "string", 25 + "description": "Any URI related to the sandbox", 26 + "format": "uri" 27 + }, 28 + "logo": { 29 + "type": "string", 30 + "description": "URI to an image logo for the sandbox", 31 + "format": "uri" 32 + }, 33 + "topics": { 34 + "type": "array", 35 + "items": { 36 + "type": "string", 37 + "minLength": 1, 38 + "maxLength": 50 39 + }, 40 + "maxLength": 50 41 + }, 42 + "repo": { 43 + "type": "string", 44 + "description": "A git repository URL to clone into the sandbox, e.g. a GitHub/Tangled repo.", 45 + "format": "uri" 46 + }, 47 + "readme": { 48 + "type": "string", 49 + "description": "A URI to a README for the sandbox.", 50 + "format": "uri" 51 + }, 52 + "vcpus": { 53 + "type": "integer", 54 + "description": "Number of virtual CPUs allocated to the sandbox" 55 + }, 56 + "memory": { 57 + "type": "integer", 58 + "description": "Amount of memory in GB allocated to the sandbox" 59 + }, 60 + "disk": { 61 + "type": "integer", 62 + "description": "Amount of disk space in GB allocated to the sandbox" 63 + }, 64 + "installs": { 65 + "type": "integer", 66 + "description": "Number of times the sandbox has been installed by users." 67 + }, 68 + "createdAt": { 69 + "type": "string", 70 + "format": "datetime" 71 + } 72 + } 73 + }, 74 + "secrets": { 75 + "type": "array", 76 + "items": { 77 + "type": "object", 78 + "required": [ 79 + "name", 80 + "value" 81 + ], 82 + "properties": { 83 + "name": { 84 + "type": "string", 85 + "description": "Name of the secret, e.g. 'DATABASE_URL', 'SSH_KEY', etc." 86 + }, 87 + "value": { 88 + "type": "string", 89 + "description": "Value of the secret. This will be encrypted at rest and redacted in any API responses." 90 + } 91 + } 92 + } 93 + }, 94 + "envs": { 95 + "type": "array", 96 + "items": { 97 + "type": "object", 98 + "required": [ 99 + "name", 100 + "value" 101 + ], 102 + "properties": { 103 + "name": { 104 + "type": "string", 105 + "description": "Name of the environment variable, e.g. 'NODE_ENV', 'PORT', etc." 106 + }, 107 + "value": { 108 + "type": "string", 109 + "description": "Value of the environment variable. This will be visible in API responses and should not contain sensitive information." 110 + } 111 + } 112 + } 113 + } 114 + } 115 + }
+30
apps/api/lexicons/sandbox/deleteSandbox.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "io.pocketenv.sandbox.deleteSandbox", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Delete a sandbox by uri", 8 + "parameters": { 9 + "type": "params", 10 + "required": [ 11 + "uri" 12 + ], 13 + "properties": { 14 + "uri": { 15 + "type": "string", 16 + "description": "The sandbox URI.", 17 + "format": "at-uri" 18 + } 19 + } 20 + }, 21 + "output": { 22 + "encoding": "application/json", 23 + "schema": { 24 + "type": "ref", 25 + "ref": "io.pocketenv.sandbox.defs#sandboxViewBasic" 26 + } 27 + } 28 + } 29 + } 30 + }
+30
apps/api/lexicons/sandbox/getSandbox.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "io.pocketenv.sandbox.getSandbox", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get a sandbox by uri", 8 + "parameters": { 9 + "type": "params", 10 + "required": [ 11 + "uri" 12 + ], 13 + "properties": { 14 + "uri": { 15 + "type": "string", 16 + "description": "The sandbox URI.", 17 + "format": "at-uri" 18 + } 19 + } 20 + }, 21 + "output": { 22 + "encoding": "application/json", 23 + "schema": { 24 + "type": "ref", 25 + "ref": "io.pocketenv.sandbox.defs#sandboxViewBasic" 26 + } 27 + } 28 + } 29 + } 30 + }
+45
apps/api/lexicons/sandbox/getSandboxes.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "io.pocketenv.sandbox.getSandboxes", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get all sandboxes", 8 + "parameters": { 9 + "type": "params", 10 + "properties": { 11 + "limit": { 12 + "type": "integer", 13 + "description": "The maximum number of sandboxes to return.", 14 + "minimum": 1 15 + }, 16 + "offset": { 17 + "type": "integer", 18 + "description": "The number of sandboxes to skip before starting to collect the result set.", 19 + "minimum": 0 20 + } 21 + } 22 + }, 23 + "output": { 24 + "encoding": "application/json", 25 + "schema": { 26 + "type": "object", 27 + "properties": { 28 + "sandboxes": { 29 + "type": "array", 30 + "items": { 31 + "type": "ref", 32 + "ref": "io.pocketenv.sandbox.defs#sandboxViewBasic" 33 + } 34 + }, 35 + "total": { 36 + "type": "integer", 37 + "description": "The total number of sandboxes available.", 38 + "minimum": 0 39 + } 40 + } 41 + } 42 + } 43 + } 44 + } 45 + }
+84
apps/api/lexicons/sandbox/sandbox.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "io.pocketenv.sandbox", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "key": "tid", 8 + "record": { 9 + "type": "object", 10 + "required": [ 11 + "name", 12 + "createdAt" 13 + ], 14 + "properties": { 15 + "name": { 16 + "type": "string", 17 + "description": "Name of the sandbox", 18 + "maxLength": 255 19 + }, 20 + "base": { 21 + "type": "ref", 22 + "description": "A strong reference to the base template for the sandbox environment.", 23 + "ref": "com.atproto.repo.strongRef" 24 + }, 25 + "provider": { 26 + "type": "string", 27 + "description": "The provider of the sandbox, e.g. 'daytona', 'vercel', 'cloudflare', etc.", 28 + "maxLength": 50 29 + }, 30 + "description": { 31 + "type": "string", 32 + "maxGraphemes": 300, 33 + "maxLength": 3000 34 + }, 35 + "website": { 36 + "type": "string", 37 + "description": "Any URI related to the sandbox", 38 + "format": "uri" 39 + }, 40 + "logo": { 41 + "type": "string", 42 + "description": "URI to an image logo for the sandbox", 43 + "format": "uri" 44 + }, 45 + "topics": { 46 + "type": "array", 47 + "items": { 48 + "type": "string", 49 + "minLength": 1, 50 + "maxLength": 50 51 + }, 52 + "maxLength": 50 53 + }, 54 + "repo": { 55 + "type": "string", 56 + "description": "A git repository URL to clone into the sandbox, e.g. a GitHub/Tangled repo.", 57 + "format": "uri" 58 + }, 59 + "readme": { 60 + "type": "string", 61 + "description": "A URI to a README for the sandbox.", 62 + "format": "uri" 63 + }, 64 + "vcpus": { 65 + "type": "integer", 66 + "description": "Number of virtual CPUs allocated to the sandbox" 67 + }, 68 + "memory": { 69 + "type": "integer", 70 + "description": "Amount of memory in GB allocated to the sandbox" 71 + }, 72 + "disk": { 73 + "type": "integer", 74 + "description": "Amount of disk space in GB allocated to the sandbox" 75 + }, 76 + "createdAt": { 77 + "type": "string", 78 + "format": "datetime" 79 + } 80 + } 81 + } 82 + } 83 + } 84 + }
+30
apps/api/lexicons/sandbox/startSandbox.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "io.pocketenv.sandbox.startSandbox", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Start a sandbox", 8 + "parameters": { 9 + "type": "params", 10 + "required": [ 11 + "uri" 12 + ], 13 + "properties": { 14 + "uri": { 15 + "type": "string", 16 + "description": "The sandbox URI.", 17 + "format": "at-uri" 18 + } 19 + } 20 + }, 21 + "output": { 22 + "encoding": "application/json", 23 + "schema": { 24 + "type": "ref", 25 + "ref": "io.pocketenv.sandbox.defs#sandboxViewBasic" 26 + } 27 + } 28 + } 29 + } 30 + }
+30
apps/api/lexicons/sandbox/stopSandbox.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "io.pocketenv.sandbox.stopSandbox", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Stop a sandbox", 8 + "parameters": { 9 + "type": "params", 10 + "required": [ 11 + "uri" 12 + ], 13 + "properties": { 14 + "uri": { 15 + "type": "string", 16 + "description": "The sandbox URI.", 17 + "format": "at-uri" 18 + } 19 + } 20 + }, 21 + "output": { 22 + "encoding": "application/json", 23 + "schema": { 24 + "type": "ref", 25 + "ref": "io.pocketenv.sandbox.defs#sandboxViewBasic" 26 + } 27 + } 28 + } 29 + } 30 + }
+24
apps/api/lexicons/strongRef.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.repo.strongRef", 4 + "description": "A URI with a content-hash fingerprint.", 5 + "defs": { 6 + "main": { 7 + "type": "object", 8 + "required": [ 9 + "uri", 10 + "cid" 11 + ], 12 + "properties": { 13 + "uri": { 14 + "type": "string", 15 + "format": "at-uri" 16 + }, 17 + "cid": { 18 + "type": "string", 19 + "format": "cid" 20 + } 21 + } 22 + } 23 + } 24 + }
+26 -7
apps/api/package.json
··· 4 4 "type": "module", 5 5 "scripts": { 6 6 "dev": "tsx --watch src/index.ts", 7 - "start": "tsx src/index.ts" 8 - }, 9 - "peerDependencies": { 10 - "typescript": "^5" 7 + "start": "tsx src/index.ts", 8 + "pkl:eval": "pkl eval -f json", 9 + "pkl:gen": "tsx ./scripts/pkl.ts", 10 + "lexgen": "lex gen-server ./src/lexicon ./lexicons/**/* ./lexicons/*", 11 + "format": "biome format src", 12 + "lint": "biome lint src", 13 + "sandbox": "tsx ./scripts/sandbox.ts", 14 + "seed": "tsx ./scripts/seed.ts" 11 15 }, 12 16 "dependencies": { 13 17 "@atproto/api": "^0.13.31", ··· 23 27 "@hono/node-server": "^1.19.9", 24 28 "@tsndr/cloudflare-worker-jwt": "^3.2.1", 25 29 "@types/node": "^25.2.3", 30 + "@types/prompts": "^2.4.9", 26 31 "@types/ramda": "^0.31.1", 27 32 "better-sqlite3": "^12.6.2", 28 33 "chalk": "^5.6.2", ··· 32 37 "dotenv": "^17.2.4", 33 38 "drizzle-orm": "^0.45.1", 34 39 "effect": "^3.19.16", 40 + "envalid": "^8.1.1", 35 41 "express": "^5.2.1", 36 42 "hono": "^4.11.9", 43 + "ioredis": "^5.9.3", 44 + "jsonwebtoken": "^9.0.3", 45 + "kysely": "^0.28.11", 37 46 "libsodium-wrappers": "^0.8.2", 47 + "morgan": "^1.10.1", 38 48 "pg": "^8.18.0", 49 + "prompts": "^2.4.2", 39 50 "ramda": "^0.32.0", 40 - "unstorage": "^1.14.4", 41 - "zod": "^4.3.6" 51 + "redis": "^5.10.0", 52 + "redlock": "^5.0.0-beta.2", 53 + "unstorage": "^1.17.4", 54 + "zod": "^4.3.6", 55 + "zx": "^8.8.5" 42 56 }, 43 57 "devDependencies": { 44 - "@biomejs/biome": "^2.3.14", 58 + "@biomejs/biome": "^2.3.15", 59 + "@types/better-sqlite3": "^7.6.13", 60 + "@types/cors": "^2.8.19", 45 61 "@types/express": "^5.0.6", 62 + "@types/jsonwebtoken": "^9.0.10", 63 + "@types/morgan": "^1.9.10", 64 + "@types/pg": "^8.16.0", 46 65 "drizzle-kit": "^0.31.9", 47 66 "tsx": "^4.21.0" 48 67 }
+54
apps/api/pkl/defs/actor/profile.pkl
··· 1 + amends "../../schema/lexicon.pkl" 2 + 3 + lexicon = 1 4 + id = "app.bsky.actor.profile" 5 + defs = new Mapping<String, Record> { 6 + ["main"] { 7 + type = "record" 8 + description = "A declaration of a Bluesky account profile." 9 + key = "literal:self" 10 + `record` { 11 + type = "object" 12 + properties { 13 + ["displayName"] = new StringType { 14 + type = "string" 15 + maxGraphemes = 64 16 + maxLength = 640 17 + } 18 + ["description"] = new StringType { 19 + type = "string" 20 + description = "Free-form profile description text." 21 + maxGraphemes = 256 22 + maxLength = 2560 23 + } 24 + ["avatar"] = new Blob { 25 + type = "blob" 26 + description = 27 + "Small image to be displayed next to posts from account. AKA, 'profile picture'" 28 + accept = List("image/png", "image/jpeg") 29 + maxSize = 1000000 30 + } 31 + ["banner"] = new Blob { 32 + type = "blob" 33 + description = "Larger horizontal image to display behind profile view." 34 + accept = List("image/png", "image/jpeg") 35 + maxSize = 10000000 36 + } 37 + ["labels"] = new Union { 38 + type = "union" 39 + description = 40 + "Self-label values, specific to the Bluesky application, on the overall account." 41 + refs = List("com.atproto.label.defs#selfLabels") 42 + } 43 + ["joinedViaStarterPack"] = new Ref { 44 + type = "ref" 45 + ref = "com.atproto.repo.strongRef" 46 + } 47 + ["createdAt"] = new StringType { 48 + type = "string" 49 + format = "datetime" 50 + } 51 + } 52 + } 53 + } 54 + }
+29
apps/api/pkl/defs/publicKey.pkl
··· 1 + amends "../schema/lexicon.pkl" 2 + 3 + lexicon = 1 4 + id = "io.pocketenv.publicKey" 5 + defs = new Mapping<String, Record> { 6 + ["main"] { 7 + type = "record" 8 + key = "tid" 9 + `record` { 10 + type = "object" 11 + required = List("name", "key", "createdAt") 12 + properties { 13 + ["name"] = new StringType { 14 + type = "string" 15 + maxLength = 255 16 + description = "Name of the public key" 17 + } 18 + ["key"] = new StringType { 19 + type = "string" 20 + description = "The public key value, e.g. an SSH public key string." 21 + } 22 + ["createdAt"] = new StringType { 23 + type = "string" 24 + format = "datetime" 25 + } 26 + } 27 + } 28 + } 29 + }
+27
apps/api/pkl/defs/sandbox/claimSandbox.pkl
··· 1 + amends "../../schema/lexicon.pkl" 2 + 3 + lexicon = 1 4 + id = "io.pocketenv.sandbox.claimSandbox" 5 + defs = new Mapping<String, Procedure> { 6 + ["main"] { 7 + type = "procedure" 8 + description = "Claim a sandbox by id" 9 + parameters { 10 + type = "params" 11 + required = List("id") 12 + properties { 13 + ["id"] = new StringType { 14 + type = "string" 15 + description = "The sandbox ID." 16 + } 17 + } 18 + } 19 + output { 20 + encoding = "application/json" 21 + schema = new Ref { 22 + type = "ref" 23 + ref = "io.pocketenv.sandbox.defs#sandboxViewBasic" 24 + } 25 + } 26 + } 27 + }
+85
apps/api/pkl/defs/sandbox/createSandbox.pkl
··· 1 + amends "../../schema/lexicon.pkl" 2 + 3 + lexicon = 1 4 + id = "io.pocketenv.sandbox.createSandbox" 5 + defs = new Mapping<String, Procedure> { 6 + ["main"] { 7 + type = "procedure" 8 + description = "Create a sandbox" 9 + input { 10 + encoding = "application/json" 11 + schema { 12 + type = "object" 13 + required = List("base") 14 + properties { 15 + ["base"] = new StringType { 16 + type = "string" 17 + description = 18 + "The base sandbox URI to clone from, e.g. a template or an existing sandbox." 19 + format = "at-uri" 20 + } 21 + ["name"] = new StringType { 22 + type = "string" 23 + description = "The name of the sandbox" 24 + minLength = 1 25 + } 26 + ["description"] = new StringType { 27 + type = "string" 28 + description = "A description for the sandbox" 29 + } 30 + ["topics"] = new Array { 31 + type = "array" 32 + description = "A list of topics/tags to associate with the sandbox" 33 + items = new StringType { 34 + type = "string" 35 + maxLength = 50 36 + } 37 + } 38 + ["repo"] = new StringType { 39 + type = "string" 40 + description = 41 + "A git repository URL to clone into the sandbox, e.g. a GitHub/Tangled repo." 42 + format = "uri" 43 + } 44 + ["vcpus"] = new IntegerType { 45 + type = "integer" 46 + description = "The number of virtual CPUs to allocate for the sandbox" 47 + minimum = 1 48 + } 49 + ["memory"] = new IntegerType { 50 + type = "integer" 51 + description = "The amount of memory (in GB) to allocate for the sandbox" 52 + minimum = 1 53 + } 54 + ["disk"] = new IntegerType { 55 + type = "integer" 56 + description = "The amount of disk space (in GB) to allocate for the sandbox" 57 + minimum = 3 58 + } 59 + ["readme"] = new StringType { 60 + type = "string" 61 + description = "A URI to a README for the sandbox." 62 + format = "uri" 63 + } 64 + ["secrets"] = new Ref { 65 + type = "ref" 66 + ref = "io.pocketenv.sandbox.defs#secrets" 67 + description = "A list of secrets to add to the sandbox" 68 + } 69 + ["envs"] = new Ref { 70 + type = "ref" 71 + ref = "io.pocketenv.sandbox.defs#envs" 72 + description = "A list of environment variables to add to the sandbox" 73 + } 74 + } 75 + } 76 + } 77 + output { 78 + encoding = "application/json" 79 + schema = new Ref { 80 + type = "ref" 81 + ref = "io.pocketenv.sandbox.defs#sandboxViewBasic" 82 + } 83 + } 84 + } 85 + }
+111
apps/api/pkl/defs/sandbox/defs.pkl
··· 1 + amends "../../schema/lexicon.pkl" 2 + 3 + lexicon = 1 4 + id = "io.pocketenv.sandbox.defs" 5 + defs = new Mapping<String, ObjectType | Array> { 6 + ["sandboxViewBasic"] = new ObjectType { 7 + type = "object" 8 + properties { 9 + ["name"] = new StringType { 10 + type = "string" 11 + maxLength = 50 12 + description = "Name of the sandbox" 13 + } 14 + ["provider"] = new StringType { 15 + type = "string" 16 + maxLength = 50 17 + description = "The provider of the sandbox, e.g. 'daytona', 'vercel', 'cloudflare', etc." 18 + } 19 + ["description"] = new StringType { 20 + type = "string" 21 + maxLength = 3000 22 + maxGraphemes = 300 23 + } 24 + ["website"] = new StringType { 25 + type = "string" 26 + format = "uri" 27 + description = "Any URI related to the sandbox" 28 + } 29 + ["logo"] = new StringType { 30 + type = "string" 31 + format = "uri" 32 + description = "URI to an image logo for the sandbox" 33 + } 34 + ["topics"] = new Array { 35 + type = "array" 36 + items = new StringType { 37 + type = "string" 38 + minLength = 1 39 + maxLength = 50 40 + } 41 + maxLength = 50 42 + } 43 + ["repo"] = new StringType { 44 + type = "string" 45 + description = "A git repository URL to clone into the sandbox, e.g. a GitHub/Tangled repo." 46 + format = "uri" 47 + } 48 + ["readme"] = new StringType { 49 + type = "string" 50 + description = "A URI to a README for the sandbox." 51 + format = "uri" 52 + } 53 + ["vcpus"] = new IntegerType { 54 + type = "integer" 55 + description = "Number of virtual CPUs allocated to the sandbox" 56 + } 57 + ["memory"] = new IntegerType { 58 + type = "integer" 59 + description = "Amount of memory in GB allocated to the sandbox" 60 + } 61 + ["disk"] = new IntegerType { 62 + type = "integer" 63 + description = "Amount of disk space in GB allocated to the sandbox" 64 + } 65 + ["installs"] = new IntegerType { 66 + type = "integer" 67 + description = "Number of times the sandbox has been installed by users." 68 + } 69 + ["createdAt"] = new StringType { 70 + type = "string" 71 + format = "datetime" 72 + } 73 + } 74 + } 75 + ["secrets"] = new Array { 76 + type = "array" 77 + items = new ObjectType { 78 + type = "object" 79 + required = List("name", "value") 80 + properties { 81 + ["name"] = new StringType { 82 + type = "string" 83 + description = "Name of the secret, e.g. 'DATABASE_URL', 'SSH_KEY', etc." 84 + } 85 + ["value"] = new StringType { 86 + type = "string" 87 + description = 88 + "Value of the secret. This will be encrypted at rest and redacted in any API responses." 89 + } 90 + } 91 + } 92 + } 93 + ["envs"] = new Array { 94 + type = "array" 95 + items = new ObjectType { 96 + type = "object" 97 + required = List("name", "value") 98 + properties { 99 + ["name"] = new StringType { 100 + type = "string" 101 + description = "Name of the environment variable, e.g. 'NODE_ENV', 'PORT', etc." 102 + } 103 + ["value"] = new StringType { 104 + type = "string" 105 + description = 106 + "Value of the environment variable. This will be visible in API responses and should not contain sensitive information." 107 + } 108 + } 109 + } 110 + } 111 + }
+28
apps/api/pkl/defs/sandbox/deleteSandbox.pkl
··· 1 + amends "../../schema/lexicon.pkl" 2 + 3 + lexicon = 1 4 + id = "io.pocketenv.sandbox.deleteSandbox" 5 + defs = new Mapping<String, Procedure> { 6 + ["main"] { 7 + type = "procedure" 8 + description = "Delete a sandbox by uri" 9 + parameters { 10 + type = "params" 11 + required = List("uri") 12 + properties { 13 + ["uri"] = new StringType { 14 + type = "string" 15 + description = "The sandbox URI." 16 + format = "at-uri" 17 + } 18 + } 19 + } 20 + output { 21 + encoding = "application/json" 22 + schema = new Ref { 23 + type = "ref" 24 + ref = "io.pocketenv.sandbox.defs#sandboxViewBasic" 25 + } 26 + } 27 + } 28 + }
+28
apps/api/pkl/defs/sandbox/getSandbox.pkl
··· 1 + amends "../../schema/lexicon.pkl" 2 + 3 + lexicon = 1 4 + id = "io.pocketenv.sandbox.getSandbox" 5 + defs = new Mapping<String, Query> { 6 + ["main"] { 7 + type = "query" 8 + description = "Get a sandbox by uri" 9 + parameters { 10 + type = "params" 11 + required = List("uri") 12 + properties { 13 + ["uri"] = new StringType { 14 + type = "string" 15 + description = "The sandbox URI." 16 + format = "at-uri" 17 + } 18 + } 19 + } 20 + output { 21 + encoding = "application/json" 22 + schema = new Ref { 23 + type = "ref" 24 + ref = "io.pocketenv.sandbox.defs#sandboxViewBasic" 25 + } 26 + } 27 + } 28 + }
+44
apps/api/pkl/defs/sandbox/getSandboxes.pkl
··· 1 + amends "../../schema/lexicon.pkl" 2 + 3 + lexicon = 1 4 + id = "io.pocketenv.sandbox.getSandboxes" 5 + defs = new Mapping<String, Query> { 6 + ["main"] { 7 + type = "query" 8 + description = "Get all sandboxes" 9 + parameters { 10 + type = "params" 11 + properties { 12 + ["limit"] = new IntegerType { 13 + type = "integer" 14 + description = "The maximum number of sandboxes to return." 15 + minimum = 1 16 + } 17 + ["offset"] = new IntegerType { 18 + type = "integer" 19 + description = "The number of sandboxes to skip before starting to collect the result set." 20 + minimum = 0 21 + } 22 + } 23 + } 24 + output { 25 + encoding = "application/json" 26 + schema = new ObjectType { 27 + type = "object" 28 + properties { 29 + ["sandboxes"] = new Array { 30 + type = "array" 31 + items = new Ref { 32 + ref = "io.pocketenv.sandbox.defs#sandboxViewBasic" 33 + } 34 + } 35 + ["total"] = new IntegerType { 36 + type = "integer" 37 + description = "The total number of sandboxes available." 38 + minimum = 0 39 + } 40 + } 41 + } 42 + } 43 + } 44 + }
+82
apps/api/pkl/defs/sandbox/sandbox.pkl
··· 1 + amends "../../schema/lexicon.pkl" 2 + 3 + lexicon = 1 4 + id = "io.pocketenv.sandbox" 5 + defs = new Mapping<String, Record> { 6 + ["main"] { 7 + type = "record" 8 + key = "tid" 9 + `record` { 10 + type = "object" 11 + required = List("name", "createdAt") 12 + properties { 13 + ["name"] = new StringType { 14 + type = "string" 15 + maxLength = 255 16 + description = "Name of the sandbox" 17 + } 18 + ["base"] = new Ref { 19 + type = "ref" 20 + ref = "com.atproto.repo.strongRef" 21 + description = "A strong reference to the base template for the sandbox environment." 22 + } 23 + ["provider"] = new StringType { 24 + type = "string" 25 + maxLength = 50 26 + description = "The provider of the sandbox, e.g. 'daytona', 'vercel', 'cloudflare', etc." 27 + } 28 + ["description"] = new StringType { 29 + type = "string" 30 + maxLength = 3000 31 + maxGraphemes = 300 32 + } 33 + ["website"] = new StringType { 34 + type = "string" 35 + format = "uri" 36 + description = "Any URI related to the sandbox" 37 + } 38 + ["logo"] = new StringType { 39 + type = "string" 40 + format = "uri" 41 + description = "URI to an image logo for the sandbox" 42 + } 43 + ["topics"] = new Array { 44 + type = "array" 45 + items = new StringType { 46 + type = "string" 47 + minLength = 1 48 + maxLength = 50 49 + } 50 + maxLength = 50 51 + } 52 + ["repo"] = new StringType { 53 + type = "string" 54 + description = 55 + "A git repository URL to clone into the sandbox, e.g. a GitHub/Tangled repo." 56 + format = "uri" 57 + } 58 + ["readme"] = new StringType { 59 + type = "string" 60 + description = "A URI to a README for the sandbox." 61 + format = "uri" 62 + } 63 + ["vcpus"] = new IntegerType { 64 + type = "integer" 65 + description = "Number of virtual CPUs allocated to the sandbox" 66 + } 67 + ["memory"] = new IntegerType { 68 + type = "integer" 69 + description = "Amount of memory in GB allocated to the sandbox" 70 + } 71 + ["disk"] = new IntegerType { 72 + type = "integer" 73 + description = "Amount of disk space in GB allocated to the sandbox" 74 + } 75 + ["createdAt"] = new StringType { 76 + type = "string" 77 + format = "datetime" 78 + } 79 + } 80 + } 81 + } 82 + }
+28
apps/api/pkl/defs/sandbox/startSandbox.pkl
··· 1 + amends "../../schema/lexicon.pkl" 2 + 3 + lexicon = 1 4 + id = "io.pocketenv.sandbox.startSandbox" 5 + defs = new Mapping<String, Procedure> { 6 + ["main"] { 7 + type = "procedure" 8 + description = "Start a sandbox" 9 + parameters { 10 + type = "params" 11 + required = List("uri") 12 + properties { 13 + ["uri"] = new StringType { 14 + type = "string" 15 + description = "The sandbox URI." 16 + format = "at-uri" 17 + } 18 + } 19 + } 20 + output { 21 + encoding = "application/json" 22 + schema = new Ref { 23 + type = "ref" 24 + ref = "io.pocketenv.sandbox.defs#sandboxViewBasic" 25 + } 26 + } 27 + } 28 + }
+28
apps/api/pkl/defs/sandbox/stopSandbox.pkl
··· 1 + amends "../../schema/lexicon.pkl" 2 + 3 + lexicon = 1 4 + id = "io.pocketenv.sandbox.stopSandbox" 5 + defs = new Mapping<String, Procedure> { 6 + ["main"] { 7 + type = "procedure" 8 + description = "Stop a sandbox" 9 + parameters { 10 + type = "params" 11 + required = List("uri") 12 + properties { 13 + ["uri"] = new StringType { 14 + type = "string" 15 + description = "The sandbox URI." 16 + format = "at-uri" 17 + } 18 + } 19 + } 20 + output { 21 + encoding = "application/json" 22 + schema = new Ref { 23 + type = "ref" 24 + ref = "io.pocketenv.sandbox.defs#sandboxViewBasic" 25 + } 26 + } 27 + } 28 + }
+21
apps/api/pkl/defs/strongRef.pkl
··· 1 + amends "../schema/lexicon.pkl" 2 + 3 + lexicon = 1 4 + id = "com.atproto.repo.strongRef" 5 + description = "A URI with a content-hash fingerprint." 6 + defs = new Mapping { 7 + ["main"] = new ObjectType { 8 + type = "object" 9 + required = List("uri", "cid") 10 + properties { 11 + ["uri"] = new StringType { 12 + type = "string" 13 + format = "at-uri" 14 + } 15 + ["cid"] = new StringType { 16 + type = "string" 17 + format = "cid" 18 + } 19 + } 20 + } 21 + }
+97
apps/api/pkl/schema/lexicon.pkl
··· 1 + open class BaseType { 2 + type: String 3 + description: String? 4 + } 5 + 6 + class StringType extends BaseType { 7 + type: "string" 8 + maxGraphemes: Int? 9 + minLength: Int? 10 + maxLength: Int? 11 + format: "uri" | "datetime" | "cid" | "at-uri" | "at-identifier" | "did" | Null = null 12 + } 13 + 14 + class IntegerType extends BaseType { 15 + type: "integer" 16 + maximum: Int? 17 + minimum: Int? 18 + default: Int? 19 + } 20 + 21 + class BooleanType extends BaseType { 22 + type: "boolean" 23 + } 24 + 25 + class Blob extends BaseType { 26 + type: "blob" 27 + accept: List<String> 28 + maxSize: Int 29 + } 30 + 31 + class Array extends BaseType { 32 + type: "array" 33 + items: StringType | IntegerType | ObjectType | Blob | Ref | Union 34 + maxLength: Int? 35 + } 36 + 37 + class Params { 38 + type: "params" 39 + required: List<String>? 40 + properties: Mapping<String, StringType | IntegerType | BooleanType | Blob | Ref | Array> 41 + } 42 + 43 + class ObjectType extends BaseType { 44 + type: "object" 45 + required: List<String>? 46 + properties: Mapping<String, StringType | IntegerType | BooleanType | Blob | Ref | Union | Array> 47 + } 48 + 49 + class Union extends BaseType { 50 + type: "union" 51 + refs: List<String> 52 + } 53 + 54 + class Ref extends BaseType { 55 + type: "ref" 56 + ref: String 57 + } 58 + 59 + class Input { 60 + encoding: "application/json" 61 + schema: ObjectType 62 + } 63 + 64 + class Output { 65 + encoding: "application/json" | "application/octet-stream" 66 + schema: ObjectType | Ref | Array | Null = null 67 + } 68 + 69 + class Query extends BaseType { 70 + type: "query" 71 + parameters: Params 72 + output: Output 73 + } 74 + 75 + class Procedure extends BaseType { 76 + type: "procedure" 77 + parameters: Params? 78 + input: Input? 79 + output: Output? 80 + } 81 + 82 + class Record extends BaseType { 83 + type: "record" 84 + key: String 85 + `record`: ObjectType 86 + } 87 + 88 + lexicon: Int = 1 89 + id: String 90 + description: String? 91 + defs: Mapping<String, Procedure> 92 + | Mapping<String, Record> 93 + | Mapping<String, ObjectType> 94 + | Mapping<String, Query> 95 + | Mapping<String, Array> 96 + | Mapping<String, ObjectType | Array> 97 + | Null = null
+35
apps/api/scripts/pkl.ts
··· 1 + import chalk from "chalk"; 2 + import { readdirSync, statSync } from "fs"; 3 + import { join } from "path"; 4 + import { $ } from "zx"; 5 + import { consola } from "consola"; 6 + 7 + function getPklFilesRecursive(dir: string): string[] { 8 + const entries = readdirSync(dir); 9 + const files: string[] = []; 10 + 11 + for (const entry of entries) { 12 + const fullPath = join(dir, entry); 13 + const stats = statSync(fullPath); 14 + 15 + if (stats.isDirectory()) { 16 + files.push(...getPklFilesRecursive(fullPath)); 17 + continue; 18 + } 19 + 20 + if (entry.endsWith(".pkl")) { 21 + files.push(fullPath); 22 + } 23 + } 24 + 25 + return files; 26 + } 27 + 28 + const files = getPklFilesRecursive(join("pkl", "defs")); 29 + 30 + await Promise.all( 31 + files.map(async (fullPath) => { 32 + consola.info(`pkl eval ${chalk.cyan(fullPath)}`); 33 + await $`pkl eval -f json ${fullPath} > ${fullPath.replace(/\.pkl$/, ".json").replace(/pkl[\\\/]defs/g, "lexicons")}`; 34 + }), 35 + );
+104
apps/api/scripts/sandbox.ts
··· 1 + import chalk from "chalk"; 2 + import { consola } from "consola"; 3 + import { ctx } from "../src/context"; 4 + import type * as Sandbox from "../src/lexicon/types/io/pocketenv/sandbox"; 5 + import { createAgent } from "../src/lib/agent"; 6 + import prompts from "prompts"; 7 + 8 + const args = process.argv.slice(2); 9 + 10 + if (args.length === 0) { 11 + consola.error("Please provide user author identifier (handle or DID)."); 12 + console.log(`Usage: ${chalk.cyan("npm run sandbox -- <handle|did>")}`); 13 + process.exit(1); 14 + } 15 + 16 + const name = await prompts({ 17 + type: "text", 18 + name: "value", 19 + message: "What is the sandbox name?", 20 + }); 21 + 22 + if (name.value.length < 3 || name.value.length > 240) { 23 + consola.error("Sandbox name must be between 3 and 240 characters."); 24 + process.exit(1); 25 + } 26 + 27 + const description = await prompts({ 28 + type: "text", 29 + name: "value", 30 + message: "Please provide a short description of the sandbox", 31 + }); 32 + 33 + if (description.value.length > 3000) { 34 + consola.error("Description is too long. Maximum length is 3000 characters."); 35 + process.exit(1); 36 + } 37 + 38 + const rkey = await prompts({ 39 + type: "text", 40 + name: "value", 41 + message: "What is the record key (rkey) for the sandbox?", 42 + }); 43 + 44 + if (!/^[a-zA-Z0-9_-]{3,30}$/.test(rkey.value)) { 45 + consola.error( 46 + "Invalid record key. Only alphanumeric characters, underscores, and hyphens are allowed. Length must be between 3 and 30 characters.", 47 + ); 48 + process.exit(1); 49 + } 50 + 51 + consola.info("Creating sandbox with the following details:"); 52 + consola.info("---"); 53 + consola.info("Sandbox name:", name.value); 54 + consola.info("Description:", description.value); 55 + consola.info("Record key (rkey):", rkey.value); 56 + 57 + const confirm = await prompts({ 58 + type: "confirm", 59 + name: "value", 60 + message: "Do you want to proceed?", 61 + initial: true, 62 + }); 63 + 64 + if (!confirm.value) { 65 + consola.info("Sandbox creation cancelled."); 66 + process.exit(0); 67 + } 68 + 69 + let userDid = args[0]; 70 + 71 + if (!userDid?.startsWith("did:plc:")) { 72 + userDid = await ctx.baseIdResolver.handle.resolve(userDid!); 73 + } 74 + 75 + const agent = await createAgent(ctx.oauthClient, userDid!); 76 + 77 + consola.info(`Writing ${chalk.greenBright("io.pocketenv.sandbox")} record...`); 78 + 79 + const record: Sandbox.Record = { 80 + $type: "io.pocketenv.sandbox", 81 + name: name.value, 82 + description: description.value, 83 + vcpus: 1, 84 + memory: 4, 85 + disk: 3, 86 + createdAt: new Date().toISOString(), 87 + }; 88 + 89 + if (!agent) { 90 + consola.error("failed to create agent"); 91 + process.exit(1); 92 + } 93 + 94 + const res = await agent.com.atproto.repo.createRecord({ 95 + repo: agent.assertDid, 96 + collection: "io.pocketenv.sandbox", 97 + record, 98 + rkey: rkey.value, 99 + }); 100 + 101 + consola.info(chalk.greenBright("Sandbox created successfully!")); 102 + consola.info(`Record created at: ${chalk.cyan(res.data.uri)}`); 103 + 104 + process.exit(0);
+82
apps/api/scripts/seed.ts
··· 1 + import type { Agent } from "@atproto/api"; 2 + import chalk from "chalk"; 3 + import { consola } from "consola"; 4 + import { ctx } from "context"; 5 + import { eq } from "drizzle-orm"; 6 + import { createAgent } from "lib/agent"; 7 + import * as Sandbox from "../src/lexicon/types/io/pocketenv/sandbox"; 8 + import schema from "schema"; 9 + import type { InsertSandbox } from "schema/sandboxes"; 10 + import { env } from "lib/env"; 11 + 12 + const args = process.argv.slice(2); 13 + 14 + if (args.length === 0) { 15 + consola.error("Please provide user author identifier (handle or DID)."); 16 + consola.info(`Usage: ${chalk.cyan("npm run seed -- <handle|did>")}`); 17 + process.exit(1); 18 + } 19 + 20 + async function getSandboxes(agent: Agent, limit: number = 100) { 21 + const res = await agent.com.atproto.repo.listRecords({ 22 + repo: agent.assertDid, 23 + collection: "io.pocketenv.sandbox", 24 + limit, 25 + }); 26 + return res.data.records.map((record) => ({ 27 + ...record, 28 + value: Sandbox.isRecord(record.value) ? record.value : null, 29 + })); 30 + } 31 + 32 + let userDid = args[0]; 33 + 34 + if (userDid && !userDid.startsWith("did:plc:")) { 35 + userDid = await ctx.baseIdResolver.handle.resolve(userDid); 36 + } 37 + 38 + if (!userDid) { 39 + consola.error("Could not resolve user DID."); 40 + process.exit(1); 41 + } 42 + 43 + const agent = await createAgent(ctx.oauthClient, userDid); 44 + if (!agent) { 45 + consola.error("Could not create agent for the provided DID."); 46 + process.exit(1); 47 + } 48 + 49 + const [user] = await ctx.db 50 + .select() 51 + .from(schema.users) 52 + .where(eq(schema.users.did, agent.assertDid)) 53 + .execute(); 54 + 55 + const sandboxes = await getSandboxes(agent); 56 + 57 + for (const sandbox of sandboxes) { 58 + if (!sandbox.value) continue; 59 + 60 + await ctx.db 61 + .insert(schema.sandboxes) 62 + .values({ 63 + name: sandbox.uri.split("/").slice(-1)[0]!, 64 + displayName: sandbox.value.name, 65 + description: sandbox.value.description, 66 + provider: "daytona", 67 + status: "STOPPED", 68 + uri: sandbox.uri, 69 + publicKey: env.PUBLIC_KEY, 70 + vcpus: sandbox.value.vcpus, 71 + memory: sandbox.value.memory, 72 + disk: sandbox.value.disk, 73 + userId: user!.id, 74 + } satisfies InsertSandbox) 75 + .onConflictDoNothing() 76 + .execute(); 77 + consola.info( 78 + `Sandbox ${chalk.cyanBright(sandbox.value.name)} seeded successfully.`, 79 + ); 80 + } 81 + 82 + process.exit(0);
+72
apps/api/src/auth/client.ts
··· 1 + import { JoseKey } from "@atproto/jwk-jose"; 2 + import type { RuntimeLock } from "@atproto/oauth-client-node"; 3 + import Redis from "ioredis"; 4 + import Redlock from "redlock"; 5 + import type { Database } from "../db"; 6 + import { env } from "lib/env"; 7 + import { SessionStore, StateStore } from "./storage"; 8 + import { CustomOAuthClient } from "./oauth-client"; 9 + 10 + export const SCOPES = [ 11 + "atproto", 12 + "repo:io.pocketenv.sandbox", 13 + "repo:sh.tangled.repo", 14 + "repo:sh.tangled.string", 15 + "repo:sh.tangled.pull", 16 + "repo:sh.tangled.pull.comment", 17 + ]; 18 + 19 + export const createClient = async (db: Database) => { 20 + const publicUrl = env.PUBLIC_URL; 21 + const url = publicUrl.includes("localhost") 22 + ? `http://127.0.0.1:${env.PORT}` 23 + : publicUrl; 24 + const enc = encodeURIComponent; 25 + 26 + const redis = new Redis(env.REDIS_URL); 27 + const redlock = new Redlock([redis]); 28 + 29 + const requestLock: RuntimeLock = async (key, fn) => { 30 + const lock = await redlock.acquire([key], 45e3); // 45 seconds 31 + try { 32 + return await fn(); 33 + } finally { 34 + await lock.release(); 35 + } 36 + }; 37 + 38 + return new CustomOAuthClient({ 39 + clientMetadata: { 40 + client_name: "Pocketenv", 41 + client_id: !publicUrl.includes("localhost") 42 + ? `${url}/oauth-client-metadata.json` 43 + : `http://localhost?redirect_uri=${enc( 44 + `${url}/oauth/callback`, 45 + )}&scope=${enc(SCOPES.join(" "))}`, 46 + client_uri: url, 47 + redirect_uris: [`${url}/oauth/callback`], 48 + scope: SCOPES.join(" "), 49 + grant_types: ["authorization_code", "refresh_token"], 50 + response_types: ["code"], 51 + application_type: "web", 52 + token_endpoint_auth_method: url.startsWith("https") 53 + ? "private_key_jwt" 54 + : "none", 55 + token_endpoint_auth_signing_alg: url.startsWith("https") 56 + ? "ES256" 57 + : undefined, 58 + dpop_bound_access_tokens: true, 59 + jwks_uri: url.startsWith("https") ? `${url}/jwks.json` : undefined, 60 + }, 61 + keyset: url.startsWith("https") 62 + ? await Promise.all([ 63 + JoseKey.fromImportable(env.PRIVATE_KEY_1), 64 + JoseKey.fromImportable(env.PRIVATE_KEY_2), 65 + JoseKey.fromImportable(env.PRIVATE_KEY_3), 66 + ]) 67 + : undefined, 68 + stateStore: new StateStore(db), 69 + sessionStore: new SessionStore(db), 70 + requestLock, 71 + }); 72 + };
+84
apps/api/src/auth/oauth-client-auth.ts
··· 1 + import type { 2 + ClientMetadata, 3 + Keyset, 4 + OAuthAuthorizationServerMetadata, 5 + } from "@atproto/oauth-client-node"; 6 + 7 + import type { ClientAuthMethod } from "@atproto/oauth-client/dist/oauth-client-auth"; 8 + 9 + export const FALLBACK_ALG = "ES256"; 10 + 11 + function supportedMethods(serverMetadata: OAuthAuthorizationServerMetadata) { 12 + return serverMetadata["token_endpoint_auth_methods_supported"]; 13 + } 14 + 15 + function supportedAlgs(serverMetadata: OAuthAuthorizationServerMetadata) { 16 + return ( 17 + serverMetadata["token_endpoint_auth_signing_alg_values_supported"] ?? [ 18 + // @NOTE If not specified, assume that the server supports the ES256 19 + // algorithm, as prescribed by the spec: 20 + // 21 + // > Clients and Authorization Servers currently must support the ES256 22 + // > cryptographic system [for client authentication]. 23 + // 24 + // https://atproto.com/specs/oauth#confidential-client-authentication 25 + FALLBACK_ALG, 26 + ] 27 + ); 28 + } 29 + 30 + export function negotiateClientAuthMethod( 31 + serverMetadata: OAuthAuthorizationServerMetadata, 32 + clientMetadata: ClientMetadata, 33 + keyset?: Keyset, 34 + ): ClientAuthMethod { 35 + const method = clientMetadata.token_endpoint_auth_method; 36 + 37 + // @NOTE ATproto spec requires that AS support both "none" and 38 + // "private_key_jwt", and that clients use one of the other. The following 39 + // check ensures that the AS is indeed compliant with this client's 40 + // configuration. 41 + const methods = supportedMethods(serverMetadata); 42 + if (!methods.includes(method)) { 43 + throw new Error( 44 + `The server does not support "${method}" authentication. Supported methods are: ${methods.join( 45 + ", ", 46 + )}.`, 47 + ); 48 + } 49 + 50 + if (method === "private_key_jwt") { 51 + // Invalid client configuration. This should not happen as 52 + // "validateClientMetadata" already check this. 53 + if (!keyset) throw new Error("A keyset is required for private_key_jwt"); 54 + 55 + const alg = supportedAlgs(serverMetadata); 56 + 57 + // @NOTE we can't use `keyset.findPrivateKey` here because we can't enforce 58 + // that the returned key contains a "kid". The following implementation is 59 + // more robust against keysets containing keys without a "kid" property. 60 + for (const key of keyset.list({ alg, usage: "sign" })) { 61 + // Return the first key from the key set that matches the server's 62 + // supported algorithms. 63 + if (key.kid) return { method: "private_key_jwt", kid: key.kid }; 64 + } 65 + 66 + throw new Error( 67 + alg.includes(FALLBACK_ALG) 68 + ? `Client authentication method "${method}" requires at least one "${FALLBACK_ALG}" signing key with a "kid" property` 69 + : // AS is not compliant with the ATproto OAuth spec. 70 + `Authorization server requires "${method}" authentication method, but does not support "${FALLBACK_ALG}" algorithm.`, 71 + ); 72 + } 73 + 74 + if (method === "none") { 75 + return { method: "none" }; 76 + } 77 + 78 + throw new Error( 79 + `The ATProto OAuth spec requires that client use either "none" or "private_key_jwt" authentication method.` + 80 + (method === "client_secret_basic" 81 + ? ' You might want to explicitly set "token_endpoint_auth_method" to one of those values in the client metadata document.' 82 + : ` You set "${method}" which is not allowed.`), 83 + ); 84 + }
+116
apps/api/src/auth/oauth-client.ts
··· 1 + import { 2 + type AuthorizeOptions, 3 + NodeOAuthClient, 4 + type NodeOAuthClientOptions, 5 + type OAuthAuthorizationRequestParameters, 6 + } from "@atproto/oauth-client-node"; 7 + import { FALLBACK_ALG, negotiateClientAuthMethod } from "./oauth-client-auth"; 8 + 9 + export class CustomOAuthClient extends NodeOAuthClient { 10 + constructor(options: NodeOAuthClientOptions) { 11 + super(options); 12 + } 13 + 14 + override async authorize( 15 + input: string, 16 + { signal, ...options }: AuthorizeOptions = {}, 17 + ): Promise<URL> { 18 + const redirectUri = 19 + options?.redirect_uri ?? this.clientMetadata.redirect_uris[0]; 20 + if (!this.clientMetadata.redirect_uris.includes(redirectUri)) { 21 + // The server will enforce this, but let's catch it early 22 + throw new TypeError("Invalid redirect_uri"); 23 + } 24 + 25 + const { identityInfo, metadata } = await this.oauthResolver.resolve(input, { 26 + signal, 27 + }); 28 + 29 + const pkce = await this.runtime.generatePKCE(); 30 + const dpopKey = await this.runtime.generateKey( 31 + metadata.dpop_signing_alg_values_supported || [FALLBACK_ALG], 32 + ); 33 + 34 + const authMethod = negotiateClientAuthMethod( 35 + metadata, 36 + this.clientMetadata, 37 + this.keyset, 38 + ); 39 + const state = await this.runtime.generateNonce(); 40 + 41 + await this.stateStore.set(state, { 42 + iss: metadata.issuer, 43 + authMethod, 44 + dpopKey, 45 + verifier: pkce.verifier, 46 + appState: options?.state, 47 + }); 48 + 49 + const parameters: OAuthAuthorizationRequestParameters = { 50 + ...options, 51 + 52 + client_id: this.clientMetadata.client_id, 53 + redirect_uri: redirectUri, 54 + code_challenge: pkce.challenge, 55 + code_challenge_method: pkce.method, 56 + state, 57 + login_hint: identityInfo && !options.prompt ? input : undefined, 58 + response_mode: this.responseMode, 59 + response_type: "code" as const, 60 + scope: options?.scope ?? this.clientMetadata.scope, 61 + }; 62 + 63 + const authorizationUrl = new URL(metadata.authorization_endpoint); 64 + 65 + // Since the user will be redirected to the authorization_endpoint url using 66 + // a browser, we need to make sure that the url is valid. 67 + if ( 68 + authorizationUrl.protocol !== "https:" && 69 + authorizationUrl.protocol !== "http:" 70 + ) { 71 + throw new TypeError( 72 + `Invalid authorization endpoint protocol: ${authorizationUrl.protocol}`, 73 + ); 74 + } 75 + 76 + if (metadata.pushed_authorization_request_endpoint) { 77 + const server = await this.serverFactory.fromMetadata( 78 + metadata, 79 + authMethod, 80 + dpopKey, 81 + ); 82 + const parResponse = await server.request( 83 + "pushed_authorization_request", 84 + parameters, 85 + ); 86 + 87 + authorizationUrl.searchParams.set( 88 + "client_id", 89 + this.clientMetadata.client_id, 90 + ); 91 + authorizationUrl.searchParams.set("request_uri", parResponse.request_uri); 92 + return authorizationUrl; 93 + } else if (metadata.require_pushed_authorization_requests) { 94 + throw new Error( 95 + "Server requires pushed authorization requests (PAR) but no PAR endpoint is available", 96 + ); 97 + } else { 98 + for (const [key, value] of Object.entries(parameters)) { 99 + if (value) authorizationUrl.searchParams.set(key, String(value)); 100 + } 101 + 102 + // Length of the URL that will be sent to the server 103 + const urlLength = 104 + authorizationUrl.pathname.length + authorizationUrl.search.length; 105 + if (urlLength < 2048) { 106 + return authorizationUrl; 107 + } else if (!metadata.pushed_authorization_request_endpoint) { 108 + throw new Error("Login URL too long"); 109 + } 110 + } 111 + 112 + throw new Error( 113 + "Server does not support pushed authorization requests (PAR)", 114 + ); 115 + } 116 + }
+57
apps/api/src/auth/storage.ts
··· 1 + import type { 2 + NodeSavedSession, 3 + NodeSavedSessionStore, 4 + NodeSavedState, 5 + NodeSavedStateStore, 6 + } from "@atproto/oauth-client-node"; 7 + import type { Database } from "../db"; 8 + 9 + export class StateStore implements NodeSavedStateStore { 10 + constructor(private db: Database) {} 11 + async get(key: string): Promise<NodeSavedState | undefined> { 12 + const result = await this.db 13 + .selectFrom("auth_state") 14 + .selectAll() 15 + .where("key", "=", key) 16 + .executeTakeFirst(); 17 + if (!result) return; 18 + return JSON.parse(result.state) as NodeSavedState; 19 + } 20 + async set(key: string, val: NodeSavedState) { 21 + const state = JSON.stringify(val); 22 + await this.db 23 + .insertInto("auth_state") 24 + .values({ key, state }) 25 + .onConflict((oc) => oc.doUpdateSet({ state })) 26 + .execute(); 27 + } 28 + async del(key: string) { 29 + await this.db.deleteFrom("auth_state").where("key", "=", key).execute(); 30 + } 31 + } 32 + 33 + export class SessionStore implements NodeSavedSessionStore { 34 + constructor(private db: Database) {} 35 + async get(key: string): Promise<NodeSavedSession | undefined> { 36 + const result = await this.db 37 + .selectFrom("auth_session") 38 + .selectAll() 39 + .where("key", "=", key) 40 + .executeTakeFirst(); 41 + if (!result) return; 42 + return JSON.parse(result.session) as NodeSavedSession; 43 + } 44 + async set(key: string, val: NodeSavedSession) { 45 + const session = JSON.stringify(val); 46 + await this.db 47 + .insertInto("auth_session") 48 + .values({ key, session, expiresAt: val.tokenSet.expires_at }) 49 + .onConflict((oc) => 50 + oc.doUpdateSet({ session, expiresAt: val.tokenSet.expires_at }), 51 + ) 52 + .execute(); 53 + } 54 + async del(key: string) { 55 + await this.db.deleteFrom("auth_session").where("key", "=", key).execute(); 56 + } 57 + }
+169
apps/api/src/bsky/index.ts
··· 1 + import { isValidHandle } from "@atproto/syntax"; 2 + import { SCOPES } from "auth/client"; 3 + import { Router } from "express"; 4 + import { env } from "lib/env"; 5 + import jwt from "jsonwebtoken"; 6 + import extractPdsFromDid from "lib/extractPdsFromDid"; 7 + import AtpAgent from "@atproto/api"; 8 + import { omit } from "ramda"; 9 + import { consola } from "consola"; 10 + 11 + const app = Router(); 12 + 13 + app.get("/login", async (req, res) => { 14 + const { handle, prompt } = req.query; 15 + 16 + if ((typeof handle !== "string" || !isValidHandle(handle)) && !prompt) { 17 + res.status(400).send("Invalid handle"); 18 + return; 19 + } 20 + 21 + const url = await req.ctx.oauthClient.authorize( 22 + prompt ? "tsiry.selfhosted.social" : (handle as string), 23 + { 24 + scope: SCOPES.join(" "), 25 + // @ts-expect-error: allow custom prompt param 26 + prompt: prompt as string, 27 + }, 28 + ); 29 + res.redirect(url.toString()); 30 + }); 31 + 32 + app.post("/login", async (req, res) => { 33 + const { handle, cli, password } = req.body; 34 + if (typeof handle !== "string" || !isValidHandle(handle)) { 35 + res.status(400); 36 + res.send("Invalid handle"); 37 + return; 38 + } 39 + 40 + if (password) { 41 + const defaultAgent = new AtpAgent({ 42 + service: new URL("https://bsky.social"), 43 + }); 44 + const { 45 + data: { did }, 46 + } = await defaultAgent.resolveHandle({ handle }); 47 + 48 + let pds = await req.ctx.redis.get(`pds:${did}`); 49 + if (!pds) { 50 + pds = await extractPdsFromDid(did); 51 + await req.ctx.redis.setEx(`pds:${did}`, 60 * 15, pds!); 52 + } 53 + 54 + const agent = new AtpAgent({ 55 + service: new URL(pds!), 56 + }); 57 + 58 + await agent.login({ 59 + identifier: handle, 60 + password, 61 + }); 62 + 63 + await req.ctx.sqliteDb 64 + .insertInto("auth_session") 65 + .values({ 66 + key: `atp:${did}`, 67 + session: JSON.stringify(agent.session), 68 + }) 69 + .onConflict((oc) => 70 + oc 71 + .column("key") 72 + .doUpdateSet({ session: JSON.stringify(agent.session) }), 73 + ) 74 + .execute(); 75 + 76 + const token = jwt.sign( 77 + { 78 + did, 79 + exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 7, 80 + }, 81 + env.JWT_SECRET, 82 + ); 83 + 84 + res.send(`jwt:${token}`); 85 + return; 86 + } 87 + 88 + const url = await req.ctx.oauthClient.authorize(handle, { 89 + scope: SCOPES.join(" "), 90 + }); 91 + 92 + if (cli) { 93 + req.ctx.kv.set(`cli:${handle}`, "1"); 94 + } 95 + 96 + res.send(url.toString()); 97 + }); 98 + 99 + app.get("/oauth/callback", async (req, res) => { 100 + const params = new URLSearchParams(req.url.split("?")[1]); 101 + let did: string, cli: string | undefined; 102 + 103 + try { 104 + const { session } = await req.ctx.oauthClient.callback(params); 105 + did = session.did; 106 + const handle = await req.ctx.resolver.resolveDidToHandle(did); 107 + cli = req.ctx.kv.get(`cli:${handle}`); 108 + req.ctx.kv.delete(`cli:${handle}`); 109 + 110 + const token = jwt.sign( 111 + { 112 + did, 113 + exp: cli 114 + ? Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 365 * 1000 115 + : Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 7, 116 + }, 117 + env.JWT_SECRET, 118 + ); 119 + req.ctx.kv.set(did, token); 120 + } catch (err) { 121 + consola.error({ err }, "oauth callback failed"); 122 + res.redirect(`${env.FRONTEND_URL}?error=1`); 123 + return; 124 + } 125 + 126 + res.redirect(`${env.FRONTEND_URL}?did=${did}&cli=${cli}`); 127 + }); 128 + 129 + app.get("/token", async (req, res) => { 130 + const did = req.header("session-did"); 131 + 132 + if (typeof did !== "string" || !did || did === "null") { 133 + res.status(401); 134 + res.send("Unauthorized"); 135 + return; 136 + } 137 + 138 + const token = req.ctx.kv.get(did); 139 + 140 + if (!token) { 141 + res.status(401); 142 + res.send("Unauthorized"); 143 + return; 144 + } 145 + 146 + req.ctx.kv.delete(did); 147 + 148 + res.json({ token }); 149 + }); 150 + 151 + app.get("/client-metadata.json", (req, res) => { 152 + res.json(req.ctx.oauthClient.clientMetadata); 153 + }); 154 + 155 + app.get("/oauth-client-metadata.json", (req, res) => { 156 + res.json(req.ctx.oauthClient.clientMetadata); 157 + }); 158 + 159 + app.get("/jwks.json", (_req, res) => 160 + res.json({ 161 + keys: [ 162 + omit(["d"], JSON.parse(env.PRIVATE_KEY_1)), 163 + omit(["d"], JSON.parse(env.PRIVATE_KEY_2)), 164 + omit(["d"], JSON.parse(env.PRIVATE_KEY_3)), 165 + ], 166 + }), 167 + ); 168 + 169 + export default app;
+44 -2
apps/api/src/context.ts
··· 1 + import { env } from "lib/env"; 1 2 import { getConnection } from "./drizzle"; 3 + import { createDb, migrateToLatest } from "db"; 4 + import { createStorage } from "unstorage"; 5 + import sqliteKv from "sqliteKv"; 6 + import { createBidirectionalResolver, createIdResolver } from "lib/idResolver"; 7 + import { createClient } from "auth/client"; 8 + import { consola } from "consola"; 9 + import authVerifier from "lib/authVerfifier"; 10 + import redis from "redis"; 11 + import type { RequestHandler } from "express"; 2 12 3 - export type Context = { 4 - db: ReturnType<typeof getConnection>; 13 + const { DB_PATH } = env; 14 + export const db = createDb(DB_PATH); 15 + await migrateToLatest(db); 16 + 17 + const kv = createStorage({ 18 + driver: sqliteKv({ location: env.KV_DB_PATH, table: "kv" }), 19 + }); 20 + 21 + const baseIdResolver = createIdResolver(kv); 22 + 23 + export const ctx = { 24 + oauthClient: await createClient(db), 25 + resolver: createBidirectionalResolver(baseIdResolver), 26 + baseIdResolver, 27 + db: getConnection(), 28 + authVerifier, 29 + sqliteDb: db, 30 + sqliteKv: kv, 31 + redis: await redis 32 + .createClient({ url: env.REDIS_URL }) 33 + .on("error", (err) => { 34 + consola.error("Uncaught Redis Client Error", err); 35 + process.exit(1); 36 + }) 37 + .connect(), 38 + kv: new Map<string, string>(), 5 39 }; 40 + 41 + export const contextMiddleware: RequestHandler = (req, _res, next) => { 42 + req.ctx = ctx; 43 + 44 + next(); 45 + }; 46 + 47 + export type Context = typeof ctx;
+130
apps/api/src/db.ts
··· 1 + import SqliteDb from "better-sqlite3"; 2 + import chalk from "chalk"; 3 + import type { Context } from "context"; 4 + import { 5 + Kysely, 6 + type Migration, 7 + type MigrationProvider, 8 + Migrator, 9 + SqliteDialect, 10 + } from "kysely"; 11 + import { consola } from "consola"; 12 + 13 + export type DatabaseSchema = { 14 + status: Status; 15 + auth_session: AuthSession; 16 + auth_state: AuthState; 17 + }; 18 + 19 + export type Status = { 20 + uri: string; 21 + authorDid: string; 22 + status: string; 23 + createdAt: string; 24 + indexedAt: string; 25 + }; 26 + 27 + export type AuthSession = { 28 + key: string; 29 + session: AuthSessionJson; 30 + expiresAt?: string | null; 31 + }; 32 + 33 + export type AuthState = { 34 + key: string; 35 + state: AuthStateJson; 36 + }; 37 + 38 + type AuthStateJson = string; 39 + 40 + type AuthSessionJson = string; 41 + 42 + // Migrations 43 + 44 + const migrations: Record<string, Migration> = {}; 45 + 46 + const migrationProvider: MigrationProvider = { 47 + async getMigrations() { 48 + return migrations; 49 + }, 50 + }; 51 + 52 + migrations["001"] = { 53 + async up(db: Kysely<unknown>) { 54 + await db.schema 55 + .createTable("status") 56 + .addColumn("uri", "varchar", (col) => col.primaryKey()) 57 + .addColumn("authorDid", "varchar", (col) => col.notNull()) 58 + .addColumn("status", "varchar", (col) => col.notNull()) 59 + .addColumn("createdAt", "varchar", (col) => col.notNull()) 60 + .addColumn("indexedAt", "varchar", (col) => col.notNull()) 61 + .execute(); 62 + await db.schema 63 + .createTable("auth_session") 64 + .addColumn("key", "varchar", (col) => col.primaryKey()) 65 + .addColumn("session", "varchar", (col) => col.notNull()) 66 + .execute(); 67 + await db.schema 68 + .createTable("auth_state") 69 + .addColumn("key", "varchar", (col) => col.primaryKey()) 70 + .addColumn("state", "varchar", (col) => col.notNull()) 71 + .execute(); 72 + }, 73 + async down(db: Kysely<unknown>) { 74 + await db.schema.dropTable("auth_state").execute(); 75 + await db.schema.dropTable("auth_session").execute(); 76 + await db.schema.dropTable("status").execute(); 77 + }, 78 + }; 79 + 80 + migrations["002"] = { 81 + async up(db: Kysely<unknown>) { 82 + await db.schema 83 + .alterTable("auth_session") 84 + .addColumn("expiresAt", "text", (col) => col.defaultTo("NULL")) 85 + .execute(); 86 + }, 87 + async down(db: Kysely<unknown>) { 88 + await db.schema 89 + .alterTable("auth_session") 90 + .dropColumn("expiresAt") 91 + .execute(); 92 + }, 93 + }; 94 + 95 + // APIs 96 + 97 + export const createDb = (location: string): Database => { 98 + return new Kysely<DatabaseSchema>({ 99 + dialect: new SqliteDialect({ 100 + database: new SqliteDb(location), 101 + }), 102 + }); 103 + }; 104 + 105 + export const migrateToLatest = async (db: Database) => { 106 + const migrator = new Migrator({ db, provider: migrationProvider }); 107 + const { error } = await migrator.migrateToLatest(); 108 + if (error) throw error; 109 + }; 110 + 111 + export const updateExpiresAt = async (db: Database) => { 112 + // get all sessions that have expiresAt is null 113 + const sessions = await db.selectFrom("auth_session").selectAll().execute(); 114 + consola.info("Found", sessions.length, "sessions to update"); 115 + for (const session of sessions) { 116 + const data = JSON.parse(session.session) as { 117 + tokenSet: { expires_at?: string | null }; 118 + }; 119 + consola.info(session.key, data.tokenSet.expires_at); 120 + await db 121 + .updateTable("auth_session") 122 + .set({ expiresAt: data.tokenSet.expires_at }) 123 + .where("key", "=", session.key) 124 + .execute(); 125 + } 126 + 127 + consola.info(`Updated ${chalk.greenBright(sessions.length)} sessions`); 128 + }; 129 + 130 + export type Database = Kysely<DatabaseSchema>;
+37 -3
apps/api/src/index.ts
··· 1 + import cors from "cors"; 1 2 import express from "express"; 2 - import consola from "consola"; 3 + import morgan from "morgan"; 4 + import { consola } from "consola"; 5 + import bsky from "bsky"; 6 + import { contextMiddleware, ctx } from "context"; 7 + import { createServer } from "lexicon"; 8 + import API from "./xrpc"; 9 + 10 + let server = createServer({ 11 + validateResponse: false, 12 + payload: { 13 + jsonLimit: 100 * 1024, // 100kb 14 + textLimit: 100 * 1024, // 100kb 15 + blobLimit: 5 * 1024 * 1024, // 5mb 16 + }, 17 + }); 18 + 19 + server = API(server, ctx); 3 20 4 21 const app = express(); 5 22 23 + app.use(contextMiddleware); 24 + app.use(cors()); 25 + app.use(express.json()); 26 + app.use(morgan("dev")); 27 + 6 28 app.get("/", (req, res) => { 7 - res.send(` 29 + const accept = req.headers.accept || ""; 30 + const wantsHTML = accept.includes("text/html"); 31 + const banner = ` 8 32 ___ __ __ 9 33 / _ \\___ ____/ /_____ / /____ ___ _ __ 10 34 / ___/ _ \\/ __/ '_/ -_) __/ -_) _ \\ |/ / 11 35 /_/ \\___/\\__/_/\\_\\__/\\__/\\__/_/ /_/___/ 12 36 13 - `); 37 + `; 38 + if (wantsHTML) { 39 + res.contentType("text/html"); 40 + res.send(`<pre>${banner}</pre>`); 41 + return; 42 + } 43 + res.contentType("text/plain"); 44 + res.send(banner); 14 45 }); 46 + 47 + app.use(bsky); 48 + app.use(server.xrpc.router); 15 49 16 50 app.listen(process.env.POCKETENV_XPRC_PORT || 8789, () => { 17 51 consola.info(
+223
apps/api/src/lexicon/index.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { 5 + createServer as createXrpcServer, 6 + type Server as XrpcServer, 7 + type Options as XrpcOptions, 8 + type AuthVerifier, 9 + type StreamAuthVerifier, 10 + } from "@atproto/xrpc-server"; 11 + import { schemas } from "./lexicons"; 12 + import type * as IoPocketenvSandboxClaimSandbox from "./types/io/pocketenv/sandbox/claimSandbox"; 13 + import type * as IoPocketenvSandboxCreateSandbox from "./types/io/pocketenv/sandbox/createSandbox"; 14 + import type * as IoPocketenvSandboxDeleteSandbox from "./types/io/pocketenv/sandbox/deleteSandbox"; 15 + import type * as IoPocketenvSandboxGetSandbox from "./types/io/pocketenv/sandbox/getSandbox"; 16 + import type * as IoPocketenvSandboxGetSandboxes from "./types/io/pocketenv/sandbox/getSandboxes"; 17 + import type * as IoPocketenvSandboxStartSandbox from "./types/io/pocketenv/sandbox/startSandbox"; 18 + import type * as IoPocketenvSandboxStopSandbox from "./types/io/pocketenv/sandbox/stopSandbox"; 19 + 20 + export function createServer(options?: XrpcOptions): Server { 21 + return new Server(options); 22 + } 23 + 24 + export class Server { 25 + xrpc: XrpcServer; 26 + app: AppNS; 27 + io: IoNS; 28 + com: ComNS; 29 + 30 + constructor(options?: XrpcOptions) { 31 + this.xrpc = createXrpcServer(schemas, options); 32 + this.app = new AppNS(this); 33 + this.io = new IoNS(this); 34 + this.com = new ComNS(this); 35 + } 36 + } 37 + 38 + export class AppNS { 39 + _server: Server; 40 + bsky: AppBskyNS; 41 + 42 + constructor(server: Server) { 43 + this._server = server; 44 + this.bsky = new AppBskyNS(server); 45 + } 46 + } 47 + 48 + export class AppBskyNS { 49 + _server: Server; 50 + actor: AppBskyActorNS; 51 + 52 + constructor(server: Server) { 53 + this._server = server; 54 + this.actor = new AppBskyActorNS(server); 55 + } 56 + } 57 + 58 + export class AppBskyActorNS { 59 + _server: Server; 60 + 61 + constructor(server: Server) { 62 + this._server = server; 63 + } 64 + } 65 + 66 + export class IoNS { 67 + _server: Server; 68 + pocketenv: IoPocketenvNS; 69 + 70 + constructor(server: Server) { 71 + this._server = server; 72 + this.pocketenv = new IoPocketenvNS(server); 73 + } 74 + } 75 + 76 + export class IoPocketenvNS { 77 + _server: Server; 78 + sandbox: IoPocketenvSandboxNS; 79 + 80 + constructor(server: Server) { 81 + this._server = server; 82 + this.sandbox = new IoPocketenvSandboxNS(server); 83 + } 84 + } 85 + 86 + export class IoPocketenvSandboxNS { 87 + _server: Server; 88 + 89 + constructor(server: Server) { 90 + this._server = server; 91 + } 92 + 93 + claimSandbox<AV extends AuthVerifier>( 94 + cfg: ConfigOf< 95 + AV, 96 + IoPocketenvSandboxClaimSandbox.Handler<ExtractAuth<AV>>, 97 + IoPocketenvSandboxClaimSandbox.HandlerReqCtx<ExtractAuth<AV>> 98 + >, 99 + ) { 100 + const nsid = "io.pocketenv.sandbox.claimSandbox"; // @ts-ignore 101 + return this._server.xrpc.method(nsid, cfg); 102 + } 103 + 104 + createSandbox<AV extends AuthVerifier>( 105 + cfg: ConfigOf< 106 + AV, 107 + IoPocketenvSandboxCreateSandbox.Handler<ExtractAuth<AV>>, 108 + IoPocketenvSandboxCreateSandbox.HandlerReqCtx<ExtractAuth<AV>> 109 + >, 110 + ) { 111 + const nsid = "io.pocketenv.sandbox.createSandbox"; // @ts-ignore 112 + return this._server.xrpc.method(nsid, cfg); 113 + } 114 + 115 + deleteSandbox<AV extends AuthVerifier>( 116 + cfg: ConfigOf< 117 + AV, 118 + IoPocketenvSandboxDeleteSandbox.Handler<ExtractAuth<AV>>, 119 + IoPocketenvSandboxDeleteSandbox.HandlerReqCtx<ExtractAuth<AV>> 120 + >, 121 + ) { 122 + const nsid = "io.pocketenv.sandbox.deleteSandbox"; // @ts-ignore 123 + return this._server.xrpc.method(nsid, cfg); 124 + } 125 + 126 + getSandbox<AV extends AuthVerifier>( 127 + cfg: ConfigOf< 128 + AV, 129 + IoPocketenvSandboxGetSandbox.Handler<ExtractAuth<AV>>, 130 + IoPocketenvSandboxGetSandbox.HandlerReqCtx<ExtractAuth<AV>> 131 + >, 132 + ) { 133 + const nsid = "io.pocketenv.sandbox.getSandbox"; // @ts-ignore 134 + return this._server.xrpc.method(nsid, cfg); 135 + } 136 + 137 + getSandboxes<AV extends AuthVerifier>( 138 + cfg: ConfigOf< 139 + AV, 140 + IoPocketenvSandboxGetSandboxes.Handler<ExtractAuth<AV>>, 141 + IoPocketenvSandboxGetSandboxes.HandlerReqCtx<ExtractAuth<AV>> 142 + >, 143 + ) { 144 + const nsid = "io.pocketenv.sandbox.getSandboxes"; // @ts-ignore 145 + return this._server.xrpc.method(nsid, cfg); 146 + } 147 + 148 + startSandbox<AV extends AuthVerifier>( 149 + cfg: ConfigOf< 150 + AV, 151 + IoPocketenvSandboxStartSandbox.Handler<ExtractAuth<AV>>, 152 + IoPocketenvSandboxStartSandbox.HandlerReqCtx<ExtractAuth<AV>> 153 + >, 154 + ) { 155 + const nsid = "io.pocketenv.sandbox.startSandbox"; // @ts-ignore 156 + return this._server.xrpc.method(nsid, cfg); 157 + } 158 + 159 + stopSandbox<AV extends AuthVerifier>( 160 + cfg: ConfigOf< 161 + AV, 162 + IoPocketenvSandboxStopSandbox.Handler<ExtractAuth<AV>>, 163 + IoPocketenvSandboxStopSandbox.HandlerReqCtx<ExtractAuth<AV>> 164 + >, 165 + ) { 166 + const nsid = "io.pocketenv.sandbox.stopSandbox"; // @ts-ignore 167 + return this._server.xrpc.method(nsid, cfg); 168 + } 169 + } 170 + 171 + export class ComNS { 172 + _server: Server; 173 + atproto: ComAtprotoNS; 174 + 175 + constructor(server: Server) { 176 + this._server = server; 177 + this.atproto = new ComAtprotoNS(server); 178 + } 179 + } 180 + 181 + export class ComAtprotoNS { 182 + _server: Server; 183 + repo: ComAtprotoRepoNS; 184 + 185 + constructor(server: Server) { 186 + this._server = server; 187 + this.repo = new ComAtprotoRepoNS(server); 188 + } 189 + } 190 + 191 + export class ComAtprotoRepoNS { 192 + _server: Server; 193 + 194 + constructor(server: Server) { 195 + this._server = server; 196 + } 197 + } 198 + 199 + type SharedRateLimitOpts<T> = { 200 + name: string; 201 + calcKey?: (ctx: T) => string | null; 202 + calcPoints?: (ctx: T) => number; 203 + }; 204 + type RouteRateLimitOpts<T> = { 205 + durationMs: number; 206 + points: number; 207 + calcKey?: (ctx: T) => string | null; 208 + calcPoints?: (ctx: T) => number; 209 + }; 210 + type HandlerOpts = { blobLimit?: number }; 211 + type HandlerRateLimitOpts<T> = SharedRateLimitOpts<T> | RouteRateLimitOpts<T>; 212 + type ConfigOf<Auth, Handler, ReqCtx> = 213 + | Handler 214 + | { 215 + auth?: Auth; 216 + opts?: HandlerOpts; 217 + rateLimit?: HandlerRateLimitOpts<ReqCtx> | HandlerRateLimitOpts<ReqCtx>[]; 218 + handler: Handler; 219 + }; 220 + type ExtractAuth<AV extends AuthVerifier | StreamAuthVerifier> = Extract< 221 + Awaited<ReturnType<AV>>, 222 + { credentials: unknown } 223 + >;
+489
apps/api/src/lexicon/lexicons.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type LexiconDoc, Lexicons } from "@atproto/lexicon"; 5 + 6 + export const schemaDict = { 7 + AppBskyActorProfile: { 8 + lexicon: 1, 9 + id: "app.bsky.actor.profile", 10 + defs: { 11 + main: { 12 + type: "record", 13 + description: "A declaration of a Bluesky account profile.", 14 + key: "literal:self", 15 + record: { 16 + type: "object", 17 + properties: { 18 + displayName: { 19 + type: "string", 20 + maxGraphemes: 64, 21 + maxLength: 640, 22 + }, 23 + description: { 24 + type: "string", 25 + description: "Free-form profile description text.", 26 + maxGraphemes: 256, 27 + maxLength: 2560, 28 + }, 29 + avatar: { 30 + type: "blob", 31 + description: 32 + "Small image to be displayed next to posts from account. AKA, 'profile picture'", 33 + accept: ["image/png", "image/jpeg"], 34 + maxSize: 1000000, 35 + }, 36 + banner: { 37 + type: "blob", 38 + description: 39 + "Larger horizontal image to display behind profile view.", 40 + accept: ["image/png", "image/jpeg"], 41 + maxSize: 10000000, 42 + }, 43 + labels: { 44 + type: "union", 45 + description: 46 + "Self-label values, specific to the Bluesky application, on the overall account.", 47 + refs: ["lex:com.atproto.label.defs#selfLabels"], 48 + }, 49 + joinedViaStarterPack: { 50 + type: "ref", 51 + ref: "lex:com.atproto.repo.strongRef", 52 + }, 53 + createdAt: { 54 + type: "string", 55 + format: "datetime", 56 + }, 57 + }, 58 + }, 59 + }, 60 + }, 61 + }, 62 + IoPocketenvSandboxClaimSandbox: { 63 + lexicon: 1, 64 + id: "io.pocketenv.sandbox.claimSandbox", 65 + defs: { 66 + main: { 67 + type: "procedure", 68 + description: "Claim a sandbox by id", 69 + parameters: { 70 + type: "params", 71 + required: ["id"], 72 + properties: { 73 + id: { 74 + type: "string", 75 + description: "The sandbox ID.", 76 + }, 77 + }, 78 + }, 79 + output: { 80 + encoding: "application/json", 81 + schema: { 82 + type: "ref", 83 + ref: "lex:io.pocketenv.sandbox.defs#sandboxViewBasic", 84 + }, 85 + }, 86 + }, 87 + }, 88 + }, 89 + IoPocketenvSandboxCreateSandbox: { 90 + lexicon: 1, 91 + id: "io.pocketenv.sandbox.createSandbox", 92 + defs: { 93 + main: { 94 + type: "procedure", 95 + description: "Create a sandbox", 96 + input: { 97 + encoding: "application/json", 98 + schema: { 99 + type: "object", 100 + required: ["base"], 101 + properties: { 102 + base: { 103 + type: "string", 104 + description: 105 + "The base sandbox URI to clone from, e.g. a template or an existing sandbox.", 106 + format: "at-uri", 107 + }, 108 + name: { 109 + type: "string", 110 + description: "The name of the sandbox", 111 + minLength: 1, 112 + }, 113 + description: { 114 + type: "string", 115 + description: "A description for the sandbox", 116 + }, 117 + topics: { 118 + type: "array", 119 + description: 120 + "A list of topics/tags to associate with the sandbox", 121 + items: { 122 + type: "string", 123 + maxLength: 50, 124 + }, 125 + }, 126 + repo: { 127 + type: "string", 128 + description: 129 + "A git repository URL to clone into the sandbox, e.g. a GitHub/Tangled repo.", 130 + format: "uri", 131 + }, 132 + vcpus: { 133 + type: "integer", 134 + description: 135 + "The number of virtual CPUs to allocate for the sandbox", 136 + minimum: 1, 137 + }, 138 + memory: { 139 + type: "integer", 140 + description: 141 + "The amount of memory (in GB) to allocate for the sandbox", 142 + minimum: 1, 143 + }, 144 + disk: { 145 + type: "integer", 146 + description: 147 + "The amount of disk space (in GB) to allocate for the sandbox", 148 + minimum: 3, 149 + }, 150 + readme: { 151 + type: "string", 152 + description: "A URI to a README for the sandbox.", 153 + format: "uri", 154 + }, 155 + secrets: { 156 + type: "ref", 157 + description: "A list of secrets to add to the sandbox", 158 + ref: "lex:io.pocketenv.sandbox.defs#secrets", 159 + }, 160 + envs: { 161 + type: "ref", 162 + description: 163 + "A list of environment variables to add to the sandbox", 164 + ref: "lex:io.pocketenv.sandbox.defs#envs", 165 + }, 166 + }, 167 + }, 168 + }, 169 + output: { 170 + encoding: "application/json", 171 + schema: { 172 + type: "ref", 173 + ref: "lex:io.pocketenv.sandbox.defs#sandboxViewBasic", 174 + }, 175 + }, 176 + }, 177 + }, 178 + }, 179 + IoPocketenvSandboxDeleteSandbox: { 180 + lexicon: 1, 181 + id: "io.pocketenv.sandbox.deleteSandbox", 182 + defs: { 183 + main: { 184 + type: "procedure", 185 + description: "Delete a sandbox by uri", 186 + parameters: { 187 + type: "params", 188 + required: ["uri"], 189 + properties: { 190 + uri: { 191 + type: "string", 192 + description: "The sandbox URI.", 193 + format: "at-uri", 194 + }, 195 + }, 196 + }, 197 + output: { 198 + encoding: "application/json", 199 + schema: { 200 + type: "ref", 201 + ref: "lex:io.pocketenv.sandbox.defs#sandboxViewBasic", 202 + }, 203 + }, 204 + }, 205 + }, 206 + }, 207 + IoPocketenvSandboxGetSandbox: { 208 + lexicon: 1, 209 + id: "io.pocketenv.sandbox.getSandbox", 210 + defs: { 211 + main: { 212 + type: "query", 213 + description: "Get a sandbox by uri", 214 + parameters: { 215 + type: "params", 216 + required: ["uri"], 217 + properties: { 218 + uri: { 219 + type: "string", 220 + description: "The sandbox URI.", 221 + format: "at-uri", 222 + }, 223 + }, 224 + }, 225 + output: { 226 + encoding: "application/json", 227 + schema: { 228 + type: "ref", 229 + ref: "lex:io.pocketenv.sandbox.defs#sandboxViewBasic", 230 + }, 231 + }, 232 + }, 233 + }, 234 + }, 235 + IoPocketenvSandboxGetSandboxes: { 236 + lexicon: 1, 237 + id: "io.pocketenv.sandbox.getSandboxes", 238 + defs: { 239 + main: { 240 + type: "query", 241 + description: "Get all sandboxes", 242 + parameters: { 243 + type: "params", 244 + properties: { 245 + limit: { 246 + type: "integer", 247 + description: "The maximum number of sandboxes to return.", 248 + minimum: 1, 249 + }, 250 + offset: { 251 + type: "integer", 252 + description: 253 + "The number of sandboxes to skip before starting to collect the result set.", 254 + minimum: 0, 255 + }, 256 + }, 257 + }, 258 + output: { 259 + encoding: "application/json", 260 + schema: { 261 + type: "object", 262 + properties: { 263 + sandboxes: { 264 + type: "array", 265 + items: { 266 + type: "ref", 267 + ref: "lex:io.pocketenv.sandbox.defs#sandboxViewBasic", 268 + }, 269 + }, 270 + total: { 271 + type: "integer", 272 + description: "The total number of sandboxes available.", 273 + minimum: 0, 274 + }, 275 + }, 276 + }, 277 + }, 278 + }, 279 + }, 280 + }, 281 + IoPocketenvSandbox: { 282 + lexicon: 1, 283 + id: "io.pocketenv.sandbox", 284 + defs: { 285 + main: { 286 + type: "record", 287 + key: "tid", 288 + record: { 289 + type: "object", 290 + required: ["name", "createdAt"], 291 + properties: { 292 + name: { 293 + type: "string", 294 + description: "Name of the sandbox", 295 + maxLength: 255, 296 + }, 297 + base: { 298 + type: "ref", 299 + description: 300 + "A strong reference to the base template for the sandbox environment.", 301 + ref: "lex:com.atproto.repo.strongRef", 302 + }, 303 + provider: { 304 + type: "string", 305 + description: 306 + "The provider of the sandbox, e.g. 'daytona', 'vercel', 'cloudflare', etc.", 307 + maxLength: 50, 308 + }, 309 + description: { 310 + type: "string", 311 + maxGraphemes: 300, 312 + maxLength: 3000, 313 + }, 314 + website: { 315 + type: "string", 316 + description: "Any URI related to the sandbox", 317 + format: "uri", 318 + }, 319 + logo: { 320 + type: "string", 321 + description: "URI to an image logo for the sandbox", 322 + format: "uri", 323 + }, 324 + topics: { 325 + type: "array", 326 + items: { 327 + type: "string", 328 + minLength: 1, 329 + maxLength: 50, 330 + }, 331 + maxLength: 50, 332 + }, 333 + repo: { 334 + type: "string", 335 + description: 336 + "A git repository URL to clone into the sandbox, e.g. a GitHub/Tangled repo.", 337 + format: "uri", 338 + }, 339 + readme: { 340 + type: "string", 341 + description: "A URI to a README for the sandbox.", 342 + format: "uri", 343 + }, 344 + vcpus: { 345 + type: "integer", 346 + description: "Number of virtual CPUs allocated to the sandbox", 347 + }, 348 + memory: { 349 + type: "integer", 350 + description: "Amount of memory in GB allocated to the sandbox", 351 + }, 352 + disk: { 353 + type: "integer", 354 + description: 355 + "Amount of disk space in GB allocated to the sandbox", 356 + }, 357 + createdAt: { 358 + type: "string", 359 + format: "datetime", 360 + }, 361 + }, 362 + }, 363 + }, 364 + }, 365 + }, 366 + IoPocketenvSandboxStartSandbox: { 367 + lexicon: 1, 368 + id: "io.pocketenv.sandbox.startSandbox", 369 + defs: { 370 + main: { 371 + type: "procedure", 372 + description: "Start a sandbox", 373 + parameters: { 374 + type: "params", 375 + required: ["uri"], 376 + properties: { 377 + uri: { 378 + type: "string", 379 + description: "The sandbox URI.", 380 + format: "at-uri", 381 + }, 382 + }, 383 + }, 384 + output: { 385 + encoding: "application/json", 386 + schema: { 387 + type: "ref", 388 + ref: "lex:io.pocketenv.sandbox.defs#sandboxViewBasic", 389 + }, 390 + }, 391 + }, 392 + }, 393 + }, 394 + IoPocketenvSandboxStopSandbox: { 395 + lexicon: 1, 396 + id: "io.pocketenv.sandbox.stopSandbox", 397 + defs: { 398 + main: { 399 + type: "procedure", 400 + description: "Stop a sandbox", 401 + parameters: { 402 + type: "params", 403 + required: ["uri"], 404 + properties: { 405 + uri: { 406 + type: "string", 407 + description: "The sandbox URI.", 408 + format: "at-uri", 409 + }, 410 + }, 411 + }, 412 + output: { 413 + encoding: "application/json", 414 + schema: { 415 + type: "ref", 416 + ref: "lex:io.pocketenv.sandbox.defs#sandboxViewBasic", 417 + }, 418 + }, 419 + }, 420 + }, 421 + }, 422 + IoPocketenvPublicKey: { 423 + lexicon: 1, 424 + id: "io.pocketenv.publicKey", 425 + defs: { 426 + main: { 427 + type: "record", 428 + key: "tid", 429 + record: { 430 + type: "object", 431 + required: ["name", "key", "createdAt"], 432 + properties: { 433 + name: { 434 + type: "string", 435 + description: "Name of the public key", 436 + maxLength: 255, 437 + }, 438 + key: { 439 + type: "string", 440 + description: 441 + "The public key value, e.g. an SSH public key string.", 442 + }, 443 + createdAt: { 444 + type: "string", 445 + format: "datetime", 446 + }, 447 + }, 448 + }, 449 + }, 450 + }, 451 + }, 452 + ComAtprotoRepoStrongRef: { 453 + lexicon: 1, 454 + id: "com.atproto.repo.strongRef", 455 + description: "A URI with a content-hash fingerprint.", 456 + defs: { 457 + main: { 458 + type: "object", 459 + required: ["uri", "cid"], 460 + properties: { 461 + uri: { 462 + type: "string", 463 + format: "at-uri", 464 + }, 465 + cid: { 466 + type: "string", 467 + format: "cid", 468 + }, 469 + }, 470 + }, 471 + }, 472 + }, 473 + } as const satisfies Record<string, LexiconDoc>; 474 + 475 + export const schemas = Object.values(schemaDict); 476 + export const lexicons: Lexicons = new Lexicons(schemas); 477 + export const ids = { 478 + AppBskyActorProfile: "app.bsky.actor.profile", 479 + IoPocketenvSandboxClaimSandbox: "io.pocketenv.sandbox.claimSandbox", 480 + IoPocketenvSandboxCreateSandbox: "io.pocketenv.sandbox.createSandbox", 481 + IoPocketenvSandboxDeleteSandbox: "io.pocketenv.sandbox.deleteSandbox", 482 + IoPocketenvSandboxGetSandbox: "io.pocketenv.sandbox.getSandbox", 483 + IoPocketenvSandboxGetSandboxes: "io.pocketenv.sandbox.getSandboxes", 484 + IoPocketenvSandbox: "io.pocketenv.sandbox", 485 + IoPocketenvSandboxStartSandbox: "io.pocketenv.sandbox.startSandbox", 486 + IoPocketenvSandboxStopSandbox: "io.pocketenv.sandbox.stopSandbox", 487 + IoPocketenvPublicKey: "io.pocketenv.publicKey", 488 + ComAtprotoRepoStrongRef: "com.atproto.repo.strongRef", 489 + };
+38
apps/api/src/lexicon/types/app/bsky/actor/profile.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import type { ValidationResult, BlobRef } from "@atproto/lexicon"; 5 + import { lexicons } from "../../../../lexicons"; 6 + import { isObj, hasProp } from "../../../../util"; 7 + import { CID } from "multiformats/cid"; 8 + import type * as ComAtprotoLabelDefs from "../../../com/atproto/label/defs"; 9 + import type * as ComAtprotoRepoStrongRef from "../../../com/atproto/repo/strongRef"; 10 + 11 + export interface Record { 12 + displayName?: string; 13 + /** Free-form profile description text. */ 14 + description?: string; 15 + /** Small image to be displayed next to posts from account. AKA, 'profile picture' */ 16 + avatar?: BlobRef; 17 + /** Larger horizontal image to display behind profile view. */ 18 + banner?: BlobRef; 19 + labels?: 20 + | ComAtprotoLabelDefs.SelfLabels 21 + | { $type: string; [k: string]: unknown }; 22 + joinedViaStarterPack?: ComAtprotoRepoStrongRef.Main; 23 + createdAt?: string; 24 + [k: string]: unknown; 25 + } 26 + 27 + export function isRecord(v: unknown): v is Record { 28 + return ( 29 + isObj(v) && 30 + hasProp(v, "$type") && 31 + (v.$type === "app.bsky.actor.profile#main" || 32 + v.$type === "app.bsky.actor.profile") 33 + ); 34 + } 35 + 36 + export function validateRecord(v: unknown): ValidationResult { 37 + return lexicons.validate("app.bsky.actor.profile#main", v); 38 + }
+26
apps/api/src/lexicon/types/com/atproto/repo/strongRef.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type ValidationResult, BlobRef } from "@atproto/lexicon"; 5 + import { lexicons } from "../../../../lexicons"; 6 + import { isObj, hasProp } from "../../../../util"; 7 + import { CID } from "multiformats/cid"; 8 + 9 + export interface Main { 10 + uri: string; 11 + cid: string; 12 + [k: string]: unknown; 13 + } 14 + 15 + export function isMain(v: unknown): v is Main { 16 + return ( 17 + isObj(v) && 18 + hasProp(v, "$type") && 19 + (v.$type === "com.atproto.repo.strongRef#main" || 20 + v.$type === "com.atproto.repo.strongRef") 21 + ); 22 + } 23 + 24 + export function validateMain(v: unknown): ValidationResult { 25 + return lexicons.validate("com.atproto.repo.strongRef#main", v); 26 + }
+29
apps/api/src/lexicon/types/io/pocketenv/publicKey.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type ValidationResult, BlobRef } from "@atproto/lexicon"; 5 + import { lexicons } from "../../../lexicons"; 6 + import { isObj, hasProp } from "../../../util"; 7 + import { CID } from "multiformats/cid"; 8 + 9 + export interface Record { 10 + /** Name of the public key */ 11 + name: string; 12 + /** The public key value, e.g. an SSH public key string. */ 13 + key: string; 14 + createdAt: string; 15 + [k: string]: unknown; 16 + } 17 + 18 + export function isRecord(v: unknown): v is Record { 19 + return ( 20 + isObj(v) && 21 + hasProp(v, "$type") && 22 + (v.$type === "io.pocketenv.publicKey#main" || 23 + v.$type === "io.pocketenv.publicKey") 24 + ); 25 + } 26 + 27 + export function validateRecord(v: unknown): ValidationResult { 28 + return lexicons.validate("io.pocketenv.publicKey#main", v); 29 + }
+47
apps/api/src/lexicon/types/io/pocketenv/sandbox.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type ValidationResult, BlobRef } from "@atproto/lexicon"; 5 + import { lexicons } from "../../../lexicons"; 6 + import { isObj, hasProp } from "../../../util"; 7 + import { CID } from "multiformats/cid"; 8 + import type * as ComAtprotoRepoStrongRef from "../../com/atproto/repo/strongRef"; 9 + 10 + export interface Record { 11 + /** Name of the sandbox */ 12 + name: string; 13 + base?: ComAtprotoRepoStrongRef.Main; 14 + /** The provider of the sandbox, e.g. 'daytona', 'vercel', 'cloudflare', etc. */ 15 + provider?: string; 16 + description?: string; 17 + /** Any URI related to the sandbox */ 18 + website?: string; 19 + /** URI to an image logo for the sandbox */ 20 + logo?: string; 21 + topics?: string[]; 22 + /** A git repository URL to clone into the sandbox, e.g. a GitHub/Tangled repo. */ 23 + repo?: string; 24 + /** A URI to a README for the sandbox. */ 25 + readme?: string; 26 + /** Number of virtual CPUs allocated to the sandbox */ 27 + vcpus?: number; 28 + /** Amount of memory in GB allocated to the sandbox */ 29 + memory?: number; 30 + /** Amount of disk space in GB allocated to the sandbox */ 31 + disk?: number; 32 + createdAt: string; 33 + [k: string]: unknown; 34 + } 35 + 36 + export function isRecord(v: unknown): v is Record { 37 + return ( 38 + isObj(v) && 39 + hasProp(v, "$type") && 40 + (v.$type === "io.pocketenv.sandbox#main" || 41 + v.$type === "io.pocketenv.sandbox") 42 + ); 43 + } 44 + 45 + export function validateRecord(v: unknown): ValidationResult { 46 + return lexicons.validate("io.pocketenv.sandbox#main", v); 47 + }
+43
apps/api/src/lexicon/types/io/pocketenv/sandbox/claimSandbox.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import type express from "express"; 5 + import { ValidationResult, BlobRef } from "@atproto/lexicon"; 6 + import { lexicons } from "../../../../lexicons"; 7 + import { isObj, hasProp } from "../../../../util"; 8 + import { CID } from "multiformats/cid"; 9 + import type { HandlerAuth, HandlerPipeThrough } from "@atproto/xrpc-server"; 10 + import type * as IoPocketenvSandboxDefs from "./defs"; 11 + 12 + export interface QueryParams { 13 + /** The sandbox ID. */ 14 + id: string; 15 + } 16 + 17 + export type InputSchema = undefined; 18 + export type OutputSchema = IoPocketenvSandboxDefs.SandboxViewBasic; 19 + export type HandlerInput = undefined; 20 + 21 + export interface HandlerSuccess { 22 + encoding: "application/json"; 23 + body: OutputSchema; 24 + headers?: { [key: string]: string }; 25 + } 26 + 27 + export interface HandlerError { 28 + status: number; 29 + message?: string; 30 + } 31 + 32 + export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough; 33 + export type HandlerReqCtx<HA extends HandlerAuth = never> = { 34 + auth: HA; 35 + params: QueryParams; 36 + input: HandlerInput; 37 + req: express.Request; 38 + res: express.Response; 39 + resetRouteRateLimits: () => Promise<void>; 40 + }; 41 + export type Handler<HA extends HandlerAuth = never> = ( 42 + ctx: HandlerReqCtx<HA>, 43 + ) => Promise<HandlerOutput> | HandlerOutput;
+67
apps/api/src/lexicon/types/io/pocketenv/sandbox/createSandbox.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import type express from "express"; 5 + import { ValidationResult, BlobRef } from "@atproto/lexicon"; 6 + import { lexicons } from "../../../../lexicons"; 7 + import { isObj, hasProp } from "../../../../util"; 8 + import { CID } from "multiformats/cid"; 9 + import type { HandlerAuth, HandlerPipeThrough } from "@atproto/xrpc-server"; 10 + import type * as IoPocketenvSandboxDefs from "./defs"; 11 + 12 + export type QueryParams = {}; 13 + 14 + export interface InputSchema { 15 + /** The base sandbox URI to clone from, e.g. a template or an existing sandbox. */ 16 + base: string; 17 + /** The name of the sandbox */ 18 + name?: string; 19 + /** A description for the sandbox */ 20 + description?: string; 21 + /** A list of topics/tags to associate with the sandbox */ 22 + topics?: string[]; 23 + /** A git repository URL to clone into the sandbox, e.g. a GitHub/Tangled repo. */ 24 + repo?: string; 25 + /** The number of virtual CPUs to allocate for the sandbox */ 26 + vcpus?: number; 27 + /** The amount of memory (in GB) to allocate for the sandbox */ 28 + memory?: number; 29 + /** The amount of disk space (in GB) to allocate for the sandbox */ 30 + disk?: number; 31 + /** A URI to a README for the sandbox. */ 32 + readme?: string; 33 + secrets?: IoPocketenvSandboxDefs.Secrets; 34 + envs?: IoPocketenvSandboxDefs.Envs; 35 + [k: string]: unknown; 36 + } 37 + 38 + export type OutputSchema = IoPocketenvSandboxDefs.SandboxViewBasic; 39 + 40 + export interface HandlerInput { 41 + encoding: "application/json"; 42 + body: InputSchema; 43 + } 44 + 45 + export interface HandlerSuccess { 46 + encoding: "application/json"; 47 + body: OutputSchema; 48 + headers?: { [key: string]: string }; 49 + } 50 + 51 + export interface HandlerError { 52 + status: number; 53 + message?: string; 54 + } 55 + 56 + export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough; 57 + export type HandlerReqCtx<HA extends HandlerAuth = never> = { 58 + auth: HA; 59 + params: QueryParams; 60 + input: HandlerInput; 61 + req: express.Request; 62 + res: express.Response; 63 + resetRouteRateLimits: () => Promise<void>; 64 + }; 65 + export type Handler<HA extends HandlerAuth = never> = ( 66 + ctx: HandlerReqCtx<HA>, 67 + ) => Promise<HandlerOutput> | HandlerOutput;
+43
apps/api/src/lexicon/types/io/pocketenv/sandbox/deleteSandbox.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import type express from "express"; 5 + import { ValidationResult, BlobRef } from "@atproto/lexicon"; 6 + import { lexicons } from "../../../../lexicons"; 7 + import { isObj, hasProp } from "../../../../util"; 8 + import { CID } from "multiformats/cid"; 9 + import type { HandlerAuth, HandlerPipeThrough } from "@atproto/xrpc-server"; 10 + import type * as IoPocketenvSandboxDefs from "./defs"; 11 + 12 + export interface QueryParams { 13 + /** The sandbox URI. */ 14 + uri: string; 15 + } 16 + 17 + export type InputSchema = undefined; 18 + export type OutputSchema = IoPocketenvSandboxDefs.SandboxViewBasic; 19 + export type HandlerInput = undefined; 20 + 21 + export interface HandlerSuccess { 22 + encoding: "application/json"; 23 + body: OutputSchema; 24 + headers?: { [key: string]: string }; 25 + } 26 + 27 + export interface HandlerError { 28 + status: number; 29 + message?: string; 30 + } 31 + 32 + export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough; 33 + export type HandlerReqCtx<HA extends HandlerAuth = never> = { 34 + auth: HA; 35 + params: QueryParams; 36 + input: HandlerInput; 37 + req: express.Request; 38 + res: express.Response; 39 + resetRouteRateLimits: () => Promise<void>; 40 + }; 41 + export type Handler<HA extends HandlerAuth = never> = ( 42 + ctx: HandlerReqCtx<HA>, 43 + ) => Promise<HandlerOutput> | HandlerOutput;
+43
apps/api/src/lexicon/types/io/pocketenv/sandbox/getSandbox.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import type express from "express"; 5 + import { ValidationResult, BlobRef } from "@atproto/lexicon"; 6 + import { lexicons } from "../../../../lexicons"; 7 + import { isObj, hasProp } from "../../../../util"; 8 + import { CID } from "multiformats/cid"; 9 + import type { HandlerAuth, HandlerPipeThrough } from "@atproto/xrpc-server"; 10 + import type * as IoPocketenvSandboxDefs from "./defs"; 11 + 12 + export interface QueryParams { 13 + /** The sandbox URI. */ 14 + uri: string; 15 + } 16 + 17 + export type InputSchema = undefined; 18 + export type OutputSchema = IoPocketenvSandboxDefs.SandboxViewBasic; 19 + export type HandlerInput = undefined; 20 + 21 + export interface HandlerSuccess { 22 + encoding: "application/json"; 23 + body: OutputSchema; 24 + headers?: { [key: string]: string }; 25 + } 26 + 27 + export interface HandlerError { 28 + status: number; 29 + message?: string; 30 + } 31 + 32 + export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough; 33 + export type HandlerReqCtx<HA extends HandlerAuth = never> = { 34 + auth: HA; 35 + params: QueryParams; 36 + input: HandlerInput; 37 + req: express.Request; 38 + res: express.Response; 39 + resetRouteRateLimits: () => Promise<void>; 40 + }; 41 + export type Handler<HA extends HandlerAuth = never> = ( 42 + ctx: HandlerReqCtx<HA>, 43 + ) => Promise<HandlerOutput> | HandlerOutput;
+52
apps/api/src/lexicon/types/io/pocketenv/sandbox/getSandboxes.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import type express from "express"; 5 + import { ValidationResult, BlobRef } from "@atproto/lexicon"; 6 + import { lexicons } from "../../../../lexicons"; 7 + import { isObj, hasProp } from "../../../../util"; 8 + import { CID } from "multiformats/cid"; 9 + import type { HandlerAuth, HandlerPipeThrough } from "@atproto/xrpc-server"; 10 + import type * as IoPocketenvSandboxDefs from "./defs"; 11 + 12 + export interface QueryParams { 13 + /** The maximum number of sandboxes to return. */ 14 + limit?: number; 15 + /** The number of sandboxes to skip before starting to collect the result set. */ 16 + offset?: number; 17 + } 18 + 19 + export type InputSchema = undefined; 20 + 21 + export interface OutputSchema { 22 + sandboxes?: IoPocketenvSandboxDefs.SandboxViewBasic[]; 23 + /** The total number of sandboxes available. */ 24 + total?: number; 25 + [k: string]: unknown; 26 + } 27 + 28 + export type HandlerInput = undefined; 29 + 30 + export interface HandlerSuccess { 31 + encoding: "application/json"; 32 + body: OutputSchema; 33 + headers?: { [key: string]: string }; 34 + } 35 + 36 + export interface HandlerError { 37 + status: number; 38 + message?: string; 39 + } 40 + 41 + export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough; 42 + export type HandlerReqCtx<HA extends HandlerAuth = never> = { 43 + auth: HA; 44 + params: QueryParams; 45 + input: HandlerInput; 46 + req: express.Request; 47 + res: express.Response; 48 + resetRouteRateLimits: () => Promise<void>; 49 + }; 50 + export type Handler<HA extends HandlerAuth = never> = ( 51 + ctx: HandlerReqCtx<HA>, 52 + ) => Promise<HandlerOutput> | HandlerOutput;
+43
apps/api/src/lexicon/types/io/pocketenv/sandbox/startSandbox.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import type express from "express"; 5 + import { ValidationResult, BlobRef } from "@atproto/lexicon"; 6 + import { lexicons } from "../../../../lexicons"; 7 + import { isObj, hasProp } from "../../../../util"; 8 + import { CID } from "multiformats/cid"; 9 + import type { HandlerAuth, HandlerPipeThrough } from "@atproto/xrpc-server"; 10 + import type * as IoPocketenvSandboxDefs from "./defs"; 11 + 12 + export interface QueryParams { 13 + /** The sandbox URI. */ 14 + uri: string; 15 + } 16 + 17 + export type InputSchema = undefined; 18 + export type OutputSchema = IoPocketenvSandboxDefs.SandboxViewBasic; 19 + export type HandlerInput = undefined; 20 + 21 + export interface HandlerSuccess { 22 + encoding: "application/json"; 23 + body: OutputSchema; 24 + headers?: { [key: string]: string }; 25 + } 26 + 27 + export interface HandlerError { 28 + status: number; 29 + message?: string; 30 + } 31 + 32 + export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough; 33 + export type HandlerReqCtx<HA extends HandlerAuth = never> = { 34 + auth: HA; 35 + params: QueryParams; 36 + input: HandlerInput; 37 + req: express.Request; 38 + res: express.Response; 39 + resetRouteRateLimits: () => Promise<void>; 40 + }; 41 + export type Handler<HA extends HandlerAuth = never> = ( 42 + ctx: HandlerReqCtx<HA>, 43 + ) => Promise<HandlerOutput> | HandlerOutput;
+43
apps/api/src/lexicon/types/io/pocketenv/sandbox/stopSandbox.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import type express from "express"; 5 + import { ValidationResult, BlobRef } from "@atproto/lexicon"; 6 + import { lexicons } from "../../../../lexicons"; 7 + import { isObj, hasProp } from "../../../../util"; 8 + import { CID } from "multiformats/cid"; 9 + import type { HandlerAuth, HandlerPipeThrough } from "@atproto/xrpc-server"; 10 + import type * as IoPocketenvSandboxDefs from "./defs"; 11 + 12 + export interface QueryParams { 13 + /** The sandbox URI. */ 14 + uri: string; 15 + } 16 + 17 + export type InputSchema = undefined; 18 + export type OutputSchema = IoPocketenvSandboxDefs.SandboxViewBasic; 19 + export type HandlerInput = undefined; 20 + 21 + export interface HandlerSuccess { 22 + encoding: "application/json"; 23 + body: OutputSchema; 24 + headers?: { [key: string]: string }; 25 + } 26 + 27 + export interface HandlerError { 28 + status: number; 29 + message?: string; 30 + } 31 + 32 + export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough; 33 + export type HandlerReqCtx<HA extends HandlerAuth = never> = { 34 + auth: HA; 35 + params: QueryParams; 36 + input: HandlerInput; 37 + req: express.Request; 38 + res: express.Response; 39 + resetRouteRateLimits: () => Promise<void>; 40 + }; 41 + export type Handler<HA extends HandlerAuth = never> = ( 42 + ctx: HandlerReqCtx<HA>, 43 + ) => Promise<HandlerOutput> | HandlerOutput;
+13
apps/api/src/lexicon/util.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + export function isObj(v: unknown): v is Record<string, unknown> { 5 + return typeof v === "object" && v !== null; 6 + } 7 + 8 + export function hasProp<K extends PropertyKey>( 9 + data: object, 10 + prop: K, 11 + ): data is Record<K, unknown> { 12 + return prop in data; 13 + }
+60
apps/api/src/lib/agent.ts
··· 1 + import { Agent, AtpAgent } from "@atproto/api"; 2 + import type { NodeOAuthClient } from "@atproto/oauth-client-node"; 3 + import { consola } from "consola"; 4 + import extractPdsFromDid from "./extractPdsFromDid"; 5 + import { ctx } from "context"; 6 + 7 + export async function createAgent( 8 + oauthClient: NodeOAuthClient, 9 + did: string, 10 + ): Promise<Agent | null> { 11 + let agent: Agent | null = null; 12 + let retry = 0; 13 + do { 14 + try { 15 + const result = await ctx.sqliteDb 16 + .selectFrom("auth_session") 17 + .selectAll() 18 + .where("key", "=", `atp:${did}`) 19 + .executeTakeFirst(); 20 + if (result) { 21 + let pds = await ctx.redis.get(`pds:${did}`); 22 + if (!pds) { 23 + pds = await extractPdsFromDid(did); 24 + await ctx.redis.setEx(`pds:${did}`, 60 * 15, pds); 25 + } 26 + const atpAgent = new AtpAgent({ 27 + service: new URL(pds), 28 + }); 29 + 30 + try { 31 + await atpAgent.resumeSession(JSON.parse(result.session)); 32 + } catch (e) { 33 + consola.info("Error resuming session"); 34 + consola.info(did); 35 + consola.info(e); 36 + await ctx.sqliteDb 37 + .deleteFrom("auth_session") 38 + .where("key", "=", `atp:${did}`) 39 + .execute(); 40 + } 41 + 42 + return atpAgent; 43 + } 44 + const oauthSession = await oauthClient.restore(did); 45 + agent = oauthSession ? new Agent(oauthSession) : null; 46 + if (agent === null) { 47 + await new Promise((r) => setTimeout(r, 1000)); 48 + retry += 1; 49 + } 50 + } catch (e) { 51 + consola.info("Error creating agent"); 52 + consola.info(did); 53 + consola.info(e); 54 + await new Promise((r) => setTimeout(r, 1000)); 55 + retry += 1; 56 + } 57 + } while (agent === null && retry < 5); 58 + 59 + return agent; 60 + }
+28
apps/api/src/lib/authVerfifier.ts
··· 1 + import type { AuthOutput } from "@atproto/xrpc-server"; 2 + import type express from "express"; 3 + import jwt from "jsonwebtoken"; 4 + import { env } from "./env"; 5 + 6 + type ReqCtx = { 7 + req: express.Request; 8 + }; 9 + 10 + export default function authVerifier(ctx: ReqCtx): AuthOutput { 11 + if (!ctx.req.headers.authorization) { 12 + return {}; 13 + } 14 + 15 + const bearer = (ctx.req.headers.authorization || "").split(" ")[1]?.trim(); 16 + 17 + if (bearer && bearer !== "null") { 18 + const credentials = jwt.verify(bearer, env.JWT_SECRET, { 19 + ignoreExpiration: true, 20 + }); 21 + 22 + return { 23 + credentials, 24 + }; 25 + } 26 + 27 + return {}; 28 + }
+72
apps/api/src/lib/didUnstorageCache.ts
··· 1 + import type { CacheResult, DidCache, DidDocument } from "@atproto/identity"; 2 + import type { Storage } from "unstorage"; 3 + 4 + const HOUR = 60e3 * 60; 5 + const DAY = HOUR * 24; 6 + 7 + type CacheVal = { 8 + doc: DidDocument; 9 + updatedAt: number; 10 + }; 11 + 12 + /** 13 + * An unstorage based DidCache with staleness and max TTL 14 + */ 15 + export class StorageCache implements DidCache { 16 + public staleTTL: number; 17 + public maxTTL: number; 18 + public cache: Storage<CacheVal>; 19 + private prefix: string; 20 + constructor({ 21 + store, 22 + prefix, 23 + staleTTL, 24 + maxTTL, 25 + }: { 26 + store: Storage; 27 + prefix: string; 28 + staleTTL?: number; 29 + maxTTL?: number; 30 + }) { 31 + this.cache = store as Storage<CacheVal>; 32 + this.prefix = prefix; 33 + this.staleTTL = staleTTL ?? HOUR; 34 + this.maxTTL = maxTTL ?? DAY; 35 + } 36 + 37 + async cacheDid(did: string, doc: DidDocument): Promise<void> { 38 + await this.cache.set(this.prefix + did, { doc, updatedAt: Date.now() }); 39 + } 40 + 41 + async refreshCache( 42 + did: string, 43 + getDoc: () => Promise<DidDocument | null>, 44 + ): Promise<void> { 45 + const doc = await getDoc(); 46 + if (doc) { 47 + await this.cacheDid(did, doc); 48 + } 49 + } 50 + 51 + async checkCache(did: string): Promise<CacheResult | null> { 52 + const val = await this.cache.get<CacheVal>(this.prefix + did); 53 + if (!val) return null; 54 + const now = Date.now(); 55 + const expired = now > val.updatedAt + this.maxTTL; 56 + const stale = now > val.updatedAt + this.staleTTL; 57 + return { 58 + ...val, 59 + did, 60 + stale, 61 + expired, 62 + }; 63 + } 64 + 65 + async clearEntry(did: string): Promise<void> { 66 + await this.cache.remove(this.prefix + did); 67 + } 68 + 69 + async clear(): Promise<void> { 70 + await this.cache.clear(this.prefix); 71 + } 72 + }
+28
apps/api/src/lib/env.ts
··· 1 + import { PRIVATE_KEY_USAGE } from "@atproto/oauth-client-node"; 2 + import dotenv from "dotenv"; 3 + import { cleanEnv, host, port, str } from "envalid"; 4 + 5 + dotenv.config(); 6 + 7 + export const env = cleanEnv(process.env, { 8 + NODE_ENV: str({ 9 + default: "development", 10 + choices: ["development", "production", "test"], 11 + }), 12 + HOST: host({ default: "localhost" }), 13 + PORT: port({ default: 8789 }), 14 + PUBLIC_URL: str({ default: "http://localhost:8000" }), 15 + DB_PATH: str({ devDefault: ":memory:" }), 16 + KV_DB_PATH: str({ devDefault: ":memory:" }), 17 + COOKIE_SECRET: str({ devDefault: "00000000000000000000000000000000" }), 18 + FRONTEND_URL: str({ devDefault: "http://localhost:5173" }), 19 + JWT_SECRET: str({ devDefault: "00000000000000000000000000000000" }), 20 + POSTGRES_URL: str({}), 21 + REDIS_URL: str({ default: "redis://localhost:6379" }), 22 + POCKETENV_DID: str({}), 23 + PRIVATE_KEY_1: str({}), 24 + PRIVATE_KEY_2: str({}), 25 + PRIVATE_KEY_3: str({}), 26 + PUBLIC_KEY: str({}), 27 + PRIVATE_KEY: str({}), 28 + });
+33
apps/api/src/lib/extractPdsFromDid.ts
··· 1 + export default async function extractPdsFromDid( 2 + did: string, 3 + ): Promise<string | null> { 4 + let didDocUrl: string; 5 + 6 + if (did.startsWith("did:plc:")) { 7 + didDocUrl = `https://plc.directory/${did}`; 8 + } else if (did.startsWith("did:web:")) { 9 + const domain = did.substring("did:web:".length); 10 + didDocUrl = `https://${domain}/.well-known/did.json`; 11 + } else { 12 + throw new Error("Unsupported DID method"); 13 + } 14 + 15 + const response = await fetch(didDocUrl); 16 + if (!response.ok) throw new Error("Failed to fetch DID doc"); 17 + 18 + const doc = (await response.json()) as { 19 + service?: Array<{ 20 + type: string; 21 + id: string; 22 + serviceEndpoint: string; 23 + }>; 24 + }; 25 + 26 + // Find the atproto PDS service 27 + const pdsService = doc.service?.find( 28 + (s: any) => 29 + s.type === "AtprotoPersonalDataServer" && s.id.endsWith("#atproto_pds"), 30 + ); 31 + 32 + return pdsService?.serviceEndpoint ?? null; 33 + }
+52
apps/api/src/lib/idResolver.ts
··· 1 + import { IdResolver } from "@atproto/identity"; 2 + import type { Storage } from "unstorage"; 3 + import { StorageCache } from "./didUnstorageCache"; 4 + 5 + const HOUR = 60e3 * 60; 6 + const DAY = HOUR * 24; 7 + const WEEK = HOUR * 7; 8 + 9 + export function createIdResolver(kv: Storage) { 10 + return new IdResolver({ 11 + didCache: new StorageCache({ 12 + store: kv, 13 + prefix: "didCache:", 14 + staleTTL: DAY, 15 + maxTTL: WEEK, 16 + }), 17 + }); 18 + } 19 + 20 + export interface BidirectionalResolver { 21 + resolveDidToHandle(did: string): Promise<string>; 22 + resolveDidsToHandles(dids: string[]): Promise<Record<string, string>>; 23 + } 24 + 25 + export function createBidirectionalResolver(resolver: IdResolver) { 26 + return { 27 + async resolveDidToHandle(did: string): Promise<string> { 28 + const didDoc = await resolver.did.resolveAtprotoData(did); 29 + 30 + // asynchronously double check that the handle resolves back 31 + resolver.handle.resolve(didDoc.handle).then((resolvedHandle) => { 32 + if (resolvedHandle !== did) { 33 + resolver.did.ensureResolve(did, true); 34 + } 35 + }); 36 + return didDoc?.handle ?? did; 37 + }, 38 + 39 + async resolveDidsToHandles( 40 + dids: string[], 41 + ): Promise<Record<string, string>> { 42 + const didHandleMap: Record<string, string> = {}; 43 + const resolves = await Promise.all( 44 + dids.map((did) => this.resolveDidToHandle(did).catch((_) => did)), 45 + ); 46 + for (let i = 0; i < dids.length; i++) { 47 + didHandleMap[dids[i]] = resolves[i]; 48 + } 49 + return didHandleMap; 50 + }, 51 + }; 52 + }
+15
apps/api/src/schema/authorized-keys.ts
··· 1 + import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 + import { pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 + import sandboxes from "./sandboxes"; 4 + 5 + const authorizedKeys = pgTable("authorized_keys", { 6 + id: text("id").primaryKey().default(sql`xata_id()`), 7 + sandboxId: text("sandbox_id").references(() => sandboxes.id), 8 + publicKey: text("public_key").notNull(), 9 + createdAt: timestamp("created_at").defaultNow().notNull(), 10 + }); 11 + 12 + export type SelectAuthorizedKeys = InferSelectModel<typeof authorizedKeys>; 13 + export type InsertAuthorizedKeys = InferInsertModel<typeof authorizedKeys>; 14 + 15 + export default authorizedKeys;
+1 -1
apps/api/src/schema/index.ts
··· 8 8 import sandboxVariables from "./sandbox-variables"; 9 9 import sandboxVolumes from "./sandbox-volumes"; 10 10 11 - export { 11 + export default { 12 12 sandboxes, 13 13 secrets, 14 14 snapshots,
+1 -3
apps/api/src/schema/sandbox-secrets.ts
··· 6 6 const sandboxSecrets = pgTable( 7 7 "sandbox_secrets", 8 8 { 9 - id: text("id") 10 - .primaryKey() 11 - .default(sql`xata_id()`), 9 + id: text("id").primaryKey().default(sql`xata_id()`), 12 10 sandboxId: text("sandbox_id") 13 11 .notNull() 14 12 .references(() => sandboxes.id),
+1 -3
apps/api/src/schema/sandbox-variables.ts
··· 6 6 const sandboxVariables = pgTable( 7 7 "sandbox_variables", 8 8 { 9 - id: text("id") 10 - .primaryKey() 11 - .default(sql`xata_id()`), 9 + id: text("id").primaryKey().default(sql`xata_id()`), 12 10 sandboxId: text("sandbox_id") 13 11 .notNull() 14 12 .references(() => sandboxes.id),
+1 -3
apps/api/src/schema/sandbox-volumes.ts
··· 4 4 import volumes from "./volumes"; 5 5 6 6 const sandboxVolumes = pgTable("sandbox_volumes", { 7 - id: text("id") 8 - .primaryKey() 9 - .default(sql`xata_id()`), 7 + id: text("id").primaryKey().default(sql`xata_id()`), 10 8 sandboxId: text("sandbox_id") 11 9 .notNull() 12 10 .references(() => sandboxes.id),
+18 -6
apps/api/src/schema/sandboxes.ts
··· 1 1 import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 - import { pgTable, text, timestamp, boolean } from "drizzle-orm/pg-core"; 2 + import { 3 + pgTable, 4 + text, 5 + timestamp, 6 + boolean, 7 + integer, 8 + } from "drizzle-orm/pg-core"; 3 9 import users from "./users"; 4 10 5 11 const sandboxes = pgTable("sandboxes", { 6 12 id: text("id") 7 13 .primaryKey() 8 14 .default(sql`sandbox_id()`), 9 - base: text("base").notNull(), 15 + base: text("base"), 10 16 name: text("name").unique().notNull(), 17 + displayName: text("display_name"), 18 + uri: text("uri").unique(), 11 19 provider: text("provider").default("cloudflare").notNull(), 12 20 description: text("description"), 21 + logo: text("logo"), 13 22 publicKey: text("public_key").notNull(), 14 - userId: text("user_id") 15 - .notNull() 16 - .references(() => users.id), 17 - instanceType: text("instance_type").notNull(), 23 + readme: text("readme"), 24 + userId: text("user_id").references(() => users.id), 25 + instanceType: text("instance_type"), 26 + vcpus: integer("vcpus"), 27 + memory: integer("memory"), 28 + disk: integer("disk"), 18 29 status: text("status").notNull(), 19 30 keepAlive: boolean("keep_alive").default(false).notNull(), 20 31 sleepAfter: text("sleep_after"), 21 32 sandbox_id: text("sandbox_id"), 33 + installs: integer("installs").default(0).notNull(), 22 34 createdAt: timestamp("created_at").defaultNow().notNull(), 23 35 updatedAt: timestamp("updated_at").defaultNow().notNull(), 24 36 });
+2 -4
apps/api/src/schema/secrets.ts
··· 1 1 import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 - import { integer, pgTable, text, timestamp } from "drizzle-orm/pg-core"; 2 + import { pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 3 4 4 const secrets = pgTable("secrets", { 5 - id: text("id") 6 - .primaryKey() 7 - .default(sql`secret_id()`), 5 + id: text("id").primaryKey().default(sql`secret_id()`), 8 6 name: text("name").notNull(), 9 7 value: text("value").notNull(), 10 8 createdAt: timestamp("created_at").defaultNow().notNull(),
+1 -3
apps/api/src/schema/snapshots.ts
··· 2 2 import { integer, pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 3 4 4 const snapshots = pgTable("snapshots", { 5 - id: text("id") 6 - .primaryKey() 7 - .default(sql`snapshot_id()`), 5 + id: text("id").primaryKey().default(sql`snapshot_id()`), 8 6 slug: text("slug").unique().notNull(), 9 7 createdAt: timestamp("created_at").defaultNow().notNull(), 10 8 });
+1 -3
apps/api/src/schema/users.ts
··· 2 2 import { integer, pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 3 4 4 const users = pgTable("users", { 5 - id: text("id") 6 - .primaryKey() 7 - .default(sql`xata_id()`), 5 + id: text("id").primaryKey().default(sql`xata_id()`), 8 6 did: text("did").unique().notNull(), 9 7 displayName: text("display_name"), 10 8 handle: text("handle").unique().notNull(),
+1 -3
apps/api/src/schema/variables.ts
··· 2 2 import { integer, pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 3 4 4 const variables = pgTable("variables", { 5 - id: text("id") 6 - .primaryKey() 7 - .default(sql`variable_id()`), 5 + id: text("id").primaryKey().default(sql`variable_id()`), 8 6 name: text("name").notNull(), 9 7 value: text("value").notNull(), 10 8 createdAt: timestamp("created_at").defaultNow().notNull(),
+1 -3
apps/api/src/schema/volumes.ts
··· 2 2 import { integer, pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 3 4 4 const volumes = pgTable("volumes", { 5 - id: text("id") 6 - .primaryKey() 7 - .default(sql`volume_id()`), 5 + id: text("id").primaryKey().default(sql`volume_id()`), 8 6 slug: text("slug").unique().notNull(), 9 7 size: integer("size").notNull(), 10 8 sizeUnit: text("size_unit").notNull(),
+173
apps/api/src/sqliteKv.ts
··· 1 + import Database from "better-sqlite3"; 2 + import { Kysely, SqliteDialect } from "kysely"; 3 + import { defineDriver } from "unstorage"; 4 + 5 + interface TableSchema { 6 + [k: string]: { 7 + id: string; 8 + value: string; 9 + created_at: string; 10 + updated_at: string; 11 + }; 12 + } 13 + 14 + export type KvDb = Kysely<TableSchema>; 15 + 16 + const DRIVER_NAME = "sqlite"; 17 + 18 + export default defineDriver< 19 + { 20 + location?: string; 21 + table: string; 22 + getDb?: () => KvDb; 23 + }, 24 + KvDb 25 + >( 26 + ({ 27 + location, 28 + table = "kv", 29 + getDb = (): KvDb => { 30 + let _db: KvDb | null = null; 31 + 32 + return (() => { 33 + if (_db) { 34 + return _db; 35 + } 36 + 37 + if (!location) { 38 + throw new Error("SQLite location is required"); 39 + } 40 + 41 + const sqlite = new Database(location, { fileMustExist: false }); 42 + 43 + // Enable WAL mode 44 + sqlite.pragma("journal_mode = WAL"); 45 + 46 + _db = new Kysely<TableSchema>({ 47 + dialect: new SqliteDialect({ 48 + database: sqlite, 49 + }), 50 + }); 51 + 52 + // Create table if not exists 53 + _db.schema 54 + .createTable(table) 55 + .ifNotExists() 56 + .addColumn("id", "text", (col) => col.primaryKey()) 57 + .addColumn("value", "text", (col) => col.notNull()) 58 + .addColumn("created_at", "text", (col) => col.notNull()) 59 + .addColumn("updated_at", "text", (col) => col.notNull()) 60 + .execute(); 61 + 62 + return _db; 63 + })(); 64 + }, 65 + }) => { 66 + return { 67 + name: DRIVER_NAME, 68 + options: { location, table }, 69 + getInstance: getDb, 70 + 71 + async hasItem(key) { 72 + const result = await getDb() 73 + .selectFrom(table) 74 + .select(["id"]) 75 + .where("id", "=", key) 76 + .executeTakeFirst(); 77 + return !!result; 78 + }, 79 + 80 + async getItem(key) { 81 + const result = await getDb() 82 + .selectFrom(table) 83 + .select(["value"]) 84 + .where("id", "=", key) 85 + .executeTakeFirst(); 86 + return result?.value ?? null; 87 + }, 88 + 89 + async setItem(key: string, value: string) { 90 + const now = new Date().toISOString(); 91 + await getDb() 92 + .insertInto(table) 93 + .values({ 94 + id: key, 95 + value, 96 + created_at: now, 97 + updated_at: now, 98 + }) 99 + .onConflict((oc) => 100 + oc.column("id").doUpdateSet({ 101 + value, 102 + updated_at: now, 103 + }), 104 + ) 105 + .execute(); 106 + }, 107 + 108 + async setItems(items) { 109 + const now = new Date().toISOString(); 110 + 111 + await getDb() 112 + .transaction() 113 + .execute(async (trx) => { 114 + await Promise.all( 115 + items.map(({ key, value }) => { 116 + return trx 117 + .insertInto(table) 118 + .values({ 119 + id: key, 120 + value, 121 + created_at: now, 122 + updated_at: now, 123 + }) 124 + .onConflict((oc) => 125 + oc.column("id").doUpdateSet({ 126 + value, 127 + updated_at: now, 128 + }), 129 + ) 130 + .execute(); 131 + }), 132 + ); 133 + }); 134 + }, 135 + 136 + async removeItem(key: string) { 137 + await getDb().deleteFrom(table).where("id", "=", key).execute(); 138 + }, 139 + 140 + async getMeta(key: string) { 141 + const result = await getDb() 142 + .selectFrom(table) 143 + .select(["created_at", "updated_at"]) 144 + .where("id", "=", key) 145 + .executeTakeFirst(); 146 + if (!result) { 147 + return null; 148 + } 149 + return { 150 + birthtime: new Date(result.created_at), 151 + mtime: new Date(result.updated_at), 152 + }; 153 + }, 154 + 155 + async getKeys(base = "") { 156 + const results = await getDb() 157 + .selectFrom(table) 158 + .select(["id"]) 159 + .where("id", "like", `${base}%`) 160 + .execute(); 161 + return results.map((r) => r.id); 162 + }, 163 + 164 + async clear() { 165 + await getDb().deleteFrom(table).execute(); 166 + }, 167 + 168 + async dispose() { 169 + await getDb().destroy(); 170 + }, 171 + }; 172 + }, 173 + );
+20
apps/api/src/xrpc/index.ts
··· 1 + import type { Context } from "context"; 2 + import type { Server } from "lexicon"; 3 + import createSandbox from "./io/pocketenv/sandbox/createSandbox"; 4 + import deleteSandbox from "./io/pocketenv/sandbox/deleteSandbox"; 5 + import getSandbox from "./io/pocketenv/sandbox/getSandbox"; 6 + import getSandboxes from "./io/pocketenv/sandbox/getSandboxes"; 7 + import startSandbox from "./io/pocketenv/sandbox/startSandbox"; 8 + import stopSandbox from "./io/pocketenv/sandbox/stopSandbox"; 9 + 10 + export default function (server: Server, ctx: Context) { 11 + // io.pocketenv 12 + createSandbox(server, ctx); 13 + deleteSandbox(server, ctx); 14 + getSandbox(server, ctx); 15 + getSandboxes(server, ctx); 16 + startSandbox(server, ctx); 17 + stopSandbox(server, ctx); 18 + 19 + return server; 20 + }
+18
apps/api/src/xrpc/io/pocketenv/sandbox/claimSandbox.ts
··· 1 + import type { HandlerAuth } from "@atproto/xrpc-server"; 2 + import type { Context } from "context"; 3 + import type { Server } from "lexicon"; 4 + import type { QueryParams } from "lexicon/types/io/pocketenv/sandbox/claimSandbox"; 5 + 6 + export default function (server: Server, ctx: Context) { 7 + const claimSandbox = (params: QueryParams, auth: HandlerAuth) => ({}); 8 + server.io.pocketenv.sandbox.claimSandbox({ 9 + auth: ctx.authVerifier, 10 + handler: async ({ params, auth }) => { 11 + const result = claimSandbox(params, auth); 12 + return { 13 + encoding: "application/json", 14 + body: result, 15 + }; 16 + }, 17 + }); 18 + }
+18
apps/api/src/xrpc/io/pocketenv/sandbox/createSandbox.ts
··· 1 + import type { HandlerAuth } from "@atproto/xrpc-server"; 2 + import type { Context } from "context"; 3 + import type { Server } from "lexicon"; 4 + import type { HandlerInput } from "lexicon/types/io/pocketenv/sandbox/createSandbox"; 5 + 6 + export default function (server: Server, ctx: Context) { 7 + const createSandbox = (input: HandlerInput, auth: HandlerAuth) => ({}); 8 + server.io.pocketenv.sandbox.createSandbox({ 9 + auth: ctx.authVerifier, 10 + handler: async ({ input, auth }) => { 11 + const result = createSandbox(input, auth); 12 + return { 13 + encoding: "application/json", 14 + body: result, 15 + }; 16 + }, 17 + }); 18 + }
+18
apps/api/src/xrpc/io/pocketenv/sandbox/deleteSandbox.ts
··· 1 + import type { HandlerAuth } from "@atproto/xrpc-server"; 2 + import type { Context } from "context"; 3 + import type { Server } from "lexicon"; 4 + import type { QueryParams } from "lexicon/types/io/pocketenv/sandbox/deleteSandbox"; 5 + 6 + export default function (server: Server, ctx: Context) { 7 + const deleteSandbox = (params: QueryParams, auth: HandlerAuth) => ({}); 8 + server.io.pocketenv.sandbox.deleteSandbox({ 9 + auth: ctx.authVerifier, 10 + handler: async ({ params, auth }) => { 11 + const result = deleteSandbox(params, auth); 12 + return { 13 + encoding: "application/json", 14 + body: result, 15 + }; 16 + }, 17 + }); 18 + }
+18
apps/api/src/xrpc/io/pocketenv/sandbox/getSandbox.ts
··· 1 + import type { HandlerAuth } from "@atproto/xrpc-server"; 2 + import type { Context } from "context"; 3 + import type { Server } from "lexicon"; 4 + import type { QueryParams } from "lexicon/types/io/pocketenv/sandbox/getSandbox"; 5 + 6 + export default function (server: Server, ctx: Context) { 7 + const getSandbox = (params: QueryParams, auth: HandlerAuth) => ({}); 8 + server.io.pocketenv.sandbox.getSandbox({ 9 + auth: ctx.authVerifier, 10 + handler: async ({ params, auth }) => { 11 + const result = getSandbox(params, auth); 12 + return { 13 + encoding: "application/json", 14 + body: result, 15 + }; 16 + }, 17 + }); 18 + }
+90
apps/api/src/xrpc/io/pocketenv/sandbox/getSandboxes.ts
··· 1 + import type { HandlerAuth } from "@atproto/xrpc-server"; 2 + import type { Context } from "context"; 3 + import type { Server } from "lexicon"; 4 + import { Effect, pipe } from "effect"; 5 + import type { 6 + QueryParams, 7 + OutputSchema, 8 + } from "lexicon/types/io/pocketenv/sandbox/getSandboxes"; 9 + import type { SelectSandbox } from "schema/sandboxes"; 10 + import { consola } from "consola"; 11 + import schema from "schema"; 12 + import { count, eq, desc } from "drizzle-orm"; 13 + 14 + export default function (server: Server, ctx: Context) { 15 + const getSandboxes = (params: QueryParams, auth: HandlerAuth) => 16 + pipe( 17 + { params, ctx }, 18 + retrieve, 19 + Effect.flatMap(presentation), 20 + Effect.retry({ times: 3 }), 21 + Effect.timeout("10 seconds"), 22 + Effect.catchAll((err) => { 23 + consola.error("Error retrieving sandboxes:", err); 24 + return Effect.succeed({ sandboxes: [] }); 25 + }), 26 + ); 27 + server.io.pocketenv.sandbox.getSandboxes({ 28 + auth: ctx.authVerifier, 29 + handler: async ({ params, auth }) => { 30 + const result = await Effect.runPromise(getSandboxes(params, auth)); 31 + return { 32 + encoding: "application/json", 33 + body: result, 34 + }; 35 + }, 36 + }); 37 + } 38 + 39 + const retrieve = ({ 40 + params, 41 + ctx, 42 + }: { 43 + params: QueryParams; 44 + ctx: Context; 45 + }): Effect.Effect<[SelectSandbox[], number], Error> => { 46 + return Effect.tryPromise({ 47 + try: async () => 48 + Promise.all([ 49 + ctx.db 50 + .select() 51 + .from(schema.sandboxes) 52 + .leftJoin(schema.users, eq(schema.sandboxes.userId, schema.users.id)) 53 + .where(eq(schema.users.handle, "pocketenv.io")) 54 + .orderBy(desc(schema.sandboxes.installs)) 55 + .limit(params.limit ?? 30) 56 + .offset(params.offset ?? 0) 57 + .execute() 58 + .then((result) => result.map((row) => row.sandboxes)), 59 + ctx.db 60 + .select({ count: count() }) 61 + .from(schema.sandboxes) 62 + .execute() 63 + .then((result) => result[0]?.count ?? 0), 64 + ]), 65 + catch: (error) => 66 + new Error( 67 + `Failed to retrieve sandboxes: ${error instanceof Error ? error.message : String(error)}`, 68 + ), 69 + }); 70 + }; 71 + 72 + const presentation = ([sandboxes, total]: [ 73 + SelectSandbox[], 74 + number, 75 + ]): Effect.Effect<OutputSchema, never> => { 76 + return Effect.sync(() => ({ 77 + sandboxes: sandboxes.map((sandbox) => ({ 78 + id: sandbox.id, 79 + name: sandbox.name, 80 + displayName: sandbox.displayName, 81 + description: sandbox.description, 82 + logo: sandbox.logo, 83 + readme: sandbox.readme, 84 + installs: sandbox.installs, 85 + uri: sandbox.uri, 86 + createdAt: sandbox.createdAt.toISOString(), 87 + })), 88 + total, 89 + })); 90 + };
+18
apps/api/src/xrpc/io/pocketenv/sandbox/startSandbox.ts
··· 1 + import type { HandlerAuth } from "@atproto/xrpc-server"; 2 + import type { Context } from "context"; 3 + import type { Server } from "lexicon"; 4 + import type { QueryParams } from "lexicon/types/io/pocketenv/sandbox/startSandbox"; 5 + 6 + export default function (server: Server, ctx: Context) { 7 + const startSandbox = (params: QueryParams, auth: HandlerAuth) => ({}); 8 + server.io.pocketenv.sandbox.startSandbox({ 9 + auth: ctx.authVerifier, 10 + handler: async ({ params, auth }) => { 11 + const result = startSandbox(params, auth); 12 + return { 13 + encoding: "application/json", 14 + body: result, 15 + }; 16 + }, 17 + }); 18 + }
+18
apps/api/src/xrpc/io/pocketenv/sandbox/stopSandbox.ts
··· 1 + import type { HandlerAuth } from "@atproto/xrpc-server"; 2 + import type { Context } from "context"; 3 + import type { Server } from "lexicon"; 4 + import type { QueryParams } from "lexicon/types/io/pocketenv/sandbox/stopSandbox"; 5 + 6 + export default function (server: Server, ctx: Context) { 7 + const stopSandbox = (params: QueryParams, auth: HandlerAuth) => ({}); 8 + server.io.pocketenv.sandbox.deleteSandbox({ 9 + auth: ctx.authVerifier, 10 + handler: async ({ params, auth }) => { 11 + const result = stopSandbox(params, auth); 12 + return { 13 + encoding: "application/json", 14 + body: result, 15 + }; 16 + }, 17 + }); 18 + }
+10 -4
apps/api/tsconfig.json
··· 3 3 // Environment setup & latest features 4 4 "lib": ["ESNext"], 5 5 "target": "ESNext", 6 - "module": "Preserve", 6 + "module": "ESNext", 7 7 "moduleDetection": "force", 8 8 "jsx": "react-jsx", 9 9 "allowJs": true, 10 + "allowSyntheticDefaultImports": true, 10 11 11 12 // Bundler mode 12 - "moduleResolution": "bundler", 13 + "moduleResolution": "node", 13 14 "allowImportingTsExtensions": true, 14 15 "verbatimModuleSyntax": true, 15 16 "noEmit": true, 16 17 18 + "rootDir": ".", 19 + "baseUrl": "src", 20 + 17 21 // Best practices 18 22 "strict": true, 19 23 "skipLibCheck": true, ··· 24 28 // Some stricter flags (disabled by default) 25 29 "noUnusedLocals": false, 26 30 "noUnusedParameters": false, 27 - "noPropertyAccessFromIndexSignature": false 28 - } 31 + "noPropertyAccessFromIndexSignature": false, 32 + }, 33 + "exclude": ["node_modules", "dist", "tests"], 34 + "include": ["src", "scripts", "types"], 29 35 }
+10
apps/api/types/express.d.ts
··· 1 + import type { Context } from "context"; 2 + import type { Db } from "./db"; 3 + 4 + declare global { 5 + namespace Express { 6 + interface Request { 7 + ctx: Context; 8 + } 9 + } 10 + }
+8
apps/cf-sandbox/drizzle/0005_flimsy_peter_parker.sql
··· 1 + ALTER TABLE "sandboxes" ALTER COLUMN "base" DROP NOT NULL;--> statement-breakpoint 2 + ALTER TABLE "sandboxes" ALTER COLUMN "user_id" DROP NOT NULL;--> statement-breakpoint 3 + ALTER TABLE "sandboxes" ALTER COLUMN "instance_type" DROP NOT NULL;--> statement-breakpoint 4 + ALTER TABLE "sandboxes" ADD COLUMN "uri" text;--> statement-breakpoint 5 + ALTER TABLE "sandboxes" ADD COLUMN "logo" text;--> statement-breakpoint 6 + ALTER TABLE "sandboxes" ADD COLUMN "vcpus" integer;--> statement-breakpoint 7 + ALTER TABLE "sandboxes" ADD COLUMN "memory" integer;--> statement-breakpoint 8 + ALTER TABLE "sandboxes" ADD COLUMN "disk" integer;
+2
apps/cf-sandbox/drizzle/0006_lively_black_bird.sql
··· 1 + ALTER TABLE "sandboxes" ADD COLUMN "display_name" text;--> statement-breakpoint 2 + ALTER TABLE "sandboxes" ADD CONSTRAINT "sandboxes_uri_unique" UNIQUE("uri");
+9
apps/cf-sandbox/drizzle/0007_organic_spiral.sql
··· 1 + CREATE TABLE "authorized_keys" ( 2 + "id" text PRIMARY KEY DEFAULT xata_id() NOT NULL, 3 + "sandbox_id" text, 4 + "public_key" text NOT NULL, 5 + "created_at" timestamp DEFAULT now() NOT NULL 6 + ); 7 + --> statement-breakpoint 8 + ALTER TABLE "sandboxes" ADD COLUMN "readme" text;--> statement-breakpoint 9 + ALTER TABLE "authorized_keys" ADD CONSTRAINT "authorized_keys_sandbox_id_sandboxes_id_fk" FOREIGN KEY ("sandbox_id") REFERENCES "public"."sandboxes"("id") ON DELETE no action ON UPDATE no action;
+1
apps/cf-sandbox/drizzle/0008_futuristic_the_hand.sql
··· 1 + ALTER TABLE "sandboxes" ADD COLUMN "installs" integer DEFAULT 0 NOT NULL;
+653
apps/cf-sandbox/drizzle/meta/0005_snapshot.json
··· 1 + { 2 + "id": "6ca7bc7d-fb92-4568-85c4-43fda1232972", 3 + "prevId": "30c146d4-bc76-46b2-89de-b6d264f59b81", 4 + "version": "7", 5 + "dialect": "postgresql", 6 + "tables": { 7 + "public.sandbox_secrets": { 8 + "name": "sandbox_secrets", 9 + "schema": "", 10 + "columns": { 11 + "id": { 12 + "name": "id", 13 + "type": "text", 14 + "primaryKey": true, 15 + "notNull": true, 16 + "default": "xata_id()" 17 + }, 18 + "sandbox_id": { 19 + "name": "sandbox_id", 20 + "type": "text", 21 + "primaryKey": false, 22 + "notNull": true 23 + }, 24 + "secret_id": { 25 + "name": "secret_id", 26 + "type": "text", 27 + "primaryKey": false, 28 + "notNull": true 29 + } 30 + }, 31 + "indexes": { 32 + "unique_sandbox_secret": { 33 + "name": "unique_sandbox_secret", 34 + "columns": [ 35 + { 36 + "expression": "sandbox_id", 37 + "isExpression": false, 38 + "asc": true, 39 + "nulls": "last" 40 + }, 41 + { 42 + "expression": "secret_id", 43 + "isExpression": false, 44 + "asc": true, 45 + "nulls": "last" 46 + } 47 + ], 48 + "isUnique": true, 49 + "concurrently": false, 50 + "method": "btree", 51 + "with": {} 52 + } 53 + }, 54 + "foreignKeys": { 55 + "sandbox_secrets_sandbox_id_sandboxes_id_fk": { 56 + "name": "sandbox_secrets_sandbox_id_sandboxes_id_fk", 57 + "tableFrom": "sandbox_secrets", 58 + "tableTo": "sandboxes", 59 + "columnsFrom": [ 60 + "sandbox_id" 61 + ], 62 + "columnsTo": [ 63 + "id" 64 + ], 65 + "onDelete": "no action", 66 + "onUpdate": "no action" 67 + }, 68 + "sandbox_secrets_secret_id_secrets_id_fk": { 69 + "name": "sandbox_secrets_secret_id_secrets_id_fk", 70 + "tableFrom": "sandbox_secrets", 71 + "tableTo": "secrets", 72 + "columnsFrom": [ 73 + "secret_id" 74 + ], 75 + "columnsTo": [ 76 + "id" 77 + ], 78 + "onDelete": "no action", 79 + "onUpdate": "no action" 80 + } 81 + }, 82 + "compositePrimaryKeys": {}, 83 + "uniqueConstraints": {}, 84 + "policies": {}, 85 + "checkConstraints": {}, 86 + "isRLSEnabled": false 87 + }, 88 + "public.sandbox_variables": { 89 + "name": "sandbox_variables", 90 + "schema": "", 91 + "columns": { 92 + "id": { 93 + "name": "id", 94 + "type": "text", 95 + "primaryKey": true, 96 + "notNull": true, 97 + "default": "xata_id()" 98 + }, 99 + "sandbox_id": { 100 + "name": "sandbox_id", 101 + "type": "text", 102 + "primaryKey": false, 103 + "notNull": true 104 + }, 105 + "variable_id": { 106 + "name": "variable_id", 107 + "type": "text", 108 + "primaryKey": false, 109 + "notNull": true 110 + } 111 + }, 112 + "indexes": { 113 + "unique_sandbox_variables": { 114 + "name": "unique_sandbox_variables", 115 + "columns": [ 116 + { 117 + "expression": "sandbox_id", 118 + "isExpression": false, 119 + "asc": true, 120 + "nulls": "last" 121 + }, 122 + { 123 + "expression": "variable_id", 124 + "isExpression": false, 125 + "asc": true, 126 + "nulls": "last" 127 + } 128 + ], 129 + "isUnique": true, 130 + "concurrently": false, 131 + "method": "btree", 132 + "with": {} 133 + } 134 + }, 135 + "foreignKeys": { 136 + "sandbox_variables_sandbox_id_sandboxes_id_fk": { 137 + "name": "sandbox_variables_sandbox_id_sandboxes_id_fk", 138 + "tableFrom": "sandbox_variables", 139 + "tableTo": "sandboxes", 140 + "columnsFrom": [ 141 + "sandbox_id" 142 + ], 143 + "columnsTo": [ 144 + "id" 145 + ], 146 + "onDelete": "no action", 147 + "onUpdate": "no action" 148 + }, 149 + "sandbox_variables_variable_id_variables_id_fk": { 150 + "name": "sandbox_variables_variable_id_variables_id_fk", 151 + "tableFrom": "sandbox_variables", 152 + "tableTo": "variables", 153 + "columnsFrom": [ 154 + "variable_id" 155 + ], 156 + "columnsTo": [ 157 + "id" 158 + ], 159 + "onDelete": "no action", 160 + "onUpdate": "no action" 161 + } 162 + }, 163 + "compositePrimaryKeys": {}, 164 + "uniqueConstraints": {}, 165 + "policies": {}, 166 + "checkConstraints": {}, 167 + "isRLSEnabled": false 168 + }, 169 + "public.sandbox_volumes": { 170 + "name": "sandbox_volumes", 171 + "schema": "", 172 + "columns": { 173 + "id": { 174 + "name": "id", 175 + "type": "text", 176 + "primaryKey": true, 177 + "notNull": true, 178 + "default": "xata_id()" 179 + }, 180 + "sandbox_id": { 181 + "name": "sandbox_id", 182 + "type": "text", 183 + "primaryKey": false, 184 + "notNull": true 185 + }, 186 + "volume_id": { 187 + "name": "volume_id", 188 + "type": "text", 189 + "primaryKey": false, 190 + "notNull": true 191 + } 192 + }, 193 + "indexes": {}, 194 + "foreignKeys": { 195 + "sandbox_volumes_sandbox_id_sandboxes_id_fk": { 196 + "name": "sandbox_volumes_sandbox_id_sandboxes_id_fk", 197 + "tableFrom": "sandbox_volumes", 198 + "tableTo": "sandboxes", 199 + "columnsFrom": [ 200 + "sandbox_id" 201 + ], 202 + "columnsTo": [ 203 + "id" 204 + ], 205 + "onDelete": "no action", 206 + "onUpdate": "no action" 207 + }, 208 + "sandbox_volumes_volume_id_volumes_id_fk": { 209 + "name": "sandbox_volumes_volume_id_volumes_id_fk", 210 + "tableFrom": "sandbox_volumes", 211 + "tableTo": "volumes", 212 + "columnsFrom": [ 213 + "volume_id" 214 + ], 215 + "columnsTo": [ 216 + "id" 217 + ], 218 + "onDelete": "no action", 219 + "onUpdate": "no action" 220 + } 221 + }, 222 + "compositePrimaryKeys": {}, 223 + "uniqueConstraints": {}, 224 + "policies": {}, 225 + "checkConstraints": {}, 226 + "isRLSEnabled": false 227 + }, 228 + "public.sandboxes": { 229 + "name": "sandboxes", 230 + "schema": "", 231 + "columns": { 232 + "id": { 233 + "name": "id", 234 + "type": "text", 235 + "primaryKey": true, 236 + "notNull": true, 237 + "default": "sandbox_id()" 238 + }, 239 + "base": { 240 + "name": "base", 241 + "type": "text", 242 + "primaryKey": false, 243 + "notNull": false 244 + }, 245 + "name": { 246 + "name": "name", 247 + "type": "text", 248 + "primaryKey": false, 249 + "notNull": true 250 + }, 251 + "uri": { 252 + "name": "uri", 253 + "type": "text", 254 + "primaryKey": false, 255 + "notNull": false 256 + }, 257 + "provider": { 258 + "name": "provider", 259 + "type": "text", 260 + "primaryKey": false, 261 + "notNull": true, 262 + "default": "'cloudflare'" 263 + }, 264 + "description": { 265 + "name": "description", 266 + "type": "text", 267 + "primaryKey": false, 268 + "notNull": false 269 + }, 270 + "logo": { 271 + "name": "logo", 272 + "type": "text", 273 + "primaryKey": false, 274 + "notNull": false 275 + }, 276 + "public_key": { 277 + "name": "public_key", 278 + "type": "text", 279 + "primaryKey": false, 280 + "notNull": true 281 + }, 282 + "user_id": { 283 + "name": "user_id", 284 + "type": "text", 285 + "primaryKey": false, 286 + "notNull": false 287 + }, 288 + "instance_type": { 289 + "name": "instance_type", 290 + "type": "text", 291 + "primaryKey": false, 292 + "notNull": false 293 + }, 294 + "vcpus": { 295 + "name": "vcpus", 296 + "type": "integer", 297 + "primaryKey": false, 298 + "notNull": false 299 + }, 300 + "memory": { 301 + "name": "memory", 302 + "type": "integer", 303 + "primaryKey": false, 304 + "notNull": false 305 + }, 306 + "disk": { 307 + "name": "disk", 308 + "type": "integer", 309 + "primaryKey": false, 310 + "notNull": false 311 + }, 312 + "status": { 313 + "name": "status", 314 + "type": "text", 315 + "primaryKey": false, 316 + "notNull": true 317 + }, 318 + "keep_alive": { 319 + "name": "keep_alive", 320 + "type": "boolean", 321 + "primaryKey": false, 322 + "notNull": true, 323 + "default": false 324 + }, 325 + "sleep_after": { 326 + "name": "sleep_after", 327 + "type": "text", 328 + "primaryKey": false, 329 + "notNull": false 330 + }, 331 + "sandbox_id": { 332 + "name": "sandbox_id", 333 + "type": "text", 334 + "primaryKey": false, 335 + "notNull": false 336 + }, 337 + "created_at": { 338 + "name": "created_at", 339 + "type": "timestamp", 340 + "primaryKey": false, 341 + "notNull": true, 342 + "default": "now()" 343 + }, 344 + "updated_at": { 345 + "name": "updated_at", 346 + "type": "timestamp", 347 + "primaryKey": false, 348 + "notNull": true, 349 + "default": "now()" 350 + } 351 + }, 352 + "indexes": {}, 353 + "foreignKeys": { 354 + "sandboxes_user_id_users_id_fk": { 355 + "name": "sandboxes_user_id_users_id_fk", 356 + "tableFrom": "sandboxes", 357 + "tableTo": "users", 358 + "columnsFrom": [ 359 + "user_id" 360 + ], 361 + "columnsTo": [ 362 + "id" 363 + ], 364 + "onDelete": "no action", 365 + "onUpdate": "no action" 366 + } 367 + }, 368 + "compositePrimaryKeys": {}, 369 + "uniqueConstraints": { 370 + "sandboxes_name_unique": { 371 + "name": "sandboxes_name_unique", 372 + "nullsNotDistinct": false, 373 + "columns": [ 374 + "name" 375 + ] 376 + } 377 + }, 378 + "policies": {}, 379 + "checkConstraints": {}, 380 + "isRLSEnabled": false 381 + }, 382 + "public.secrets": { 383 + "name": "secrets", 384 + "schema": "", 385 + "columns": { 386 + "id": { 387 + "name": "id", 388 + "type": "text", 389 + "primaryKey": true, 390 + "notNull": true, 391 + "default": "secret_id()" 392 + }, 393 + "name": { 394 + "name": "name", 395 + "type": "text", 396 + "primaryKey": false, 397 + "notNull": true 398 + }, 399 + "value": { 400 + "name": "value", 401 + "type": "text", 402 + "primaryKey": false, 403 + "notNull": true 404 + }, 405 + "created_at": { 406 + "name": "created_at", 407 + "type": "timestamp", 408 + "primaryKey": false, 409 + "notNull": true, 410 + "default": "now()" 411 + } 412 + }, 413 + "indexes": {}, 414 + "foreignKeys": {}, 415 + "compositePrimaryKeys": {}, 416 + "uniqueConstraints": {}, 417 + "policies": {}, 418 + "checkConstraints": {}, 419 + "isRLSEnabled": false 420 + }, 421 + "public.snapshots": { 422 + "name": "snapshots", 423 + "schema": "", 424 + "columns": { 425 + "id": { 426 + "name": "id", 427 + "type": "text", 428 + "primaryKey": true, 429 + "notNull": true, 430 + "default": "snapshot_id()" 431 + }, 432 + "slug": { 433 + "name": "slug", 434 + "type": "text", 435 + "primaryKey": false, 436 + "notNull": true 437 + }, 438 + "created_at": { 439 + "name": "created_at", 440 + "type": "timestamp", 441 + "primaryKey": false, 442 + "notNull": true, 443 + "default": "now()" 444 + } 445 + }, 446 + "indexes": {}, 447 + "foreignKeys": {}, 448 + "compositePrimaryKeys": {}, 449 + "uniqueConstraints": { 450 + "snapshots_slug_unique": { 451 + "name": "snapshots_slug_unique", 452 + "nullsNotDistinct": false, 453 + "columns": [ 454 + "slug" 455 + ] 456 + } 457 + }, 458 + "policies": {}, 459 + "checkConstraints": {}, 460 + "isRLSEnabled": false 461 + }, 462 + "public.users": { 463 + "name": "users", 464 + "schema": "", 465 + "columns": { 466 + "id": { 467 + "name": "id", 468 + "type": "text", 469 + "primaryKey": true, 470 + "notNull": true, 471 + "default": "xata_id()" 472 + }, 473 + "did": { 474 + "name": "did", 475 + "type": "text", 476 + "primaryKey": false, 477 + "notNull": true 478 + }, 479 + "display_name": { 480 + "name": "display_name", 481 + "type": "text", 482 + "primaryKey": false, 483 + "notNull": false 484 + }, 485 + "handle": { 486 + "name": "handle", 487 + "type": "text", 488 + "primaryKey": false, 489 + "notNull": true 490 + }, 491 + "avatar": { 492 + "name": "avatar", 493 + "type": "text", 494 + "primaryKey": false, 495 + "notNull": false 496 + }, 497 + "created_at": { 498 + "name": "created_at", 499 + "type": "timestamp", 500 + "primaryKey": false, 501 + "notNull": true, 502 + "default": "now()" 503 + }, 504 + "updated_at": { 505 + "name": "updated_at", 506 + "type": "timestamp", 507 + "primaryKey": false, 508 + "notNull": true, 509 + "default": "now()" 510 + } 511 + }, 512 + "indexes": {}, 513 + "foreignKeys": {}, 514 + "compositePrimaryKeys": {}, 515 + "uniqueConstraints": { 516 + "users_did_unique": { 517 + "name": "users_did_unique", 518 + "nullsNotDistinct": false, 519 + "columns": [ 520 + "did" 521 + ] 522 + }, 523 + "users_handle_unique": { 524 + "name": "users_handle_unique", 525 + "nullsNotDistinct": false, 526 + "columns": [ 527 + "handle" 528 + ] 529 + } 530 + }, 531 + "policies": {}, 532 + "checkConstraints": {}, 533 + "isRLSEnabled": false 534 + }, 535 + "public.variables": { 536 + "name": "variables", 537 + "schema": "", 538 + "columns": { 539 + "id": { 540 + "name": "id", 541 + "type": "text", 542 + "primaryKey": true, 543 + "notNull": true, 544 + "default": "variable_id()" 545 + }, 546 + "name": { 547 + "name": "name", 548 + "type": "text", 549 + "primaryKey": false, 550 + "notNull": true 551 + }, 552 + "value": { 553 + "name": "value", 554 + "type": "text", 555 + "primaryKey": false, 556 + "notNull": true 557 + }, 558 + "created_at": { 559 + "name": "created_at", 560 + "type": "timestamp", 561 + "primaryKey": false, 562 + "notNull": true, 563 + "default": "now()" 564 + }, 565 + "updated_at": { 566 + "name": "updated_at", 567 + "type": "timestamp", 568 + "primaryKey": false, 569 + "notNull": true, 570 + "default": "now()" 571 + } 572 + }, 573 + "indexes": {}, 574 + "foreignKeys": {}, 575 + "compositePrimaryKeys": {}, 576 + "uniqueConstraints": {}, 577 + "policies": {}, 578 + "checkConstraints": {}, 579 + "isRLSEnabled": false 580 + }, 581 + "public.volumes": { 582 + "name": "volumes", 583 + "schema": "", 584 + "columns": { 585 + "id": { 586 + "name": "id", 587 + "type": "text", 588 + "primaryKey": true, 589 + "notNull": true, 590 + "default": "volume_id()" 591 + }, 592 + "slug": { 593 + "name": "slug", 594 + "type": "text", 595 + "primaryKey": false, 596 + "notNull": true 597 + }, 598 + "size": { 599 + "name": "size", 600 + "type": "integer", 601 + "primaryKey": false, 602 + "notNull": true 603 + }, 604 + "size_unit": { 605 + "name": "size_unit", 606 + "type": "text", 607 + "primaryKey": false, 608 + "notNull": true 609 + }, 610 + "created_at": { 611 + "name": "created_at", 612 + "type": "timestamp", 613 + "primaryKey": false, 614 + "notNull": true, 615 + "default": "now()" 616 + }, 617 + "updated_at": { 618 + "name": "updated_at", 619 + "type": "timestamp", 620 + "primaryKey": false, 621 + "notNull": true, 622 + "default": "now()" 623 + } 624 + }, 625 + "indexes": {}, 626 + "foreignKeys": {}, 627 + "compositePrimaryKeys": {}, 628 + "uniqueConstraints": { 629 + "volumes_slug_unique": { 630 + "name": "volumes_slug_unique", 631 + "nullsNotDistinct": false, 632 + "columns": [ 633 + "slug" 634 + ] 635 + } 636 + }, 637 + "policies": {}, 638 + "checkConstraints": {}, 639 + "isRLSEnabled": false 640 + } 641 + }, 642 + "enums": {}, 643 + "schemas": {}, 644 + "sequences": {}, 645 + "roles": {}, 646 + "policies": {}, 647 + "views": {}, 648 + "_meta": { 649 + "columns": {}, 650 + "schemas": {}, 651 + "tables": {} 652 + } 653 + }
+666
apps/cf-sandbox/drizzle/meta/0006_snapshot.json
··· 1 + { 2 + "id": "1f74f79d-40cf-43b5-a0af-2e962541d315", 3 + "prevId": "6ca7bc7d-fb92-4568-85c4-43fda1232972", 4 + "version": "7", 5 + "dialect": "postgresql", 6 + "tables": { 7 + "public.sandbox_secrets": { 8 + "name": "sandbox_secrets", 9 + "schema": "", 10 + "columns": { 11 + "id": { 12 + "name": "id", 13 + "type": "text", 14 + "primaryKey": true, 15 + "notNull": true, 16 + "default": "xata_id()" 17 + }, 18 + "sandbox_id": { 19 + "name": "sandbox_id", 20 + "type": "text", 21 + "primaryKey": false, 22 + "notNull": true 23 + }, 24 + "secret_id": { 25 + "name": "secret_id", 26 + "type": "text", 27 + "primaryKey": false, 28 + "notNull": true 29 + } 30 + }, 31 + "indexes": { 32 + "unique_sandbox_secret": { 33 + "name": "unique_sandbox_secret", 34 + "columns": [ 35 + { 36 + "expression": "sandbox_id", 37 + "isExpression": false, 38 + "asc": true, 39 + "nulls": "last" 40 + }, 41 + { 42 + "expression": "secret_id", 43 + "isExpression": false, 44 + "asc": true, 45 + "nulls": "last" 46 + } 47 + ], 48 + "isUnique": true, 49 + "concurrently": false, 50 + "method": "btree", 51 + "with": {} 52 + } 53 + }, 54 + "foreignKeys": { 55 + "sandbox_secrets_sandbox_id_sandboxes_id_fk": { 56 + "name": "sandbox_secrets_sandbox_id_sandboxes_id_fk", 57 + "tableFrom": "sandbox_secrets", 58 + "tableTo": "sandboxes", 59 + "columnsFrom": [ 60 + "sandbox_id" 61 + ], 62 + "columnsTo": [ 63 + "id" 64 + ], 65 + "onDelete": "no action", 66 + "onUpdate": "no action" 67 + }, 68 + "sandbox_secrets_secret_id_secrets_id_fk": { 69 + "name": "sandbox_secrets_secret_id_secrets_id_fk", 70 + "tableFrom": "sandbox_secrets", 71 + "tableTo": "secrets", 72 + "columnsFrom": [ 73 + "secret_id" 74 + ], 75 + "columnsTo": [ 76 + "id" 77 + ], 78 + "onDelete": "no action", 79 + "onUpdate": "no action" 80 + } 81 + }, 82 + "compositePrimaryKeys": {}, 83 + "uniqueConstraints": {}, 84 + "policies": {}, 85 + "checkConstraints": {}, 86 + "isRLSEnabled": false 87 + }, 88 + "public.sandbox_variables": { 89 + "name": "sandbox_variables", 90 + "schema": "", 91 + "columns": { 92 + "id": { 93 + "name": "id", 94 + "type": "text", 95 + "primaryKey": true, 96 + "notNull": true, 97 + "default": "xata_id()" 98 + }, 99 + "sandbox_id": { 100 + "name": "sandbox_id", 101 + "type": "text", 102 + "primaryKey": false, 103 + "notNull": true 104 + }, 105 + "variable_id": { 106 + "name": "variable_id", 107 + "type": "text", 108 + "primaryKey": false, 109 + "notNull": true 110 + } 111 + }, 112 + "indexes": { 113 + "unique_sandbox_variables": { 114 + "name": "unique_sandbox_variables", 115 + "columns": [ 116 + { 117 + "expression": "sandbox_id", 118 + "isExpression": false, 119 + "asc": true, 120 + "nulls": "last" 121 + }, 122 + { 123 + "expression": "variable_id", 124 + "isExpression": false, 125 + "asc": true, 126 + "nulls": "last" 127 + } 128 + ], 129 + "isUnique": true, 130 + "concurrently": false, 131 + "method": "btree", 132 + "with": {} 133 + } 134 + }, 135 + "foreignKeys": { 136 + "sandbox_variables_sandbox_id_sandboxes_id_fk": { 137 + "name": "sandbox_variables_sandbox_id_sandboxes_id_fk", 138 + "tableFrom": "sandbox_variables", 139 + "tableTo": "sandboxes", 140 + "columnsFrom": [ 141 + "sandbox_id" 142 + ], 143 + "columnsTo": [ 144 + "id" 145 + ], 146 + "onDelete": "no action", 147 + "onUpdate": "no action" 148 + }, 149 + "sandbox_variables_variable_id_variables_id_fk": { 150 + "name": "sandbox_variables_variable_id_variables_id_fk", 151 + "tableFrom": "sandbox_variables", 152 + "tableTo": "variables", 153 + "columnsFrom": [ 154 + "variable_id" 155 + ], 156 + "columnsTo": [ 157 + "id" 158 + ], 159 + "onDelete": "no action", 160 + "onUpdate": "no action" 161 + } 162 + }, 163 + "compositePrimaryKeys": {}, 164 + "uniqueConstraints": {}, 165 + "policies": {}, 166 + "checkConstraints": {}, 167 + "isRLSEnabled": false 168 + }, 169 + "public.sandbox_volumes": { 170 + "name": "sandbox_volumes", 171 + "schema": "", 172 + "columns": { 173 + "id": { 174 + "name": "id", 175 + "type": "text", 176 + "primaryKey": true, 177 + "notNull": true, 178 + "default": "xata_id()" 179 + }, 180 + "sandbox_id": { 181 + "name": "sandbox_id", 182 + "type": "text", 183 + "primaryKey": false, 184 + "notNull": true 185 + }, 186 + "volume_id": { 187 + "name": "volume_id", 188 + "type": "text", 189 + "primaryKey": false, 190 + "notNull": true 191 + } 192 + }, 193 + "indexes": {}, 194 + "foreignKeys": { 195 + "sandbox_volumes_sandbox_id_sandboxes_id_fk": { 196 + "name": "sandbox_volumes_sandbox_id_sandboxes_id_fk", 197 + "tableFrom": "sandbox_volumes", 198 + "tableTo": "sandboxes", 199 + "columnsFrom": [ 200 + "sandbox_id" 201 + ], 202 + "columnsTo": [ 203 + "id" 204 + ], 205 + "onDelete": "no action", 206 + "onUpdate": "no action" 207 + }, 208 + "sandbox_volumes_volume_id_volumes_id_fk": { 209 + "name": "sandbox_volumes_volume_id_volumes_id_fk", 210 + "tableFrom": "sandbox_volumes", 211 + "tableTo": "volumes", 212 + "columnsFrom": [ 213 + "volume_id" 214 + ], 215 + "columnsTo": [ 216 + "id" 217 + ], 218 + "onDelete": "no action", 219 + "onUpdate": "no action" 220 + } 221 + }, 222 + "compositePrimaryKeys": {}, 223 + "uniqueConstraints": {}, 224 + "policies": {}, 225 + "checkConstraints": {}, 226 + "isRLSEnabled": false 227 + }, 228 + "public.sandboxes": { 229 + "name": "sandboxes", 230 + "schema": "", 231 + "columns": { 232 + "id": { 233 + "name": "id", 234 + "type": "text", 235 + "primaryKey": true, 236 + "notNull": true, 237 + "default": "sandbox_id()" 238 + }, 239 + "base": { 240 + "name": "base", 241 + "type": "text", 242 + "primaryKey": false, 243 + "notNull": false 244 + }, 245 + "name": { 246 + "name": "name", 247 + "type": "text", 248 + "primaryKey": false, 249 + "notNull": true 250 + }, 251 + "display_name": { 252 + "name": "display_name", 253 + "type": "text", 254 + "primaryKey": false, 255 + "notNull": false 256 + }, 257 + "uri": { 258 + "name": "uri", 259 + "type": "text", 260 + "primaryKey": false, 261 + "notNull": false 262 + }, 263 + "provider": { 264 + "name": "provider", 265 + "type": "text", 266 + "primaryKey": false, 267 + "notNull": true, 268 + "default": "'cloudflare'" 269 + }, 270 + "description": { 271 + "name": "description", 272 + "type": "text", 273 + "primaryKey": false, 274 + "notNull": false 275 + }, 276 + "logo": { 277 + "name": "logo", 278 + "type": "text", 279 + "primaryKey": false, 280 + "notNull": false 281 + }, 282 + "public_key": { 283 + "name": "public_key", 284 + "type": "text", 285 + "primaryKey": false, 286 + "notNull": true 287 + }, 288 + "user_id": { 289 + "name": "user_id", 290 + "type": "text", 291 + "primaryKey": false, 292 + "notNull": false 293 + }, 294 + "instance_type": { 295 + "name": "instance_type", 296 + "type": "text", 297 + "primaryKey": false, 298 + "notNull": false 299 + }, 300 + "vcpus": { 301 + "name": "vcpus", 302 + "type": "integer", 303 + "primaryKey": false, 304 + "notNull": false 305 + }, 306 + "memory": { 307 + "name": "memory", 308 + "type": "integer", 309 + "primaryKey": false, 310 + "notNull": false 311 + }, 312 + "disk": { 313 + "name": "disk", 314 + "type": "integer", 315 + "primaryKey": false, 316 + "notNull": false 317 + }, 318 + "status": { 319 + "name": "status", 320 + "type": "text", 321 + "primaryKey": false, 322 + "notNull": true 323 + }, 324 + "keep_alive": { 325 + "name": "keep_alive", 326 + "type": "boolean", 327 + "primaryKey": false, 328 + "notNull": true, 329 + "default": false 330 + }, 331 + "sleep_after": { 332 + "name": "sleep_after", 333 + "type": "text", 334 + "primaryKey": false, 335 + "notNull": false 336 + }, 337 + "sandbox_id": { 338 + "name": "sandbox_id", 339 + "type": "text", 340 + "primaryKey": false, 341 + "notNull": false 342 + }, 343 + "created_at": { 344 + "name": "created_at", 345 + "type": "timestamp", 346 + "primaryKey": false, 347 + "notNull": true, 348 + "default": "now()" 349 + }, 350 + "updated_at": { 351 + "name": "updated_at", 352 + "type": "timestamp", 353 + "primaryKey": false, 354 + "notNull": true, 355 + "default": "now()" 356 + } 357 + }, 358 + "indexes": {}, 359 + "foreignKeys": { 360 + "sandboxes_user_id_users_id_fk": { 361 + "name": "sandboxes_user_id_users_id_fk", 362 + "tableFrom": "sandboxes", 363 + "tableTo": "users", 364 + "columnsFrom": [ 365 + "user_id" 366 + ], 367 + "columnsTo": [ 368 + "id" 369 + ], 370 + "onDelete": "no action", 371 + "onUpdate": "no action" 372 + } 373 + }, 374 + "compositePrimaryKeys": {}, 375 + "uniqueConstraints": { 376 + "sandboxes_name_unique": { 377 + "name": "sandboxes_name_unique", 378 + "nullsNotDistinct": false, 379 + "columns": [ 380 + "name" 381 + ] 382 + }, 383 + "sandboxes_uri_unique": { 384 + "name": "sandboxes_uri_unique", 385 + "nullsNotDistinct": false, 386 + "columns": [ 387 + "uri" 388 + ] 389 + } 390 + }, 391 + "policies": {}, 392 + "checkConstraints": {}, 393 + "isRLSEnabled": false 394 + }, 395 + "public.secrets": { 396 + "name": "secrets", 397 + "schema": "", 398 + "columns": { 399 + "id": { 400 + "name": "id", 401 + "type": "text", 402 + "primaryKey": true, 403 + "notNull": true, 404 + "default": "secret_id()" 405 + }, 406 + "name": { 407 + "name": "name", 408 + "type": "text", 409 + "primaryKey": false, 410 + "notNull": true 411 + }, 412 + "value": { 413 + "name": "value", 414 + "type": "text", 415 + "primaryKey": false, 416 + "notNull": true 417 + }, 418 + "created_at": { 419 + "name": "created_at", 420 + "type": "timestamp", 421 + "primaryKey": false, 422 + "notNull": true, 423 + "default": "now()" 424 + } 425 + }, 426 + "indexes": {}, 427 + "foreignKeys": {}, 428 + "compositePrimaryKeys": {}, 429 + "uniqueConstraints": {}, 430 + "policies": {}, 431 + "checkConstraints": {}, 432 + "isRLSEnabled": false 433 + }, 434 + "public.snapshots": { 435 + "name": "snapshots", 436 + "schema": "", 437 + "columns": { 438 + "id": { 439 + "name": "id", 440 + "type": "text", 441 + "primaryKey": true, 442 + "notNull": true, 443 + "default": "snapshot_id()" 444 + }, 445 + "slug": { 446 + "name": "slug", 447 + "type": "text", 448 + "primaryKey": false, 449 + "notNull": true 450 + }, 451 + "created_at": { 452 + "name": "created_at", 453 + "type": "timestamp", 454 + "primaryKey": false, 455 + "notNull": true, 456 + "default": "now()" 457 + } 458 + }, 459 + "indexes": {}, 460 + "foreignKeys": {}, 461 + "compositePrimaryKeys": {}, 462 + "uniqueConstraints": { 463 + "snapshots_slug_unique": { 464 + "name": "snapshots_slug_unique", 465 + "nullsNotDistinct": false, 466 + "columns": [ 467 + "slug" 468 + ] 469 + } 470 + }, 471 + "policies": {}, 472 + "checkConstraints": {}, 473 + "isRLSEnabled": false 474 + }, 475 + "public.users": { 476 + "name": "users", 477 + "schema": "", 478 + "columns": { 479 + "id": { 480 + "name": "id", 481 + "type": "text", 482 + "primaryKey": true, 483 + "notNull": true, 484 + "default": "xata_id()" 485 + }, 486 + "did": { 487 + "name": "did", 488 + "type": "text", 489 + "primaryKey": false, 490 + "notNull": true 491 + }, 492 + "display_name": { 493 + "name": "display_name", 494 + "type": "text", 495 + "primaryKey": false, 496 + "notNull": false 497 + }, 498 + "handle": { 499 + "name": "handle", 500 + "type": "text", 501 + "primaryKey": false, 502 + "notNull": true 503 + }, 504 + "avatar": { 505 + "name": "avatar", 506 + "type": "text", 507 + "primaryKey": false, 508 + "notNull": false 509 + }, 510 + "created_at": { 511 + "name": "created_at", 512 + "type": "timestamp", 513 + "primaryKey": false, 514 + "notNull": true, 515 + "default": "now()" 516 + }, 517 + "updated_at": { 518 + "name": "updated_at", 519 + "type": "timestamp", 520 + "primaryKey": false, 521 + "notNull": true, 522 + "default": "now()" 523 + } 524 + }, 525 + "indexes": {}, 526 + "foreignKeys": {}, 527 + "compositePrimaryKeys": {}, 528 + "uniqueConstraints": { 529 + "users_did_unique": { 530 + "name": "users_did_unique", 531 + "nullsNotDistinct": false, 532 + "columns": [ 533 + "did" 534 + ] 535 + }, 536 + "users_handle_unique": { 537 + "name": "users_handle_unique", 538 + "nullsNotDistinct": false, 539 + "columns": [ 540 + "handle" 541 + ] 542 + } 543 + }, 544 + "policies": {}, 545 + "checkConstraints": {}, 546 + "isRLSEnabled": false 547 + }, 548 + "public.variables": { 549 + "name": "variables", 550 + "schema": "", 551 + "columns": { 552 + "id": { 553 + "name": "id", 554 + "type": "text", 555 + "primaryKey": true, 556 + "notNull": true, 557 + "default": "variable_id()" 558 + }, 559 + "name": { 560 + "name": "name", 561 + "type": "text", 562 + "primaryKey": false, 563 + "notNull": true 564 + }, 565 + "value": { 566 + "name": "value", 567 + "type": "text", 568 + "primaryKey": false, 569 + "notNull": true 570 + }, 571 + "created_at": { 572 + "name": "created_at", 573 + "type": "timestamp", 574 + "primaryKey": false, 575 + "notNull": true, 576 + "default": "now()" 577 + }, 578 + "updated_at": { 579 + "name": "updated_at", 580 + "type": "timestamp", 581 + "primaryKey": false, 582 + "notNull": true, 583 + "default": "now()" 584 + } 585 + }, 586 + "indexes": {}, 587 + "foreignKeys": {}, 588 + "compositePrimaryKeys": {}, 589 + "uniqueConstraints": {}, 590 + "policies": {}, 591 + "checkConstraints": {}, 592 + "isRLSEnabled": false 593 + }, 594 + "public.volumes": { 595 + "name": "volumes", 596 + "schema": "", 597 + "columns": { 598 + "id": { 599 + "name": "id", 600 + "type": "text", 601 + "primaryKey": true, 602 + "notNull": true, 603 + "default": "volume_id()" 604 + }, 605 + "slug": { 606 + "name": "slug", 607 + "type": "text", 608 + "primaryKey": false, 609 + "notNull": true 610 + }, 611 + "size": { 612 + "name": "size", 613 + "type": "integer", 614 + "primaryKey": false, 615 + "notNull": true 616 + }, 617 + "size_unit": { 618 + "name": "size_unit", 619 + "type": "text", 620 + "primaryKey": false, 621 + "notNull": true 622 + }, 623 + "created_at": { 624 + "name": "created_at", 625 + "type": "timestamp", 626 + "primaryKey": false, 627 + "notNull": true, 628 + "default": "now()" 629 + }, 630 + "updated_at": { 631 + "name": "updated_at", 632 + "type": "timestamp", 633 + "primaryKey": false, 634 + "notNull": true, 635 + "default": "now()" 636 + } 637 + }, 638 + "indexes": {}, 639 + "foreignKeys": {}, 640 + "compositePrimaryKeys": {}, 641 + "uniqueConstraints": { 642 + "volumes_slug_unique": { 643 + "name": "volumes_slug_unique", 644 + "nullsNotDistinct": false, 645 + "columns": [ 646 + "slug" 647 + ] 648 + } 649 + }, 650 + "policies": {}, 651 + "checkConstraints": {}, 652 + "isRLSEnabled": false 653 + } 654 + }, 655 + "enums": {}, 656 + "schemas": {}, 657 + "sequences": {}, 658 + "roles": {}, 659 + "policies": {}, 660 + "views": {}, 661 + "_meta": { 662 + "columns": {}, 663 + "schemas": {}, 664 + "tables": {} 665 + } 666 + }
+725
apps/cf-sandbox/drizzle/meta/0007_snapshot.json
··· 1 + { 2 + "id": "401d6791-4767-47c6-8ef4-5409d022c72a", 3 + "prevId": "1f74f79d-40cf-43b5-a0af-2e962541d315", 4 + "version": "7", 5 + "dialect": "postgresql", 6 + "tables": { 7 + "public.authorized_keys": { 8 + "name": "authorized_keys", 9 + "schema": "", 10 + "columns": { 11 + "id": { 12 + "name": "id", 13 + "type": "text", 14 + "primaryKey": true, 15 + "notNull": true, 16 + "default": "xata_id()" 17 + }, 18 + "sandbox_id": { 19 + "name": "sandbox_id", 20 + "type": "text", 21 + "primaryKey": false, 22 + "notNull": false 23 + }, 24 + "public_key": { 25 + "name": "public_key", 26 + "type": "text", 27 + "primaryKey": false, 28 + "notNull": true 29 + }, 30 + "created_at": { 31 + "name": "created_at", 32 + "type": "timestamp", 33 + "primaryKey": false, 34 + "notNull": true, 35 + "default": "now()" 36 + } 37 + }, 38 + "indexes": {}, 39 + "foreignKeys": { 40 + "authorized_keys_sandbox_id_sandboxes_id_fk": { 41 + "name": "authorized_keys_sandbox_id_sandboxes_id_fk", 42 + "tableFrom": "authorized_keys", 43 + "tableTo": "sandboxes", 44 + "columnsFrom": [ 45 + "sandbox_id" 46 + ], 47 + "columnsTo": [ 48 + "id" 49 + ], 50 + "onDelete": "no action", 51 + "onUpdate": "no action" 52 + } 53 + }, 54 + "compositePrimaryKeys": {}, 55 + "uniqueConstraints": {}, 56 + "policies": {}, 57 + "checkConstraints": {}, 58 + "isRLSEnabled": false 59 + }, 60 + "public.sandbox_secrets": { 61 + "name": "sandbox_secrets", 62 + "schema": "", 63 + "columns": { 64 + "id": { 65 + "name": "id", 66 + "type": "text", 67 + "primaryKey": true, 68 + "notNull": true, 69 + "default": "xata_id()" 70 + }, 71 + "sandbox_id": { 72 + "name": "sandbox_id", 73 + "type": "text", 74 + "primaryKey": false, 75 + "notNull": true 76 + }, 77 + "secret_id": { 78 + "name": "secret_id", 79 + "type": "text", 80 + "primaryKey": false, 81 + "notNull": true 82 + } 83 + }, 84 + "indexes": { 85 + "unique_sandbox_secret": { 86 + "name": "unique_sandbox_secret", 87 + "columns": [ 88 + { 89 + "expression": "sandbox_id", 90 + "isExpression": false, 91 + "asc": true, 92 + "nulls": "last" 93 + }, 94 + { 95 + "expression": "secret_id", 96 + "isExpression": false, 97 + "asc": true, 98 + "nulls": "last" 99 + } 100 + ], 101 + "isUnique": true, 102 + "concurrently": false, 103 + "method": "btree", 104 + "with": {} 105 + } 106 + }, 107 + "foreignKeys": { 108 + "sandbox_secrets_sandbox_id_sandboxes_id_fk": { 109 + "name": "sandbox_secrets_sandbox_id_sandboxes_id_fk", 110 + "tableFrom": "sandbox_secrets", 111 + "tableTo": "sandboxes", 112 + "columnsFrom": [ 113 + "sandbox_id" 114 + ], 115 + "columnsTo": [ 116 + "id" 117 + ], 118 + "onDelete": "no action", 119 + "onUpdate": "no action" 120 + }, 121 + "sandbox_secrets_secret_id_secrets_id_fk": { 122 + "name": "sandbox_secrets_secret_id_secrets_id_fk", 123 + "tableFrom": "sandbox_secrets", 124 + "tableTo": "secrets", 125 + "columnsFrom": [ 126 + "secret_id" 127 + ], 128 + "columnsTo": [ 129 + "id" 130 + ], 131 + "onDelete": "no action", 132 + "onUpdate": "no action" 133 + } 134 + }, 135 + "compositePrimaryKeys": {}, 136 + "uniqueConstraints": {}, 137 + "policies": {}, 138 + "checkConstraints": {}, 139 + "isRLSEnabled": false 140 + }, 141 + "public.sandbox_variables": { 142 + "name": "sandbox_variables", 143 + "schema": "", 144 + "columns": { 145 + "id": { 146 + "name": "id", 147 + "type": "text", 148 + "primaryKey": true, 149 + "notNull": true, 150 + "default": "xata_id()" 151 + }, 152 + "sandbox_id": { 153 + "name": "sandbox_id", 154 + "type": "text", 155 + "primaryKey": false, 156 + "notNull": true 157 + }, 158 + "variable_id": { 159 + "name": "variable_id", 160 + "type": "text", 161 + "primaryKey": false, 162 + "notNull": true 163 + } 164 + }, 165 + "indexes": { 166 + "unique_sandbox_variables": { 167 + "name": "unique_sandbox_variables", 168 + "columns": [ 169 + { 170 + "expression": "sandbox_id", 171 + "isExpression": false, 172 + "asc": true, 173 + "nulls": "last" 174 + }, 175 + { 176 + "expression": "variable_id", 177 + "isExpression": false, 178 + "asc": true, 179 + "nulls": "last" 180 + } 181 + ], 182 + "isUnique": true, 183 + "concurrently": false, 184 + "method": "btree", 185 + "with": {} 186 + } 187 + }, 188 + "foreignKeys": { 189 + "sandbox_variables_sandbox_id_sandboxes_id_fk": { 190 + "name": "sandbox_variables_sandbox_id_sandboxes_id_fk", 191 + "tableFrom": "sandbox_variables", 192 + "tableTo": "sandboxes", 193 + "columnsFrom": [ 194 + "sandbox_id" 195 + ], 196 + "columnsTo": [ 197 + "id" 198 + ], 199 + "onDelete": "no action", 200 + "onUpdate": "no action" 201 + }, 202 + "sandbox_variables_variable_id_variables_id_fk": { 203 + "name": "sandbox_variables_variable_id_variables_id_fk", 204 + "tableFrom": "sandbox_variables", 205 + "tableTo": "variables", 206 + "columnsFrom": [ 207 + "variable_id" 208 + ], 209 + "columnsTo": [ 210 + "id" 211 + ], 212 + "onDelete": "no action", 213 + "onUpdate": "no action" 214 + } 215 + }, 216 + "compositePrimaryKeys": {}, 217 + "uniqueConstraints": {}, 218 + "policies": {}, 219 + "checkConstraints": {}, 220 + "isRLSEnabled": false 221 + }, 222 + "public.sandbox_volumes": { 223 + "name": "sandbox_volumes", 224 + "schema": "", 225 + "columns": { 226 + "id": { 227 + "name": "id", 228 + "type": "text", 229 + "primaryKey": true, 230 + "notNull": true, 231 + "default": "xata_id()" 232 + }, 233 + "sandbox_id": { 234 + "name": "sandbox_id", 235 + "type": "text", 236 + "primaryKey": false, 237 + "notNull": true 238 + }, 239 + "volume_id": { 240 + "name": "volume_id", 241 + "type": "text", 242 + "primaryKey": false, 243 + "notNull": true 244 + } 245 + }, 246 + "indexes": {}, 247 + "foreignKeys": { 248 + "sandbox_volumes_sandbox_id_sandboxes_id_fk": { 249 + "name": "sandbox_volumes_sandbox_id_sandboxes_id_fk", 250 + "tableFrom": "sandbox_volumes", 251 + "tableTo": "sandboxes", 252 + "columnsFrom": [ 253 + "sandbox_id" 254 + ], 255 + "columnsTo": [ 256 + "id" 257 + ], 258 + "onDelete": "no action", 259 + "onUpdate": "no action" 260 + }, 261 + "sandbox_volumes_volume_id_volumes_id_fk": { 262 + "name": "sandbox_volumes_volume_id_volumes_id_fk", 263 + "tableFrom": "sandbox_volumes", 264 + "tableTo": "volumes", 265 + "columnsFrom": [ 266 + "volume_id" 267 + ], 268 + "columnsTo": [ 269 + "id" 270 + ], 271 + "onDelete": "no action", 272 + "onUpdate": "no action" 273 + } 274 + }, 275 + "compositePrimaryKeys": {}, 276 + "uniqueConstraints": {}, 277 + "policies": {}, 278 + "checkConstraints": {}, 279 + "isRLSEnabled": false 280 + }, 281 + "public.sandboxes": { 282 + "name": "sandboxes", 283 + "schema": "", 284 + "columns": { 285 + "id": { 286 + "name": "id", 287 + "type": "text", 288 + "primaryKey": true, 289 + "notNull": true, 290 + "default": "sandbox_id()" 291 + }, 292 + "base": { 293 + "name": "base", 294 + "type": "text", 295 + "primaryKey": false, 296 + "notNull": false 297 + }, 298 + "name": { 299 + "name": "name", 300 + "type": "text", 301 + "primaryKey": false, 302 + "notNull": true 303 + }, 304 + "display_name": { 305 + "name": "display_name", 306 + "type": "text", 307 + "primaryKey": false, 308 + "notNull": false 309 + }, 310 + "uri": { 311 + "name": "uri", 312 + "type": "text", 313 + "primaryKey": false, 314 + "notNull": false 315 + }, 316 + "provider": { 317 + "name": "provider", 318 + "type": "text", 319 + "primaryKey": false, 320 + "notNull": true, 321 + "default": "'cloudflare'" 322 + }, 323 + "description": { 324 + "name": "description", 325 + "type": "text", 326 + "primaryKey": false, 327 + "notNull": false 328 + }, 329 + "logo": { 330 + "name": "logo", 331 + "type": "text", 332 + "primaryKey": false, 333 + "notNull": false 334 + }, 335 + "readme": { 336 + "name": "readme", 337 + "type": "text", 338 + "primaryKey": false, 339 + "notNull": false 340 + }, 341 + "public_key": { 342 + "name": "public_key", 343 + "type": "text", 344 + "primaryKey": false, 345 + "notNull": true 346 + }, 347 + "user_id": { 348 + "name": "user_id", 349 + "type": "text", 350 + "primaryKey": false, 351 + "notNull": false 352 + }, 353 + "instance_type": { 354 + "name": "instance_type", 355 + "type": "text", 356 + "primaryKey": false, 357 + "notNull": false 358 + }, 359 + "vcpus": { 360 + "name": "vcpus", 361 + "type": "integer", 362 + "primaryKey": false, 363 + "notNull": false 364 + }, 365 + "memory": { 366 + "name": "memory", 367 + "type": "integer", 368 + "primaryKey": false, 369 + "notNull": false 370 + }, 371 + "disk": { 372 + "name": "disk", 373 + "type": "integer", 374 + "primaryKey": false, 375 + "notNull": false 376 + }, 377 + "status": { 378 + "name": "status", 379 + "type": "text", 380 + "primaryKey": false, 381 + "notNull": true 382 + }, 383 + "keep_alive": { 384 + "name": "keep_alive", 385 + "type": "boolean", 386 + "primaryKey": false, 387 + "notNull": true, 388 + "default": false 389 + }, 390 + "sleep_after": { 391 + "name": "sleep_after", 392 + "type": "text", 393 + "primaryKey": false, 394 + "notNull": false 395 + }, 396 + "sandbox_id": { 397 + "name": "sandbox_id", 398 + "type": "text", 399 + "primaryKey": false, 400 + "notNull": false 401 + }, 402 + "created_at": { 403 + "name": "created_at", 404 + "type": "timestamp", 405 + "primaryKey": false, 406 + "notNull": true, 407 + "default": "now()" 408 + }, 409 + "updated_at": { 410 + "name": "updated_at", 411 + "type": "timestamp", 412 + "primaryKey": false, 413 + "notNull": true, 414 + "default": "now()" 415 + } 416 + }, 417 + "indexes": {}, 418 + "foreignKeys": { 419 + "sandboxes_user_id_users_id_fk": { 420 + "name": "sandboxes_user_id_users_id_fk", 421 + "tableFrom": "sandboxes", 422 + "tableTo": "users", 423 + "columnsFrom": [ 424 + "user_id" 425 + ], 426 + "columnsTo": [ 427 + "id" 428 + ], 429 + "onDelete": "no action", 430 + "onUpdate": "no action" 431 + } 432 + }, 433 + "compositePrimaryKeys": {}, 434 + "uniqueConstraints": { 435 + "sandboxes_name_unique": { 436 + "name": "sandboxes_name_unique", 437 + "nullsNotDistinct": false, 438 + "columns": [ 439 + "name" 440 + ] 441 + }, 442 + "sandboxes_uri_unique": { 443 + "name": "sandboxes_uri_unique", 444 + "nullsNotDistinct": false, 445 + "columns": [ 446 + "uri" 447 + ] 448 + } 449 + }, 450 + "policies": {}, 451 + "checkConstraints": {}, 452 + "isRLSEnabled": false 453 + }, 454 + "public.secrets": { 455 + "name": "secrets", 456 + "schema": "", 457 + "columns": { 458 + "id": { 459 + "name": "id", 460 + "type": "text", 461 + "primaryKey": true, 462 + "notNull": true, 463 + "default": "secret_id()" 464 + }, 465 + "name": { 466 + "name": "name", 467 + "type": "text", 468 + "primaryKey": false, 469 + "notNull": true 470 + }, 471 + "value": { 472 + "name": "value", 473 + "type": "text", 474 + "primaryKey": false, 475 + "notNull": true 476 + }, 477 + "created_at": { 478 + "name": "created_at", 479 + "type": "timestamp", 480 + "primaryKey": false, 481 + "notNull": true, 482 + "default": "now()" 483 + } 484 + }, 485 + "indexes": {}, 486 + "foreignKeys": {}, 487 + "compositePrimaryKeys": {}, 488 + "uniqueConstraints": {}, 489 + "policies": {}, 490 + "checkConstraints": {}, 491 + "isRLSEnabled": false 492 + }, 493 + "public.snapshots": { 494 + "name": "snapshots", 495 + "schema": "", 496 + "columns": { 497 + "id": { 498 + "name": "id", 499 + "type": "text", 500 + "primaryKey": true, 501 + "notNull": true, 502 + "default": "snapshot_id()" 503 + }, 504 + "slug": { 505 + "name": "slug", 506 + "type": "text", 507 + "primaryKey": false, 508 + "notNull": true 509 + }, 510 + "created_at": { 511 + "name": "created_at", 512 + "type": "timestamp", 513 + "primaryKey": false, 514 + "notNull": true, 515 + "default": "now()" 516 + } 517 + }, 518 + "indexes": {}, 519 + "foreignKeys": {}, 520 + "compositePrimaryKeys": {}, 521 + "uniqueConstraints": { 522 + "snapshots_slug_unique": { 523 + "name": "snapshots_slug_unique", 524 + "nullsNotDistinct": false, 525 + "columns": [ 526 + "slug" 527 + ] 528 + } 529 + }, 530 + "policies": {}, 531 + "checkConstraints": {}, 532 + "isRLSEnabled": false 533 + }, 534 + "public.users": { 535 + "name": "users", 536 + "schema": "", 537 + "columns": { 538 + "id": { 539 + "name": "id", 540 + "type": "text", 541 + "primaryKey": true, 542 + "notNull": true, 543 + "default": "xata_id()" 544 + }, 545 + "did": { 546 + "name": "did", 547 + "type": "text", 548 + "primaryKey": false, 549 + "notNull": true 550 + }, 551 + "display_name": { 552 + "name": "display_name", 553 + "type": "text", 554 + "primaryKey": false, 555 + "notNull": false 556 + }, 557 + "handle": { 558 + "name": "handle", 559 + "type": "text", 560 + "primaryKey": false, 561 + "notNull": true 562 + }, 563 + "avatar": { 564 + "name": "avatar", 565 + "type": "text", 566 + "primaryKey": false, 567 + "notNull": false 568 + }, 569 + "created_at": { 570 + "name": "created_at", 571 + "type": "timestamp", 572 + "primaryKey": false, 573 + "notNull": true, 574 + "default": "now()" 575 + }, 576 + "updated_at": { 577 + "name": "updated_at", 578 + "type": "timestamp", 579 + "primaryKey": false, 580 + "notNull": true, 581 + "default": "now()" 582 + } 583 + }, 584 + "indexes": {}, 585 + "foreignKeys": {}, 586 + "compositePrimaryKeys": {}, 587 + "uniqueConstraints": { 588 + "users_did_unique": { 589 + "name": "users_did_unique", 590 + "nullsNotDistinct": false, 591 + "columns": [ 592 + "did" 593 + ] 594 + }, 595 + "users_handle_unique": { 596 + "name": "users_handle_unique", 597 + "nullsNotDistinct": false, 598 + "columns": [ 599 + "handle" 600 + ] 601 + } 602 + }, 603 + "policies": {}, 604 + "checkConstraints": {}, 605 + "isRLSEnabled": false 606 + }, 607 + "public.variables": { 608 + "name": "variables", 609 + "schema": "", 610 + "columns": { 611 + "id": { 612 + "name": "id", 613 + "type": "text", 614 + "primaryKey": true, 615 + "notNull": true, 616 + "default": "variable_id()" 617 + }, 618 + "name": { 619 + "name": "name", 620 + "type": "text", 621 + "primaryKey": false, 622 + "notNull": true 623 + }, 624 + "value": { 625 + "name": "value", 626 + "type": "text", 627 + "primaryKey": false, 628 + "notNull": true 629 + }, 630 + "created_at": { 631 + "name": "created_at", 632 + "type": "timestamp", 633 + "primaryKey": false, 634 + "notNull": true, 635 + "default": "now()" 636 + }, 637 + "updated_at": { 638 + "name": "updated_at", 639 + "type": "timestamp", 640 + "primaryKey": false, 641 + "notNull": true, 642 + "default": "now()" 643 + } 644 + }, 645 + "indexes": {}, 646 + "foreignKeys": {}, 647 + "compositePrimaryKeys": {}, 648 + "uniqueConstraints": {}, 649 + "policies": {}, 650 + "checkConstraints": {}, 651 + "isRLSEnabled": false 652 + }, 653 + "public.volumes": { 654 + "name": "volumes", 655 + "schema": "", 656 + "columns": { 657 + "id": { 658 + "name": "id", 659 + "type": "text", 660 + "primaryKey": true, 661 + "notNull": true, 662 + "default": "volume_id()" 663 + }, 664 + "slug": { 665 + "name": "slug", 666 + "type": "text", 667 + "primaryKey": false, 668 + "notNull": true 669 + }, 670 + "size": { 671 + "name": "size", 672 + "type": "integer", 673 + "primaryKey": false, 674 + "notNull": true 675 + }, 676 + "size_unit": { 677 + "name": "size_unit", 678 + "type": "text", 679 + "primaryKey": false, 680 + "notNull": true 681 + }, 682 + "created_at": { 683 + "name": "created_at", 684 + "type": "timestamp", 685 + "primaryKey": false, 686 + "notNull": true, 687 + "default": "now()" 688 + }, 689 + "updated_at": { 690 + "name": "updated_at", 691 + "type": "timestamp", 692 + "primaryKey": false, 693 + "notNull": true, 694 + "default": "now()" 695 + } 696 + }, 697 + "indexes": {}, 698 + "foreignKeys": {}, 699 + "compositePrimaryKeys": {}, 700 + "uniqueConstraints": { 701 + "volumes_slug_unique": { 702 + "name": "volumes_slug_unique", 703 + "nullsNotDistinct": false, 704 + "columns": [ 705 + "slug" 706 + ] 707 + } 708 + }, 709 + "policies": {}, 710 + "checkConstraints": {}, 711 + "isRLSEnabled": false 712 + } 713 + }, 714 + "enums": {}, 715 + "schemas": {}, 716 + "sequences": {}, 717 + "roles": {}, 718 + "policies": {}, 719 + "views": {}, 720 + "_meta": { 721 + "columns": {}, 722 + "schemas": {}, 723 + "tables": {} 724 + } 725 + }
+732
apps/cf-sandbox/drizzle/meta/0008_snapshot.json
··· 1 + { 2 + "id": "abd2ef17-5288-4dba-804d-72c62f3f1ca1", 3 + "prevId": "401d6791-4767-47c6-8ef4-5409d022c72a", 4 + "version": "7", 5 + "dialect": "postgresql", 6 + "tables": { 7 + "public.authorized_keys": { 8 + "name": "authorized_keys", 9 + "schema": "", 10 + "columns": { 11 + "id": { 12 + "name": "id", 13 + "type": "text", 14 + "primaryKey": true, 15 + "notNull": true, 16 + "default": "xata_id()" 17 + }, 18 + "sandbox_id": { 19 + "name": "sandbox_id", 20 + "type": "text", 21 + "primaryKey": false, 22 + "notNull": false 23 + }, 24 + "public_key": { 25 + "name": "public_key", 26 + "type": "text", 27 + "primaryKey": false, 28 + "notNull": true 29 + }, 30 + "created_at": { 31 + "name": "created_at", 32 + "type": "timestamp", 33 + "primaryKey": false, 34 + "notNull": true, 35 + "default": "now()" 36 + } 37 + }, 38 + "indexes": {}, 39 + "foreignKeys": { 40 + "authorized_keys_sandbox_id_sandboxes_id_fk": { 41 + "name": "authorized_keys_sandbox_id_sandboxes_id_fk", 42 + "tableFrom": "authorized_keys", 43 + "tableTo": "sandboxes", 44 + "columnsFrom": [ 45 + "sandbox_id" 46 + ], 47 + "columnsTo": [ 48 + "id" 49 + ], 50 + "onDelete": "no action", 51 + "onUpdate": "no action" 52 + } 53 + }, 54 + "compositePrimaryKeys": {}, 55 + "uniqueConstraints": {}, 56 + "policies": {}, 57 + "checkConstraints": {}, 58 + "isRLSEnabled": false 59 + }, 60 + "public.sandbox_secrets": { 61 + "name": "sandbox_secrets", 62 + "schema": "", 63 + "columns": { 64 + "id": { 65 + "name": "id", 66 + "type": "text", 67 + "primaryKey": true, 68 + "notNull": true, 69 + "default": "xata_id()" 70 + }, 71 + "sandbox_id": { 72 + "name": "sandbox_id", 73 + "type": "text", 74 + "primaryKey": false, 75 + "notNull": true 76 + }, 77 + "secret_id": { 78 + "name": "secret_id", 79 + "type": "text", 80 + "primaryKey": false, 81 + "notNull": true 82 + } 83 + }, 84 + "indexes": { 85 + "unique_sandbox_secret": { 86 + "name": "unique_sandbox_secret", 87 + "columns": [ 88 + { 89 + "expression": "sandbox_id", 90 + "isExpression": false, 91 + "asc": true, 92 + "nulls": "last" 93 + }, 94 + { 95 + "expression": "secret_id", 96 + "isExpression": false, 97 + "asc": true, 98 + "nulls": "last" 99 + } 100 + ], 101 + "isUnique": true, 102 + "concurrently": false, 103 + "method": "btree", 104 + "with": {} 105 + } 106 + }, 107 + "foreignKeys": { 108 + "sandbox_secrets_sandbox_id_sandboxes_id_fk": { 109 + "name": "sandbox_secrets_sandbox_id_sandboxes_id_fk", 110 + "tableFrom": "sandbox_secrets", 111 + "tableTo": "sandboxes", 112 + "columnsFrom": [ 113 + "sandbox_id" 114 + ], 115 + "columnsTo": [ 116 + "id" 117 + ], 118 + "onDelete": "no action", 119 + "onUpdate": "no action" 120 + }, 121 + "sandbox_secrets_secret_id_secrets_id_fk": { 122 + "name": "sandbox_secrets_secret_id_secrets_id_fk", 123 + "tableFrom": "sandbox_secrets", 124 + "tableTo": "secrets", 125 + "columnsFrom": [ 126 + "secret_id" 127 + ], 128 + "columnsTo": [ 129 + "id" 130 + ], 131 + "onDelete": "no action", 132 + "onUpdate": "no action" 133 + } 134 + }, 135 + "compositePrimaryKeys": {}, 136 + "uniqueConstraints": {}, 137 + "policies": {}, 138 + "checkConstraints": {}, 139 + "isRLSEnabled": false 140 + }, 141 + "public.sandbox_variables": { 142 + "name": "sandbox_variables", 143 + "schema": "", 144 + "columns": { 145 + "id": { 146 + "name": "id", 147 + "type": "text", 148 + "primaryKey": true, 149 + "notNull": true, 150 + "default": "xata_id()" 151 + }, 152 + "sandbox_id": { 153 + "name": "sandbox_id", 154 + "type": "text", 155 + "primaryKey": false, 156 + "notNull": true 157 + }, 158 + "variable_id": { 159 + "name": "variable_id", 160 + "type": "text", 161 + "primaryKey": false, 162 + "notNull": true 163 + } 164 + }, 165 + "indexes": { 166 + "unique_sandbox_variables": { 167 + "name": "unique_sandbox_variables", 168 + "columns": [ 169 + { 170 + "expression": "sandbox_id", 171 + "isExpression": false, 172 + "asc": true, 173 + "nulls": "last" 174 + }, 175 + { 176 + "expression": "variable_id", 177 + "isExpression": false, 178 + "asc": true, 179 + "nulls": "last" 180 + } 181 + ], 182 + "isUnique": true, 183 + "concurrently": false, 184 + "method": "btree", 185 + "with": {} 186 + } 187 + }, 188 + "foreignKeys": { 189 + "sandbox_variables_sandbox_id_sandboxes_id_fk": { 190 + "name": "sandbox_variables_sandbox_id_sandboxes_id_fk", 191 + "tableFrom": "sandbox_variables", 192 + "tableTo": "sandboxes", 193 + "columnsFrom": [ 194 + "sandbox_id" 195 + ], 196 + "columnsTo": [ 197 + "id" 198 + ], 199 + "onDelete": "no action", 200 + "onUpdate": "no action" 201 + }, 202 + "sandbox_variables_variable_id_variables_id_fk": { 203 + "name": "sandbox_variables_variable_id_variables_id_fk", 204 + "tableFrom": "sandbox_variables", 205 + "tableTo": "variables", 206 + "columnsFrom": [ 207 + "variable_id" 208 + ], 209 + "columnsTo": [ 210 + "id" 211 + ], 212 + "onDelete": "no action", 213 + "onUpdate": "no action" 214 + } 215 + }, 216 + "compositePrimaryKeys": {}, 217 + "uniqueConstraints": {}, 218 + "policies": {}, 219 + "checkConstraints": {}, 220 + "isRLSEnabled": false 221 + }, 222 + "public.sandbox_volumes": { 223 + "name": "sandbox_volumes", 224 + "schema": "", 225 + "columns": { 226 + "id": { 227 + "name": "id", 228 + "type": "text", 229 + "primaryKey": true, 230 + "notNull": true, 231 + "default": "xata_id()" 232 + }, 233 + "sandbox_id": { 234 + "name": "sandbox_id", 235 + "type": "text", 236 + "primaryKey": false, 237 + "notNull": true 238 + }, 239 + "volume_id": { 240 + "name": "volume_id", 241 + "type": "text", 242 + "primaryKey": false, 243 + "notNull": true 244 + } 245 + }, 246 + "indexes": {}, 247 + "foreignKeys": { 248 + "sandbox_volumes_sandbox_id_sandboxes_id_fk": { 249 + "name": "sandbox_volumes_sandbox_id_sandboxes_id_fk", 250 + "tableFrom": "sandbox_volumes", 251 + "tableTo": "sandboxes", 252 + "columnsFrom": [ 253 + "sandbox_id" 254 + ], 255 + "columnsTo": [ 256 + "id" 257 + ], 258 + "onDelete": "no action", 259 + "onUpdate": "no action" 260 + }, 261 + "sandbox_volumes_volume_id_volumes_id_fk": { 262 + "name": "sandbox_volumes_volume_id_volumes_id_fk", 263 + "tableFrom": "sandbox_volumes", 264 + "tableTo": "volumes", 265 + "columnsFrom": [ 266 + "volume_id" 267 + ], 268 + "columnsTo": [ 269 + "id" 270 + ], 271 + "onDelete": "no action", 272 + "onUpdate": "no action" 273 + } 274 + }, 275 + "compositePrimaryKeys": {}, 276 + "uniqueConstraints": {}, 277 + "policies": {}, 278 + "checkConstraints": {}, 279 + "isRLSEnabled": false 280 + }, 281 + "public.sandboxes": { 282 + "name": "sandboxes", 283 + "schema": "", 284 + "columns": { 285 + "id": { 286 + "name": "id", 287 + "type": "text", 288 + "primaryKey": true, 289 + "notNull": true, 290 + "default": "sandbox_id()" 291 + }, 292 + "base": { 293 + "name": "base", 294 + "type": "text", 295 + "primaryKey": false, 296 + "notNull": false 297 + }, 298 + "name": { 299 + "name": "name", 300 + "type": "text", 301 + "primaryKey": false, 302 + "notNull": true 303 + }, 304 + "display_name": { 305 + "name": "display_name", 306 + "type": "text", 307 + "primaryKey": false, 308 + "notNull": false 309 + }, 310 + "uri": { 311 + "name": "uri", 312 + "type": "text", 313 + "primaryKey": false, 314 + "notNull": false 315 + }, 316 + "provider": { 317 + "name": "provider", 318 + "type": "text", 319 + "primaryKey": false, 320 + "notNull": true, 321 + "default": "'cloudflare'" 322 + }, 323 + "description": { 324 + "name": "description", 325 + "type": "text", 326 + "primaryKey": false, 327 + "notNull": false 328 + }, 329 + "logo": { 330 + "name": "logo", 331 + "type": "text", 332 + "primaryKey": false, 333 + "notNull": false 334 + }, 335 + "readme": { 336 + "name": "readme", 337 + "type": "text", 338 + "primaryKey": false, 339 + "notNull": false 340 + }, 341 + "public_key": { 342 + "name": "public_key", 343 + "type": "text", 344 + "primaryKey": false, 345 + "notNull": true 346 + }, 347 + "user_id": { 348 + "name": "user_id", 349 + "type": "text", 350 + "primaryKey": false, 351 + "notNull": false 352 + }, 353 + "instance_type": { 354 + "name": "instance_type", 355 + "type": "text", 356 + "primaryKey": false, 357 + "notNull": false 358 + }, 359 + "vcpus": { 360 + "name": "vcpus", 361 + "type": "integer", 362 + "primaryKey": false, 363 + "notNull": false 364 + }, 365 + "memory": { 366 + "name": "memory", 367 + "type": "integer", 368 + "primaryKey": false, 369 + "notNull": false 370 + }, 371 + "disk": { 372 + "name": "disk", 373 + "type": "integer", 374 + "primaryKey": false, 375 + "notNull": false 376 + }, 377 + "status": { 378 + "name": "status", 379 + "type": "text", 380 + "primaryKey": false, 381 + "notNull": true 382 + }, 383 + "keep_alive": { 384 + "name": "keep_alive", 385 + "type": "boolean", 386 + "primaryKey": false, 387 + "notNull": true, 388 + "default": false 389 + }, 390 + "sleep_after": { 391 + "name": "sleep_after", 392 + "type": "text", 393 + "primaryKey": false, 394 + "notNull": false 395 + }, 396 + "sandbox_id": { 397 + "name": "sandbox_id", 398 + "type": "text", 399 + "primaryKey": false, 400 + "notNull": false 401 + }, 402 + "installs": { 403 + "name": "installs", 404 + "type": "integer", 405 + "primaryKey": false, 406 + "notNull": true, 407 + "default": 0 408 + }, 409 + "created_at": { 410 + "name": "created_at", 411 + "type": "timestamp", 412 + "primaryKey": false, 413 + "notNull": true, 414 + "default": "now()" 415 + }, 416 + "updated_at": { 417 + "name": "updated_at", 418 + "type": "timestamp", 419 + "primaryKey": false, 420 + "notNull": true, 421 + "default": "now()" 422 + } 423 + }, 424 + "indexes": {}, 425 + "foreignKeys": { 426 + "sandboxes_user_id_users_id_fk": { 427 + "name": "sandboxes_user_id_users_id_fk", 428 + "tableFrom": "sandboxes", 429 + "tableTo": "users", 430 + "columnsFrom": [ 431 + "user_id" 432 + ], 433 + "columnsTo": [ 434 + "id" 435 + ], 436 + "onDelete": "no action", 437 + "onUpdate": "no action" 438 + } 439 + }, 440 + "compositePrimaryKeys": {}, 441 + "uniqueConstraints": { 442 + "sandboxes_name_unique": { 443 + "name": "sandboxes_name_unique", 444 + "nullsNotDistinct": false, 445 + "columns": [ 446 + "name" 447 + ] 448 + }, 449 + "sandboxes_uri_unique": { 450 + "name": "sandboxes_uri_unique", 451 + "nullsNotDistinct": false, 452 + "columns": [ 453 + "uri" 454 + ] 455 + } 456 + }, 457 + "policies": {}, 458 + "checkConstraints": {}, 459 + "isRLSEnabled": false 460 + }, 461 + "public.secrets": { 462 + "name": "secrets", 463 + "schema": "", 464 + "columns": { 465 + "id": { 466 + "name": "id", 467 + "type": "text", 468 + "primaryKey": true, 469 + "notNull": true, 470 + "default": "secret_id()" 471 + }, 472 + "name": { 473 + "name": "name", 474 + "type": "text", 475 + "primaryKey": false, 476 + "notNull": true 477 + }, 478 + "value": { 479 + "name": "value", 480 + "type": "text", 481 + "primaryKey": false, 482 + "notNull": true 483 + }, 484 + "created_at": { 485 + "name": "created_at", 486 + "type": "timestamp", 487 + "primaryKey": false, 488 + "notNull": true, 489 + "default": "now()" 490 + } 491 + }, 492 + "indexes": {}, 493 + "foreignKeys": {}, 494 + "compositePrimaryKeys": {}, 495 + "uniqueConstraints": {}, 496 + "policies": {}, 497 + "checkConstraints": {}, 498 + "isRLSEnabled": false 499 + }, 500 + "public.snapshots": { 501 + "name": "snapshots", 502 + "schema": "", 503 + "columns": { 504 + "id": { 505 + "name": "id", 506 + "type": "text", 507 + "primaryKey": true, 508 + "notNull": true, 509 + "default": "snapshot_id()" 510 + }, 511 + "slug": { 512 + "name": "slug", 513 + "type": "text", 514 + "primaryKey": false, 515 + "notNull": true 516 + }, 517 + "created_at": { 518 + "name": "created_at", 519 + "type": "timestamp", 520 + "primaryKey": false, 521 + "notNull": true, 522 + "default": "now()" 523 + } 524 + }, 525 + "indexes": {}, 526 + "foreignKeys": {}, 527 + "compositePrimaryKeys": {}, 528 + "uniqueConstraints": { 529 + "snapshots_slug_unique": { 530 + "name": "snapshots_slug_unique", 531 + "nullsNotDistinct": false, 532 + "columns": [ 533 + "slug" 534 + ] 535 + } 536 + }, 537 + "policies": {}, 538 + "checkConstraints": {}, 539 + "isRLSEnabled": false 540 + }, 541 + "public.users": { 542 + "name": "users", 543 + "schema": "", 544 + "columns": { 545 + "id": { 546 + "name": "id", 547 + "type": "text", 548 + "primaryKey": true, 549 + "notNull": true, 550 + "default": "xata_id()" 551 + }, 552 + "did": { 553 + "name": "did", 554 + "type": "text", 555 + "primaryKey": false, 556 + "notNull": true 557 + }, 558 + "display_name": { 559 + "name": "display_name", 560 + "type": "text", 561 + "primaryKey": false, 562 + "notNull": false 563 + }, 564 + "handle": { 565 + "name": "handle", 566 + "type": "text", 567 + "primaryKey": false, 568 + "notNull": true 569 + }, 570 + "avatar": { 571 + "name": "avatar", 572 + "type": "text", 573 + "primaryKey": false, 574 + "notNull": false 575 + }, 576 + "created_at": { 577 + "name": "created_at", 578 + "type": "timestamp", 579 + "primaryKey": false, 580 + "notNull": true, 581 + "default": "now()" 582 + }, 583 + "updated_at": { 584 + "name": "updated_at", 585 + "type": "timestamp", 586 + "primaryKey": false, 587 + "notNull": true, 588 + "default": "now()" 589 + } 590 + }, 591 + "indexes": {}, 592 + "foreignKeys": {}, 593 + "compositePrimaryKeys": {}, 594 + "uniqueConstraints": { 595 + "users_did_unique": { 596 + "name": "users_did_unique", 597 + "nullsNotDistinct": false, 598 + "columns": [ 599 + "did" 600 + ] 601 + }, 602 + "users_handle_unique": { 603 + "name": "users_handle_unique", 604 + "nullsNotDistinct": false, 605 + "columns": [ 606 + "handle" 607 + ] 608 + } 609 + }, 610 + "policies": {}, 611 + "checkConstraints": {}, 612 + "isRLSEnabled": false 613 + }, 614 + "public.variables": { 615 + "name": "variables", 616 + "schema": "", 617 + "columns": { 618 + "id": { 619 + "name": "id", 620 + "type": "text", 621 + "primaryKey": true, 622 + "notNull": true, 623 + "default": "variable_id()" 624 + }, 625 + "name": { 626 + "name": "name", 627 + "type": "text", 628 + "primaryKey": false, 629 + "notNull": true 630 + }, 631 + "value": { 632 + "name": "value", 633 + "type": "text", 634 + "primaryKey": false, 635 + "notNull": true 636 + }, 637 + "created_at": { 638 + "name": "created_at", 639 + "type": "timestamp", 640 + "primaryKey": false, 641 + "notNull": true, 642 + "default": "now()" 643 + }, 644 + "updated_at": { 645 + "name": "updated_at", 646 + "type": "timestamp", 647 + "primaryKey": false, 648 + "notNull": true, 649 + "default": "now()" 650 + } 651 + }, 652 + "indexes": {}, 653 + "foreignKeys": {}, 654 + "compositePrimaryKeys": {}, 655 + "uniqueConstraints": {}, 656 + "policies": {}, 657 + "checkConstraints": {}, 658 + "isRLSEnabled": false 659 + }, 660 + "public.volumes": { 661 + "name": "volumes", 662 + "schema": "", 663 + "columns": { 664 + "id": { 665 + "name": "id", 666 + "type": "text", 667 + "primaryKey": true, 668 + "notNull": true, 669 + "default": "volume_id()" 670 + }, 671 + "slug": { 672 + "name": "slug", 673 + "type": "text", 674 + "primaryKey": false, 675 + "notNull": true 676 + }, 677 + "size": { 678 + "name": "size", 679 + "type": "integer", 680 + "primaryKey": false, 681 + "notNull": true 682 + }, 683 + "size_unit": { 684 + "name": "size_unit", 685 + "type": "text", 686 + "primaryKey": false, 687 + "notNull": true 688 + }, 689 + "created_at": { 690 + "name": "created_at", 691 + "type": "timestamp", 692 + "primaryKey": false, 693 + "notNull": true, 694 + "default": "now()" 695 + }, 696 + "updated_at": { 697 + "name": "updated_at", 698 + "type": "timestamp", 699 + "primaryKey": false, 700 + "notNull": true, 701 + "default": "now()" 702 + } 703 + }, 704 + "indexes": {}, 705 + "foreignKeys": {}, 706 + "compositePrimaryKeys": {}, 707 + "uniqueConstraints": { 708 + "volumes_slug_unique": { 709 + "name": "volumes_slug_unique", 710 + "nullsNotDistinct": false, 711 + "columns": [ 712 + "slug" 713 + ] 714 + } 715 + }, 716 + "policies": {}, 717 + "checkConstraints": {}, 718 + "isRLSEnabled": false 719 + } 720 + }, 721 + "enums": {}, 722 + "schemas": {}, 723 + "sequences": {}, 724 + "roles": {}, 725 + "policies": {}, 726 + "views": {}, 727 + "_meta": { 728 + "columns": {}, 729 + "schemas": {}, 730 + "tables": {} 731 + } 732 + }
+28
apps/cf-sandbox/drizzle/meta/_journal.json
··· 36 36 "when": 1770799687223, 37 37 "tag": "0004_wonderful_nightcrawler", 38 38 "breakpoints": true 39 + }, 40 + { 41 + "idx": 5, 42 + "version": "7", 43 + "when": 1771080689487, 44 + "tag": "0005_flimsy_peter_parker", 45 + "breakpoints": true 46 + }, 47 + { 48 + "idx": 6, 49 + "version": "7", 50 + "when": 1771084098197, 51 + "tag": "0006_lively_black_bird", 52 + "breakpoints": true 53 + }, 54 + { 55 + "idx": 7, 56 + "version": "7", 57 + "when": 1771126453338, 58 + "tag": "0007_organic_spiral", 59 + "breakpoints": true 60 + }, 61 + { 62 + "idx": 8, 63 + "version": "7", 64 + "when": 1771135267679, 65 + "tag": "0008_futuristic_the_hand", 66 + "breakpoints": true 39 67 } 40 68 ] 41 69 }
+17
apps/cf-sandbox/src/schema/authorized-keys.ts
··· 1 + import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 + import { pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 + import sandboxes from "./sandboxes"; 4 + 5 + const authorizedKeys = pgTable("authorized_keys", { 6 + id: text("id") 7 + .primaryKey() 8 + .default(sql`xata_id()`), 9 + sandboxId: text("sandbox_id").references(() => sandboxes.id), 10 + publicKey: text("public_key").notNull(), 11 + createdAt: timestamp("created_at").defaultNow().notNull(), 12 + }); 13 + 14 + export type SelectAuthorizedKeys = InferSelectModel<typeof authorizedKeys>; 15 + export type InsertAuthorizedKeys = InferInsertModel<typeof authorizedKeys>; 16 + 17 + export default authorizedKeys;
+18 -6
apps/cf-sandbox/src/schema/sandboxes.ts
··· 1 1 import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 - import { pgTable, text, timestamp, boolean } from "drizzle-orm/pg-core"; 2 + import { 3 + pgTable, 4 + text, 5 + timestamp, 6 + boolean, 7 + integer, 8 + } from "drizzle-orm/pg-core"; 3 9 import users from "./users"; 4 10 5 11 const sandboxes = pgTable("sandboxes", { 6 12 id: text("id") 7 13 .primaryKey() 8 14 .default(sql`sandbox_id()`), 9 - base: text("base").notNull(), 15 + base: text("base"), 10 16 name: text("name").unique().notNull(), 17 + displayName: text("display_name"), 18 + uri: text("uri").unique(), 11 19 provider: text("provider").default("cloudflare").notNull(), 12 20 description: text("description"), 21 + logo: text("logo"), 22 + readme: text("readme"), 13 23 publicKey: text("public_key").notNull(), 14 - userId: text("user_id") 15 - .notNull() 16 - .references(() => users.id), 17 - instanceType: text("instance_type").notNull(), 24 + userId: text("user_id").references(() => users.id), 25 + instanceType: text("instance_type"), 26 + vcpus: integer("vcpus"), 27 + memory: integer("memory"), 28 + disk: integer("disk"), 18 29 status: text("status").notNull(), 19 30 keepAlive: boolean("keep_alive").default(false).notNull(), 20 31 sleepAfter: text("sleep_after"), 21 32 sandbox_id: text("sandbox_id"), 33 + installs: integer("installs").default(0).notNull(), 22 34 createdAt: timestamp("created_at").defaultNow().notNull(), 23 35 updatedAt: timestamp("updated_at").defaultNow().notNull(), 24 36 });
+6 -1
apps/sandbox/deno.json
··· 23 23 }, 24 24 "deploy": { 25 25 "org": "tsirysndr" 26 - } 26 + }, 27 + "allowScripts": { 28 + "allow": ["npm:esbuild@0.25.12", "npm:protobufjs@7.5.4"], 29 + "deny": ["npm:esbuild@0.18.20"] 30 + }, 31 + "nodeModulesDir": "auto" 27 32 }
+15
apps/sandbox/deno.lock
··· 2 2 "version": "5", 3 3 "specifiers": { 4 4 "jsr:@deno/sandbox@0.11": "0.11.0", 5 + "jsr:@std/assert@1": "1.0.18", 5 6 "jsr:@std/fs@^1.0.19": "1.0.22", 6 7 "jsr:@std/internal@^1.0.12": "1.0.12", 7 8 "jsr:@std/path@^1.1.2": "1.1.4", 8 9 "jsr:@std/path@^1.1.4": "1.1.4", 9 10 "npm:@daytonaio/sdk@0.141": "0.141.0_@aws-sdk+client-s3@3.988.0_@opentelemetry+api@1.9.0_ws@8.19.0", 10 11 "npm:@tsndr/cloudflare-worker-jwt@^3.2.1": "3.2.1", 12 + "npm:@types/ws@^8.18.1": "8.18.1", 11 13 "npm:@vercel/sandbox@^1.5.0": "1.5.0", 12 14 "npm:chalk@^5.6.2": "5.6.2", 13 15 "npm:consola@^3.4.2": "3.4.2", ··· 30 32 "dependencies": [ 31 33 "jsr:@std/fs", 32 34 "jsr:@std/path@^1.1.2", 35 + "npm:@types/ws", 33 36 "npm:ws", 34 37 "npm:zod@^4.1.5" 38 + ] 39 + }, 40 + "@std/assert@1.0.18": { 41 + "integrity": "270245e9c2c13b446286de475131dc688ca9abcd94fc5db41d43a219b34d1c78", 42 + "dependencies": [ 43 + "jsr:@std/internal" 35 44 ] 36 45 }, 37 46 "@std/fs@1.0.22": { ··· 1743 1752 "@types/node", 1744 1753 "pg-protocol", 1745 1754 "pg-types" 1755 + ] 1756 + }, 1757 + "@types/ws@8.18.1": { 1758 + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", 1759 + "dependencies": [ 1760 + "@types/node" 1746 1761 ] 1747 1762 }, 1748 1763 "@vercel/oidc@3.2.0": {
+17
apps/sandbox/src/schema/authorized-keys.ts
··· 1 + import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 + import { pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 + import sandboxes from "./sandboxes.ts"; 4 + 5 + const authorizedKeys = pgTable("authorized_keys", { 6 + id: text("id") 7 + .primaryKey() 8 + .default(sql`xata_id()`), 9 + sandboxId: text("sandbox_id").references(() => sandboxes.id), 10 + publicKey: text("public_key").notNull(), 11 + createdAt: timestamp("created_at").defaultNow().notNull(), 12 + }); 13 + 14 + export type SelectAuthorizedKeys = InferSelectModel<typeof authorizedKeys>; 15 + export type InsertAuthorizedKeys = InferInsertModel<typeof authorizedKeys>; 16 + 17 + export default authorizedKeys;
+17 -6
apps/sandbox/src/schema/sandboxes.ts
··· 1 1 import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 - import { pgTable, text, timestamp, boolean } from "drizzle-orm/pg-core"; 2 + import { 3 + pgTable, 4 + text, 5 + timestamp, 6 + boolean, 7 + integer, 8 + } from "drizzle-orm/pg-core"; 3 9 import users from "./users.ts"; 4 10 5 11 const sandboxes = pgTable("sandboxes", { 6 12 id: text("id") 7 13 .primaryKey() 8 14 .default(sql`sandbox_id()`), 9 - base: text("base").notNull(), 15 + base: text("base"), 10 16 name: text("name").unique().notNull(), 17 + uri: text("uri").unique(), 11 18 provider: text("provider").default("cloudflare").notNull(), 12 19 description: text("description"), 20 + logo: text("logo"), 21 + readme: text("readme"), 13 22 publicKey: text("public_key").notNull(), 14 - userId: text("user_id") 15 - .notNull() 16 - .references(() => users.id), 17 - instanceType: text("instance_type").notNull(), 23 + userId: text("user_id").references(() => users.id), 24 + instanceType: text("instance_type"), 25 + vcpus: integer("vcpus"), 26 + memory: integer("memory"), 27 + disk: integer("disk"), 18 28 status: text("status").notNull(), 19 29 keepAlive: boolean("keep_alive").default(false).notNull(), 20 30 sleepAfter: text("sleep_after"), 21 31 sandbox_id: text("sandbox_id"), 32 + installs: integer("installs").default(0).notNull(), 22 33 createdAt: timestamp("created_at").defaultNow().notNull(), 23 34 updatedAt: timestamp("updated_at").defaultNow().notNull(), 24 35 });
apps/web/.env.example

This is a binary file and will not be displayed.

+3
apps/web/.gitignore
··· 22 22 *.njsproj 23 23 *.sln 24 24 *.sw? 25 + 26 + .env 27 + .env.prod
-293
apps/web/DARK_THEME_SETUP.md
··· 1 - # Uniform Dark Theme Setup for FlyonUI + Tailwind CSS 2 - 3 - This project uses a **uniform dark theme** with a consistent `#1a1a1a` background color throughout the entire application. 4 - 5 - ## 🎨 Theme Philosophy 6 - 7 - Unlike traditional dark themes that use varying shades of gray/blue, this implementation uses a single, uniform dark background (`#06051d`) for all components, creating a cohesive and modern appearance. 8 - 9 - ## ✅ Features 10 - 11 - - **Uniform Background**: All components use the same `#06051d` background in dark mode 12 - - **Automatic Theme Detection**: Detects system preferences on first load 13 - - **Persistent Theme**: Saves user's theme choice in localStorage 14 - - **No FOUC**: Flash prevention with inline script in HTML 15 - - **Smooth Transitions**: 0.2s ease transitions between theme changes 16 - - **Theme Toggle**: Sun/moon icon button in navbar 17 - 18 - ## 📁 File Structure 19 - 20 - ``` 21 - apps/web/ 22 - ├── src/ 23 - │ ├── hooks/ 24 - │ │ └── useTheme.ts # Theme management hook 25 - │ ├── components/ 26 - │ │ ├── ThemeToggle.tsx # Toggle button component 27 - │ │ └── DarkThemeDemo.tsx # Demo of themed components 28 - │ └── index.css # Theme CSS variables & overrides 29 - ├── tailwind.config.js # Tailwind & FlyonUI configuration 30 - └── index.html # FOUC prevention script 31 - ``` 32 - 33 - ## 🎯 Color Scheme 34 - 35 - ### Light Theme 36 - ```css 37 - Background: #ffffff 38 - Text: #1f2937 39 - Secondary: #6b7280 40 - ``` 41 - 42 - ### Dark Theme (Uniform) 43 - ```css 44 - Background: #06051d /* Same everywhere */ 45 - Text: #e5e5e5 46 - Secondary: #a0a0a0 47 - ``` 48 - 49 - ### Accent Colors (Both Themes) 50 - ```css 51 - Primary: #6366f1 (Indigo) 52 - Secondary: #7c3aed (Darker Purple) 53 - Accent: #f59e0b (Amber) 54 - Success: #22c55e (Green) 55 - Warning: #fbbd23 (Yellow) 56 - Error: #ef4444 (Red) 57 - ``` 58 - 59 - ## 🚀 Quick Start 60 - 61 - ### 1. Using the Theme Toggle 62 - 63 - The theme toggle is already integrated into the navbar: 64 - 65 - ```tsx 66 - import { ThemeToggle } from "../components/ThemeToggle"; 67 - 68 - // Already added to Navbar component 69 - <ThemeToggle /> 70 - ``` 71 - 72 - ### 2. Using the useTheme Hook 73 - 74 - ```tsx 75 - import { useTheme } from "../hooks/useTheme"; 76 - 77 - function MyComponent() { 78 - const { theme, toggleTheme, isDark } = useTheme(); 79 - 80 - return ( 81 - <div> 82 - <p>Current theme: {theme}</p> 83 - <button onClick={toggleTheme}>Toggle Theme</button> 84 - {isDark && <p>Uniform dark mode is active!</p>} 85 - </div> 86 - ); 87 - } 88 - ``` 89 - 90 - ### 3. Styling Components for Dark Theme 91 - 92 - All FlyonUI components automatically work with the uniform dark theme: 93 - 94 - ```tsx 95 - // Cards - automatically uniform dark 96 - <div className="card bg-base-100"> 97 - <div className="card-body"> 98 - <h2 className="card-title text-base-content">Title</h2> 99 - <p className="text-base-content/70">Content</p> 100 - </div> 101 - </div> 102 - 103 - // Buttons - colored accents on dark 104 - <button className="btn btn-primary">Primary</button> 105 - <button className="btn btn-secondary">Secondary</button> 106 - 107 - // Forms - dark inputs with subtle borders 108 - <input className="input input-bordered" /> 109 - <select className="select select-bordered" /> 110 - <textarea className="textarea textarea-bordered" /> 111 - 112 - // Alerts 113 - <div className="alert alert-info"> 114 - <span>Info message</span> 115 - </div> 116 - ``` 117 - 118 - ## 🔧 Technical Implementation 119 - 120 - ### 1. Tailwind Configuration (`tailwind.config.js`) 121 - 122 - ```javascript 123 - flyonui: { 124 - themes: [ 125 - { 126 - dark: { 127 - "base-100": "#06051d", // Uniform background 128 - "base-200": "#06051d", // Same as base-100 129 - "base-300": "#06051d", // Same as base-100 130 - "base-content": "#e5e5e5", 131 - // ... accent colors 132 - }, 133 - }, 134 - ], 135 - } 136 - ``` 137 - 138 - ### 2. CSS Overrides (`index.css`) 139 - 140 - Comprehensive CSS rules ensure ALL elements use the uniform background: 141 - 142 - ```css 143 - /* Force uniform dark background */ 144 - :root.dark body, 145 - :root.dark #root, 146 - :root.dark main, 147 - :root.dark .navbar, 148 - :root.dark .drawer, 149 - :root.dark .card { 150 - background-color: #06051d !important; 151 - } 152 - 153 - /* All base-* classes use same color */ 154 - :root.dark [class*="bg-base-"] { 155 - background-color: #06051d !important; 156 - } 157 - ``` 158 - 159 - ### 3. FOUC Prevention (`index.html`) 160 - 161 - Inline script runs before page renders: 162 - 163 - ```html 164 - <script> 165 - (function () { 166 - const theme = localStorage.getItem("theme") || 167 - (window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"); 168 - document.documentElement.classList.add(theme); 169 - document.documentElement.setAttribute("data-theme", theme); 170 - })(); 171 - </script> 172 - ``` 173 - 174 - ### 4. Theme Hook (`useTheme.ts`) 175 - 176 - Manages theme state with localStorage persistence: 177 - 178 - ```typescript 179 - export function useTheme() { 180 - const [theme, setTheme] = useState<Theme>(() => { 181 - // Check localStorage first, then system preference 182 - const savedTheme = localStorage.getItem("theme"); 183 - if (savedTheme) return savedTheme; 184 - return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; 185 - }); 186 - 187 - useEffect(() => { 188 - document.documentElement.classList.remove("light", "dark"); 189 - document.documentElement.classList.add(theme); 190 - document.documentElement.setAttribute("data-theme", theme); 191 - localStorage.setItem("theme", theme); 192 - }, [theme]); 193 - 194 - return { theme, toggleTheme, isDark: theme === "dark" }; 195 - } 196 - ``` 197 - 198 - ## 🎨 Best Practices 199 - 200 - ### ✅ DO: 201 - 202 - ```tsx 203 - // Use semantic classes that adapt to theme 204 - <div className="bg-base-100 text-base-content"> 205 - 206 - // Use opacity for subtle elements 207 - <p className="text-base-content/70">Secondary text</p> 208 - 209 - // Use borders for visual separation in uniform dark 210 - <div className="border border-base-content/10"> 211 - ``` 212 - 213 - ### ❌ DON'T: 214 - 215 - ```tsx 216 - // Don't use hardcoded colors 217 - <div style={{ backgroundColor: "#ffffff" }}> 218 - 219 - // Don't use multiple shades manually 220 - <div className="bg-gray-800"> // Use bg-base-100 instead 221 - 222 - // Don't fight the uniform theme 223 - <div className="bg-gray-900 dark:bg-gray-800"> // Not needed 224 - ``` 225 - 226 - ## 🎭 Visual Separation in Uniform Dark 227 - 228 - Since everything uses the same background, use these techniques for visual hierarchy: 229 - 230 - ### 1. Borders 231 - ```tsx 232 - <div className="border border-base-content/10"> 233 - ``` 234 - 235 - ### 2. Subtle Shadows 236 - ```tsx 237 - <div className="shadow-lg"> 238 - ``` 239 - 240 - ### 3. Accent Colors 241 - ```tsx 242 - <div className="border-l-4 border-primary"> 243 - ``` 244 - 245 - ### 4. Opacity Variations 246 - ```tsx 247 - <div className="bg-base-content/5"> {/* Very subtle highlight */} 248 - ``` 249 - 250 - ## 🧪 Testing 251 - 252 - The demo component (`DarkThemeDemo.tsx`) showcases all themed elements: 253 - 254 - - Buttons (all variants) 255 - - Cards with borders 256 - - Alerts (info, success, warning, error) 257 - - Badges 258 - - Form elements 259 - - Progress bars 260 - - Loading indicators 261 - - Stats components 262 - 263 - ## 🐛 Troubleshooting 264 - 265 - ### Component has different background color 266 - - Check if component has inline styles overriding the theme 267 - - Ensure `!important` rules in `index.css` are loading 268 - - Clear browser cache and rebuild 269 - 270 - ### Theme not persisting 271 - - Check browser console for localStorage errors 272 - - Verify `useTheme` hook is called at app root level 273 - 274 - ### Flash of wrong theme on load 275 - - Ensure inline script in `index.html` is in `<head>` 276 - - Script must run before body content renders 277 - 278 - ### Text not readable 279 - - Ensure using `text-base-content` class, not hardcoded colors 280 - - Check contrast ratios (should be 4.5:1 minimum) 281 - 282 - ## 📝 Summary 283 - 284 - Your application now features a **uniform dark theme** where: 285 - 286 - - ✅ All backgrounds are `#06051d` in dark mode 287 - - ✅ No varying shades of gray/blue/slate 288 - - ✅ Clean, modern, consistent appearance 289 - - ✅ Visual hierarchy through borders and accent colors 290 - - ✅ Smooth transitions between light and dark 291 - - ✅ Toggle button in navbar for easy switching 292 - 293 - Toggle the theme and enjoy the cohesive dark experience! 🌙
+50
apps/web/bun.lock
··· 13 13 "@tanstack/react-router": "^1.159.5", 14 14 "@tanstack/react-router-devtools": "^1.159.5", 15 15 "@tanstack/router-plugin": "^1.159.5", 16 + "axios": "^1.13.5", 17 + "consola": "^3.4.2", 16 18 "dayjs": "^1.11.19", 17 19 "effect": "^3.19.16", 18 20 "flyonui": "^2.4.1", ··· 381 383 382 384 "ast-types": ["ast-types@0.16.1", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg=="], 383 385 386 + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], 387 + 388 + "axios": ["axios@1.13.5", "", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q=="], 389 + 384 390 "babel-dead-code-elimination": ["babel-dead-code-elimination@1.0.12", "", { "dependencies": { "@babel/core": "^7.23.7", "@babel/parser": "^7.23.6", "@babel/traverse": "^7.23.7", "@babel/types": "^7.23.6" } }, "sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig=="], 385 391 386 392 "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], ··· 396 402 "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], 397 403 398 404 "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], 405 + 406 + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], 399 407 400 408 "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], 401 409 ··· 411 419 412 420 "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], 413 421 422 + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], 423 + 414 424 "commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="], 415 425 416 426 "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], 417 427 418 428 "confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], 429 + 430 + "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], 419 431 420 432 "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], 421 433 ··· 438 450 "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], 439 451 440 452 "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], 453 + 454 + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], 441 455 442 456 "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], 443 457 ··· 451 465 452 466 "domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="], 453 467 468 + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], 469 + 454 470 "effect": ["effect@3.19.16", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-7+XC3vGrbAhCHd8LTFHvnZjRpZKZ8YHRZqJTkpNoxcJ2mCyNs2SwI+6VkV/ij8Y3YW7wfBN4EbU06/F5+m/wkQ=="], 455 471 456 472 "electron-to-chromium": ["electron-to-chromium@1.5.286", "", {}, "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A=="], ··· 458 474 "enhanced-resolve": ["enhanced-resolve@5.19.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg=="], 459 475 460 476 "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], 477 + 478 + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], 479 + 480 + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], 481 + 482 + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], 483 + 484 + "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], 461 485 462 486 "esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="], 463 487 ··· 511 535 512 536 "flyonui": ["flyonui@2.4.1", "", { "dependencies": { "@floating-ui/dom": "^1.7.1" } }, "sha512-WgP/YP1nZXbEtRkl3ThJIcIaisXOyON3zS2Qz2IwikQA/2X/PmqnP6E+eOz7WLBzEQI4ec7wViYAcRf9vljcbg=="], 513 537 538 + "follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="], 539 + 540 + "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], 541 + 514 542 "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], 515 543 544 + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], 545 + 516 546 "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], 517 547 548 + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], 549 + 550 + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], 551 + 518 552 "get-tsconfig": ["get-tsconfig@4.13.6", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw=="], 519 553 520 554 "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], ··· 523 557 524 558 "goober": ["goober@2.1.18", "", { "peerDependencies": { "csstype": "^3.0.10" } }, "sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw=="], 525 559 560 + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], 561 + 526 562 "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], 527 563 528 564 "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], 529 565 566 + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], 567 + 568 + "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], 569 + 570 + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], 571 + 530 572 "hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="], 531 573 532 574 "hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="], ··· 603 645 604 646 "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], 605 647 648 + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], 649 + 606 650 "mdn-data": ["mdn-data@2.12.2", "", {}, "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA=="], 651 + 652 + "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], 653 + 654 + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], 607 655 608 656 "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], 609 657 ··· 650 698 "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], 651 699 652 700 "prettier": ["prettier@3.8.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg=="], 701 + 702 + "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], 653 703 654 704 "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], 655 705
+2
apps/web/package.json
··· 18 18 "@tanstack/react-router": "^1.159.5", 19 19 "@tanstack/react-router-devtools": "^1.159.5", 20 20 "@tanstack/router-plugin": "^1.159.5", 21 + "axios": "^1.13.5", 22 + "consola": "^3.4.2", 21 23 "dayjs": "^1.11.19", 22 24 "effect": "^3.19.16", 23 25 "flyonui": "^2.4.1",
+6
apps/web/src/api/index.ts
··· 1 + import axios from "axios"; 2 + import { API_URL } from "../consts"; 3 + 4 + export const client = axios.create({ 5 + baseURL: API_URL, 6 + });
+13
apps/web/src/api/sandbox.ts
··· 1 + import { client } from "."; 2 + import type { Sandbox } from "../types/sandbox"; 3 + 4 + export const createSandbox = () => 5 + client.post("/xrpc/io.pocketenv.sandbox.createSandbox"); 6 + 7 + export const getSandbox = (id: string) => 8 + client.get(`/xrpc/io.pocketenv.sandbox.getSandbox?id=${id}`); 9 + 10 + export const getSandboxes = (offset?: number, limit?: number) => 11 + client.get<{ sandboxes: Sandbox[]; total: number }>( 12 + `/xrpc/io.pocketenv.sandbox.getSandboxes?offset=${offset ?? 0}&limit=${limit ?? 30}`, 13 + );
+4 -2
apps/web/src/components/navbar/Navbar.tsx
··· 6 6 7 7 export type NavbarProps = { 8 8 title: string; 9 + project?: string; 9 10 }; 10 11 11 - function Navbar({ title }: NavbarProps) { 12 + function Navbar({ title, project }: NavbarProps) { 12 13 const [open, setOpen] = useState(false); 13 14 const [modalOpen, setModalOpen] = useState(false); 14 15 const dropdownRef = useRef<HTMLDivElement>(null); ··· 44 45 }, [open]); 45 46 46 47 return ( 47 - <nav className="navbar bg-base-100"> 48 + <nav className="navbar bg-base-100 h-[65px]"> 48 49 <div className="flex flex-1 items-center"> 49 50 <div className="text-base-content link-neutral font-semibold no-underline text-[23px]"> 50 51 {title} 51 52 </div> 53 + <div className="text-[15px]">{project}</div> 52 54 </div> 53 55 <div className="navbar-end flex items-center gap-4"> 54 56 <div>
+37 -25
apps/web/src/components/newproject/NewProject.tsx
··· 1 - import { useEffect, useRef } from "react"; 1 + import { useEffect, useRef, useState } from "react"; 2 + import { useSandboxesQuery } from "../../hooks/useSandbox"; 2 3 3 4 export type NewProjectProps = { 4 5 isOpen: boolean; ··· 7 8 8 9 function NewProject({ isOpen, onClose }: NewProjectProps) { 9 10 const inputRef = useRef<HTMLInputElement>(null); 11 + const [filter, setFilter] = useState(""); 12 + const { data, isLoading } = useSandboxesQuery(); 13 + 14 + const sandboxes = data?.sandboxes.filter((sandbox) => 15 + filter 16 + ? sandbox.displayName.toLowerCase().includes(filter.toLowerCase()) 17 + : true, 18 + ); 10 19 11 20 useEffect(() => { 12 21 if (isOpen) { 13 - console.log("Attempting to focus input:", inputRef.current); 14 22 inputRef.current?.focus(); 15 23 } 16 24 }, [isOpen]); ··· 19 27 const handleEscapeKey = (event: KeyboardEvent) => { 20 28 if (event.key === "Escape" && isOpen) { 21 29 onClose(); 30 + setFilter(""); 22 31 } 23 32 }; 24 33 ··· 31 40 const handleBackdropClick = (e: React.MouseEvent<HTMLDivElement>) => { 32 41 if (e.target === e.currentTarget) { 33 42 onClose(); 43 + setFilter(""); 34 44 } 35 45 }; 36 46 47 + const onFilter = (value: string) => { 48 + setFilter(value); 49 + }; 50 + 37 51 return ( 38 52 <> 39 53 <div ··· 51 65 ref={inputRef} 52 66 placeholder="What would you like to try?" 53 67 className="grow" 68 + value={filter} 69 + onChange={(e) => onFilter(e.target.value)} 54 70 /> 55 71 </div> 56 72 </div> 57 73 </div> 58 74 <div className="modal-body"> 59 - <div className="p-3 hover:bg-white/7 cursor-pointer rounded-md"> 60 - <div className="font-semibold">OpenClaw</div> 61 - </div> 62 - <div className="p-3 hover:bg-white/7 cursor-pointer rounded-md"> 63 - <div className="font-semibold">Claude Code</div> 64 - </div> 65 - <div className="p-3 hover:bg-white/7 cursor-pointer rounded-md"> 66 - <div className="font-semibold">OpenAI Codex CLI</div> 67 - </div> 68 - <div className="p-3 hover:bg-white/7 cursor-pointer rounded-md"> 69 - <div className="font-semibold">GitHub Copilot CLI</div> 70 - </div> 71 - <div className="p-3 hover:bg-white/7 cursor-pointer rounded-md"> 72 - <div className="font-semibold">Gemini CLI</div> 73 - </div> 74 - <div className="p-3 hover:bg-white/7 cursor-pointer rounded-md"> 75 - <div className="font-semibold">OpenCode</div> 76 - </div> 77 - 78 - <div className="p-3 hover:bg-white/7 cursor-pointer rounded-md"> 79 - <div className="font-semibold">Aider</div> 80 - </div> 75 + {!isLoading && 76 + sandboxes?.map((item) => ( 77 + <div 78 + key={item.id} 79 + className="p-3 hover:bg-white/7 cursor-pointer rounded-md" 80 + > 81 + <div className="font-semibold">{item.displayName}</div> 82 + </div> 83 + ))} 84 + {!isLoading && sandboxes?.length === 0 && ( 85 + <div className="p-3 text-center font-semibold opacity-70"> 86 + No results found for <br /> 87 + <b>{filter}</b> 88 + </div> 89 + )} 81 90 </div> 82 91 </div> 83 92 </div> ··· 89 98 data-overlay-backdrop-template="" 90 99 style={{ zIndex: 79 }} 91 100 className="overlay-backdrop transition duration-300 fixed inset-0 bg-base-300/60 overflow-y-auto opacity-75" 92 - onClick={onClose} 101 + onClick={() => { 102 + onClose(); 103 + setFilter(""); 104 + }} 93 105 ></div> 94 106 )} 95 107 </>
+2
apps/web/src/consts.ts
··· 1 + export const API_URL = import.meta.env.VITE_API_URL || "http://localhost:8789"; 2 + export const WS_URL = import.meta.env.VITE_WS_URL || "ws://localhost:8790";
+9
apps/web/src/hooks/useSandbox.ts
··· 1 + import { useQuery } from "@tanstack/react-query"; 2 + import { getSandboxes } from "../api/sandbox"; 3 + 4 + export const useSandboxesQuery = (offset?: number, limit?: number) => 5 + useQuery({ 6 + queryKey: ["sandboxes", offset, limit], 7 + queryFn: () => getSandboxes(offset, limit), 8 + select: (response) => response.data, 9 + });
+13
apps/web/src/pages/sandbox/Sandbox.tsx
··· 1 + import Navbar from "../../components/navbar"; 2 + 3 + function New() { 4 + return ( 5 + <> 6 + <div className="flex flex-col min-h-screen bg-base-100"> 7 + <Navbar title="" project="lucky-quietude" /> 8 + </div> 9 + </> 10 + ); 11 + } 12 + 13 + export default New;
+3
apps/web/src/pages/sandbox/index.tsx
··· 1 + import Sandbox from "./Sandbox"; 2 + 3 + export default Sandbox;
+21
apps/web/src/routeTree.gen.ts
··· 16 16 import { Route as SecretsRouteImport } from './routes/secrets' 17 17 import { Route as ProjectsRouteImport } from './routes/projects' 18 18 import { Route as IndexRouteImport } from './routes/index' 19 + import { Route as SandboxIdRouteImport } from './routes/sandbox/$id' 19 20 20 21 const VolumesRoute = VolumesRouteImport.update({ 21 22 id: '/volumes', ··· 50 51 const IndexRoute = IndexRouteImport.update({ 51 52 id: '/', 52 53 path: '/', 54 + getParentRoute: () => rootRouteImport, 55 + } as any) 56 + const SandboxIdRoute = SandboxIdRouteImport.update({ 57 + id: '/sandbox/$id', 58 + path: '/sandbox/$id', 53 59 getParentRoute: () => rootRouteImport, 54 60 } as any) 55 61 ··· 61 67 '/signin': typeof SigninRoute 62 68 '/snapshots': typeof SnapshotsRoute 63 69 '/volumes': typeof VolumesRoute 70 + '/sandbox/$id': typeof SandboxIdRoute 64 71 } 65 72 export interface FileRoutesByTo { 66 73 '/': typeof IndexRoute ··· 70 77 '/signin': typeof SigninRoute 71 78 '/snapshots': typeof SnapshotsRoute 72 79 '/volumes': typeof VolumesRoute 80 + '/sandbox/$id': typeof SandboxIdRoute 73 81 } 74 82 export interface FileRoutesById { 75 83 __root__: typeof rootRouteImport ··· 80 88 '/signin': typeof SigninRoute 81 89 '/snapshots': typeof SnapshotsRoute 82 90 '/volumes': typeof VolumesRoute 91 + '/sandbox/$id': typeof SandboxIdRoute 83 92 } 84 93 export interface FileRouteTypes { 85 94 fileRoutesByFullPath: FileRoutesByFullPath ··· 91 100 | '/signin' 92 101 | '/snapshots' 93 102 | '/volumes' 103 + | '/sandbox/$id' 94 104 fileRoutesByTo: FileRoutesByTo 95 105 to: 96 106 | '/' ··· 100 110 | '/signin' 101 111 | '/snapshots' 102 112 | '/volumes' 113 + | '/sandbox/$id' 103 114 id: 104 115 | '__root__' 105 116 | '/' ··· 109 120 | '/signin' 110 121 | '/snapshots' 111 122 | '/volumes' 123 + | '/sandbox/$id' 112 124 fileRoutesById: FileRoutesById 113 125 } 114 126 export interface RootRouteChildren { ··· 119 131 SigninRoute: typeof SigninRoute 120 132 SnapshotsRoute: typeof SnapshotsRoute 121 133 VolumesRoute: typeof VolumesRoute 134 + SandboxIdRoute: typeof SandboxIdRoute 122 135 } 123 136 124 137 declare module '@tanstack/react-router' { ··· 172 185 preLoaderRoute: typeof IndexRouteImport 173 186 parentRoute: typeof rootRouteImport 174 187 } 188 + '/sandbox/$id': { 189 + id: '/sandbox/$id' 190 + path: '/sandbox/$id' 191 + fullPath: '/sandbox/$id' 192 + preLoaderRoute: typeof SandboxIdRouteImport 193 + parentRoute: typeof rootRouteImport 194 + } 175 195 } 176 196 } 177 197 ··· 183 203 SigninRoute: SigninRoute, 184 204 SnapshotsRoute: SnapshotsRoute, 185 205 VolumesRoute: VolumesRoute, 206 + SandboxIdRoute: SandboxIdRoute, 186 207 } 187 208 export const routeTree = rootRouteImport 188 209 ._addFileChildren(rootRouteChildren)
+6
apps/web/src/routes/sandbox/$id.tsx
··· 1 + import { createFileRoute } from "@tanstack/react-router"; 2 + import Sandbox from "../../pages/sandbox"; 3 + 4 + export const Route = createFileRoute("/sandbox/$id")({ 5 + component: Sandbox, 6 + });
+10
apps/web/src/types/sandbox.ts
··· 1 + export type Sandbox = { 2 + id: string; 3 + name: string; 4 + displayName: string; 5 + description?: string; 6 + logo?: string; 7 + readme?: string; 8 + installs: number; 9 + createdAt: string; 10 + };
+9 -1
compose.yaml
··· 14 14 interval: 10s 15 15 timeout: 5s 16 16 retries: 5 17 - 17 + dragonfly: 18 + image: docker.dragonflydb.io/dragonflydb/dragonfly:latest 19 + command: --maxmemory=4g 20 + ports: 21 + - "6379:6379" 22 + ulimits: 23 + memlock: 24 + soft: -1 25 + hard: -1 18 26 volumes: 19 27 pgdata: