Skip to content

Commit 3db0488

Browse files
committed
feat(files): return the produced revision from the v2 write responses
1 parent 92ecc09 commit 3db0488

6 files changed

Lines changed: 174 additions & 8 deletions

File tree

‎apps/docs/openapi-v2-files-audit.json‎

Lines changed: 130 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2791,7 +2791,7 @@
27912791
"content": {
27922792
"application/json": {
27932793
"schema": {
2794-
"$ref": "#/components/schemas/V2FileResponse"
2794+
"$ref": "#/components/schemas/V2WrittenFileResponse"
27952795
}
27962796
}
27972797
}
@@ -5810,6 +5810,10 @@
58105810
"minimum": 0,
58115811
"maximum": 9007199254740991,
58125812
"description": "Lines the file holds after the edit."
5813+
},
5814+
"revision": {
5815+
"description": "Opaque token for the content this write produced. Send it back as `expectedRevision` on the next write. Absent for a file with no recorded content version.",
5816+
"type": "string"
58135817
}
58145818
},
58155819
"required": ["file", "lineCount"],
@@ -6137,6 +6141,131 @@
61376141
}
61386142
]
61396143
},
6144+
"V2WrittenFile": {
6145+
"type": "object",
6146+
"properties": {
6147+
"id": {
6148+
"type": "string",
6149+
"description": "Unique file identifier.",
6150+
"examples": ["wf_V1StGXR8z5jdHi6BmyT91"]
6151+
},
6152+
"webUrl": {
6153+
"type": "string",
6154+
"format": "uri",
6155+
"description": "Canonical absolute URL for opening this resource in the Sim web application."
6156+
},
6157+
"name": {
6158+
"type": "string",
6159+
"description": "Original file name.",
6160+
"examples": ["data.csv"]
6161+
},
6162+
"size": {
6163+
"type": "number",
6164+
"minimum": 0,
6165+
"description": "Size in bytes of the stored file. For a generated document (docx, pptx, pdf, xlsx) this is the generation source, not the rendered document, so it does not predict how many bytes downloading the file returns.",
6166+
"examples": [1024]
6167+
},
6168+
"type": {
6169+
"type": "string",
6170+
"description": "MIME type of the stored file. For a generated document (docx, pptx, pdf, xlsx) this is the generation source type, not the rendered document type a download serves.",
6171+
"examples": ["text/csv"]
6172+
},
6173+
"key": {
6174+
"type": "string",
6175+
"description": "Storage key for the file.",
6176+
"examples": ["workspace/example/data.csv"]
6177+
},
6178+
"folderPath": {
6179+
"type": "string",
6180+
"title": "Folder path",
6181+
"description": "Canonical containing-folder path. `/` is the workspace root.",
6182+
"maxLength": 4096
6183+
},
6184+
"uploadedByEmail": {
6185+
"type": "string",
6186+
"format": "email",
6187+
"pattern": "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$",
6188+
"description": "Current email address of the uploader.",
6189+
"examples": ["jane@example.com"]
6190+
},
6191+
"uploadedAt": {
6192+
"type": "string",
6193+
"description": "ISO 8601 timestamp when the file was uploaded.",
6194+
"format": "date-time",
6195+
"examples": ["2026-01-15T10:30:00Z"]
6196+
},
6197+
"updatedAt": {
6198+
"type": "string",
6199+
"description": "ISO 8601 timestamp of the last content or metadata write.",
6200+
"format": "date-time",
6201+
"examples": ["2026-01-15T10:30:00Z"]
6202+
},
6203+
"deletedAt": {
6204+
"anyOf": [
6205+
{
6206+
"type": "string"
6207+
},
6208+
{
6209+
"type": "null"
6210+
}
6211+
],
6212+
"description": "ISO 8601 timestamp when the file was archived by deleting it, or null while the file is active. Only an archived-scope file list returns files with a non-null value.",
6213+
"format": "date-time",
6214+
"examples": ["2026-01-16T09:00:00Z"]
6215+
},
6216+
"revision": {
6217+
"description": "Opaque token for the content this write produced. Send it back as `expectedRevision` on the next write. Absent for a file with no recorded content version.",
6218+
"type": "string"
6219+
}
6220+
},
6221+
"required": [
6222+
"id",
6223+
"webUrl",
6224+
"name",
6225+
"size",
6226+
"type",
6227+
"key",
6228+
"folderPath",
6229+
"uploadedByEmail",
6230+
"uploadedAt",
6231+
"updatedAt",
6232+
"deletedAt"
6233+
],
6234+
"additionalProperties": false,
6235+
"title": "Written file",
6236+
"description": "A workspace file after a content replacement, with the revision it produced."
6237+
},
6238+
"V2WrittenFileResponse": {
6239+
"type": "object",
6240+
"properties": {
6241+
"data": {
6242+
"description": "Response data.",
6243+
"$ref": "#/components/schemas/V2WrittenFile"
6244+
}
6245+
},
6246+
"required": ["data"],
6247+
"additionalProperties": false,
6248+
"title": "Written file response",
6249+
"description": "A workspace file after a content replacement, with the revision the write produced.",
6250+
"examples": [
6251+
{
6252+
"data": {
6253+
"id": "wf_V1StGXR8z5jdHi6BmyT91",
6254+
"webUrl": "https://www.sim.ai/workspace/a91c4b2e-6d3f-4e8a-b5c7-0d9e2f1a8c64/files/wf_V1StGXR8z5jdHi6BmyT91",
6255+
"name": "data.csv",
6256+
"size": 1024,
6257+
"type": "text/csv",
6258+
"key": "workspace/example/data.csv",
6259+
"folderPath": "/Engineering",
6260+
"uploadedByEmail": "jane@example.com",
6261+
"uploadedAt": "2026-01-15T10:30:00Z",
6262+
"updatedAt": "2026-01-15T10:30:00Z",
6263+
"deletedAt": null,
6264+
"revision": "d2ZfVjFTdEdYUjh6NWpkSGk2Qm15VDkxOjIwMjYtMDEtMTVUMTA6MzA6MDAuMDAwWg"
6265+
}
6266+
}
6267+
]
6268+
},
61406269
"UpdateFileContentRequest": {
61416270
"type": "object",
61426271
"properties": {

‎apps/sim/app/api/v2/files/[fileId]/content/route.test.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,8 @@ describe('PUT /api/v2/files/[fileId]/content', () => {
191191
uploadedAt: '2024-01-01T00:00:00.000Z',
192192
updatedAt: '2024-01-03T00:00:00.000Z',
193193
deletedAt: null,
194+
/** The token for the content this write produced, for the caller's next conditional write. */
195+
revision: expect.any(String),
194196
},
195197
})
196198
expect(mocks.updateContent).toHaveBeenCalledWith({

‎apps/sim/app/api/v2/files/[fileId]/content/route.ts‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
import { defineV2JsonRoute, v2ApiKeyAuth, v2RateLimits } from '@/lib/api/server/routes'
66
import { v2FileErrorPolicies } from '@/lib/workspace-files/api'
77
import { editWorkspaceFileContent } from '@/lib/workspace-files/application/edit-workspace-file-content'
8+
import { workspaceFileRevision } from '@/lib/workspace-files/application/file-revision'
89
import { fileOperations } from '@/lib/workspace-files/application/operations'
910
import {
1011
admitUpdateWorkspaceFileContent,
@@ -39,7 +40,10 @@ export const PUT = defineV2JsonRoute({
3940
expectedRevision: body.expectedRevision,
4041
}),
4142
useCase: updateWorkspaceFileContent,
42-
present: async ({ file }) => ({ data: await toV2File(file) }),
43+
present: async ({ file }) => {
44+
const revision = workspaceFileRevision(file)
45+
return { data: { ...(await toV2File(file)), ...(revision === null ? {} : { revision }) } }
46+
},
4347
})
4448

4549
/**
@@ -74,5 +78,10 @@ export const PATCH = defineV2JsonRoute({
7478
expectedRevision: body.expectedRevision,
7579
}),
7680
useCase: editWorkspaceFileContent,
77-
present: async ({ file, lineCount }) => ({ data: { file: await toV2File(file), lineCount } }),
81+
present: async ({ file, lineCount }) => {
82+
const revision = workspaceFileRevision(file)
83+
return {
84+
data: { file: await toV2File(file), lineCount, ...(revision === null ? {} : { revision }) },
85+
}
86+
},
7887
})

‎apps/sim/lib/api/contracts/v2/files.ts‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,14 @@ import { FILE_SEARCH_MODES } from '@/lib/workspace-files/search/pattern'
7171
* by the `revision` Get File Metadata returns. A version number cannot express this: collaborative
7272
* and workflow writes fold into the current version rather than adding one.
7373
*/
74+
/** The token naming the content a write produced, for the caller's next conditional write. */
75+
const writtenFileRevisionSchema = z
76+
.string()
77+
.optional()
78+
.describe(
79+
'Opaque token for the content this write produced. Send it back as `expectedRevision` on the next write. Absent for a file with no recorded content version.'
80+
)
81+
7482
const expectedFileRevisionSchema = z
7583
.string()
7684
.min(1)
@@ -1236,6 +1244,7 @@ export const v2EditedFileSchema = z
12361244
.object({
12371245
file: v2FileSchema.describe('The file after the edit.'),
12381246
lineCount: z.number().int().nonnegative().describe('Lines the file holds after the edit.'),
1247+
revision: writtenFileRevisionSchema,
12391248
})
12401249
.strict()
12411250
.meta({
@@ -1466,6 +1475,14 @@ export const v2EditFileContentContract = defineRouteContract({
14661475
},
14671476
})
14681477

1478+
export const v2WrittenFileSchema = v2FileSchema
1479+
.extend({ revision: writtenFileRevisionSchema })
1480+
.meta({
1481+
id: 'V2WrittenFile',
1482+
title: 'Written file',
1483+
description: 'A workspace file after a content replacement, with the revision it produced.',
1484+
})
1485+
14691486
export const v2UpdateFileContentContract = defineRouteContract({
14701487
method: 'PUT',
14711488
path: '/api/v2/files/[fileId]/content',
@@ -1474,6 +1491,6 @@ export const v2UpdateFileContentContract = defineRouteContract({
14741491
body: v2UpdateFileContentBodySchema,
14751492
response: {
14761493
mode: 'json',
1477-
schema: v2DataResponse(v2FileSchema),
1494+
schema: v2DataResponse(v2WrittenFileSchema),
14781495
},
14791496
})

‎apps/sim/lib/api/contracts/v2/openapi/files-audit.ts‎

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1194,10 +1194,17 @@ const declaredRoutes = [
11941194
),
11951195
response: documentedSchema(
11961196
v2UpdateFileContentContract.response.schema,
1197-
'V2FileResponse',
1198-
'File response',
1199-
'A single workspace file.',
1200-
[{ data: FILE_EXAMPLE }]
1197+
'V2WrittenFileResponse',
1198+
'Written file response',
1199+
'A workspace file after a content replacement, with the revision the write produced.',
1200+
[
1201+
{
1202+
data: {
1203+
...FILE_EXAMPLE,
1204+
revision: 'd2ZfVjFTdEdYUjh6NWpkSGk2Qm15VDkxOjIwMjYtMDEtMTVUMTA6MzA6MDAuMDAwWg',
1205+
},
1206+
},
1207+
]
12011208
),
12021209
}
12031210
),

‎packages/sim-cli/src/generated/v2-api.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3592,6 +3592,7 @@ type EditFileContentResponseRef0 = {
35923592
type EditFileContentResponseRef1 = {
35933593
file: EditFileContentResponseRef0
35943594
lineCount: number
3595+
revision?: string
35953596
}
35963597

35973598
export type EditFileContentResponse = {
@@ -9997,6 +9998,7 @@ type UpdateFileContentResponseRef0 = {
99979998
uploadedAt: string
99989999
updatedAt: string
999910000
deletedAt: string | null
10001+
revision?: string
1000010002
}
1000110003

1000210004
export type UpdateFileContentResponse = {

0 commit comments

Comments
 (0)