Skip to content

Commit 136f38a

Browse files
fix(tables): migrate legacy multiselect columns; readable contract error
- Tables created before the select/multiselect merge stored `type: 'multiselect'`, which the removed type no longer accepts — every response carrying such a column failed contract validation and column edits threw. getTableById now maps `multiselect` → `select` + `multiple: true` on read (covers reads, column ops via withLockedTable, and their responses); the migrated shape persists on the table's next schema write. - requestJson's "Response failed contract validation" now appends a short "field: reason" summary of the Zod issues instead of a bare message, so the failing field is visible.
1 parent 7cd6f77 commit 136f38a

2 files changed

Lines changed: 44 additions & 3 deletions

File tree

apps/sim/lib/api/client/request.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,26 @@ function isSchemaValidationError(error: unknown): boolean {
161161
)
162162
}
163163

164+
/**
165+
* Compresses a ZodError's issues into a short, readable "field: reason" summary
166+
* so a failed response tells you which field was wrong instead of a bare
167+
* "Response failed contract validation".
168+
*/
169+
function summarizeSchemaIssues(error: unknown): string {
170+
const issues = (error as { issues?: unknown }).issues
171+
if (!Array.isArray(issues)) return ''
172+
const summary = issues
173+
.slice(0, 3)
174+
.map((issue) => {
175+
const path = Array.isArray(issue?.path) ? issue.path.join('.') : ''
176+
const message = typeof issue?.message === 'string' ? issue.message : 'invalid'
177+
return path ? `${path}: ${message}` : message
178+
})
179+
.join('; ')
180+
const extra = issues.length > 3 ? ` (+${issues.length - 3} more)` : ''
181+
return summary ? `${summary}${extra}` : ''
182+
}
183+
164184
export async function requestJson<C extends AnyApiRouteContract>(
165185
contract: C,
166186
input: ApiClientRequest<C>
@@ -199,9 +219,12 @@ export async function requestJson<C extends AnyApiRouteContract>(
199219
return contract.response.schema.parse(parsed) as ContractJsonResponse<C>
200220
} catch (error) {
201221
if (isSchemaValidationError(error)) {
222+
const details = summarizeSchemaIssues(error)
202223
throw new ApiClientError({
203224
status: response.status,
204-
message: 'Response failed contract validation',
225+
message: details
226+
? `Response failed contract validation — ${details}`
227+
: 'Response failed contract validation',
205228
body: parsed,
206229
rawBody: raw,
207230
})

apps/sim/lib/table/service.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,24 @@ export async function withLockedTable<T>(
8686
* the wire. The client doesn't have to join the two arrays itself — every
8787
* consumer (grid, sidebar, copilot, mothership) gets the same ordered list.
8888
*/
89+
/**
90+
* Backward-compat for the removed `'multiselect'` column type: map it to a
91+
* `select` column with `multiple: true`. Applied on every schema read so legacy
92+
* tables — created before the two types were unified — load, edit, and validate
93+
* under the current model; the migrated shape persists on the table's next
94+
* schema write.
95+
*/
96+
function migrateLegacyColumns(schema: TableSchema): TableSchema {
97+
let mutated = false
98+
const columns = schema.columns.map((c) => {
99+
if ((c.type as string) !== 'multiselect') return c
100+
mutated = true
101+
const { type: _legacyType, ...rest } = c
102+
return { ...rest, type: 'select' as const, multiple: true }
103+
})
104+
return mutated ? { ...schema, columns } : schema
105+
}
106+
89107
function applyColumnOrderToSchema(
90108
schema: TableSchema,
91109
metadata: TableMetadata | null
@@ -151,7 +169,7 @@ export async function getTableById(
151169
id: table.id,
152170
name: table.name,
153171
description: table.description,
154-
schema: applyColumnOrderToSchema(table.schema as TableSchema, metadata),
172+
schema: applyColumnOrderToSchema(migrateLegacyColumns(table.schema as TableSchema), metadata),
155173
metadata,
156174
rowCount: Math.max(0, table.rowCount - pendingDeleteRemaining),
157175
maxRows: table.maxRows,
@@ -215,7 +233,7 @@ export async function listTables(
215233
id: t.id,
216234
name: t.name,
217235
description: t.description,
218-
schema: applyColumnOrderToSchema(t.schema as TableSchema, metadata),
236+
schema: applyColumnOrderToSchema(migrateLegacyColumns(t.schema as TableSchema), metadata),
219237
metadata,
220238
rowCount: Math.max(0, t.rowCount - pendingDeleteRemaining),
221239
maxRows: t.maxRows,

0 commit comments

Comments
 (0)