diff --git a/apps/sim/lib/core/config/env.ts b/apps/sim/lib/core/config/env.ts index 91659f96398..1ee0381f39e 100644 --- a/apps/sim/lib/core/config/env.ts +++ b/apps/sim/lib/core/config/env.ts @@ -94,6 +94,7 @@ export const env = createEnv({ TEAM_TABLE_ROWS_LIMIT: z.number().optional(), // Max rows per table on team tier (default: 500000) ENTERPRISE_TABLES_LIMIT: z.number().optional(), // Max user tables per workspace on enterprise tier (default: 10000) ENTERPRISE_TABLE_ROWS_LIMIT: z.number().optional(), // Max rows per table on enterprise tier (default: 1000000) + TABLE_MAX_ROW_SIZE_BYTES: z.number().optional(), // Max serialized size in bytes of a single user-table row (default: 409600) // Credit-tier Stripe prices (monthly) STRIPE_PRICE_TIER_25_MO: z.string().min(1).optional(), // Pro: $25/mo (6,000 credits) diff --git a/apps/sim/lib/table/constants.ts b/apps/sim/lib/table/constants.ts index 22a3bd9a595..e68340bbe44 100644 --- a/apps/sim/lib/table/constants.ts +++ b/apps/sim/lib/table/constants.ts @@ -8,7 +8,7 @@ import { env, envNumber } from '@/lib/core/config/env' export const TABLE_LIMITS = { MAX_TABLES_PER_WORKSPACE: 100, MAX_ROWS_PER_TABLE: 10000, - MAX_ROW_SIZE_BYTES: 100 * 1024, // 100KB + MAX_ROW_SIZE_BYTES: 400 * 1024, // 400KB MAX_COLUMNS_PER_TABLE: 50, MAX_TABLE_NAME_LENGTH: 128, MAX_COLUMN_NAME_LENGTH: 50, @@ -61,6 +61,18 @@ export const DEFAULT_TABLE_PLAN_LIMITS = { }, } as const +/** + * Maximum serialized size in bytes of a single row. Defaults to + * `TABLE_LIMITS.MAX_ROW_SIZE_BYTES`; overridable via the + * `TABLE_MAX_ROW_SIZE_BYTES` env var (server-only, read at call time). + */ +export function getMaxRowSizeBytes(): number { + return envNumber(env.TABLE_MAX_ROW_SIZE_BYTES, TABLE_LIMITS.MAX_ROW_SIZE_BYTES, { + min: 1, + integer: true, + }) +} + export type PlanName = keyof typeof DEFAULT_TABLE_PLAN_LIMITS export interface TablePlanLimits { diff --git a/apps/sim/lib/table/validation.ts b/apps/sim/lib/table/validation.ts index b42e2a62b8d..a3066f1e341 100644 --- a/apps/sim/lib/table/validation.ts +++ b/apps/sim/lib/table/validation.ts @@ -7,7 +7,7 @@ import { userTableRows } from '@sim/db/schema' import { and, eq, or, type SQL, sql } from 'drizzle-orm' import { NextResponse } from 'next/server' import { getColumnId } from '@/lib/table/column-keys' -import { COLUMN_TYPES, NAME_PATTERN, TABLE_LIMITS } from '@/lib/table/constants' +import { COLUMN_TYPES, getMaxRowSizeBytes, NAME_PATTERN, TABLE_LIMITS } from '@/lib/table/constants' import { withSeqscanOff } from '@/lib/table/planner' import type { ColumnDefinition, @@ -356,11 +356,12 @@ export function coerceRowToSchema(data: RowData, schema: TableSchema): Validatio /** Validates row data size is within limits. */ export function validateRowSize(data: RowData): ValidationResult { + const maxRowSizeBytes = getMaxRowSizeBytes() const size = JSON.stringify(data).length - if (size > TABLE_LIMITS.MAX_ROW_SIZE_BYTES) { + if (size > maxRowSizeBytes) { return { valid: false, - errors: [`Row size exceeds limit (${size} bytes > ${TABLE_LIMITS.MAX_ROW_SIZE_BYTES} bytes)`], + errors: [`Row size exceeds limit (${size} bytes > ${maxRowSizeBytes} bytes)`], } } return { valid: true, errors: [] }