diff --git a/modules.json b/modules.json index 862d3764..fc8342b4 100644 --- a/modules.json +++ b/modules.json @@ -12,7 +12,7 @@ { "id": "s3", "name": "S3", - "version": "0.1.8", + "version": "0.1.9", "description": "S3 and S3-compatible backend support.", "icon": "server", "main": "https://sync.consensia.cc/modules/s3.js", diff --git a/packages/s3/src/optimizer.ts b/packages/s3/src/optimizer.ts index 42595d95..3efd5884 100644 --- a/packages/s3/src/optimizer.ts +++ b/packages/s3/src/optimizer.ts @@ -1,4 +1,4 @@ -import type { OptimizerInput, OptimizerOutput } from '@hesprs/sync-engine-sdk'; +import type { DeleteAtom, OptimizerInput, OptimizerOutput } from '@hesprs/sync-engine-sdk'; import { digOriginal } from '@hesprs/sync-engine-sdk'; import S3Fs, { BATCH_DELETE_MAX_KEYS } from './s3/fs'; @@ -9,10 +9,9 @@ export default function s3BatchDeleteOptimizer({ const original = digOriginal(fs); if (!(original instanceof S3Fs)) return undefined; const s3Fs = original; - type DeleteAtom = Extract<(typeof atoms)[number], { type: 'delete' }>; const deleteAtoms = atoms.filter((a): a is DeleteAtom => a.type === 'delete'); + if (deleteAtoms.length <= 1) return atoms; const otherAtoms = atoms.filter((a) => a.type !== 'delete'); - if (deleteAtoms.length === 0) return atoms; const batchGroups: Array> = []; for (let i = 0; i < deleteAtoms.length; i += BATCH_DELETE_MAX_KEYS) batchGroups.push(deleteAtoms.slice(i, i + BATCH_DELETE_MAX_KEYS)); diff --git a/packages/s3/test/fs-s3.test.ts b/packages/s3/test/fs-s3.test.ts index b6ef042d..3b11d0d7 100644 --- a/packages/s3/test/fs-s3.test.ts +++ b/packages/s3/test/fs-s3.test.ts @@ -278,6 +278,31 @@ test('batch delete rejects only atoms with S3 partial failures', async () => { ); }); +test('batchDelete retries each key individually when the batch request fails', async () => { + const s3 = createS3Fs(); + const methods: Array = []; + parsedResponse = { Error: { Code: 'InternalError', Message: 'boom' } }; + s3.setRequest((url, params) => { + methods.push(params.method ?? ''); + if (params.method === 'POST') + return response({ + status: 500, + text: 'InternalErrorboom', + }); + expect(params.method).toBe('DELETE'); + if (url.endsWith('/broken.md')) + return response({ + status: 500, + text: 'InternalErrorboom', + }); + return response({ status: 204 }); + }); + const result = await s3.fs.batchDelete(['ok.md', 'broken.md']); + expect(result['ok.md']).toBe(true); + expect(result['broken.md']).toBe('S3 InternalError: boom'); + expect(methods).toStrictEqual(['POST', 'DELETE', 'DELETE']); +}); + test('move copies encoded source before deleting old key', async () => { const s3 = createS3Fs(); s3.setRequest((url, params) => {