Skip to content

Commit 2372284

Browse files
Merge pull request #80 from BunnyWay/feat/storage-remove-throw-on-error-opt-in
feat(bunny-storage): add opt-in throwOnError for remove/removeDirectory
2 parents 594f0fb + f34405f commit 2372284

3 files changed

Lines changed: 87 additions & 4 deletions

File tree

.changeset/many-berries-move.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@bunny.net/storage-sdk": patch
3+
---
4+
5+
handle throw on error for remove/removeDirectory

libs/bunny-storage/src/file.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,28 @@ describe('StorageFile operations', () => {
253253

254254
expect(result).toBe(true);
255255
});
256+
257+
it('should resolve to false on failure by default', async () => {
258+
(global.fetch as jest.Mock).mockResolvedValueOnce({
259+
ok: false,
260+
status: 404
261+
});
262+
263+
const result = await File.removeDirectory(mockStorageZone, '/test/missing-folder');
264+
265+
expect(result).toBe(false);
266+
});
267+
268+
it('should throw on failure when throwOnError is set', async () => {
269+
(global.fetch as jest.Mock).mockResolvedValueOnce({
270+
ok: false,
271+
status: 404
272+
});
273+
274+
await expect(
275+
File.removeDirectory(mockStorageZone, '/test/missing-folder', { throwOnError: true })
276+
).rejects.toThrow('File not found');
277+
});
256278
});
257279

258280
describe('remove function', () => {
@@ -275,5 +297,27 @@ describe('StorageFile operations', () => {
275297

276298
expect(result).toBe(true);
277299
});
300+
301+
it('should resolve to false on failure by default', async () => {
302+
(global.fetch as jest.Mock).mockResolvedValueOnce({
303+
ok: false,
304+
status: 404
305+
});
306+
307+
const result = await File.remove(mockStorageZone, '/test/missing.txt');
308+
309+
expect(result).toBe(false);
310+
});
311+
312+
it('should throw on failure when throwOnError is set', async () => {
313+
(global.fetch as jest.Mock).mockResolvedValueOnce({
314+
ok: false,
315+
status: 404
316+
});
317+
318+
await expect(
319+
File.remove(mockStorageZone, '/test/missing.txt', { throwOnError: true })
320+
).rejects.toThrow('File not found');
321+
});
278322
});
279323
});

libs/bunny-storage/src/file.ts

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -208,18 +208,44 @@ export async function list(storageZone: StorageZone.StorageZone, path: string):
208208
}));
209209
}
210210

211+
/**
212+
* Options for the deletion operations ([remove], [removeDirectory]).
213+
*/
214+
export type RemoveOptions = {
215+
/**
216+
* When `true`, the operation throws on a failed request (mirroring the
217+
* behaviour of [upload] and [download]) instead of resolving to `false`.
218+
*
219+
* Defaults to `false` to preserve backwards compatibility: by default a
220+
* failed request still resolves to `false`.
221+
*
222+
* @deprecated The opt-in is temporary. In v1 throwing becomes the default
223+
* behaviour and this option (along with the `boolean` return) will be
224+
* removed. Adopt `{ throwOnError: true }` now to ease the migration.
225+
*/
226+
throwOnError?: boolean;
227+
};
228+
211229
/**
212230
* Remove files and folders in a directory from a [StorageZone].
213231
*
214-
* @throws
232+
* By default a failed request resolves to `false`. Pass
233+
* `{ throwOnError: true }` to instead throw on failure, mirroring [upload]
234+
* and [download].
235+
*
236+
* @throws When the request fails and `options.throwOnError` is `true`.
215237
*/
216-
export async function remove(storageZone: StorageZone.StorageZone, path: string): Promise<boolean> {
238+
export async function remove(storageZone: StorageZone.StorageZone, path: string, options?: RemoveOptions): Promise<boolean> {
217239
const url = StorageZone.addr(storageZone);
218240
url.pathname = `${url.pathname}${path}`;
219241

220242
const [auth_header, key] = StorageZone.key(storageZone);
221243
const response = await fetch(url, { method: "DELETE", headers: { [auth_header]: key } });
222244

245+
if (!response.ok && options?.throwOnError) {
246+
throw statusCodeToException(storageZone, response.status, path);
247+
}
248+
223249
return response.ok;
224250
}
225251

@@ -241,15 +267,23 @@ export async function createDirectory(storageZone: StorageZone.StorageZone, path
241267
/**
242268
* Remove recursively a Directory in the [StorageZone].
243269
*
244-
* @throws
270+
* By default a failed request resolves to `false`. Pass
271+
* `{ throwOnError: true }` to instead throw on failure, mirroring [upload]
272+
* and [download].
273+
*
274+
* @throws When the request fails and `options.throwOnError` is `true`.
245275
*/
246-
export async function removeDirectory(storageZone: StorageZone.StorageZone, path: string): Promise<boolean> {
276+
export async function removeDirectory(storageZone: StorageZone.StorageZone, path: string, options?: RemoveOptions): Promise<boolean> {
247277
const url = StorageZone.addr(storageZone);
248278
const directory_path = path.endsWith("/") ? path : `${path}/`;
249279
url.pathname = `${url.pathname}${directory_path}`;
250280
const [auth_header, key] = StorageZone.key(storageZone);
251281
const response = await fetch(url, { method: "DELETE", headers: { [auth_header]: key } });
252282

283+
if (!response.ok && options?.throwOnError) {
284+
throw statusCodeToException(storageZone, response.status, path);
285+
}
286+
253287
return response.ok;
254288
}
255289

0 commit comments

Comments
 (0)