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 Modal provider and modal_auth support

Add Modal auth schema and DB migration, and introduce lexicon
fields for modal token id/secret and their redacted variants. Update
CLI, sandbox types, API handlers and web UI to accept and persist
Modal tokens and surface redacted values in responses.

+2558 -13
+16
apps/api/lexicons/sandbox/createSandbox.json
··· 126 126 "vercelTeamId": { 127 127 "type": "string", 128 128 "description": "The team ID for Vercel resources" 129 + }, 130 + "modalTokenId": { 131 + "type": "string", 132 + "description": "A token (encrypted) for accessing Modal resources" 133 + }, 134 + "redactedModalTokenId": { 135 + "type": "string", 136 + "description": "A redacted token for accessing Modal resources" 137 + }, 138 + "modalTokenSecret": { 139 + "type": "string", 140 + "description": "A token (encrypted) for accessing Modal resources" 141 + }, 142 + "redactedModalTokenSecret": { 143 + "type": "string", 144 + "description": "A redacted token for accessing Modal resources" 129 145 } 130 146 } 131 147 }
+16
apps/api/lexicons/sandbox/defs.json
··· 330 330 "vercelTeamId": { 331 331 "type": "string", 332 332 "description": "The team ID for Vercel, if the sandbox provider is Vercel and the sandbox should be created within a specific team. This is used to determine which team within the Vercel project the sandbox should be associated with." 333 + }, 334 + "modalTokenId": { 335 + "type": "string", 336 + "description": "The token ID for Modal, if the sandbox provider is Modal. This is used to determine which Modal token to use when creating the sandbox." 337 + }, 338 + "modalTokenSecret": { 339 + "type": "string", 340 + "description": "The token secret for Modal, if the sandbox provider is Modal. This is used to determine which Modal token secret to use when creating the sandbox." 341 + }, 342 + "redactedModalTokenId": { 343 + "type": "string", 344 + "description": "The redacted token ID for Modal, returned in API responses when the sandbox provider is Modal. This can be used to identify which Modal token is being used without exposing the actual token ID." 345 + }, 346 + "redactedModalTokenSecret": { 347 + "type": "string", 348 + "description": "The redacted token secret for Modal, returned in API responses when the sandbox provider is Modal. This can be used to identify which Modal token secret is being used without exposing the actual token secret." 333 349 } 334 350 } 335 351 },
+16
apps/api/pkl/defs/sandbox/createSandbox.pkl
··· 124 124 type = "string" 125 125 description = "The team ID for Vercel resources" 126 126 } 127 + ["modalTokenId"] = new StringType { 128 + type = "string" 129 + description = "A token (encrypted) for accessing Modal resources" 130 + } 131 + ["redactedModalTokenId"] = new StringType { 132 + type = "string" 133 + description = "A redacted token for accessing Modal resources" 134 + } 135 + ["modalTokenSecret"] = new StringType { 136 + type = "string" 137 + description = "A token (encrypted) for accessing Modal resources" 138 + } 139 + ["redactedModalTokenSecret"] = new StringType { 140 + type = "string" 141 + description = "A redacted token for accessing Modal resources" 142 + } 127 143 } 128 144 } 129 145 }
+20
apps/api/pkl/defs/sandbox/defs.pkl
··· 335 335 description = 336 336 "The team ID for Vercel, if the sandbox provider is Vercel and the sandbox should be created within a specific team. This is used to determine which team within the Vercel project the sandbox should be associated with." 337 337 } 338 + ["modalTokenId"] = new StringType { 339 + type = "string" 340 + description = 341 + "The token ID for Modal, if the sandbox provider is Modal. This is used to determine which Modal token to use when creating the sandbox." 342 + } 343 + ["modalTokenSecret"] = new StringType { 344 + type = "string" 345 + description = 346 + "The token secret for Modal, if the sandbox provider is Modal. This is used to determine which Modal token secret to use when creating the sandbox." 347 + } 348 + ["redactedModalTokenId"] = new StringType { 349 + type = "string" 350 + description = 351 + "The redacted token ID for Modal, returned in API responses when the sandbox provider is Modal. This can be used to identify which Modal token is being used without exposing the actual token ID." 352 + } 353 + ["redactedModalTokenSecret"] = new StringType { 354 + type = "string" 355 + description = 356 + "The redacted token secret for Modal, returned in API responses when the sandbox provider is Modal. This can be used to identify which Modal token secret is being used without exposing the actual token secret." 357 + } 338 358 } 339 359 } 340 360 ["preferences"] = new Array {
+38
apps/api/src/lexicon/lexicons.ts
··· 681 681 type: "string", 682 682 description: "The team ID for Vercel resources", 683 683 }, 684 + modalTokenId: { 685 + type: "string", 686 + description: 687 + "A token (encrypted) for accessing Modal resources", 688 + }, 689 + redactedModalTokenId: { 690 + type: "string", 691 + description: "A redacted token for accessing Modal resources", 692 + }, 693 + modalTokenSecret: { 694 + type: "string", 695 + description: 696 + "A token (encrypted) for accessing Modal resources", 697 + }, 698 + redactedModalTokenSecret: { 699 + type: "string", 700 + description: "A redacted token for accessing Modal resources", 701 + }, 684 702 }, 685 703 }, 686 704 }, ··· 1042 1060 type: "string", 1043 1061 description: 1044 1062 "The team ID for Vercel, if the sandbox provider is Vercel and the sandbox should be created within a specific team. This is used to determine which team within the Vercel project the sandbox should be associated with.", 1063 + }, 1064 + modalTokenId: { 1065 + type: "string", 1066 + description: 1067 + "The token ID for Modal, if the sandbox provider is Modal. This is used to determine which Modal token to use when creating the sandbox.", 1068 + }, 1069 + modalTokenSecret: { 1070 + type: "string", 1071 + description: 1072 + "The token secret for Modal, if the sandbox provider is Modal. This is used to determine which Modal token secret to use when creating the sandbox.", 1073 + }, 1074 + redactedModalTokenId: { 1075 + type: "string", 1076 + description: 1077 + "The redacted token ID for Modal, returned in API responses when the sandbox provider is Modal. This can be used to identify which Modal token is being used without exposing the actual token ID.", 1078 + }, 1079 + redactedModalTokenSecret: { 1080 + type: "string", 1081 + description: 1082 + "The redacted token secret for Modal, returned in API responses when the sandbox provider is Modal. This can be used to identify which Modal token secret is being used without exposing the actual token secret.", 1045 1083 }, 1046 1084 }, 1047 1085 },
+8
apps/api/src/lexicon/types/io/pocketenv/sandbox/createSandbox.ts
··· 58 58 vercelProjectId?: string; 59 59 /** The team ID for Vercel resources */ 60 60 vercelTeamId?: string; 61 + /** A token (encrypted) for accessing Modal resources */ 62 + modalTokenId?: string; 63 + /** A redacted token for accessing Modal resources */ 64 + redactedModalTokenId?: string; 65 + /** A token (encrypted) for accessing Modal resources */ 66 + modalTokenSecret?: string; 67 + /** A redacted token for accessing Modal resources */ 68 + redactedModalTokenSecret?: string; 61 69 [k: string]: unknown; 62 70 } 63 71
+8
apps/api/src/lexicon/types/io/pocketenv/sandbox/defs.ts
··· 229 229 vercelProjectId?: string; 230 230 /** The team ID for Vercel, if the sandbox provider is Vercel and the sandbox should be created within a specific team. This is used to determine which team within the Vercel project the sandbox should be associated with. */ 231 231 vercelTeamId?: string; 232 + /** The token ID for Modal, if the sandbox provider is Modal. This is used to determine which Modal token to use when creating the sandbox. */ 233 + modalTokenId?: string; 234 + /** The token secret for Modal, if the sandbox provider is Modal. This is used to determine which Modal token secret to use when creating the sandbox. */ 235 + modalTokenSecret?: string; 236 + /** The redacted token ID for Modal, returned in API responses when the sandbox provider is Modal. This can be used to identify which Modal token is being used without exposing the actual token ID. */ 237 + redactedModalTokenId?: string; 238 + /** The redacted token secret for Modal, returned in API responses when the sandbox provider is Modal. This can be used to identify which Modal token secret is being used without exposing the actual token secret. */ 239 + redactedModalTokenSecret?: string; 232 240 [k: string]: unknown; 233 241 } 234 242
+2
apps/api/src/schema/index.ts
··· 19 19 import vercelAuth from "./vercel-auth"; 20 20 import sandboxCp from "./sandbox-cp"; 21 21 import backups from "./backups"; 22 + import modalAuth from "./modal-auth"; 22 23 23 24 export default { 24 25 sandboxes, ··· 42 43 vercelAuth, 43 44 sandboxCp, 44 45 backups, 46 + modalAuth, 45 47 };
+28
apps/api/src/schema/modal-auth.ts
··· 1 + import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 + import { pgTable, text, timestamp, uniqueIndex } from "drizzle-orm/pg-core"; 3 + import sandboxes from "./sandboxes"; 4 + import users from "./users"; 5 + 6 + const modalAuth = pgTable( 7 + "modal_auth", 8 + { 9 + id: text("id").primaryKey().default(sql`xata_id()`), 10 + sandboxId: text("sandbox_id") 11 + .notNull() 12 + .references(() => sandboxes.id, { onDelete: "cascade" }), 13 + userId: text("user_id") 14 + .notNull() 15 + .references(() => users.id), 16 + tokenId: text("token_id").notNull(), 17 + redactedTokenId: text("redacted_token_id").notNull(), 18 + tokenSecret: text("token_secret").notNull(), 19 + redactedTokenSecret: text("redacted_token_secret").notNull(), 20 + createdAt: timestamp("created_at").defaultNow().notNull(), 21 + }, 22 + (t) => [uniqueIndex("unique_modal_auth").on(t.sandboxId, t.userId)], 23 + ); 24 + 25 + export type SelectModalAuth = InferSelectModel<typeof modalAuth>; 26 + export type InsertModalAuth = InferInsertModel<typeof modalAuth>; 27 + 28 + export default modalAuth;
+4
apps/api/src/xrpc/io/pocketenv/sandbox/createSandbox.ts
··· 120 120 redactedVercelApiToken: input.redactedVercelApiToken, 121 121 vercelProjectId: input.vercelProjectId, 122 122 vercelTeamId: input.vercelTeamId, 123 + modalTokenId: input.modalTokenId, 124 + redactedModalTokenId: input.redactedModalTokenId, 125 + modalTokenSecret: input.modalTokenSecret, 126 + redactedModalTokenSecret: input.redactedModalTokenSecret, 123 127 }, 124 128 { 125 129 headers: {
+18 -2
apps/api/src/xrpc/io/pocketenv/sandbox/getPreferences.ts
··· 9 9 } from "lexicon/types/io/pocketenv/sandbox/getPreferences"; 10 10 import daytonaAuth from "schema/daytona-auth"; 11 11 import denoAuth from "schema/deno-auth"; 12 + import modalAuth from "schema/modal-auth"; 12 13 import sandboxes from "schema/sandboxes"; 13 14 import spriteAuth from "schema/sprite-auth"; 14 15 import users from "schema/users"; ··· 36 37 eq(sandboxes.name, params.id), 37 38 ); 38 39 39 - const [daytona, deno, sprite, vercel] = await Promise.all([ 40 + const [daytona, deno, sprite, vercel, modal] = await Promise.all([ 40 41 ctx.db 41 42 .select() 42 43 .from(daytonaAuth) ··· 65 66 .where(and(eq(vercelAuth.userId, user.id), sandboxFilter)) 66 67 .execute() 67 68 .then(([row]) => row?.vercel_auth), 69 + ctx.db 70 + .select() 71 + .from(modalAuth) 72 + .leftJoin(sandboxes, eq(modalAuth.sandboxId, sandboxes.id)) 73 + .where(and(eq(modalAuth.userId, user.id), sandboxFilter)) 74 + .execute() 75 + .then(([row]) => row?.modal_auth), 68 76 ]); 69 77 70 - if (!daytona && !deno && !sprite && !vercel) { 78 + if (!daytona && !deno && !sprite && !vercel && !modal) { 71 79 return []; 72 80 } 73 81 ··· 93 101 redactedApiKey: vercel.redactedVercelToken, 94 102 vercelProjectId: vercel.projectId, 95 103 vercelTeamId: vercel.teamId, 104 + }) || 105 + (modal && { 106 + $type: "io.pocketenv.sandbox.defs#sandboxProviderPref" as const, 107 + name: "modal" as const, 108 + redactedTokenId: modal.redactedTokenId, 109 + modalTokenId: modal.tokenId, 110 + modalTokenSecret: modal.tokenSecret, 111 + redactedModalTokenSecret: modal.redactedTokenSecret, 96 112 }))!; 97 113 98 114 return [provider satisfies SandboxProviderPref];
+32
apps/api/src/xrpc/io/pocketenv/sandbox/putPreferences.ts
··· 19 19 import spriteAuth from "schema/sprite-auth"; 20 20 import type { PgTransaction } from "drizzle-orm/pg-core"; 21 21 import type { NodePgQueryResultHKT } from "drizzle-orm/node-postgres"; 22 + import modalAuth from "schema/modal-auth"; 22 23 23 24 export default function (server: Server, ctx: Context) { 24 25 const putPreferences = async (input: HandlerInput, auth: HandlerAuth) => { ··· 216 217 }) 217 218 .execute(); 218 219 break; 220 + case "modal": 221 + await tx 222 + .insert(modalAuth) 223 + .values({ 224 + userId: user.id, 225 + sandboxId: input.body.sandboxId, 226 + tokenId: pref.tokenId!, 227 + redactedTokenId: pref.redactedTokenId!, 228 + tokenSecret: pref.tokenSecret!, 229 + redactedTokenSecret: pref.redactedTokenSecret!, 230 + }) 231 + .onConflictDoUpdate({ 232 + target: [modalAuth.sandboxId, modalAuth.userId], 233 + set: { 234 + tokenId: pref.tokenId!, 235 + redactedTokenId: pref.redactedTokenId!, 236 + tokenSecret: pref.tokenSecret!, 237 + redactedTokenSecret: pref.redactedTokenSecret!, 238 + }, 239 + }) 240 + .execute(); 241 + break; 219 242 case "cloudflare": { 220 243 const [record] = await tx 221 244 .select() ··· 265 288 and( 266 289 eq(spriteAuth.userId, user.id), 267 290 eq(spriteAuth.sandboxId, record!.id), 291 + ), 292 + ) 293 + .execute(), 294 + tx 295 + .delete(modalAuth) 296 + .where( 297 + and( 298 + eq(modalAuth.userId, user.id), 299 + eq(modalAuth.sandboxId, record!.id), 268 300 ), 269 301 ) 270 302 .execute(),
+14
apps/cf-sandbox/drizzle/0043_supreme_ezekiel.sql
··· 1 + CREATE TABLE "modal_auth" ( 2 + "id" text PRIMARY KEY DEFAULT xata_id() NOT NULL, 3 + "sandbox_id" text NOT NULL, 4 + "user_id" text NOT NULL, 5 + "token_id" text NOT NULL, 6 + "redacted_token_id" text NOT NULL, 7 + "token_secret" text NOT NULL, 8 + "redacted_token_secret" text NOT NULL, 9 + "created_at" timestamp DEFAULT now() NOT NULL 10 + ); 11 + --> statement-breakpoint 12 + ALTER TABLE "modal_auth" ADD CONSTRAINT "modal_auth_sandbox_id_sandboxes_id_fk" FOREIGN KEY ("sandbox_id") REFERENCES "public"."sandboxes"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint 13 + ALTER TABLE "modal_auth" ADD CONSTRAINT "modal_auth_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint 14 + CREATE UNIQUE INDEX "unique_modal_auth" ON "modal_auth" USING btree ("sandbox_id","user_id");
+2093
apps/cf-sandbox/drizzle/meta/0043_snapshot.json
··· 1 + { 2 + "id": "5d279e2e-881b-45fe-9032-279340ddab81", 3 + "prevId": "b8efac8c-de18-420c-8621-74850b2771ef", 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.backups": { 61 + "name": "backups", 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 + "backup_id": { 78 + "name": "backup_id", 79 + "type": "text", 80 + "primaryKey": false, 81 + "notNull": true 82 + }, 83 + "directory": { 84 + "name": "directory", 85 + "type": "text", 86 + "primaryKey": false, 87 + "notNull": true 88 + }, 89 + "description": { 90 + "name": "description", 91 + "type": "text", 92 + "primaryKey": false, 93 + "notNull": false 94 + }, 95 + "expires_at": { 96 + "name": "expires_at", 97 + "type": "timestamp", 98 + "primaryKey": false, 99 + "notNull": false 100 + }, 101 + "created_at": { 102 + "name": "created_at", 103 + "type": "timestamp", 104 + "primaryKey": false, 105 + "notNull": true, 106 + "default": "now()" 107 + } 108 + }, 109 + "indexes": {}, 110 + "foreignKeys": { 111 + "backups_sandbox_id_sandboxes_id_fk": { 112 + "name": "backups_sandbox_id_sandboxes_id_fk", 113 + "tableFrom": "backups", 114 + "tableTo": "sandboxes", 115 + "columnsFrom": [ 116 + "sandbox_id" 117 + ], 118 + "columnsTo": [ 119 + "id" 120 + ], 121 + "onDelete": "cascade", 122 + "onUpdate": "no action" 123 + } 124 + }, 125 + "compositePrimaryKeys": {}, 126 + "uniqueConstraints": {}, 127 + "policies": {}, 128 + "checkConstraints": {}, 129 + "isRLSEnabled": false 130 + }, 131 + "public.daytona_auth": { 132 + "name": "daytona_auth", 133 + "schema": "", 134 + "columns": { 135 + "id": { 136 + "name": "id", 137 + "type": "text", 138 + "primaryKey": true, 139 + "notNull": true, 140 + "default": "xata_id()" 141 + }, 142 + "sandbox_id": { 143 + "name": "sandbox_id", 144 + "type": "text", 145 + "primaryKey": false, 146 + "notNull": true 147 + }, 148 + "user_id": { 149 + "name": "user_id", 150 + "type": "text", 151 + "primaryKey": false, 152 + "notNull": true 153 + }, 154 + "api_key": { 155 + "name": "api_key", 156 + "type": "text", 157 + "primaryKey": false, 158 + "notNull": true 159 + }, 160 + "organization_id": { 161 + "name": "organization_id", 162 + "type": "text", 163 + "primaryKey": false, 164 + "notNull": true 165 + }, 166 + "redacted_api_key": { 167 + "name": "redacted_api_key", 168 + "type": "text", 169 + "primaryKey": false, 170 + "notNull": true 171 + }, 172 + "created_at": { 173 + "name": "created_at", 174 + "type": "timestamp", 175 + "primaryKey": false, 176 + "notNull": true, 177 + "default": "now()" 178 + } 179 + }, 180 + "indexes": { 181 + "unique_daytona_auth": { 182 + "name": "unique_daytona_auth", 183 + "columns": [ 184 + { 185 + "expression": "sandbox_id", 186 + "isExpression": false, 187 + "asc": true, 188 + "nulls": "last" 189 + }, 190 + { 191 + "expression": "user_id", 192 + "isExpression": false, 193 + "asc": true, 194 + "nulls": "last" 195 + } 196 + ], 197 + "isUnique": true, 198 + "concurrently": false, 199 + "method": "btree", 200 + "with": {} 201 + } 202 + }, 203 + "foreignKeys": { 204 + "daytona_auth_sandbox_id_sandboxes_id_fk": { 205 + "name": "daytona_auth_sandbox_id_sandboxes_id_fk", 206 + "tableFrom": "daytona_auth", 207 + "tableTo": "sandboxes", 208 + "columnsFrom": [ 209 + "sandbox_id" 210 + ], 211 + "columnsTo": [ 212 + "id" 213 + ], 214 + "onDelete": "cascade", 215 + "onUpdate": "no action" 216 + }, 217 + "daytona_auth_user_id_users_id_fk": { 218 + "name": "daytona_auth_user_id_users_id_fk", 219 + "tableFrom": "daytona_auth", 220 + "tableTo": "users", 221 + "columnsFrom": [ 222 + "user_id" 223 + ], 224 + "columnsTo": [ 225 + "id" 226 + ], 227 + "onDelete": "no action", 228 + "onUpdate": "no action" 229 + } 230 + }, 231 + "compositePrimaryKeys": {}, 232 + "uniqueConstraints": {}, 233 + "policies": {}, 234 + "checkConstraints": {}, 235 + "isRLSEnabled": false 236 + }, 237 + "public.deno_auth": { 238 + "name": "deno_auth", 239 + "schema": "", 240 + "columns": { 241 + "id": { 242 + "name": "id", 243 + "type": "text", 244 + "primaryKey": true, 245 + "notNull": true, 246 + "default": "xata_id()" 247 + }, 248 + "sandbox_id": { 249 + "name": "sandbox_id", 250 + "type": "text", 251 + "primaryKey": false, 252 + "notNull": true 253 + }, 254 + "deploy_token": { 255 + "name": "deploy_token", 256 + "type": "text", 257 + "primaryKey": false, 258 + "notNull": true 259 + }, 260 + "user_id": { 261 + "name": "user_id", 262 + "type": "text", 263 + "primaryKey": false, 264 + "notNull": true 265 + }, 266 + "redacted_deno_token": { 267 + "name": "redacted_deno_token", 268 + "type": "text", 269 + "primaryKey": false, 270 + "notNull": true 271 + }, 272 + "created_at": { 273 + "name": "created_at", 274 + "type": "timestamp", 275 + "primaryKey": false, 276 + "notNull": true, 277 + "default": "now()" 278 + } 279 + }, 280 + "indexes": { 281 + "unique_deno_auth": { 282 + "name": "unique_deno_auth", 283 + "columns": [ 284 + { 285 + "expression": "sandbox_id", 286 + "isExpression": false, 287 + "asc": true, 288 + "nulls": "last" 289 + }, 290 + { 291 + "expression": "user_id", 292 + "isExpression": false, 293 + "asc": true, 294 + "nulls": "last" 295 + } 296 + ], 297 + "isUnique": true, 298 + "concurrently": false, 299 + "method": "btree", 300 + "with": {} 301 + } 302 + }, 303 + "foreignKeys": { 304 + "deno_auth_sandbox_id_sandboxes_id_fk": { 305 + "name": "deno_auth_sandbox_id_sandboxes_id_fk", 306 + "tableFrom": "deno_auth", 307 + "tableTo": "sandboxes", 308 + "columnsFrom": [ 309 + "sandbox_id" 310 + ], 311 + "columnsTo": [ 312 + "id" 313 + ], 314 + "onDelete": "cascade", 315 + "onUpdate": "no action" 316 + }, 317 + "deno_auth_user_id_users_id_fk": { 318 + "name": "deno_auth_user_id_users_id_fk", 319 + "tableFrom": "deno_auth", 320 + "tableTo": "users", 321 + "columnsFrom": [ 322 + "user_id" 323 + ], 324 + "columnsTo": [ 325 + "id" 326 + ], 327 + "onDelete": "no action", 328 + "onUpdate": "no action" 329 + } 330 + }, 331 + "compositePrimaryKeys": {}, 332 + "uniqueConstraints": {}, 333 + "policies": {}, 334 + "checkConstraints": {}, 335 + "isRLSEnabled": false 336 + }, 337 + "public.files": { 338 + "name": "files", 339 + "schema": "", 340 + "columns": { 341 + "id": { 342 + "name": "id", 343 + "type": "text", 344 + "primaryKey": true, 345 + "notNull": true, 346 + "default": "xata_id()" 347 + }, 348 + "content": { 349 + "name": "content", 350 + "type": "text", 351 + "primaryKey": false, 352 + "notNull": true 353 + }, 354 + "created_at": { 355 + "name": "created_at", 356 + "type": "timestamp", 357 + "primaryKey": false, 358 + "notNull": true, 359 + "default": "now()" 360 + }, 361 + "updated_at": { 362 + "name": "updated_at", 363 + "type": "timestamp", 364 + "primaryKey": false, 365 + "notNull": true, 366 + "default": "now()" 367 + } 368 + }, 369 + "indexes": {}, 370 + "foreignKeys": {}, 371 + "compositePrimaryKeys": {}, 372 + "uniqueConstraints": {}, 373 + "policies": {}, 374 + "checkConstraints": {}, 375 + "isRLSEnabled": false 376 + }, 377 + "public.modal_auth": { 378 + "name": "modal_auth", 379 + "schema": "", 380 + "columns": { 381 + "id": { 382 + "name": "id", 383 + "type": "text", 384 + "primaryKey": true, 385 + "notNull": true, 386 + "default": "xata_id()" 387 + }, 388 + "sandbox_id": { 389 + "name": "sandbox_id", 390 + "type": "text", 391 + "primaryKey": false, 392 + "notNull": true 393 + }, 394 + "user_id": { 395 + "name": "user_id", 396 + "type": "text", 397 + "primaryKey": false, 398 + "notNull": true 399 + }, 400 + "token_id": { 401 + "name": "token_id", 402 + "type": "text", 403 + "primaryKey": false, 404 + "notNull": true 405 + }, 406 + "redacted_token_id": { 407 + "name": "redacted_token_id", 408 + "type": "text", 409 + "primaryKey": false, 410 + "notNull": true 411 + }, 412 + "token_secret": { 413 + "name": "token_secret", 414 + "type": "text", 415 + "primaryKey": false, 416 + "notNull": true 417 + }, 418 + "redacted_token_secret": { 419 + "name": "redacted_token_secret", 420 + "type": "text", 421 + "primaryKey": false, 422 + "notNull": true 423 + }, 424 + "created_at": { 425 + "name": "created_at", 426 + "type": "timestamp", 427 + "primaryKey": false, 428 + "notNull": true, 429 + "default": "now()" 430 + } 431 + }, 432 + "indexes": { 433 + "unique_modal_auth": { 434 + "name": "unique_modal_auth", 435 + "columns": [ 436 + { 437 + "expression": "sandbox_id", 438 + "isExpression": false, 439 + "asc": true, 440 + "nulls": "last" 441 + }, 442 + { 443 + "expression": "user_id", 444 + "isExpression": false, 445 + "asc": true, 446 + "nulls": "last" 447 + } 448 + ], 449 + "isUnique": true, 450 + "concurrently": false, 451 + "method": "btree", 452 + "with": {} 453 + } 454 + }, 455 + "foreignKeys": { 456 + "modal_auth_sandbox_id_sandboxes_id_fk": { 457 + "name": "modal_auth_sandbox_id_sandboxes_id_fk", 458 + "tableFrom": "modal_auth", 459 + "tableTo": "sandboxes", 460 + "columnsFrom": [ 461 + "sandbox_id" 462 + ], 463 + "columnsTo": [ 464 + "id" 465 + ], 466 + "onDelete": "cascade", 467 + "onUpdate": "no action" 468 + }, 469 + "modal_auth_user_id_users_id_fk": { 470 + "name": "modal_auth_user_id_users_id_fk", 471 + "tableFrom": "modal_auth", 472 + "tableTo": "users", 473 + "columnsFrom": [ 474 + "user_id" 475 + ], 476 + "columnsTo": [ 477 + "id" 478 + ], 479 + "onDelete": "no action", 480 + "onUpdate": "no action" 481 + } 482 + }, 483 + "compositePrimaryKeys": {}, 484 + "uniqueConstraints": {}, 485 + "policies": {}, 486 + "checkConstraints": {}, 487 + "isRLSEnabled": false 488 + }, 489 + "public.sandbox_cp": { 490 + "name": "sandbox_cp", 491 + "schema": "", 492 + "columns": { 493 + "id": { 494 + "name": "id", 495 + "type": "text", 496 + "primaryKey": true, 497 + "notNull": true, 498 + "default": "xata_id()" 499 + }, 500 + "copy_uuid": { 501 + "name": "copy_uuid", 502 + "type": "text", 503 + "primaryKey": false, 504 + "notNull": true 505 + }, 506 + "created_at": { 507 + "name": "created_at", 508 + "type": "timestamp", 509 + "primaryKey": false, 510 + "notNull": true, 511 + "default": "now()" 512 + } 513 + }, 514 + "indexes": {}, 515 + "foreignKeys": {}, 516 + "compositePrimaryKeys": {}, 517 + "uniqueConstraints": { 518 + "sandbox_cp_copy_uuid_unique": { 519 + "name": "sandbox_cp_copy_uuid_unique", 520 + "nullsNotDistinct": false, 521 + "columns": [ 522 + "copy_uuid" 523 + ] 524 + } 525 + }, 526 + "policies": {}, 527 + "checkConstraints": {}, 528 + "isRLSEnabled": false 529 + }, 530 + "public.sandbox_files": { 531 + "name": "sandbox_files", 532 + "schema": "", 533 + "columns": { 534 + "id": { 535 + "name": "id", 536 + "type": "text", 537 + "primaryKey": true, 538 + "notNull": true, 539 + "default": "file_id()" 540 + }, 541 + "sandbox_id": { 542 + "name": "sandbox_id", 543 + "type": "text", 544 + "primaryKey": false, 545 + "notNull": true 546 + }, 547 + "file_id": { 548 + "name": "file_id", 549 + "type": "text", 550 + "primaryKey": false, 551 + "notNull": true 552 + }, 553 + "path": { 554 + "name": "path", 555 + "type": "text", 556 + "primaryKey": false, 557 + "notNull": true 558 + }, 559 + "created_at": { 560 + "name": "created_at", 561 + "type": "timestamp", 562 + "primaryKey": false, 563 + "notNull": true, 564 + "default": "now()" 565 + }, 566 + "updated_at": { 567 + "name": "updated_at", 568 + "type": "timestamp", 569 + "primaryKey": false, 570 + "notNull": true, 571 + "default": "now()" 572 + } 573 + }, 574 + "indexes": { 575 + "unique_sandbox_file_path": { 576 + "name": "unique_sandbox_file_path", 577 + "columns": [ 578 + { 579 + "expression": "sandbox_id", 580 + "isExpression": false, 581 + "asc": true, 582 + "nulls": "last" 583 + }, 584 + { 585 + "expression": "path", 586 + "isExpression": false, 587 + "asc": true, 588 + "nulls": "last" 589 + } 590 + ], 591 + "isUnique": true, 592 + "concurrently": false, 593 + "method": "btree", 594 + "with": {} 595 + } 596 + }, 597 + "foreignKeys": { 598 + "sandbox_files_sandbox_id_sandboxes_id_fk": { 599 + "name": "sandbox_files_sandbox_id_sandboxes_id_fk", 600 + "tableFrom": "sandbox_files", 601 + "tableTo": "sandboxes", 602 + "columnsFrom": [ 603 + "sandbox_id" 604 + ], 605 + "columnsTo": [ 606 + "id" 607 + ], 608 + "onDelete": "cascade", 609 + "onUpdate": "no action" 610 + }, 611 + "sandbox_files_file_id_files_id_fk": { 612 + "name": "sandbox_files_file_id_files_id_fk", 613 + "tableFrom": "sandbox_files", 614 + "tableTo": "files", 615 + "columnsFrom": [ 616 + "file_id" 617 + ], 618 + "columnsTo": [ 619 + "id" 620 + ], 621 + "onDelete": "no action", 622 + "onUpdate": "no action" 623 + } 624 + }, 625 + "compositePrimaryKeys": {}, 626 + "uniqueConstraints": {}, 627 + "policies": {}, 628 + "checkConstraints": {}, 629 + "isRLSEnabled": false 630 + }, 631 + "public.sandbox_ports": { 632 + "name": "sandbox_ports", 633 + "schema": "", 634 + "columns": { 635 + "id": { 636 + "name": "id", 637 + "type": "text", 638 + "primaryKey": true, 639 + "notNull": true, 640 + "default": "xata_id()" 641 + }, 642 + "sandbox_id": { 643 + "name": "sandbox_id", 644 + "type": "text", 645 + "primaryKey": false, 646 + "notNull": true 647 + }, 648 + "exposed_port": { 649 + "name": "exposed_port", 650 + "type": "integer", 651 + "primaryKey": false, 652 + "notNull": true 653 + }, 654 + "preview_url": { 655 + "name": "preview_url", 656 + "type": "text", 657 + "primaryKey": false, 658 + "notNull": false 659 + }, 660 + "description": { 661 + "name": "description", 662 + "type": "text", 663 + "primaryKey": false, 664 + "notNull": false 665 + }, 666 + "service_id": { 667 + "name": "service_id", 668 + "type": "text", 669 + "primaryKey": false, 670 + "notNull": false 671 + }, 672 + "created_at": { 673 + "name": "created_at", 674 + "type": "timestamp", 675 + "primaryKey": false, 676 + "notNull": true, 677 + "default": "now()" 678 + }, 679 + "updated_at": { 680 + "name": "updated_at", 681 + "type": "timestamp", 682 + "primaryKey": false, 683 + "notNull": true, 684 + "default": "now()" 685 + } 686 + }, 687 + "indexes": { 688 + "unique_sandbox_port": { 689 + "name": "unique_sandbox_port", 690 + "columns": [ 691 + { 692 + "expression": "sandbox_id", 693 + "isExpression": false, 694 + "asc": true, 695 + "nulls": "last" 696 + }, 697 + { 698 + "expression": "exposed_port", 699 + "isExpression": false, 700 + "asc": true, 701 + "nulls": "last" 702 + } 703 + ], 704 + "isUnique": true, 705 + "concurrently": false, 706 + "method": "btree", 707 + "with": {} 708 + } 709 + }, 710 + "foreignKeys": { 711 + "sandbox_ports_sandbox_id_sandboxes_id_fk": { 712 + "name": "sandbox_ports_sandbox_id_sandboxes_id_fk", 713 + "tableFrom": "sandbox_ports", 714 + "tableTo": "sandboxes", 715 + "columnsFrom": [ 716 + "sandbox_id" 717 + ], 718 + "columnsTo": [ 719 + "id" 720 + ], 721 + "onDelete": "cascade", 722 + "onUpdate": "no action" 723 + }, 724 + "sandbox_ports_service_id_services_id_fk": { 725 + "name": "sandbox_ports_service_id_services_id_fk", 726 + "tableFrom": "sandbox_ports", 727 + "tableTo": "services", 728 + "columnsFrom": [ 729 + "service_id" 730 + ], 731 + "columnsTo": [ 732 + "id" 733 + ], 734 + "onDelete": "no action", 735 + "onUpdate": "no action" 736 + } 737 + }, 738 + "compositePrimaryKeys": {}, 739 + "uniqueConstraints": {}, 740 + "policies": {}, 741 + "checkConstraints": {}, 742 + "isRLSEnabled": false 743 + }, 744 + "public.sandbox_secrets": { 745 + "name": "sandbox_secrets", 746 + "schema": "", 747 + "columns": { 748 + "id": { 749 + "name": "id", 750 + "type": "text", 751 + "primaryKey": true, 752 + "notNull": true, 753 + "default": "xata_id()" 754 + }, 755 + "sandbox_id": { 756 + "name": "sandbox_id", 757 + "type": "text", 758 + "primaryKey": false, 759 + "notNull": true 760 + }, 761 + "secret_id": { 762 + "name": "secret_id", 763 + "type": "text", 764 + "primaryKey": false, 765 + "notNull": true 766 + }, 767 + "name": { 768 + "name": "name", 769 + "type": "text", 770 + "primaryKey": false, 771 + "notNull": false 772 + }, 773 + "created_at": { 774 + "name": "created_at", 775 + "type": "timestamp", 776 + "primaryKey": false, 777 + "notNull": true, 778 + "default": "now()" 779 + }, 780 + "updated_at": { 781 + "name": "updated_at", 782 + "type": "timestamp", 783 + "primaryKey": false, 784 + "notNull": true, 785 + "default": "now()" 786 + } 787 + }, 788 + "indexes": { 789 + "unique_sandbox_secret_by_name": { 790 + "name": "unique_sandbox_secret_by_name", 791 + "columns": [ 792 + { 793 + "expression": "sandbox_id", 794 + "isExpression": false, 795 + "asc": true, 796 + "nulls": "last" 797 + }, 798 + { 799 + "expression": "name", 800 + "isExpression": false, 801 + "asc": true, 802 + "nulls": "last" 803 + } 804 + ], 805 + "isUnique": true, 806 + "concurrently": false, 807 + "method": "btree", 808 + "with": {} 809 + } 810 + }, 811 + "foreignKeys": { 812 + "sandbox_secrets_sandbox_id_sandboxes_id_fk": { 813 + "name": "sandbox_secrets_sandbox_id_sandboxes_id_fk", 814 + "tableFrom": "sandbox_secrets", 815 + "tableTo": "sandboxes", 816 + "columnsFrom": [ 817 + "sandbox_id" 818 + ], 819 + "columnsTo": [ 820 + "id" 821 + ], 822 + "onDelete": "cascade", 823 + "onUpdate": "no action" 824 + }, 825 + "sandbox_secrets_secret_id_secrets_id_fk": { 826 + "name": "sandbox_secrets_secret_id_secrets_id_fk", 827 + "tableFrom": "sandbox_secrets", 828 + "tableTo": "secrets", 829 + "columnsFrom": [ 830 + "secret_id" 831 + ], 832 + "columnsTo": [ 833 + "id" 834 + ], 835 + "onDelete": "no action", 836 + "onUpdate": "no action" 837 + } 838 + }, 839 + "compositePrimaryKeys": {}, 840 + "uniqueConstraints": {}, 841 + "policies": {}, 842 + "checkConstraints": {}, 843 + "isRLSEnabled": false 844 + }, 845 + "public.sandbox_variables": { 846 + "name": "sandbox_variables", 847 + "schema": "", 848 + "columns": { 849 + "id": { 850 + "name": "id", 851 + "type": "text", 852 + "primaryKey": true, 853 + "notNull": true, 854 + "default": "xata_id()" 855 + }, 856 + "sandbox_id": { 857 + "name": "sandbox_id", 858 + "type": "text", 859 + "primaryKey": false, 860 + "notNull": true 861 + }, 862 + "variable_id": { 863 + "name": "variable_id", 864 + "type": "text", 865 + "primaryKey": false, 866 + "notNull": true 867 + }, 868 + "name": { 869 + "name": "name", 870 + "type": "text", 871 + "primaryKey": false, 872 + "notNull": true 873 + }, 874 + "created_at": { 875 + "name": "created_at", 876 + "type": "timestamp", 877 + "primaryKey": false, 878 + "notNull": true, 879 + "default": "now()" 880 + }, 881 + "updated_at": { 882 + "name": "updated_at", 883 + "type": "timestamp", 884 + "primaryKey": false, 885 + "notNull": true, 886 + "default": "now()" 887 + } 888 + }, 889 + "indexes": { 890 + "unique_sandbox_variables_by_name": { 891 + "name": "unique_sandbox_variables_by_name", 892 + "columns": [ 893 + { 894 + "expression": "sandbox_id", 895 + "isExpression": false, 896 + "asc": true, 897 + "nulls": "last" 898 + }, 899 + { 900 + "expression": "name", 901 + "isExpression": false, 902 + "asc": true, 903 + "nulls": "last" 904 + } 905 + ], 906 + "isUnique": true, 907 + "concurrently": false, 908 + "method": "btree", 909 + "with": {} 910 + } 911 + }, 912 + "foreignKeys": { 913 + "sandbox_variables_sandbox_id_sandboxes_id_fk": { 914 + "name": "sandbox_variables_sandbox_id_sandboxes_id_fk", 915 + "tableFrom": "sandbox_variables", 916 + "tableTo": "sandboxes", 917 + "columnsFrom": [ 918 + "sandbox_id" 919 + ], 920 + "columnsTo": [ 921 + "id" 922 + ], 923 + "onDelete": "cascade", 924 + "onUpdate": "no action" 925 + }, 926 + "sandbox_variables_variable_id_variables_id_fk": { 927 + "name": "sandbox_variables_variable_id_variables_id_fk", 928 + "tableFrom": "sandbox_variables", 929 + "tableTo": "variables", 930 + "columnsFrom": [ 931 + "variable_id" 932 + ], 933 + "columnsTo": [ 934 + "id" 935 + ], 936 + "onDelete": "no action", 937 + "onUpdate": "no action" 938 + } 939 + }, 940 + "compositePrimaryKeys": {}, 941 + "uniqueConstraints": {}, 942 + "policies": {}, 943 + "checkConstraints": {}, 944 + "isRLSEnabled": false 945 + }, 946 + "public.sandbox_volumes": { 947 + "name": "sandbox_volumes", 948 + "schema": "", 949 + "columns": { 950 + "id": { 951 + "name": "id", 952 + "type": "text", 953 + "primaryKey": true, 954 + "notNull": true, 955 + "default": "volume_id()" 956 + }, 957 + "sandbox_id": { 958 + "name": "sandbox_id", 959 + "type": "text", 960 + "primaryKey": false, 961 + "notNull": true 962 + }, 963 + "volume_id": { 964 + "name": "volume_id", 965 + "type": "text", 966 + "primaryKey": false, 967 + "notNull": true 968 + }, 969 + "name": { 970 + "name": "name", 971 + "type": "text", 972 + "primaryKey": false, 973 + "notNull": false 974 + }, 975 + "path": { 976 + "name": "path", 977 + "type": "text", 978 + "primaryKey": false, 979 + "notNull": true 980 + }, 981 + "created_at": { 982 + "name": "created_at", 983 + "type": "timestamp", 984 + "primaryKey": false, 985 + "notNull": true, 986 + "default": "now()" 987 + }, 988 + "updated_at": { 989 + "name": "updated_at", 990 + "type": "timestamp", 991 + "primaryKey": false, 992 + "notNull": true, 993 + "default": "now()" 994 + } 995 + }, 996 + "indexes": { 997 + "unique_sandbox_volume_path": { 998 + "name": "unique_sandbox_volume_path", 999 + "columns": [ 1000 + { 1001 + "expression": "sandbox_id", 1002 + "isExpression": false, 1003 + "asc": true, 1004 + "nulls": "last" 1005 + }, 1006 + { 1007 + "expression": "path", 1008 + "isExpression": false, 1009 + "asc": true, 1010 + "nulls": "last" 1011 + } 1012 + ], 1013 + "isUnique": true, 1014 + "concurrently": false, 1015 + "method": "btree", 1016 + "with": {} 1017 + } 1018 + }, 1019 + "foreignKeys": { 1020 + "sandbox_volumes_sandbox_id_sandboxes_id_fk": { 1021 + "name": "sandbox_volumes_sandbox_id_sandboxes_id_fk", 1022 + "tableFrom": "sandbox_volumes", 1023 + "tableTo": "sandboxes", 1024 + "columnsFrom": [ 1025 + "sandbox_id" 1026 + ], 1027 + "columnsTo": [ 1028 + "id" 1029 + ], 1030 + "onDelete": "cascade", 1031 + "onUpdate": "no action" 1032 + }, 1033 + "sandbox_volumes_volume_id_volumes_id_fk": { 1034 + "name": "sandbox_volumes_volume_id_volumes_id_fk", 1035 + "tableFrom": "sandbox_volumes", 1036 + "tableTo": "volumes", 1037 + "columnsFrom": [ 1038 + "volume_id" 1039 + ], 1040 + "columnsTo": [ 1041 + "id" 1042 + ], 1043 + "onDelete": "no action", 1044 + "onUpdate": "no action" 1045 + } 1046 + }, 1047 + "compositePrimaryKeys": {}, 1048 + "uniqueConstraints": {}, 1049 + "policies": {}, 1050 + "checkConstraints": {}, 1051 + "isRLSEnabled": false 1052 + }, 1053 + "public.sandboxes": { 1054 + "name": "sandboxes", 1055 + "schema": "", 1056 + "columns": { 1057 + "id": { 1058 + "name": "id", 1059 + "type": "text", 1060 + "primaryKey": true, 1061 + "notNull": true, 1062 + "default": "sandbox_id()" 1063 + }, 1064 + "base": { 1065 + "name": "base", 1066 + "type": "text", 1067 + "primaryKey": false, 1068 + "notNull": false 1069 + }, 1070 + "name": { 1071 + "name": "name", 1072 + "type": "text", 1073 + "primaryKey": false, 1074 + "notNull": true 1075 + }, 1076 + "display_name": { 1077 + "name": "display_name", 1078 + "type": "text", 1079 + "primaryKey": false, 1080 + "notNull": false 1081 + }, 1082 + "uri": { 1083 + "name": "uri", 1084 + "type": "text", 1085 + "primaryKey": false, 1086 + "notNull": false 1087 + }, 1088 + "cid": { 1089 + "name": "cid", 1090 + "type": "text", 1091 + "primaryKey": false, 1092 + "notNull": false 1093 + }, 1094 + "repo": { 1095 + "name": "repo", 1096 + "type": "text", 1097 + "primaryKey": false, 1098 + "notNull": false 1099 + }, 1100 + "provider": { 1101 + "name": "provider", 1102 + "type": "text", 1103 + "primaryKey": false, 1104 + "notNull": true, 1105 + "default": "'cloudflare'" 1106 + }, 1107 + "description": { 1108 + "name": "description", 1109 + "type": "text", 1110 + "primaryKey": false, 1111 + "notNull": false 1112 + }, 1113 + "topics": { 1114 + "name": "topics", 1115 + "type": "text[]", 1116 + "primaryKey": false, 1117 + "notNull": false 1118 + }, 1119 + "logo": { 1120 + "name": "logo", 1121 + "type": "text", 1122 + "primaryKey": false, 1123 + "notNull": false 1124 + }, 1125 + "readme": { 1126 + "name": "readme", 1127 + "type": "text", 1128 + "primaryKey": false, 1129 + "notNull": false 1130 + }, 1131 + "public_key": { 1132 + "name": "public_key", 1133 + "type": "text", 1134 + "primaryKey": false, 1135 + "notNull": true 1136 + }, 1137 + "user_id": { 1138 + "name": "user_id", 1139 + "type": "text", 1140 + "primaryKey": false, 1141 + "notNull": false 1142 + }, 1143 + "instance_type": { 1144 + "name": "instance_type", 1145 + "type": "text", 1146 + "primaryKey": false, 1147 + "notNull": false 1148 + }, 1149 + "vcpus": { 1150 + "name": "vcpus", 1151 + "type": "integer", 1152 + "primaryKey": false, 1153 + "notNull": false 1154 + }, 1155 + "memory": { 1156 + "name": "memory", 1157 + "type": "integer", 1158 + "primaryKey": false, 1159 + "notNull": false 1160 + }, 1161 + "disk": { 1162 + "name": "disk", 1163 + "type": "integer", 1164 + "primaryKey": false, 1165 + "notNull": false 1166 + }, 1167 + "status": { 1168 + "name": "status", 1169 + "type": "text", 1170 + "primaryKey": false, 1171 + "notNull": true 1172 + }, 1173 + "keep_alive": { 1174 + "name": "keep_alive", 1175 + "type": "boolean", 1176 + "primaryKey": false, 1177 + "notNull": true, 1178 + "default": false 1179 + }, 1180 + "sleep_after": { 1181 + "name": "sleep_after", 1182 + "type": "text", 1183 + "primaryKey": false, 1184 + "notNull": false 1185 + }, 1186 + "sandbox_id": { 1187 + "name": "sandbox_id", 1188 + "type": "text", 1189 + "primaryKey": false, 1190 + "notNull": false 1191 + }, 1192 + "installs": { 1193 + "name": "installs", 1194 + "type": "integer", 1195 + "primaryKey": false, 1196 + "notNull": true, 1197 + "default": 0 1198 + }, 1199 + "started_at": { 1200 + "name": "started_at", 1201 + "type": "timestamp", 1202 + "primaryKey": false, 1203 + "notNull": false 1204 + }, 1205 + "created_at": { 1206 + "name": "created_at", 1207 + "type": "timestamp", 1208 + "primaryKey": false, 1209 + "notNull": true, 1210 + "default": "now()" 1211 + }, 1212 + "updated_at": { 1213 + "name": "updated_at", 1214 + "type": "timestamp", 1215 + "primaryKey": false, 1216 + "notNull": true, 1217 + "default": "now()" 1218 + } 1219 + }, 1220 + "indexes": {}, 1221 + "foreignKeys": { 1222 + "sandboxes_user_id_users_id_fk": { 1223 + "name": "sandboxes_user_id_users_id_fk", 1224 + "tableFrom": "sandboxes", 1225 + "tableTo": "users", 1226 + "columnsFrom": [ 1227 + "user_id" 1228 + ], 1229 + "columnsTo": [ 1230 + "id" 1231 + ], 1232 + "onDelete": "no action", 1233 + "onUpdate": "no action" 1234 + } 1235 + }, 1236 + "compositePrimaryKeys": {}, 1237 + "uniqueConstraints": { 1238 + "sandboxes_name_unique": { 1239 + "name": "sandboxes_name_unique", 1240 + "nullsNotDistinct": false, 1241 + "columns": [ 1242 + "name" 1243 + ] 1244 + }, 1245 + "sandboxes_uri_unique": { 1246 + "name": "sandboxes_uri_unique", 1247 + "nullsNotDistinct": false, 1248 + "columns": [ 1249 + "uri" 1250 + ] 1251 + }, 1252 + "sandboxes_cid_unique": { 1253 + "name": "sandboxes_cid_unique", 1254 + "nullsNotDistinct": false, 1255 + "columns": [ 1256 + "cid" 1257 + ] 1258 + } 1259 + }, 1260 + "policies": {}, 1261 + "checkConstraints": {}, 1262 + "isRLSEnabled": false 1263 + }, 1264 + "public.secrets": { 1265 + "name": "secrets", 1266 + "schema": "", 1267 + "columns": { 1268 + "id": { 1269 + "name": "id", 1270 + "type": "text", 1271 + "primaryKey": true, 1272 + "notNull": true, 1273 + "default": "secret_id()" 1274 + }, 1275 + "name": { 1276 + "name": "name", 1277 + "type": "text", 1278 + "primaryKey": false, 1279 + "notNull": true 1280 + }, 1281 + "value": { 1282 + "name": "value", 1283 + "type": "text", 1284 + "primaryKey": false, 1285 + "notNull": true 1286 + }, 1287 + "redacted": { 1288 + "name": "redacted", 1289 + "type": "text", 1290 + "primaryKey": false, 1291 + "notNull": false 1292 + }, 1293 + "created_at": { 1294 + "name": "created_at", 1295 + "type": "timestamp", 1296 + "primaryKey": false, 1297 + "notNull": true, 1298 + "default": "now()" 1299 + } 1300 + }, 1301 + "indexes": {}, 1302 + "foreignKeys": {}, 1303 + "compositePrimaryKeys": {}, 1304 + "uniqueConstraints": {}, 1305 + "policies": {}, 1306 + "checkConstraints": {}, 1307 + "isRLSEnabled": false 1308 + }, 1309 + "public.services": { 1310 + "name": "services", 1311 + "schema": "", 1312 + "columns": { 1313 + "id": { 1314 + "name": "id", 1315 + "type": "text", 1316 + "primaryKey": true, 1317 + "notNull": true, 1318 + "default": "xata_id()" 1319 + }, 1320 + "sandbox_id": { 1321 + "name": "sandbox_id", 1322 + "type": "text", 1323 + "primaryKey": false, 1324 + "notNull": true 1325 + }, 1326 + "name": { 1327 + "name": "name", 1328 + "type": "text", 1329 + "primaryKey": false, 1330 + "notNull": true 1331 + }, 1332 + "command": { 1333 + "name": "command", 1334 + "type": "text", 1335 + "primaryKey": false, 1336 + "notNull": true 1337 + }, 1338 + "description": { 1339 + "name": "description", 1340 + "type": "text", 1341 + "primaryKey": false, 1342 + "notNull": false 1343 + }, 1344 + "service_id": { 1345 + "name": "service_id", 1346 + "type": "text", 1347 + "primaryKey": false, 1348 + "notNull": false 1349 + }, 1350 + "status": { 1351 + "name": "status", 1352 + "type": "text", 1353 + "primaryKey": false, 1354 + "notNull": true, 1355 + "default": "'STOPPED'" 1356 + }, 1357 + "created_at": { 1358 + "name": "created_at", 1359 + "type": "timestamp", 1360 + "primaryKey": false, 1361 + "notNull": true, 1362 + "default": "now()" 1363 + }, 1364 + "updated_at": { 1365 + "name": "updated_at", 1366 + "type": "timestamp", 1367 + "primaryKey": false, 1368 + "notNull": true, 1369 + "default": "now()" 1370 + } 1371 + }, 1372 + "indexes": { 1373 + "unique_sandbox_service": { 1374 + "name": "unique_sandbox_service", 1375 + "columns": [ 1376 + { 1377 + "expression": "name", 1378 + "isExpression": false, 1379 + "asc": true, 1380 + "nulls": "last" 1381 + }, 1382 + { 1383 + "expression": "sandbox_id", 1384 + "isExpression": false, 1385 + "asc": true, 1386 + "nulls": "last" 1387 + } 1388 + ], 1389 + "isUnique": true, 1390 + "concurrently": false, 1391 + "method": "btree", 1392 + "with": {} 1393 + } 1394 + }, 1395 + "foreignKeys": { 1396 + "services_sandbox_id_sandboxes_id_fk": { 1397 + "name": "services_sandbox_id_sandboxes_id_fk", 1398 + "tableFrom": "services", 1399 + "tableTo": "sandboxes", 1400 + "columnsFrom": [ 1401 + "sandbox_id" 1402 + ], 1403 + "columnsTo": [ 1404 + "id" 1405 + ], 1406 + "onDelete": "cascade", 1407 + "onUpdate": "no action" 1408 + } 1409 + }, 1410 + "compositePrimaryKeys": {}, 1411 + "uniqueConstraints": {}, 1412 + "policies": {}, 1413 + "checkConstraints": {}, 1414 + "isRLSEnabled": false 1415 + }, 1416 + "public.snapshots": { 1417 + "name": "snapshots", 1418 + "schema": "", 1419 + "columns": { 1420 + "id": { 1421 + "name": "id", 1422 + "type": "text", 1423 + "primaryKey": true, 1424 + "notNull": true, 1425 + "default": "snapshot_id()" 1426 + }, 1427 + "slug": { 1428 + "name": "slug", 1429 + "type": "text", 1430 + "primaryKey": false, 1431 + "notNull": true 1432 + }, 1433 + "created_at": { 1434 + "name": "created_at", 1435 + "type": "timestamp", 1436 + "primaryKey": false, 1437 + "notNull": true, 1438 + "default": "now()" 1439 + } 1440 + }, 1441 + "indexes": {}, 1442 + "foreignKeys": {}, 1443 + "compositePrimaryKeys": {}, 1444 + "uniqueConstraints": { 1445 + "snapshots_slug_unique": { 1446 + "name": "snapshots_slug_unique", 1447 + "nullsNotDistinct": false, 1448 + "columns": [ 1449 + "slug" 1450 + ] 1451 + } 1452 + }, 1453 + "policies": {}, 1454 + "checkConstraints": {}, 1455 + "isRLSEnabled": false 1456 + }, 1457 + "public.sprite_auth": { 1458 + "name": "sprite_auth", 1459 + "schema": "", 1460 + "columns": { 1461 + "id": { 1462 + "name": "id", 1463 + "type": "text", 1464 + "primaryKey": true, 1465 + "notNull": true, 1466 + "default": "xata_id()" 1467 + }, 1468 + "sandbox_id": { 1469 + "name": "sandbox_id", 1470 + "type": "text", 1471 + "primaryKey": false, 1472 + "notNull": true 1473 + }, 1474 + "user_id": { 1475 + "name": "user_id", 1476 + "type": "text", 1477 + "primaryKey": false, 1478 + "notNull": true 1479 + }, 1480 + "sprite_token": { 1481 + "name": "sprite_token", 1482 + "type": "text", 1483 + "primaryKey": false, 1484 + "notNull": true 1485 + }, 1486 + "redacted_sprite_token": { 1487 + "name": "redacted_sprite_token", 1488 + "type": "text", 1489 + "primaryKey": false, 1490 + "notNull": true 1491 + }, 1492 + "created_at": { 1493 + "name": "created_at", 1494 + "type": "timestamp", 1495 + "primaryKey": false, 1496 + "notNull": true, 1497 + "default": "now()" 1498 + } 1499 + }, 1500 + "indexes": { 1501 + "unique_sprite_auth": { 1502 + "name": "unique_sprite_auth", 1503 + "columns": [ 1504 + { 1505 + "expression": "sandbox_id", 1506 + "isExpression": false, 1507 + "asc": true, 1508 + "nulls": "last" 1509 + }, 1510 + { 1511 + "expression": "user_id", 1512 + "isExpression": false, 1513 + "asc": true, 1514 + "nulls": "last" 1515 + } 1516 + ], 1517 + "isUnique": true, 1518 + "concurrently": false, 1519 + "method": "btree", 1520 + "with": {} 1521 + } 1522 + }, 1523 + "foreignKeys": { 1524 + "sprite_auth_sandbox_id_sandboxes_id_fk": { 1525 + "name": "sprite_auth_sandbox_id_sandboxes_id_fk", 1526 + "tableFrom": "sprite_auth", 1527 + "tableTo": "sandboxes", 1528 + "columnsFrom": [ 1529 + "sandbox_id" 1530 + ], 1531 + "columnsTo": [ 1532 + "id" 1533 + ], 1534 + "onDelete": "cascade", 1535 + "onUpdate": "no action" 1536 + }, 1537 + "sprite_auth_user_id_users_id_fk": { 1538 + "name": "sprite_auth_user_id_users_id_fk", 1539 + "tableFrom": "sprite_auth", 1540 + "tableTo": "users", 1541 + "columnsFrom": [ 1542 + "user_id" 1543 + ], 1544 + "columnsTo": [ 1545 + "id" 1546 + ], 1547 + "onDelete": "no action", 1548 + "onUpdate": "no action" 1549 + } 1550 + }, 1551 + "compositePrimaryKeys": {}, 1552 + "uniqueConstraints": {}, 1553 + "policies": {}, 1554 + "checkConstraints": {}, 1555 + "isRLSEnabled": false 1556 + }, 1557 + "public.ssh_keys": { 1558 + "name": "ssh_keys", 1559 + "schema": "", 1560 + "columns": { 1561 + "id": { 1562 + "name": "id", 1563 + "type": "text", 1564 + "primaryKey": true, 1565 + "notNull": true, 1566 + "default": "xata_id()" 1567 + }, 1568 + "sandbox_id": { 1569 + "name": "sandbox_id", 1570 + "type": "text", 1571 + "primaryKey": false, 1572 + "notNull": true 1573 + }, 1574 + "public_key": { 1575 + "name": "public_key", 1576 + "type": "text", 1577 + "primaryKey": false, 1578 + "notNull": true 1579 + }, 1580 + "private_key": { 1581 + "name": "private_key", 1582 + "type": "text", 1583 + "primaryKey": false, 1584 + "notNull": true 1585 + }, 1586 + "redacted": { 1587 + "name": "redacted", 1588 + "type": "text", 1589 + "primaryKey": false, 1590 + "notNull": false 1591 + }, 1592 + "created_at": { 1593 + "name": "created_at", 1594 + "type": "timestamp", 1595 + "primaryKey": false, 1596 + "notNull": true, 1597 + "default": "now()" 1598 + } 1599 + }, 1600 + "indexes": { 1601 + "unique_sandbox_ssh_key": { 1602 + "name": "unique_sandbox_ssh_key", 1603 + "columns": [ 1604 + { 1605 + "expression": "public_key", 1606 + "isExpression": false, 1607 + "asc": true, 1608 + "nulls": "last" 1609 + }, 1610 + { 1611 + "expression": "sandbox_id", 1612 + "isExpression": false, 1613 + "asc": true, 1614 + "nulls": "last" 1615 + } 1616 + ], 1617 + "isUnique": true, 1618 + "concurrently": false, 1619 + "method": "btree", 1620 + "with": {} 1621 + } 1622 + }, 1623 + "foreignKeys": { 1624 + "ssh_keys_sandbox_id_sandboxes_id_fk": { 1625 + "name": "ssh_keys_sandbox_id_sandboxes_id_fk", 1626 + "tableFrom": "ssh_keys", 1627 + "tableTo": "sandboxes", 1628 + "columnsFrom": [ 1629 + "sandbox_id" 1630 + ], 1631 + "columnsTo": [ 1632 + "id" 1633 + ], 1634 + "onDelete": "cascade", 1635 + "onUpdate": "no action" 1636 + } 1637 + }, 1638 + "compositePrimaryKeys": {}, 1639 + "uniqueConstraints": {}, 1640 + "policies": {}, 1641 + "checkConstraints": {}, 1642 + "isRLSEnabled": false 1643 + }, 1644 + "public.tailscale_auth_keys": { 1645 + "name": "tailscale_auth_keys", 1646 + "schema": "", 1647 + "columns": { 1648 + "id": { 1649 + "name": "id", 1650 + "type": "text", 1651 + "primaryKey": true, 1652 + "notNull": true, 1653 + "default": "xata_id()" 1654 + }, 1655 + "sandbox_id": { 1656 + "name": "sandbox_id", 1657 + "type": "text", 1658 + "primaryKey": false, 1659 + "notNull": true 1660 + }, 1661 + "auth_key": { 1662 + "name": "auth_key", 1663 + "type": "text", 1664 + "primaryKey": false, 1665 + "notNull": true 1666 + }, 1667 + "redacted": { 1668 + "name": "redacted", 1669 + "type": "text", 1670 + "primaryKey": false, 1671 + "notNull": true 1672 + }, 1673 + "created_at": { 1674 + "name": "created_at", 1675 + "type": "timestamp", 1676 + "primaryKey": false, 1677 + "notNull": true, 1678 + "default": "now()" 1679 + } 1680 + }, 1681 + "indexes": {}, 1682 + "foreignKeys": { 1683 + "tailscale_auth_keys_sandbox_id_sandboxes_id_fk": { 1684 + "name": "tailscale_auth_keys_sandbox_id_sandboxes_id_fk", 1685 + "tableFrom": "tailscale_auth_keys", 1686 + "tableTo": "sandboxes", 1687 + "columnsFrom": [ 1688 + "sandbox_id" 1689 + ], 1690 + "columnsTo": [ 1691 + "id" 1692 + ], 1693 + "onDelete": "cascade", 1694 + "onUpdate": "no action" 1695 + } 1696 + }, 1697 + "compositePrimaryKeys": {}, 1698 + "uniqueConstraints": {}, 1699 + "policies": {}, 1700 + "checkConstraints": {}, 1701 + "isRLSEnabled": false 1702 + }, 1703 + "public.users": { 1704 + "name": "users", 1705 + "schema": "", 1706 + "columns": { 1707 + "id": { 1708 + "name": "id", 1709 + "type": "text", 1710 + "primaryKey": true, 1711 + "notNull": true, 1712 + "default": "xata_id()" 1713 + }, 1714 + "did": { 1715 + "name": "did", 1716 + "type": "text", 1717 + "primaryKey": false, 1718 + "notNull": true 1719 + }, 1720 + "display_name": { 1721 + "name": "display_name", 1722 + "type": "text", 1723 + "primaryKey": false, 1724 + "notNull": false 1725 + }, 1726 + "handle": { 1727 + "name": "handle", 1728 + "type": "text", 1729 + "primaryKey": false, 1730 + "notNull": true 1731 + }, 1732 + "avatar": { 1733 + "name": "avatar", 1734 + "type": "text", 1735 + "primaryKey": false, 1736 + "notNull": false 1737 + }, 1738 + "created_at": { 1739 + "name": "created_at", 1740 + "type": "timestamp", 1741 + "primaryKey": false, 1742 + "notNull": true, 1743 + "default": "now()" 1744 + }, 1745 + "updated_at": { 1746 + "name": "updated_at", 1747 + "type": "timestamp", 1748 + "primaryKey": false, 1749 + "notNull": true, 1750 + "default": "now()" 1751 + } 1752 + }, 1753 + "indexes": {}, 1754 + "foreignKeys": {}, 1755 + "compositePrimaryKeys": {}, 1756 + "uniqueConstraints": { 1757 + "users_did_unique": { 1758 + "name": "users_did_unique", 1759 + "nullsNotDistinct": false, 1760 + "columns": [ 1761 + "did" 1762 + ] 1763 + }, 1764 + "users_handle_unique": { 1765 + "name": "users_handle_unique", 1766 + "nullsNotDistinct": false, 1767 + "columns": [ 1768 + "handle" 1769 + ] 1770 + } 1771 + }, 1772 + "policies": {}, 1773 + "checkConstraints": {}, 1774 + "isRLSEnabled": false 1775 + }, 1776 + "public.variables": { 1777 + "name": "variables", 1778 + "schema": "", 1779 + "columns": { 1780 + "id": { 1781 + "name": "id", 1782 + "type": "text", 1783 + "primaryKey": true, 1784 + "notNull": true, 1785 + "default": "variable_id()" 1786 + }, 1787 + "name": { 1788 + "name": "name", 1789 + "type": "text", 1790 + "primaryKey": false, 1791 + "notNull": true 1792 + }, 1793 + "value": { 1794 + "name": "value", 1795 + "type": "text", 1796 + "primaryKey": false, 1797 + "notNull": true 1798 + }, 1799 + "created_at": { 1800 + "name": "created_at", 1801 + "type": "timestamp", 1802 + "primaryKey": false, 1803 + "notNull": true, 1804 + "default": "now()" 1805 + }, 1806 + "updated_at": { 1807 + "name": "updated_at", 1808 + "type": "timestamp", 1809 + "primaryKey": false, 1810 + "notNull": true, 1811 + "default": "now()" 1812 + } 1813 + }, 1814 + "indexes": {}, 1815 + "foreignKeys": {}, 1816 + "compositePrimaryKeys": {}, 1817 + "uniqueConstraints": {}, 1818 + "policies": {}, 1819 + "checkConstraints": {}, 1820 + "isRLSEnabled": false 1821 + }, 1822 + "public.vercel_auth": { 1823 + "name": "vercel_auth", 1824 + "schema": "", 1825 + "columns": { 1826 + "id": { 1827 + "name": "id", 1828 + "type": "text", 1829 + "primaryKey": true, 1830 + "notNull": true, 1831 + "default": "xata_id()" 1832 + }, 1833 + "sandbox_id": { 1834 + "name": "sandbox_id", 1835 + "type": "text", 1836 + "primaryKey": false, 1837 + "notNull": true 1838 + }, 1839 + "user_id": { 1840 + "name": "user_id", 1841 + "type": "text", 1842 + "primaryKey": false, 1843 + "notNull": true 1844 + }, 1845 + "vercel_token": { 1846 + "name": "vercel_token", 1847 + "type": "text", 1848 + "primaryKey": false, 1849 + "notNull": true 1850 + }, 1851 + "redacted_vercel_token": { 1852 + "name": "redacted_vercel_token", 1853 + "type": "text", 1854 + "primaryKey": false, 1855 + "notNull": true 1856 + }, 1857 + "project_id": { 1858 + "name": "project_id", 1859 + "type": "text", 1860 + "primaryKey": false, 1861 + "notNull": true 1862 + }, 1863 + "team_id": { 1864 + "name": "team_id", 1865 + "type": "text", 1866 + "primaryKey": false, 1867 + "notNull": true 1868 + }, 1869 + "created_at": { 1870 + "name": "created_at", 1871 + "type": "timestamp", 1872 + "primaryKey": false, 1873 + "notNull": true, 1874 + "default": "now()" 1875 + } 1876 + }, 1877 + "indexes": { 1878 + "unique_vercel_auth": { 1879 + "name": "unique_vercel_auth", 1880 + "columns": [ 1881 + { 1882 + "expression": "sandbox_id", 1883 + "isExpression": false, 1884 + "asc": true, 1885 + "nulls": "last" 1886 + }, 1887 + { 1888 + "expression": "user_id", 1889 + "isExpression": false, 1890 + "asc": true, 1891 + "nulls": "last" 1892 + } 1893 + ], 1894 + "isUnique": true, 1895 + "concurrently": false, 1896 + "method": "btree", 1897 + "with": {} 1898 + } 1899 + }, 1900 + "foreignKeys": { 1901 + "vercel_auth_sandbox_id_sandboxes_id_fk": { 1902 + "name": "vercel_auth_sandbox_id_sandboxes_id_fk", 1903 + "tableFrom": "vercel_auth", 1904 + "tableTo": "sandboxes", 1905 + "columnsFrom": [ 1906 + "sandbox_id" 1907 + ], 1908 + "columnsTo": [ 1909 + "id" 1910 + ], 1911 + "onDelete": "cascade", 1912 + "onUpdate": "no action" 1913 + }, 1914 + "vercel_auth_user_id_users_id_fk": { 1915 + "name": "vercel_auth_user_id_users_id_fk", 1916 + "tableFrom": "vercel_auth", 1917 + "tableTo": "users", 1918 + "columnsFrom": [ 1919 + "user_id" 1920 + ], 1921 + "columnsTo": [ 1922 + "id" 1923 + ], 1924 + "onDelete": "no action", 1925 + "onUpdate": "no action" 1926 + } 1927 + }, 1928 + "compositePrimaryKeys": {}, 1929 + "uniqueConstraints": {}, 1930 + "policies": {}, 1931 + "checkConstraints": {}, 1932 + "isRLSEnabled": false 1933 + }, 1934 + "public.volumes": { 1935 + "name": "volumes", 1936 + "schema": "", 1937 + "columns": { 1938 + "id": { 1939 + "name": "id", 1940 + "type": "text", 1941 + "primaryKey": true, 1942 + "notNull": true, 1943 + "default": "volume_id()" 1944 + }, 1945 + "slug": { 1946 + "name": "slug", 1947 + "type": "text", 1948 + "primaryKey": false, 1949 + "notNull": true 1950 + }, 1951 + "size": { 1952 + "name": "size", 1953 + "type": "integer", 1954 + "primaryKey": false, 1955 + "notNull": true 1956 + }, 1957 + "size_unit": { 1958 + "name": "size_unit", 1959 + "type": "text", 1960 + "primaryKey": false, 1961 + "notNull": true 1962 + }, 1963 + "created_at": { 1964 + "name": "created_at", 1965 + "type": "timestamp", 1966 + "primaryKey": false, 1967 + "notNull": true, 1968 + "default": "now()" 1969 + }, 1970 + "updated_at": { 1971 + "name": "updated_at", 1972 + "type": "timestamp", 1973 + "primaryKey": false, 1974 + "notNull": true, 1975 + "default": "now()" 1976 + } 1977 + }, 1978 + "indexes": {}, 1979 + "foreignKeys": {}, 1980 + "compositePrimaryKeys": {}, 1981 + "uniqueConstraints": { 1982 + "volumes_slug_unique": { 1983 + "name": "volumes_slug_unique", 1984 + "nullsNotDistinct": false, 1985 + "columns": [ 1986 + "slug" 1987 + ] 1988 + } 1989 + }, 1990 + "policies": {}, 1991 + "checkConstraints": {}, 1992 + "isRLSEnabled": false 1993 + }, 1994 + "public.integrations": { 1995 + "name": "integrations", 1996 + "schema": "", 1997 + "columns": { 1998 + "id": { 1999 + "name": "id", 2000 + "type": "text", 2001 + "primaryKey": true, 2002 + "notNull": true, 2003 + "default": "xata_id()" 2004 + }, 2005 + "sandbox_id": { 2006 + "name": "sandbox_id", 2007 + "type": "text", 2008 + "primaryKey": false, 2009 + "notNull": true 2010 + }, 2011 + "name": { 2012 + "name": "name", 2013 + "type": "text", 2014 + "primaryKey": false, 2015 + "notNull": true 2016 + }, 2017 + "description": { 2018 + "name": "description", 2019 + "type": "text", 2020 + "primaryKey": false, 2021 + "notNull": false 2022 + }, 2023 + "webhook_url": { 2024 + "name": "webhook_url", 2025 + "type": "text", 2026 + "primaryKey": false, 2027 + "notNull": true 2028 + }, 2029 + "created_at": { 2030 + "name": "created_at", 2031 + "type": "timestamp", 2032 + "primaryKey": false, 2033 + "notNull": true, 2034 + "default": "now()" 2035 + } 2036 + }, 2037 + "indexes": { 2038 + "unique_sandbox_integration": { 2039 + "name": "unique_sandbox_integration", 2040 + "columns": [ 2041 + { 2042 + "expression": "sandbox_id", 2043 + "isExpression": false, 2044 + "asc": true, 2045 + "nulls": "last" 2046 + }, 2047 + { 2048 + "expression": "name", 2049 + "isExpression": false, 2050 + "asc": true, 2051 + "nulls": "last" 2052 + } 2053 + ], 2054 + "isUnique": true, 2055 + "concurrently": false, 2056 + "method": "btree", 2057 + "with": {} 2058 + } 2059 + }, 2060 + "foreignKeys": { 2061 + "integrations_sandbox_id_sandboxes_id_fk": { 2062 + "name": "integrations_sandbox_id_sandboxes_id_fk", 2063 + "tableFrom": "integrations", 2064 + "tableTo": "sandboxes", 2065 + "columnsFrom": [ 2066 + "sandbox_id" 2067 + ], 2068 + "columnsTo": [ 2069 + "id" 2070 + ], 2071 + "onDelete": "cascade", 2072 + "onUpdate": "no action" 2073 + } 2074 + }, 2075 + "compositePrimaryKeys": {}, 2076 + "uniqueConstraints": {}, 2077 + "policies": {}, 2078 + "checkConstraints": {}, 2079 + "isRLSEnabled": false 2080 + } 2081 + }, 2082 + "enums": {}, 2083 + "schemas": {}, 2084 + "sequences": {}, 2085 + "roles": {}, 2086 + "policies": {}, 2087 + "views": {}, 2088 + "_meta": { 2089 + "columns": {}, 2090 + "schemas": {}, 2091 + "tables": {} 2092 + } 2093 + }
+7
apps/cf-sandbox/drizzle/meta/_journal.json
··· 302 302 "when": 1775497153309, 303 303 "tag": "0042_awesome_havok", 304 304 "breakpoints": true 305 + }, 306 + { 307 + "idx": 43, 308 + "version": "7", 309 + "when": 1775713354864, 310 + "tag": "0043_supreme_ezekiel", 311 + "breakpoints": true 305 312 } 306 313 ] 307 314 }
+2
apps/cf-sandbox/src/schema/index.ts
··· 19 19 import vercelAuth from "./vercel-auth"; 20 20 import sandboxCp from "./sandbox-cp"; 21 21 import backups from "./backups"; 22 + import modalAuth from "./modal-auth"; 22 23 23 24 export { 24 25 sandboxes, ··· 42 43 vercelAuth, 43 44 sandboxCp, 44 45 backups, 46 + modalAuth, 45 47 };
+30
apps/cf-sandbox/src/schema/modal-auth.ts
··· 1 + import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 + import { pgTable, text, timestamp, uniqueIndex } from "drizzle-orm/pg-core"; 3 + import sandboxes from "./sandboxes"; 4 + import users from "./users"; 5 + 6 + const modalAuth = pgTable( 7 + "modal_auth", 8 + { 9 + id: text("id") 10 + .primaryKey() 11 + .default(sql`xata_id()`), 12 + sandboxId: text("sandbox_id") 13 + .notNull() 14 + .references(() => sandboxes.id, { onDelete: "cascade" }), 15 + userId: text("user_id") 16 + .notNull() 17 + .references(() => users.id), 18 + tokenId: text("token_id").notNull(), 19 + redactedTokenId: text("redacted_token_id").notNull(), 20 + tokenSecret: text("token_secret").notNull(), 21 + redactedTokenSecret: text("redacted_token_secret").notNull(), 22 + createdAt: timestamp("created_at").defaultNow().notNull(), 23 + }, 24 + (t) => [uniqueIndex("unique_modal_auth").on(t.sandboxId, t.userId)], 25 + ); 26 + 27 + export type SelectModalAuth = InferSelectModel<typeof modalAuth>; 28 + export type InsertModalAuth = InferInsertModel<typeof modalAuth>; 29 + 30 + export default modalAuth;
+2
apps/cli/src/cmd/create.test.ts
··· 40 40 delete process.env.VERCEL_API_TOKEN; 41 41 delete process.env.VERCEL_PROJECT_ID; 42 42 delete process.env.VERCEL_TEAM_ID; 43 + delete process.env.MODAL_TOKEN_ID; 44 + delete process.env.MODAL_TOKEN_SECRET; 43 45 }); 44 46 45 47 it("creates a sandbox with default provider and logs success", async () => {
+17 -2
apps/cli/src/cmd/create.ts
··· 27 27 const providerOptions: Record<string, any> = {}; 28 28 29 29 if ( 30 - !["sprites", "daytona", "deno", "vercel", "cloudflare"].includes( 30 + !["sprites", "daytona", "deno", "vercel", "cloudflare", "modal"].includes( 31 31 provider ?? "cloudflare", 32 32 ) 33 33 ) { 34 34 consola.error( 35 - `Unsupported provider: ${provider}. Supported providers are: sprites, daytona, deno, vercel, cloudflare (default).`, 35 + `Unsupported provider: ${provider}. Supported providers are: sprites, daytona, deno, vercel, modal, cloudflare (default).`, 36 36 ); 37 37 process.exit(1); 38 38 } ··· 89 89 providerOptions.redactedVercelApiToken = redact(vercelApiToken); 90 90 providerOptions.vercelProjectId = vercelProjectId; 91 91 providerOptions.vercelTeamId = vercelTeamId; 92 + } 93 + 94 + if (provider === "modal") { 95 + const modalTokenId = process.env.MODAL_TOKEN_ID; 96 + const modalTokenSecret = process.env.MODAL_TOKEN_SECRET; 97 + if (!modalTokenId || !modalTokenSecret) { 98 + consola.error( 99 + "MODAL_TOKEN_ID and MODAL_TOKEN_SECRET environment variables are required for Modal provider.", 100 + ); 101 + process.exit(1); 102 + } 103 + providerOptions.modalTokenId = await encrypt(modalTokenId); 104 + providerOptions.redactedModalTokenId = redact(modalTokenId); 105 + providerOptions.modalTokenSecret = await encrypt(modalTokenSecret); 106 + providerOptions.redactedModalTokenSecret = redact(modalTokenSecret); 92 107 } 93 108 94 109 try {
+7 -1
apps/cli/src/types/providers.ts
··· 1 - export type Provider = "daytona" | "deno" | "cloudflare" | "vercel" | "sprites"; 1 + export type Provider = 2 + | "daytona" 3 + | "deno" 4 + | "cloudflare" 5 + | "vercel" 6 + | "sprites" 7 + | "modal";
+17
apps/sandbox/src/routes/sandboxes.ts
··· 19 19 spriteAuth, 20 20 denoAuth, 21 21 vercelAuth, 22 + modalAuth, 22 23 } from "../schema/mod.ts"; 23 24 import { 24 25 SandboxConfig, ··· 164 165 .execute(); 165 166 } 166 167 168 + if (params.modalTokenId && user?.id) { 169 + await tx 170 + .insert(modalAuth) 171 + .values({ 172 + sandboxId: record.id, 173 + tokenId: params.modalTokenId!, 174 + redactedTokenId: params.redactedModalTokenId!, 175 + tokenSecret: params.modalTokenSecret!, 176 + redactedTokenSecret: params.redactedModalTokenSecret!, 177 + userId: user.id, 178 + }) 179 + .execute(); 180 + } 181 + 167 182 const sandbox = await createSandbox(params.provider, { 168 183 id: record.id, 169 184 keepAlive: params.keepAlive, ··· 177 192 vercelApiToken: decrypt(params.vercelApiToken), 178 193 vercelProjectId: params.vercelProjectId, 179 194 vercelTeamId: params.vercelTeamId, 195 + modalTokenId: decrypt(params.modalTokenId), 196 + modalTokenSecret: decrypt(params.modalTokenSecret), 180 197 }); 181 198 const sandboxId = await sandbox.id(); 182 199
+3 -1
apps/sandbox/src/schema/mod.ts
··· 19 19 import vercelAuth from "./vercel-auth.ts"; 20 20 import sandboxCp from "./sandbox-cp.ts"; 21 21 import backups from "./backups.ts"; 22 + import modalAuth from "./modal-auth.ts"; 22 23 23 24 export { 24 25 sandboxes, ··· 41 42 spriteAuth, 42 43 vercelAuth, 43 44 sandboxCp, 44 - backups 45 + backups, 46 + modalAuth, 45 47 };
+30
apps/sandbox/src/schema/modal-auth.ts
··· 1 + import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 + import { pgTable, text, timestamp, uniqueIndex } from "drizzle-orm/pg-core"; 3 + import sandboxes from "./sandboxes.ts"; 4 + import users from "./users.ts"; 5 + 6 + const modalAuth = pgTable( 7 + "modal_auth", 8 + { 9 + id: text("id") 10 + .primaryKey() 11 + .default(sql`xata_id()`), 12 + sandboxId: text("sandbox_id") 13 + .notNull() 14 + .references(() => sandboxes.id, { onDelete: "cascade" }), 15 + userId: text("user_id") 16 + .notNull() 17 + .references(() => users.id), 18 + tokenId: text("token_id").notNull(), 19 + redactedTokenId: text("redacted_token_id").notNull(), 20 + tokenSecret: text("token_secret").notNull(), 21 + redactedTokenSecret: text("redacted_token_secret").notNull(), 22 + createdAt: timestamp("created_at").defaultNow().notNull(), 23 + }, 24 + (t) => [uniqueIndex("unique_modal_auth").on(t.sandboxId, t.userId)], 25 + ); 26 + 27 + export type SelectModalAuth = InferSelectModel<typeof modalAuth>; 28 + export type InsertModalAuth = InferInsertModel<typeof modalAuth>; 29 + 30 + export default modalAuth;
+36
apps/sandbox/src/types/sandbox.ts
··· 48 48 redactedVercelApiToken: z.string().optional(), 49 49 vercelProjectId: z.string().optional(), 50 50 vercelTeamId: z.string().optional(), 51 + modalTokenId: z.string().optional(), 52 + redactedModalTokenId: z.string().optional(), 53 + modalTokenSecret: z.string().optional(), 54 + redactedModalTokenSecret: z.string().optional(), 51 55 vcpus: z.number().optional().default(2), 52 56 memory: z.number().optional().default(4), 53 57 disk: z.number().optional().default(3), ··· 163 167 code: z.ZodIssueCode.custom, 164 168 message: "vercelProjectId is required when provider is 'vercel'", 165 169 path: ["vercelProjectId"], 170 + }); 171 + } 172 + } 173 + 174 + if (data.provider === "modal") { 175 + if (!data.modalTokenId) { 176 + ctx.addIssue({ 177 + code: z.ZodIssueCode.custom, 178 + message: "modalTokenId is required when provider is 'modal'", 179 + path: ["modalTokenId"], 180 + }); 181 + } 182 + if (!data.redactedModalTokenId) { 183 + ctx.addIssue({ 184 + code: z.ZodIssueCode.custom, 185 + message: "redactedModalTokenId is required when provider is 'modal'", 186 + path: ["redactedModalTokenId"], 187 + }); 188 + } 189 + if (!data.modalTokenSecret) { 190 + ctx.addIssue({ 191 + code: z.ZodIssueCode.custom, 192 + message: "modalTokenSecret is required when provider is 'modal'", 193 + path: ["modalTokenSecret"], 194 + }); 195 + } 196 + if (!data.redactedModalTokenSecret) { 197 + ctx.addIssue({ 198 + code: z.ZodIssueCode.custom, 199 + message: 200 + "redactedModalTokenSecret is required when provider is 'modal'", 201 + path: ["redactedModalTokenSecret"], 166 202 }); 167 203 } 168 204 }
+91 -7
apps/web/src/pages/settings/provider/Provider.tsx
··· 23 23 sprites: "Sprites API Key", 24 24 } as const; 25 25 26 - type Provider = keyof typeof LABELS | "cloudflare"; 26 + type Provider = keyof typeof LABELS | "cloudflare" | "modal"; 27 27 28 28 const schema = z 29 29 .object({ 30 - provider: z.enum(["cloudflare", "daytona", "vercel", "deno", "sprites"]), 30 + provider: z.enum([ 31 + "cloudflare", 32 + "daytona", 33 + "vercel", 34 + "deno", 35 + "sprites", 36 + "modal", 37 + ]), 31 38 apiKey: z.string().optional(), 32 39 organizationId: z.string().optional(), 33 40 vercelProjectId: z.string().optional(), 34 41 vercelTeamId: z.string().optional(), 42 + tokenId: z.string().optional(), 43 + tokenSecret: z.string().optional(), 35 44 }) 36 45 .superRefine((data, ctx) => { 37 - if (data.provider !== "cloudflare" && !data.apiKey?.trim()) { 46 + if ( 47 + data.provider !== "cloudflare" && 48 + data.provider !== "modal" && 49 + !data.apiKey?.trim() 50 + ) { 38 51 ctx.addIssue({ 39 52 code: z.ZodIssueCode.custom, 40 53 message: `${LABELS[data.provider as keyof typeof LABELS]} is required`, ··· 62 75 path: ["vercelTeamId"], 63 76 }); 64 77 } 78 + if (data.provider === "modal" && !data.tokenId?.trim()) { 79 + ctx.addIssue({ 80 + code: z.ZodIssueCode.custom, 81 + message: "Modal Token ID is required", 82 + path: ["tokenId"], 83 + }); 84 + } 85 + if (data.provider === "modal" && !data.tokenSecret?.trim()) { 86 + ctx.addIssue({ 87 + code: z.ZodIssueCode.custom, 88 + message: "Modal Token Secret is required", 89 + path: ["tokenSecret"], 90 + }); 91 + } 65 92 }); 66 93 67 94 type FormValues = z.infer<typeof schema>; ··· 93 120 apiKey: "", 94 121 vercelProjectId: "", 95 122 vercelTeamId: "", 123 + tokenId: "", 124 + tokenSecret: "", 96 125 }, 97 126 }); 98 127 ··· 108 137 setValue("organizationId", providerPref.organizationId ?? ""); 109 138 setValue("vercelProjectId", providerPref.vercelProjectId ?? ""); 110 139 setValue("vercelTeamId", providerPref.vercelTeamId ?? ""); 140 + setValue("tokenId", providerPref.modalTokenId ?? ""); 141 + setValue("tokenSecret", providerPref.redactedModalTokenSecret ?? ""); 111 142 } 112 143 }, [preferences, setValue]); 113 144 ··· 131 162 pref.vercelTeamId = values.vercelTeamId?.trim(); 132 163 } 133 164 134 - if (values.apiKey?.includes("**") && values.provider !== "cloudflare") { 165 + if (values.provider === "modal") { 166 + if (values.tokenId?.trim()) { 167 + pref.modalTokenId = values.tokenId.trim(); 168 + } 169 + if (values.tokenSecret && !values.tokenSecret.includes("**")) { 170 + const sealed = sodium.cryptoBoxSeal( 171 + sodium.fromString(values.tokenSecret), 172 + sodium.fromHex(PUBLIC_KEY), 173 + ); 174 + pref.modalTokenSecret = sodium.toBase64( 175 + sealed, 176 + sodium.base64Variants.URLSAFE_NO_PADDING, 177 + ); 178 + pref.redactedModalTokenSecret = 179 + values.tokenSecret.length > 14 180 + ? values.tokenSecret.slice(0, 11) + 181 + "*".repeat(24) + 182 + values.tokenSecret.slice(-3) 183 + : values.tokenSecret; 184 + } 185 + } else if (values.apiKey?.includes("**") && values.provider !== "cloudflare") { 135 186 if (values.provider !== "daytona" && values.provider !== "vercel") { 136 187 return; 137 188 } 138 189 } 139 190 140 - if (values.apiKey && !values.apiKey.includes("**")) { 191 + if (values.provider !== "modal" && values.apiKey && !values.apiKey.includes("**")) { 141 192 const sealed = sodium.cryptoBoxSeal( 142 193 sodium.fromString(values.apiKey), 143 194 sodium.fromHex(PUBLIC_KEY), ··· 196 247 setValue("organizationId", ""); 197 248 setValue("vercelProjectId", ""); 198 249 setValue("vercelTeamId", ""); 250 + setValue("tokenId", ""); 251 + setValue("tokenSecret", ""); 199 252 }} 200 253 className="select select-lg font-medium text-[15px]" 201 254 > ··· 206 259 <option value="vercel">Vercel Sandbox</option> 207 260 <option value="deno">Deno Sandbox</option> 208 261 <option value="sprites">Sprites</option> 262 + <option value="modal">Modal</option> 209 263 </select> 210 264 </div> 211 - {provider !== "cloudflare" && ( 265 + {provider !== "cloudflare" && provider !== "modal" && ( 212 266 <div className="w-96"> 213 - <label className="label-text">{LABELS[provider]}</label> 267 + <label className="label-text">{LABELS[provider as keyof typeof LABELS]}</label> 214 268 <input 215 269 {...register("apiKey")} 216 270 type="text" ··· 266 320 {errors.vercelTeamId && ( 267 321 <p className="text-error text-sm mt-1"> 268 322 {errors.vercelTeamId.message} 323 + </p> 324 + )} 325 + </div> 326 + </div> 327 + )} 328 + {provider === "modal" && ( 329 + <div className="flex flex-row mt-4 gap-6"> 330 + <div className="w-96"> 331 + <label className="label-text">Modal Token ID</label> 332 + <input 333 + {...register("tokenId")} 334 + type="text" 335 + className="input input-lg font-medium text-[15px]" 336 + /> 337 + {errors.tokenId && ( 338 + <p className="text-error text-sm mt-1"> 339 + {errors.tokenId.message} 340 + </p> 341 + )} 342 + </div> 343 + <div className="w-96"> 344 + <label className="label-text">Modal Token Secret</label> 345 + <input 346 + {...register("tokenSecret")} 347 + type="text" 348 + className="input input-lg font-medium text-[15px]" 349 + /> 350 + {errors.tokenSecret && ( 351 + <p className="text-error text-sm mt-1"> 352 + {errors.tokenSecret.message} 269 353 </p> 270 354 )} 271 355 </div>
+3
apps/web/src/types/preferences.ts
··· 13 13 organizationId?: string; 14 14 vercelProjectId?: string; 15 15 vercelTeamId?: string; 16 + modalTokenId?: string; 17 + modalTokenSecret?: string; 18 + redactedModalTokenSecret?: string; 16 19 $type: "io.pocketenv.sandbox.defs#sandboxProviderPref"; 17 20 }; 18 21