Skip to content

Commit c00437c

Browse files
committed
feat(table): make plan table limits configurable via env vars
1 parent 1a76a22 commit c00437c

4 files changed

Lines changed: 63 additions & 7 deletions

File tree

apps/sim/lib/core/config/env.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,16 @@ export const env = createEnv({
6060
ENTERPRISE_STORAGE_LIMIT_GB: z.number().optional().default(500), // Default storage limit in GB for enterprise tier (can be overridden per org)
6161
BILLING_ENABLED: z.boolean().optional(), // Enable billing enforcement and usage tracking
6262

63+
// Table feature limits (per plan). Apply when billing is disabled (free tier defaults) or for billed plans.
64+
FREE_TABLES_LIMIT: z.number().optional(), // Max user tables per workspace on free tier (default: 3)
65+
FREE_TABLE_ROWS_LIMIT: z.number().optional(), // Max rows per table on free tier (default: 1000)
66+
PRO_TABLES_LIMIT: z.number().optional(), // Max user tables per workspace on pro tier (default: 25)
67+
PRO_TABLE_ROWS_LIMIT: z.number().optional(), // Max rows per table on pro tier (default: 5000)
68+
TEAM_TABLES_LIMIT: z.number().optional(), // Max user tables per workspace on team tier (default: 100)
69+
TEAM_TABLE_ROWS_LIMIT: z.number().optional(), // Max rows per table on team tier (default: 10000)
70+
ENTERPRISE_TABLES_LIMIT: z.number().optional(), // Max user tables per workspace on enterprise tier (default: 10000)
71+
ENTERPRISE_TABLE_ROWS_LIMIT: z.number().optional(), // Max rows per table on enterprise tier (default: 1000000)
72+
6373
// Credit-tier Stripe prices (monthly)
6474
STRIPE_PRICE_TIER_25_MO: z.string().min(1).optional(), // Pro: $25/mo (6,000 credits)
6575
STRIPE_PRICE_TIER_100_MO: z.string().min(1).optional(), // Max: $100/mo (25,000 credits)

apps/sim/lib/table/billing.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { createLogger } from '@sim/logger'
88
import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription'
99
import { getPlanTypeForLimits } from '@/lib/billing/plan-helpers'
1010
import { getWorkspaceBilledAccountUserId } from '@/lib/workspaces/utils'
11-
import { type PlanName, TABLE_PLAN_LIMITS, type TablePlanLimits } from './constants'
11+
import { getTablePlanLimits, type PlanName, type TablePlanLimits } from './constants'
1212

1313
const logger = createLogger('TableBilling')
1414

@@ -22,18 +22,20 @@ const logger = createLogger('TableBilling')
2222
* @returns Table limits based on the workspace's billing plan
2323
*/
2424
export async function getWorkspaceTableLimits(workspaceId: string): Promise<TablePlanLimits> {
25+
const planLimits = getTablePlanLimits()
26+
2527
try {
2628
const billedAccountUserId = await getWorkspaceBilledAccountUserId(workspaceId)
2729

2830
if (!billedAccountUserId) {
2931
logger.warn('No billed account found for workspace, using free tier limits', { workspaceId })
30-
return TABLE_PLAN_LIMITS.free
32+
return planLimits.free
3133
}
3234

3335
const subscription = await getHighestPrioritySubscription(billedAccountUserId)
3436
const planName = getPlanTypeForLimits(subscription?.plan) as PlanName
3537

36-
const limits = TABLE_PLAN_LIMITS[planName] ?? TABLE_PLAN_LIMITS.free
38+
const limits = planLimits[planName] ?? planLimits.free
3739

3840
logger.info('Retrieved workspace table limits', {
3941
workspaceId,
@@ -48,7 +50,7 @@ export async function getWorkspaceTableLimits(workspaceId: string): Promise<Tabl
4850
workspaceId,
4951
error,
5052
})
51-
return TABLE_PLAN_LIMITS.free
53+
return planLimits.free
5254
}
5355
}
5456

apps/sim/lib/table/constants.ts

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
* Limits and constants for user-defined tables.
33
*/
44

5+
import { env } from '@/lib/core/config/env'
6+
57
export const TABLE_LIMITS = {
68
MAX_TABLES_PER_WORKSPACE: 100,
79
MAX_ROWS_PER_TABLE: 10000,
@@ -24,9 +26,11 @@ export const TABLE_LIMITS = {
2426
} as const
2527

2628
/**
27-
* Plan-based table limits.
29+
* Default plan-based table limits. Each value can be overridden via env vars
30+
* (see `getTablePlanLimits`) so self-hosted deployments can raise the free-tier
31+
* caps that apply when billing is disabled.
2832
*/
29-
export const TABLE_PLAN_LIMITS = {
33+
export const DEFAULT_TABLE_PLAN_LIMITS = {
3034
free: {
3135
maxTables: 3,
3236
maxRowsPerTable: 1000,
@@ -45,13 +49,42 @@ export const TABLE_PLAN_LIMITS = {
4549
},
4650
} as const
4751

48-
export type PlanName = keyof typeof TABLE_PLAN_LIMITS
52+
export type PlanName = keyof typeof DEFAULT_TABLE_PLAN_LIMITS
4953

5054
export interface TablePlanLimits {
5155
maxTables: number
5256
maxRowsPerTable: number
5357
}
5458

59+
export type TablePlanLimitsByPlan = Record<PlanName, TablePlanLimits>
60+
61+
/**
62+
* Returns plan-based table limits, applying env var overrides on top of the
63+
* defaults. When no override is set the value falls back to the hosted-default
64+
* constant so behavior is unchanged for the hosted product.
65+
*/
66+
export function getTablePlanLimits(): TablePlanLimitsByPlan {
67+
return {
68+
free: {
69+
maxTables: env.FREE_TABLES_LIMIT ?? DEFAULT_TABLE_PLAN_LIMITS.free.maxTables,
70+
maxRowsPerTable: env.FREE_TABLE_ROWS_LIMIT ?? DEFAULT_TABLE_PLAN_LIMITS.free.maxRowsPerTable,
71+
},
72+
pro: {
73+
maxTables: env.PRO_TABLES_LIMIT ?? DEFAULT_TABLE_PLAN_LIMITS.pro.maxTables,
74+
maxRowsPerTable: env.PRO_TABLE_ROWS_LIMIT ?? DEFAULT_TABLE_PLAN_LIMITS.pro.maxRowsPerTable,
75+
},
76+
team: {
77+
maxTables: env.TEAM_TABLES_LIMIT ?? DEFAULT_TABLE_PLAN_LIMITS.team.maxTables,
78+
maxRowsPerTable: env.TEAM_TABLE_ROWS_LIMIT ?? DEFAULT_TABLE_PLAN_LIMITS.team.maxRowsPerTable,
79+
},
80+
enterprise: {
81+
maxTables: env.ENTERPRISE_TABLES_LIMIT ?? DEFAULT_TABLE_PLAN_LIMITS.enterprise.maxTables,
82+
maxRowsPerTable:
83+
env.ENTERPRISE_TABLE_ROWS_LIMIT ?? DEFAULT_TABLE_PLAN_LIMITS.enterprise.maxRowsPerTable,
84+
},
85+
}
86+
}
87+
5588
export const COLUMN_TYPES = ['string', 'number', 'boolean', 'date', 'json'] as const
5689

5790
export const NAME_PATTERN = /^[a-z_][a-z0-9_]*$/i

helm/sim/values.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,17 @@ app:
166166
EXECUTION_TIMEOUT_ASYNC_TEAM: "5400" # Team tier async timeout (90 minutes)
167167
EXECUTION_TIMEOUT_ASYNC_ENTERPRISE: "5400" # Enterprise tier async timeout (90 minutes)
168168

169+
# Table Feature Limits (per workspace, per plan)
170+
# Apply when billing is disabled (free tier defaults) or for billed plans
171+
FREE_TABLES_LIMIT: "3" # Max user tables per workspace on free tier
172+
FREE_TABLE_ROWS_LIMIT: "1000" # Max rows per table on free tier
173+
PRO_TABLES_LIMIT: "25" # Max user tables per workspace on pro tier
174+
PRO_TABLE_ROWS_LIMIT: "5000" # Max rows per table on pro tier
175+
TEAM_TABLES_LIMIT: "100" # Max user tables per workspace on team tier
176+
TEAM_TABLE_ROWS_LIMIT: "10000" # Max rows per table on team tier
177+
ENTERPRISE_TABLES_LIMIT: "10000" # Max user tables per workspace on enterprise tier
178+
ENTERPRISE_TABLE_ROWS_LIMIT: "1000000" # Max rows per table on enterprise tier
179+
169180
# Isolated-VM Worker Pool Configuration
170181
IVM_POOL_SIZE: "4" # Max worker processes in pool
171182
IVM_MAX_CONCURRENT: "10000" # Max concurrent executions globally

0 commit comments

Comments
 (0)