Skip to content

Commit 409ea56

Browse files
waleedlatif1claude
andcommitted
improvement(contracts): promote userFileSchema to primitives
Move the canonical UserFile boundary schema out of tools/media/shared.ts (where it didn't belong — logs aren't media tools) into primitives.ts as userFileSchema. Update logs, stt, and video contracts to import from the shared primitive. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 16ecff3 commit 409ea56

5 files changed

Lines changed: 28 additions & 19 deletions

File tree

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { z } from 'zod'
2-
import { mediaUserFileSchema } from '@/lib/api/contracts/tools/media/shared'
2+
import { userFileSchema } from '@/lib/api/contracts/primitives'
33
import { defineRouteContract } from '@/lib/api/contracts/types'
44

55
const comparisonOperatorSchema = z.enum(['=', '>', '<', '>=', '<=', '!='])
@@ -225,7 +225,7 @@ export const workflowLogSummarySchema = z.object({
225225

226226
export const workflowLogDetailSchema = workflowLogSummarySchema.extend({
227227
executionData: executionDataDetailSchema,
228-
files: z.array(mediaUserFileSchema).nullable(),
228+
files: z.array(userFileSchema).nullable(),
229229
})
230230

231231
export type WorkflowLogSummary = z.output<typeof workflowLogSummarySchema>

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,26 @@ export const workflowIdSchema = z.string().min(1, 'Workflow ID is required')
5959
* Use `.optional()` / `.default(...)` at the call site, not here, so each
6060
* query field controls its own omission/default semantics.
6161
*/
62+
/**
63+
* Canonical boundary schema for `UserFile` (`apps/sim/executor/types.ts`) — the
64+
* shape produced by the executor and persisted in `workflowExecutionLogs.files`,
65+
* forwarded through tool inputs, and rendered in the logs UI. `.passthrough()`
66+
* tolerates legacy/extra fields on stored rows (e.g. `uploadedAt`, `expiresAt`,
67+
* `storageProvider`) without rejecting the whole payload.
68+
*/
69+
export const userFileSchema = z
70+
.object({
71+
id: z.string().optional().default(''),
72+
name: z.string().min(1),
73+
url: z.string().optional().default(''),
74+
size: z.coerce.number().nonnegative(),
75+
type: z.string().optional().default('application/octet-stream'),
76+
key: z.string().min(1),
77+
context: z.string().optional(),
78+
base64: z.string().optional(),
79+
})
80+
.passthrough()
81+
6282
export const booleanQueryFlagSchema = z.preprocess(
6383
(value) => {
6484
if (typeof value === 'boolean') return value

apps/sim/lib/api/contracts/tools/media/shared.ts

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,6 @@ import { z } from 'zod'
33
export const AWS_REGION_PATTERN =
44
/^(eu-isoe|us-isob|us-iso|us-gov|af|ap|ca|cn|eu|il|me|mx|sa|us)-(central|north|northeast|northwest|south|southeast|southwest|east|west)-\d{1,2}$/
55

6-
export const mediaUserFileSchema = z
7-
.object({
8-
id: z.string().optional().default(''),
9-
name: z.string().min(1),
10-
url: z.string().optional().default(''),
11-
size: z.coerce.number().nonnegative(),
12-
type: z.string().optional().default('application/octet-stream'),
13-
key: z.string().min(1),
14-
context: z.string().optional(),
15-
base64: z.string().optional(),
16-
})
17-
.passthrough()
18-
196
export const toolJsonResponseSchema = z
207
.object({
218
success: z.boolean().optional(),

apps/sim/lib/api/contracts/tools/media/stt.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { z } from 'zod'
2-
import { mediaUserFileSchema, toolJsonResponseSchema } from '@/lib/api/contracts/tools/media/shared'
2+
import { userFileSchema } from '@/lib/api/contracts/primitives'
3+
import { toolJsonResponseSchema } from '@/lib/api/contracts/tools/media/shared'
34
import { defineRouteContract } from '@/lib/api/contracts/types'
45

56
export const sttProviders = ['whisper', 'deepgram', 'elevenlabs', 'assemblyai', 'gemini'] as const
67
const MISSING_STT_FIELDS_ERROR = 'Missing required fields: provider and apiKey'
78

8-
export const sttUserFileSchema = mediaUserFileSchema.extend({
9+
export const sttUserFileSchema = userFileSchema.extend({
910
type: z.string().optional().default(''),
1011
})
1112

apps/sim/lib/api/contracts/tools/media/video.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { z } from 'zod'
2-
import { mediaUserFileSchema, toolJsonResponseSchema } from '@/lib/api/contracts/tools/media/shared'
2+
import { userFileSchema } from '@/lib/api/contracts/primitives'
3+
import { toolJsonResponseSchema } from '@/lib/api/contracts/tools/media/shared'
34
import { defineRouteContract } from '@/lib/api/contracts/types'
45

56
export const videoProviders = ['runway', 'veo', 'luma', 'minimax', 'falai'] as const
@@ -19,7 +20,7 @@ export const videoToolBodySchema = z
1920
duration: z.coerce.number().optional(),
2021
aspectRatio: z.string().optional(),
2122
resolution: z.string().optional(),
22-
visualReference: mediaUserFileSchema.optional(),
23+
visualReference: userFileSchema.optional(),
2324
cameraControl: z.unknown().optional(),
2425
endpoint: z.string().optional(),
2526
promptOptimizer: z.boolean().optional(),

0 commit comments

Comments
 (0)