Skip to content

Commit 172ebb4

Browse files
fix(tables): keep insert body base omittable for v1 contract
The afterRowId/beforeRowId mutual-exclusion .refine() turned the schema into a ZodEffects, which Zod forbids .omit() on — v1's insertTableRowBodySchema.omit({ position }) threw at module load (runtime-only; tsc misses it). Split the plain object base out, apply the shared refine on top, and have v1 omit from the base then re-apply it.
1 parent acde71b commit 172ebb4

2 files changed

Lines changed: 27 additions & 15 deletions

File tree

apps/sim/lib/api/contracts/tables.ts

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -147,19 +147,28 @@ export const rowDataSchema = domainObjectSchema<RowData>()
147147
export const tableDefinitionSchema = domainObjectSchema<TableDefinition>()
148148
export const tableRowSchema = domainObjectSchema<TableRow>()
149149

150-
export const insertTableRowBodySchema = z
151-
.object({
152-
workspaceId: z.string().min(1, 'Workspace ID is required'),
153-
data: rowDataSchema,
154-
position: z.number().int().min(0).optional(),
155-
/** Fractional ordering: insert directly after this row id. Takes precedence over `position`. */
156-
afterRowId: z.string().min(1).optional(),
157-
/** Fractional ordering: insert directly before this row id. Takes precedence over `position`. */
158-
beforeRowId: z.string().min(1).optional(),
159-
})
160-
.refine((data) => !data.afterRowId || !data.beforeRowId, {
161-
message: 'afterRowId and beforeRowId are mutually exclusive',
162-
})
150+
/**
151+
* Plain-object base for the single-row insert body. Kept un-refined so callers
152+
* (e.g. the v1 public contract) can `.omit()` fields before applying
153+
* {@link rowAnchorMutexRefine} — Zod forbids `.omit()` on a refined schema.
154+
*/
155+
export const insertTableRowBodyBaseSchema = z.object({
156+
workspaceId: z.string().min(1, 'Workspace ID is required'),
157+
data: rowDataSchema,
158+
position: z.number().int().min(0).optional(),
159+
/** Fractional ordering: insert directly after this row id. Takes precedence over `position`. */
160+
afterRowId: z.string().min(1).optional(),
161+
/** Fractional ordering: insert directly before this row id. Takes precedence over `position`. */
162+
beforeRowId: z.string().min(1).optional(),
163+
})
164+
165+
/** `afterRowId` and `beforeRowId` are mutually exclusive insert anchors. */
166+
export const rowAnchorMutexRefine = [
167+
(data: { afterRowId?: string; beforeRowId?: string }) => !data.afterRowId || !data.beforeRowId,
168+
{ message: 'afterRowId and beforeRowId are mutually exclusive' },
169+
] as const
170+
171+
export const insertTableRowBodySchema = insertTableRowBodyBaseSchema.refine(...rowAnchorMutexRefine)
163172

164173
/**
165174
* POST `/api/table/[tableId]/rows/upsert` body — insert-or-update keyed by a

apps/sim/lib/api/contracts/v1/tables/index.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import {
44
createTableColumnBodySchema,
55
deleteTableColumnBodySchema,
66
deleteTableRowsBodySchema,
7-
insertTableRowBodySchema,
7+
insertTableRowBodyBaseSchema,
8+
rowAnchorMutexRefine,
89
rowDataSchema,
910
tableIdParamsSchema,
1011
tableRowParamsSchema,
@@ -60,7 +61,9 @@ export const v1CreateTableBodySchema = createTableBodySchema.omit({
6061
* Public API insert row body — no caller-controlled `position`. Server places
6162
* new rows at the tail; ordering by index is an in-app affordance only.
6263
*/
63-
export const v1InsertTableRowBodySchema = insertTableRowBodySchema.omit({ position: true })
64+
export const v1InsertTableRowBodySchema = insertTableRowBodyBaseSchema
65+
.omit({ position: true })
66+
.refine(...rowAnchorMutexRefine)
6467

6568
/**
6669
* Public API batch insert body — no `positions`. Same rationale as above.

0 commit comments

Comments
 (0)