Skip to content

Commit 2b55eb6

Browse files
committed
improvement(files): correct two comments that described the wrong behavior
The resolution loop's comment claimed at most one rendered document is resident. It is not: every resolved buffer is held until the archive is assembled, bounded by the request's remaining budget. The loop resolves one at a time, it does not release one at a time. RENDERED_DOCUMENT_HEADROOM_BYTES was documented as bounding the expansion beyond the declared size, but it is passed straight through as maxBytes and is an absolute ceiling on the rendered document — renamed to MAX_RENDERED_DOCUMENT_BYTES so the name matches. Download failures also carry a fallback message, so a transport error surfaces something better than a bare "Failed to fetch".
1 parent 1013b2b commit 2b55eb6

5 files changed

Lines changed: 17 additions & 18 deletions

File tree

apps/sim/app/api/workspaces/[id]/files/download/route.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import { downloadFileStream } from '@/lib/uploads/core/storage-service'
2727
import {
2828
formatFileSize,
2929
isRenderableDocumentName,
30-
RENDERED_DOCUMENT_HEADROOM_BYTES,
30+
MAX_RENDERED_DOCUMENT_BYTES,
3131
} from '@/lib/uploads/utils/file-utils'
3232
import { docNotReadyMessage, isDocNotReadyError } from '@/lib/uploads/utils/servable-file-response'
3333
import { buildZipEntryPaths } from '@/lib/uploads/zip-entry-path'
@@ -153,10 +153,10 @@ export const GET = withRouteHandler(
153153
return selectionTooLargeResponse(declaredBytes)
154154
}
155155

156-
// Generated documents are resolved before the archive starts. They are the only
157-
// entries whose bytes decide anything, and every status this route returns comes
158-
// from them — once the first byte is written the status code is committed. They
159-
// resolve one at a time so at most one rendered document is resident.
156+
// Generated documents are resolved before the archive starts: once the first byte
157+
// is written the status code is committed, so anything that can still fail the
158+
// request has to fail here. Their buffers are held until the archive is assembled,
159+
// bounded by what is left of the request's byte budget.
160160
const renderedDocuments = new Map<string, Buffer>()
161161
const pendingNames: string[] = []
162162
let renderedBytes = 0
@@ -165,9 +165,9 @@ export const GET = withRouteHandler(
165165
if (!needsRendering(file)) continue
166166

167167
const remaining = MAX_ZIP_DOWNLOAD_BYTES - renderedBytes
168-
// A source renders to something larger than it declared, so the allowance is the
169-
// render headroom — bounded by what is left of the request's budget.
170-
const allowance = Math.min(remaining, RENDERED_DOCUMENT_HEADROOM_BYTES)
168+
// A source's declared size says nothing about what it renders to, so the cap is
169+
// the per-document ceiling, bounded by what is left of the budget.
170+
const allowance = Math.min(remaining, MAX_RENDERED_DOCUMENT_BYTES)
171171

172172
try {
173173
const { buffer } = await fetchServableWorkspaceFileBuffer(file, { maxBytes: allowance })
@@ -177,10 +177,10 @@ export const GET = withRouteHandler(
177177
if (error instanceof PayloadSizeLimitError) {
178178
// Blamed on the entry when its own ceiling was the binding cap; otherwise the
179179
// documents ahead of it have consumed the budget.
180-
return allowance === RENDERED_DOCUMENT_HEADROOM_BYTES
180+
return allowance === MAX_RENDERED_DOCUMENT_BYTES
181181
? NextResponse.json(
182182
{
183-
error: `"${file.name}" renders to more than ${formatFileSize(RENDERED_DOCUMENT_HEADROOM_BYTES)} and is too large to include in a zip; download it on its own instead.`,
183+
error: `"${file.name}" renders to more than ${formatFileSize(MAX_RENDERED_DOCUMENT_BYTES)} and is too large to include in a zip; download it on its own instead.`,
184184
},
185185
{ status: 400 }
186186
)

apps/sim/app/workspace/[workspaceId]/files/files.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -951,7 +951,7 @@ export function Files() {
951951
})
952952
} catch (err) {
953953
logger.error('Failed to download file:', err)
954-
toast.error(toError(err).message)
954+
toast.error(getErrorMessage(err, `Failed to download "${file.name}"`))
955955
}
956956
},
957957
[workspaceId]
@@ -1078,7 +1078,7 @@ export function Files() {
10781078
await triggerArchiveDownload({ workspaceId, ...selection })
10791079
} catch (err) {
10801080
logger.error('Failed to download selection:', err)
1081-
toast.error(toError(err).message)
1081+
toast.error(getErrorMessage(err, 'Failed to download the selected files'))
10821082
}
10831083
},
10841084
[workspaceId]

apps/sim/lib/core/utils/node-stream.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Readable } from 'stream'
1+
import type { Readable } from 'node:stream'
22

33
/**
44
* Bridges a Node `Readable` into a WHATWG `ReadableStream` suitable for a `Response`

apps/sim/lib/uploads/client/download.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { requestRaw } from '@/lib/api/client/request'
22
import { downloadWorkspaceFileItemsContract } from '@/lib/api/contracts/workspace-file-folders'
33
import type { WorkspaceFileRecord } from '@/lib/uploads/contexts/workspace'
44

5-
/** Hand a fetched blob to the browser as a file save, then release the object URL. */
65
export function saveBlob(blob: Blob, fileName: string): void {
76
const objectUrl = URL.createObjectURL(blob)
87
const anchor = document.createElement('a')

apps/sim/lib/uploads/utils/file-utils.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -218,11 +218,11 @@ export function getFileExtension(filename: string): string {
218218
const RENDERABLE_DOCUMENT_EXTENSIONS = new Set(['pdf', 'docx', 'pptx', 'xlsx'])
219219

220220
/**
221-
* How far a generated document may render beyond the source it declared. A generator
222-
* source is text and is orders of magnitude smaller than the document it produces, so
223-
* this bounds the expansion rather than the document.
221+
* Ceiling on a single rendered generated document. A generator source is text and is
222+
* orders of magnitude smaller than the document it produces, so the declared size is no
223+
* bound at all and the rendered bytes need a cap of their own.
224224
*/
225-
export const RENDERED_DOCUMENT_HEADROOM_BYTES = 50 * 1024 * 1024
225+
export const MAX_RENDERED_DOCUMENT_BYTES = 50 * 1024 * 1024
226226

227227
/** True when `fileName` may be backed by a generation source rather than final bytes. */
228228
export function isRenderableDocumentName(fileName: string): boolean {

0 commit comments

Comments
 (0)