Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 7 additions & 11 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 12 additions & 6 deletions convex/importData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ export const importProject = internalMutation({
v.literal("ANGULAR"),
v.literal("REACT"),
v.literal("VUE"),
v.literal("SVELTE")
v.literal("SVELTE"),
v.literal("EXPO")
),
createdAt: v.string(), // ISO date string
updatedAt: v.string(), // ISO date string
Expand Down Expand Up @@ -89,7 +90,8 @@ export const importFragment = internalMutation({
v.literal("ANGULAR"),
v.literal("REACT"),
v.literal("VUE"),
v.literal("SVELTE")
v.literal("SVELTE"),
v.literal("EXPO")
),
createdAt: v.string(),
updatedAt: v.string(),
Expand Down Expand Up @@ -130,7 +132,8 @@ export const importFragmentDraft = internalMutation({
v.literal("ANGULAR"),
v.literal("REACT"),
v.literal("VUE"),
v.literal("SVELTE")
v.literal("SVELTE"),
v.literal("EXPO")
),
createdAt: v.string(),
updatedAt: v.string(),
Expand Down Expand Up @@ -278,7 +281,8 @@ export const importProjectAction = action({
v.literal("ANGULAR"),
v.literal("REACT"),
v.literal("VUE"),
v.literal("SVELTE")
v.literal("SVELTE"),
v.literal("EXPO")
),
createdAt: v.string(),
updatedAt: v.string(),
Expand Down Expand Up @@ -320,7 +324,8 @@ export const importFragmentAction = action({
v.literal("ANGULAR"),
v.literal("REACT"),
v.literal("VUE"),
v.literal("SVELTE")
v.literal("SVELTE"),
v.literal("EXPO")
),
createdAt: v.string(),
updatedAt: v.string(),
Expand All @@ -343,7 +348,8 @@ export const importFragmentDraftAction = action({
v.literal("ANGULAR"),
v.literal("REACT"),
v.literal("VUE"),
v.literal("SVELTE")
v.literal("SVELTE"),
v.literal("EXPO")
),
createdAt: v.string(),
updatedAt: v.string(),
Expand Down
9 changes: 6 additions & 3 deletions convex/sandboxSessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,22 @@ export const create = mutation({
v.literal("ANGULAR"),
v.literal("REACT"),
v.literal("VUE"),
v.literal("SVELTE")
v.literal("SVELTE"),
v.literal("EXPO")
),
autoPauseTimeout: v.optional(v.number()), // Default 10 minutes
runtimeType: v.optional(v.union(v.literal("webcontainer"), v.literal("e2b"))),
autoPauseTimeout: v.optional(v.number()),
},
handler: async (ctx, args) => {
const now = Date.now();
const autoPauseTimeout = args.autoPauseTimeout || 10 * 60 * 1000; // Default 10 minutes
const autoPauseTimeout = args.autoPauseTimeout || 10 * 60 * 1000;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Prefer ?? for the auto‑pause default.
If 0 is a valid value (e.g., disabling auto‑pause), || will override it.

♻️ Suggested fix
-    const autoPauseTimeout = args.autoPauseTimeout || 10 * 60 * 1000;
+    const autoPauseTimeout = args.autoPauseTimeout ?? 10 * 60 * 1000;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const autoPauseTimeout = args.autoPauseTimeout || 10 * 60 * 1000;
const autoPauseTimeout = args.autoPauseTimeout ?? 10 * 60 * 1000;
🤖 Prompt for AI Agents
In `@convex/sandboxSessions.ts` at line 27, The defaulting for autoPauseTimeout
uses the || operator which treats 0 as falsy; update the assignment for
autoPauseTimeout to use the nullish coalescing operator so a caller can pass 0
to disable auto‑pause (change the expression using args.autoPauseTimeout to use
?? instead of ||); locate the variable autoPauseTimeout and the use of
args.autoPauseTimeout in the sandboxSessions.ts function and replace the
fallback logic accordingly.


const sessionId = await ctx.db.insert("sandboxSessions", {
sandboxId: args.sandboxId,
projectId: args.projectId,
userId: args.userId,
framework: args.framework,
runtimeType: args.runtimeType || "e2b",
state: "RUNNING",
lastActivity: now,
autoPauseTimeout,
Expand Down
24 changes: 22 additions & 2 deletions convex/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@ export const frameworkEnum = v.union(
v.literal("ANGULAR"),
v.literal("REACT"),
v.literal("VUE"),
v.literal("SVELTE")
v.literal("SVELTE"),
v.literal("EXPO")
);

export const expoPreviewModeEnum = v.union(
v.literal("web"),
v.literal("expo-go"),
v.literal("android-emulator"),
v.literal("eas-build")
);

export const messageRoleEnum = v.union(
Expand Down Expand Up @@ -55,6 +63,11 @@ export const sandboxStateEnum = v.union(
v.literal("KILLED")
);

export const runtimeTypeEnum = v.union(
v.literal("webcontainer"),
v.literal("e2b")
);

export const webhookEventStatusEnum = v.union(
v.literal("received"),
v.literal("processed"),
Expand Down Expand Up @@ -115,6 +128,11 @@ export default defineSchema({
files: v.any(),
metadata: v.optional(v.any()),
framework: frameworkEnum,
expoPreviewMode: v.optional(expoPreviewModeEnum),
expoQrCodeUrl: v.optional(v.string()),
expoVncUrl: v.optional(v.string()),
expoEasBuildUrl: v.optional(v.string()),
expoApkUrl: v.optional(v.string()),
createdAt: v.optional(v.number()),
updatedAt: v.optional(v.number()),
})
Expand Down Expand Up @@ -255,6 +273,7 @@ export default defineSchema({
projectId: v.id("projects"),
userId: v.string(),
framework: frameworkEnum,
runtimeType: v.optional(runtimeTypeEnum),
state: sandboxStateEnum,
lastActivity: v.number(),
autoPauseTimeout: v.number(),
Expand All @@ -265,5 +284,6 @@ export default defineSchema({
.index("by_projectId", ["projectId"])
.index("by_userId", ["userId"])
.index("by_state", ["state"])
.index("by_sandboxId", ["sandboxId"]),
.index("by_sandboxId", ["sandboxId"])
.index("by_runtimeType", ["runtimeType"]),
});
53 changes: 53 additions & 0 deletions convex/usage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,59 @@ const UNLIMITED_POINTS = Number.MAX_SAFE_INTEGER;
const DURATION_MS = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
const GENERATION_COST = 1;

// Expo-specific limits by tier
export const EXPO_LIMITS = {
free: {
webPreview: true,
expoGo: true,
androidEmulator: false,
easBuild: false,
maxBuildsPerDay: 5,
maxEmulatorMinutes: 0
},
pro: {
webPreview: true,
expoGo: true,
androidEmulator: true,
easBuild: true,
maxBuildsPerDay: 50,
maxEmulatorMinutes: 120 // 2 hours per day
},
enterprise: {
webPreview: true,
expoGo: true,
androidEmulator: true,
easBuild: true,
maxBuildsPerDay: 500,
maxEmulatorMinutes: 600 // 10 hours per day
}
} as const;

export type ExpoPreviewMode = 'web' | 'expo-go' | 'android-emulator' | 'eas-build';
export type UserTier = 'free' | 'pro' | 'enterprise';

/**
* Check if user can use a specific Expo preview mode
*/
export function canUseExpoPreviewMode(
tier: UserTier,
mode: ExpoPreviewMode
): boolean {
const limits = EXPO_LIMITS[tier];
switch (mode) {
case 'web':
return limits.webPreview;
case 'expo-go':
return limits.expoGo;
case 'android-emulator':
return limits.androidEmulator;
case 'eas-build':
return limits.easBuild;
default:
return false;
}
}

/**
* Check and consume credits for a generation
* Returns true if credits were successfully consumed, false if insufficient credits
Expand Down
5 changes: 4 additions & 1 deletion env.example
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,12 @@ VERCEL_AI_GATEWAY_API_KEY="" # Get from https://vercel.com/dashboard/ai-gateway
# Brave Search API (web search for subagent research - optional)
BRAVE_SEARCH_API_KEY="" # Get from https://api-dashboard.search.brave.com/app/keys

# E2B
# E2B (Cloud-based sandboxes for native builds)
E2B_API_KEY=""

# Expo EAS (Native mobile builds)
EXPO_ACCESS_TOKEN="" # Get from https://expo.dev/accounts/[account]/settings/access-tokens

# Firecrawl
FIRECRAWL_API_KEY=""

Expand Down
Loading
Loading