Skip to content

Commit 36c8ea0

Browse files
committed
fix(account-deletion): list storage keys through the S3 client so Trigger runs can sweep
The account storage sweep and Project file prefix cleanup listed S3 keys with `await import('@aws-sdk/client-s3')`. The Trigger.dev bundle resolves that package to its CommonJS build, and the dynamic import's namespace does not carry `ListObjectsV2Command`, so every listing threw "ListObjectsV2Command is not a constructor" and both outbox event types dead-lettered after 10 attempts. The listing now lives in `listS3ObjectKeyPage` beside the other S3 commands, which the S3 client module imports statically.
1 parent 3aad201 commit 36c8ea0

2 files changed

Lines changed: 38 additions & 16 deletions

File tree

‎apps/sim/lib/uploads/core/prefix-sweep.ts‎

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -120,22 +120,17 @@ export async function listStorageKeyPage(
120120
): Promise<StorageKeyPage> {
121121
signal.throwIfAborted()
122122
if (USE_S3_STORAGE) {
123-
const { ListObjectsV2Command } = await import('@aws-sdk/client-s3')
124-
const { getS3Client } = await import('@/lib/uploads/providers/s3/client')
123+
const { listS3ObjectKeyPage } = await import('@/lib/uploads/providers/s3/client')
125124
const config = getStorageConfig(context)
126-
const page = await getS3Client().send(
127-
new ListObjectsV2Command({
128-
Bucket: config.bucket,
129-
Prefix: prefix,
130-
MaxKeys: STORAGE_KEY_PAGE_SIZE,
131-
...(pageToken ? { ContinuationToken: pageToken } : {}),
132-
}),
133-
{ abortSignal: signal }
134-
)
135-
return keyPage(
136-
(page.Contents ?? []).flatMap((object) => (object.Key ? [object.Key] : [])),
137-
page.IsTruncated ? page.NextContinuationToken : undefined
138-
)
125+
if (!config.bucket) throw new Error(`Bucket missing for ${context} storage`)
126+
const page = await listS3ObjectKeyPage({
127+
bucket: config.bucket,
128+
prefix,
129+
maxKeys: STORAGE_KEY_PAGE_SIZE,
130+
continuationToken: pageToken,
131+
signal,
132+
})
133+
return keyPage(page.keys, page.nextContinuationToken)
139134
}
140135
if (USE_BLOB_STORAGE) {
141136
const { getBlobServiceClient } = await import('@/lib/uploads/providers/blob/client')

‎apps/sim/lib/uploads/providers/s3/client.ts‎

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
DeleteObjectsCommand,
88
GetObjectCommand,
99
HeadObjectCommand,
10+
ListObjectsV2Command,
1011
ListPartsCommand,
1112
PutObjectCommand,
1213
S3Client,
@@ -325,7 +326,7 @@ export async function deleteS3ObjectVersion(params: {
325326
if (!params.etag) throw new Error('S3 upload object is missing its ETag')
326327
await getS3Client().send(
327328
new DeleteObjectCommand({
328-
Bucket: params.customConfig.bucket,
329+
Bucket: params.bucket,
329330
Key: params.key,
330331
IfMatch: params.etag,
331332
})
@@ -413,6 +414,32 @@ export async function deleteManyFromS3(
413414
return { failed }
414415
}
415416

417+
/**
418+
* Lists one page of object keys under `prefix` in `bucket`, resuming from `continuationToken`
419+
* when given; `nextContinuationToken` is set while the listing has more.
420+
*/
421+
export async function listS3ObjectKeyPage(params: {
422+
bucket: string
423+
prefix: string
424+
maxKeys: number
425+
continuationToken?: string
426+
signal: AbortSignal
427+
}): Promise<{ keys: string[]; nextContinuationToken?: string }> {
428+
const page = await getS3Client().send(
429+
new ListObjectsV2Command({
430+
Bucket: params.bucket,
431+
Prefix: params.prefix,
432+
MaxKeys: params.maxKeys,
433+
...(params.continuationToken ? { ContinuationToken: params.continuationToken } : {}),
434+
}),
435+
{ abortSignal: params.signal }
436+
)
437+
const keys = (page.Contents ?? []).flatMap((object) => (object.Key ? [object.Key] : []))
438+
return page.IsTruncated && page.NextContinuationToken
439+
? { keys, nextContinuationToken: page.NextContinuationToken }
440+
: { keys }
441+
}
442+
416443
/**
417444
* Initiate a multipart upload for S3
418445
*/

0 commit comments

Comments
 (0)