From ffbc65a155c0b71e78a53efdb5a377081d032469 Mon Sep 17 00:00:00 2001 From: Chandranil Bakshi Date: Fri, 10 Jul 2026 18:29:24 +0530 Subject: [PATCH 01/15] feat(storage): add removeMany for batch object deletion --- src/modules/storage.ts | 108 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/src/modules/storage.ts b/src/modules/storage.ts index 2ffd5dd..a9289bd 100644 --- a/src/modules/storage.ts +++ b/src/modules/storage.ts @@ -28,6 +28,35 @@ interface DownloadStrategy { expiresAt?: Date; } +/** + * Max object keys accepted per batch-delete request. Requests with more keys are + * split into multiple requests client-side. Mirrors the server-side limit. + */ +const DELETE_OBJECTS_MAX_KEYS = 1000; + +/** Result for a single key in the raw batch-delete HTTP response. */ +export interface RemoveManyResult { + key: string; + status: 'deleted' | 'notFound' | 'failed'; + /** Present only when `status` is `'failed'`. */ + message?: string; +} + +/** Raw batch-delete HTTP response body: one entry per requested (deduped) key. */ +export interface RemoveManyResponse { + results: RemoveManyResult[]; +} + +/** + * Aggregated outcome of {@link StorageBucket.removeMany} across all batches. + * `success` holds the keys that were deleted; `failures` pairs each remaining + * key with the reason it wasn't (not found, delete failed, or a request error). + */ +export interface RemoveManyOutcome { + success: string[]; + failures: { key: string; error: Error }[]; +} + /** * Storage bucket operations */ @@ -483,6 +512,85 @@ export class StorageBucket { }; } } + + /** + * Delete multiple files. + * + * Keys are split into batches of at most {@link DELETE_OBJECTS_MAX_KEYS} and + * sent concurrently, so there is no client-side cap on how many keys you pass. + * Each key resolves independently: a missing key or a failed request does not + * prevent the others from being deleted. The result aggregates every batch into + * `success` (deleted keys) and `failures` (each remaining key with its reason). + * + * @param paths - The object keys/paths (any length; deduped server-side per batch) + * @returns `{ data: { success, failures }, error }`. `error` is non-null only + * for an unexpected top-level failure; per-key problems surface in `failures`. + */ + async removeMany(paths: string[]): Promise> { + try { + if (paths.length === 0) { + return { data: { success: [], failures: [] }, error: null }; + } + + const batches: string[][] = []; + for (let index = 0; index < paths.length; index += DELETE_OBJECTS_MAX_KEYS) { + batches.push(paths.slice(index, index + DELETE_OBJECTS_MAX_KEYS)); + } + + const settled = await Promise.allSettled( + batches.map((keys) => + this.http.delete( + `/api/storage/buckets/${encodeURIComponent(this.bucketName)}/objects`, + { body: { keys } } + ) + ) + ); + + const success: string[] = []; + const failures: { key: string; error: Error }[] = []; + + settled.forEach((result, index) => { + const keys = batches[index]; + + // A rejected batch fails the whole slice of keys with the same reason. + if (result.status === 'rejected') { + const error = + result.reason instanceof Error + ? result.reason + : new InsForgeError(String(result.reason), 500, 'STORAGE_ERROR'); + keys.forEach((key) => failures.push({ key, error })); + return; + } + + result.value.results.forEach((deleteResult) => { + if (deleteResult.status === 'deleted') { + success.push(deleteResult.key); + return; + } + failures.push({ + key: deleteResult.key, + error: new InsForgeError( + deleteResult.status === 'notFound' + ? 'Object not found' + : (deleteResult.message ?? 'Failed to delete object'), + deleteResult.status === 'notFound' ? 404 : 500, + 'STORAGE_ERROR' + ), + }); + }); + }); + + return { data: { success, failures }, error: null }; + } catch (error) { + return { + data: null, + error: + error instanceof InsForgeError + ? error + : new InsForgeError('Delete failed', 500, 'STORAGE_ERROR'), + }; + } + } } /** From cfadf0143a58a0f1c81fd2ace36d090f52cb7f55 Mon Sep 17 00:00:00 2001 From: Chandranil Bakshi Date: Fri, 10 Jul 2026 18:29:49 +0530 Subject: [PATCH 02/15] feat(storage): export removeMany result types --- src/index.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 0b56295..bbba6d3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -36,7 +36,12 @@ export { Database } from './modules/database-postgrest'; // Re-export storage module and types export { Storage, StorageBucket } from './modules/storage'; -export type { StorageResponse } from './modules/storage'; +export type { + StorageResponse, + RemoveManyResult, + RemoveManyResponse, + RemoveManyOutcome, +} from './modules/storage'; // Re-export AI module export { AI } from './modules/ai'; From 93d39144547cd6ff83d9869863641743e6c9000b Mon Sep 17 00:00:00 2001 From: Chandranil Bakshi Date: Fri, 10 Jul 2026 18:30:10 +0530 Subject: [PATCH 03/15] docs: fix storage remove() usage and document removeMany --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e156864..d0813b1 100644 --- a/README.md +++ b/README.md @@ -212,7 +212,13 @@ const { data, error } = await insforge.storage.from('avatars').upload(file); const { data, error } = await insforge.storage.from('avatars').download('user-avatar.png'); // Delete a file -const { data, error } = await insforge.storage.from('avatars').remove(['user-avatar.png']); +const { data, error } = await insforge.storage.from('avatars').remove('user-avatar.png'); + +// Delete multiple files (auto-batched, any number of keys) +const { data, error } = await insforge.storage + .from('avatars') + .removeMany(['user-avatar.png', 'old-avatar.png']); +// data: { success: string[], failures: { key, error }[] } // List files const { data, error } = await insforge.storage.from('avatars').list(); From 622daa8ff28e6f832eca89d7e06073f1df4ddc42 Mon Sep 17 00:00:00 2001 From: Chandranil Bakshi Date: Fri, 10 Jul 2026 18:30:23 +0530 Subject: [PATCH 04/15] docs: add bucket.removeMany() reference --- SDK-REFERENCE.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/SDK-REFERENCE.md b/SDK-REFERENCE.md index e297b7c..35e7997 100644 --- a/SDK-REFERENCE.md +++ b/SDK-REFERENCE.md @@ -746,6 +746,16 @@ await bucket.remove('path/file.jpg'); // Response: { data: { message }, error } ``` +### `bucket.removeMany()` + +```javascript +await bucket.removeMany(['path/a.jpg', 'path/b.txt']); // any number of keys +// Response: { data: { success, failures }, error } +// success: string[] — keys that were deleted +// failures: { key, error }[] — each remaining key with the reason it wasn't deleted +// Keys are auto-batched (max 1000 per request); each key resolves independently. +``` + ### `bucket.getPublicUrl()` ```javascript From bb7da6ae1f60fb2ea1aa8ffd79200b6f26efce0c Mon Sep 17 00:00:00 2001 From: Chandranil Bakshi Date: Fri, 10 Jul 2026 19:09:38 +0530 Subject: [PATCH 05/15] updated the code to follow existing coding style --- src/modules/storage.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/modules/storage.ts b/src/modules/storage.ts index a9289bd..822f452 100644 --- a/src/modules/storage.ts +++ b/src/modules/storage.ts @@ -539,10 +539,9 @@ export class StorageBucket { const settled = await Promise.allSettled( batches.map((keys) => - this.http.delete( - `/api/storage/buckets/${encodeURIComponent(this.bucketName)}/objects`, - { body: { keys } } - ) + this.http.delete(`/api/storage/buckets/${this.bucketName}/objects`, { + body: { keys }, + }) ) ); From f876c87767514d4c4776be6ddfd68b4db76cd428 Mon Sep 17 00:00:00 2001 From: Chandranil Bakshi Date: Tue, 14 Jul 2026 21:58:28 +0530 Subject: [PATCH 06/15] refactor(storage): overload remove for batch deletion --- src/modules/storage.ts | 136 ++++++++--------------------------------- 1 file changed, 25 insertions(+), 111 deletions(-) diff --git a/src/modules/storage.ts b/src/modules/storage.ts index 822f452..e497def 100644 --- a/src/modules/storage.ts +++ b/src/modules/storage.ts @@ -5,7 +5,11 @@ import { HttpClient } from '../lib/http-client'; import { InsForgeError } from '../types'; -import type { StorageFileSchema, ListObjectsResponseSchema } from '@insforge/shared-schemas'; +import type { + StorageFileSchema, + ListObjectsResponseSchema, + DeleteObjectsResponse, +} from '@insforge/shared-schemas'; export interface StorageResponse { data: T | null; @@ -28,35 +32,6 @@ interface DownloadStrategy { expiresAt?: Date; } -/** - * Max object keys accepted per batch-delete request. Requests with more keys are - * split into multiple requests client-side. Mirrors the server-side limit. - */ -const DELETE_OBJECTS_MAX_KEYS = 1000; - -/** Result for a single key in the raw batch-delete HTTP response. */ -export interface RemoveManyResult { - key: string; - status: 'deleted' | 'notFound' | 'failed'; - /** Present only when `status` is `'failed'`. */ - message?: string; -} - -/** Raw batch-delete HTTP response body: one entry per requested (deduped) key. */ -export interface RemoveManyResponse { - results: RemoveManyResult[]; -} - -/** - * Aggregated outcome of {@link StorageBucket.removeMany} across all batches. - * `success` holds the keys that were deleted; `failures` pairs each remaining - * key with the reason it wasn't (not found, delete failed, or a request error). - */ -export interface RemoveManyOutcome { - success: string[]; - failures: { key: string; error: Error }[]; -} - /** * Storage bucket operations */ @@ -491,95 +466,34 @@ export class StorageBucket { } } - /** - * Delete a file - * @param path - The object key/path - */ - async remove(path: string): Promise> { - try { - const response = await this.http.delete<{ message: string }>( - `/api/storage/buckets/${this.bucketName}/objects/${encodeURIComponent(path)}` - ); + /** Delete a single file. */ + async remove(path: string): Promise>; - return { data: response, error: null }; - } catch (error) { - return { - data: null, - error: - error instanceof InsForgeError - ? error - : new InsForgeError('Delete failed', 500, 'STORAGE_ERROR'), - }; - } - } + /** Delete multiple files in a single request. */ + async remove(paths: string[]): Promise>; /** - * Delete multiple files. + * Delete one or more files. * - * Keys are split into batches of at most {@link DELETE_OBJECTS_MAX_KEYS} and - * sent concurrently, so there is no client-side cap on how many keys you pass. - * Each key resolves independently: a missing key or a failed request does not - * prevent the others from being deleted. The result aggregates every batch into - * `success` (deleted keys) and `failures` (each remaining key with its reason). + * A string uses the single-object endpoint. An array uses the batch endpoint, + * which accepts at most 1000 keys and returns one result per key. * - * @param paths - The object keys/paths (any length; deduped server-side per batch) - * @returns `{ data: { success, failures }, error }`. `error` is non-null only - * for an unexpected top-level failure; per-key problems surface in `failures`. + * @param pathOrPaths - One object key/path or an array of object keys/paths */ - async removeMany(paths: string[]): Promise> { + async remove( + pathOrPaths: string | string[] + ): Promise> { try { - if (paths.length === 0) { - return { data: { success: [], failures: [] }, error: null }; - } - - const batches: string[][] = []; - for (let index = 0; index < paths.length; index += DELETE_OBJECTS_MAX_KEYS) { - batches.push(paths.slice(index, index + DELETE_OBJECTS_MAX_KEYS)); - } - - const settled = await Promise.allSettled( - batches.map((keys) => - this.http.delete(`/api/storage/buckets/${this.bucketName}/objects`, { - body: { keys }, - }) - ) - ); - - const success: string[] = []; - const failures: { key: string; error: Error }[] = []; - - settled.forEach((result, index) => { - const keys = batches[index]; - - // A rejected batch fails the whole slice of keys with the same reason. - if (result.status === 'rejected') { - const error = - result.reason instanceof Error - ? result.reason - : new InsForgeError(String(result.reason), 500, 'STORAGE_ERROR'); - keys.forEach((key) => failures.push({ key, error })); - return; - } - - result.value.results.forEach((deleteResult) => { - if (deleteResult.status === 'deleted') { - success.push(deleteResult.key); - return; - } - failures.push({ - key: deleteResult.key, - error: new InsForgeError( - deleteResult.status === 'notFound' - ? 'Object not found' - : (deleteResult.message ?? 'Failed to delete object'), - deleteResult.status === 'notFound' ? 404 : 500, - 'STORAGE_ERROR' - ), - }); - }); - }); + const response = Array.isArray(pathOrPaths) + ? await this.http.delete( + `/api/storage/buckets/${this.bucketName}/objects`, + { body: { keys: pathOrPaths } } + ) + : await this.http.delete<{ message: string }>( + `/api/storage/buckets/${this.bucketName}/objects/${encodeURIComponent(pathOrPaths)}` + ); - return { data: { success, failures }, error: null }; + return { data: response, error: null }; } catch (error) { return { data: null, From 7e68810c2e261ae8b2e07c12e56a732fe0bce2f7 Mon Sep 17 00:00:00 2001 From: Chandranil Bakshi Date: Tue, 14 Jul 2026 21:58:49 +0530 Subject: [PATCH 07/15] test(storage): cover single and batch remove behavior --- src/modules/__tests__/storage.test.ts | 86 +++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/src/modules/__tests__/storage.test.ts b/src/modules/__tests__/storage.test.ts index 3a17589..e72c2c4 100644 --- a/src/modules/__tests__/storage.test.ts +++ b/src/modules/__tests__/storage.test.ts @@ -174,3 +174,89 @@ describe('StorageBucket.createSignedUrls', () => { expect(new URL(String(fetchFn.mock.calls[0][0])).searchParams.get('expiresIn')).toBe('60'); }); }); + +describe('StorageBucket.remove', () => { + beforeEach(() => { + vi.restoreAllMocks(); + }); + + it('deletes a single path with the single-object endpoint', async () => { + const fetchFn = vi.fn().mockResolvedValue(jsonRes(200, { message: 'Object deleted' })); + const bucket = new StorageBucket('docs', makeHttp(fetchFn)); + + const result = await bucket.remove('files/old report.pdf'); + + expect(result).toEqual({ data: { message: 'Object deleted' }, error: null }); + expect(fetchFn).toHaveBeenCalledOnce(); + expect(new URL(String(fetchFn.mock.calls[0][0])).pathname).toBe( + '/api/storage/buckets/docs/objects/files%2Fold%20report.pdf' + ); + expect(fetchFn.mock.calls[0][1]?.method).toBe('DELETE'); + }); + + it('deletes multiple paths in one request and returns the server results unchanged', async () => { + const response = { + results: [ + { key: 'a.pdf', status: 'deleted' }, + { key: 'missing.pdf', status: 'notFound' }, + { key: 'locked.pdf', status: 'failed', message: 'Delete denied' }, + ], + } as const; + const fetchFn = vi.fn().mockResolvedValue(jsonRes(200, response)); + const bucket = new StorageBucket('docs', makeHttp(fetchFn)); + + const result = await bucket.remove(['a.pdf', 'missing.pdf', 'locked.pdf']); + + expect(result).toEqual({ data: response, error: null }); + expect(fetchFn).toHaveBeenCalledOnce(); + expect(new URL(String(fetchFn.mock.calls[0][0])).pathname).toBe( + '/api/storage/buckets/docs/objects' + ); + expect(fetchFn.mock.calls[0][1]?.method).toBe('DELETE'); + expect(JSON.parse(String(fetchFn.mock.calls[0][1]?.body))).toEqual({ + keys: ['a.pdf', 'missing.pdf', 'locked.pdf'], + }); + }); + + it('surfaces a batch request error through the storage response envelope', async () => { + const fetchFn = vi + .fn() + .mockResolvedValue( + jsonRes( + 400, + { error: 'STORAGE_ERROR', message: 'At least one object key is required' }, + 'Bad Request' + ) + ); + const bucket = new StorageBucket('docs', makeHttp(fetchFn)); + + const result = await bucket.remove([]); + + expect(result.data).toBeNull(); + expect(result.error).toBeInstanceOf(InsForgeError); + expect(result.error?.statusCode).toBe(400); + expect(result.error?.message).toBe('At least one object key is required'); + expect(fetchFn).toHaveBeenCalledOnce(); + }); + + it('does not split arrays that exceed the server limit into multiple requests', async () => { + const paths = Array.from({ length: 1001 }, (_, index) => `file-${index}.txt`); + const fetchFn = vi + .fn() + .mockResolvedValue( + jsonRes( + 400, + { error: 'STORAGE_ERROR', message: 'Cannot delete more than 1000 objects at once' }, + 'Bad Request' + ) + ); + const bucket = new StorageBucket('docs', makeHttp(fetchFn)); + + const result = await bucket.remove(paths); + + expect(result.data).toBeNull(); + expect(result.error?.statusCode).toBe(400); + expect(fetchFn).toHaveBeenCalledOnce(); + expect(JSON.parse(String(fetchFn.mock.calls[0][1]?.body)).keys).toHaveLength(1001); + }); +}); From 9808c523815d4b72e4518eb16d7e860cf2ff55a6 Mon Sep 17 00:00:00 2001 From: Chandranil Bakshi Date: Tue, 14 Jul 2026 21:59:04 +0530 Subject: [PATCH 08/15] feat(storage): export DeleteObjectsResponse type --- src/index.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/index.ts b/src/index.ts index bbba6d3..4821b9b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -25,6 +25,7 @@ export type { CreateUserRequest, CreateSessionRequest, AuthErrorResponse, + DeleteObjectsResponse, } from '@insforge/shared-schemas'; // Re-export auth module for advanced usage @@ -36,12 +37,7 @@ export { Database } from './modules/database-postgrest'; // Re-export storage module and types export { Storage, StorageBucket } from './modules/storage'; -export type { - StorageResponse, - RemoveManyResult, - RemoveManyResponse, - RemoveManyOutcome, -} from './modules/storage'; +export type { StorageResponse } from './modules/storage'; // Re-export AI module export { AI } from './modules/ai'; From eea12ef825eda73eb03e9c0ed96b39d9f5d4e9c3 Mon Sep 17 00:00:00 2001 From: Chandranil Bakshi Date: Tue, 14 Jul 2026 21:59:27 +0530 Subject: [PATCH 09/15] docs: update storage batch removal example --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d0813b1..dd004d2 100644 --- a/README.md +++ b/README.md @@ -214,11 +214,11 @@ const { data, error } = await insforge.storage.from('avatars').download('user-av // Delete a file const { data, error } = await insforge.storage.from('avatars').remove('user-avatar.png'); -// Delete multiple files (auto-batched, any number of keys) +// Delete multiple files (maximum 1000 keys) const { data, error } = await insforge.storage .from('avatars') - .removeMany(['user-avatar.png', 'old-avatar.png']); -// data: { success: string[], failures: { key, error }[] } + .remove(['user-avatar.png', 'old-avatar.png']); +// data: { results: [{ key, status: 'deleted' | 'notFound' | 'failed', message? }] } // List files const { data, error } = await insforge.storage.from('avatars').list(); From 8600e2857caae367d76c381ce9d95a8d765aeefd Mon Sep 17 00:00:00 2001 From: Chandranil Bakshi Date: Tue, 14 Jul 2026 21:59:39 +0530 Subject: [PATCH 10/15] docs: document overloaded storage remove method --- SDK-REFERENCE.md | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/SDK-REFERENCE.md b/SDK-REFERENCE.md index 35e7997..5d28d20 100644 --- a/SDK-REFERENCE.md +++ b/SDK-REFERENCE.md @@ -742,18 +742,14 @@ await bucket.list({ prefix: 'users/', limit: 10 }); ### `bucket.remove()` ```javascript +// Delete one object await bucket.remove('path/file.jpg'); // Response: { data: { message }, error } -``` - -### `bucket.removeMany()` -```javascript -await bucket.removeMany(['path/a.jpg', 'path/b.txt']); // any number of keys -// Response: { data: { success, failures }, error } -// success: string[] — keys that were deleted -// failures: { key, error }[] — each remaining key with the reason it wasn't deleted -// Keys are auto-batched (max 1000 per request); each key resolves independently. +// Delete multiple objects in one request (maximum 1000 keys) +await bucket.remove(['path/a.jpg', 'path/b.txt']); +// Response: { data: { results }, error } +// results: { key, status: 'deleted' | 'notFound' | 'failed', message? }[] ``` ### `bucket.getPublicUrl()` From aeaa5a5b9500d9909058e0074e65dc06bfab8938 Mon Sep 17 00:00:00 2001 From: Chandranil Bakshi Date: Tue, 14 Jul 2026 21:59:55 +0530 Subject: [PATCH 11/15] chore(deps): bump shared schemas to 1.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 891aa8b..a722795 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "url": "https://github.com/InsForge/insforge-sdk-js/issues" }, "dependencies": { - "@insforge/shared-schemas": "^1.1.58", + "@insforge/shared-schemas": "^1.2.0", "@supabase/postgrest-js": "^1.21.3", "socket.io-client": "^4.8.1" }, From 1c152cc20e561a3c907639565f1c9fbab8d112c0 Mon Sep 17 00:00:00 2001 From: Chandranil Bakshi Date: Tue, 14 Jul 2026 22:00:06 +0530 Subject: [PATCH 12/15] chore(deps): update lockfile for shared schemas 1.2.0 --- package-lock.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 71c7e89..99ee781 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.4.3", "license": "Apache-2.0", "dependencies": { - "@insforge/shared-schemas": "^1.1.58", + "@insforge/shared-schemas": "^1.2.0", "@supabase/postgrest-js": "^1.21.3", "socket.io-client": "^4.8.1" }, @@ -785,9 +785,7 @@ } }, "node_modules/@insforge/shared-schemas": { - "version": "1.1.58", - "resolved": "https://registry.npmjs.org/@insforge/shared-schemas/-/shared-schemas-1.1.58.tgz", - "integrity": "sha512-of494/PttH+nf5SBz63mAtgCBimDPIDF/tPEYb9zMNOySsGYNXYXWyqcyPuxnQcVuThlV5cdUwxW3cImvXyAvw==", + "version": "1.2.0", "license": "Apache-2.0", "dependencies": { "zod": "^3.23.8" From 1eda685562b085a2d170db2ab13af08d5460216c Mon Sep 17 00:00:00 2001 From: Chandranil Bakshi Date: Thu, 16 Jul 2026 15:46:56 +0530 Subject: [PATCH 13/15] chore(deps): lock shared schemas 1.2.0 --- package-lock.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package-lock.json b/package-lock.json index 99ee781..4c5d3fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -786,6 +786,8 @@ }, "node_modules/@insforge/shared-schemas": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@insforge/shared-schemas/-/shared-schemas-1.2.0.tgz", + "integrity": "sha512-qUYNXkCKVPTKXC/jWtG+FEgH/NFY0d2EnEgjYj8vu+R6wezGpKXP8T0rSMtoavgXo1ZcOTCR8+pQJBWJSR9BvA==", "license": "Apache-2.0", "dependencies": { "zod": "^3.23.8" From 61a2f07665b43cd5a07aba5c8db35e403606c373 Mon Sep 17 00:00:00 2001 From: Chandranil Bakshi Date: Thu, 16 Jul 2026 15:47:22 +0530 Subject: [PATCH 14/15] feat(storage): export delete object result type --- src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.ts b/src/index.ts index 4821b9b..c539eef 100644 --- a/src/index.ts +++ b/src/index.ts @@ -25,6 +25,7 @@ export type { CreateUserRequest, CreateSessionRequest, AuthErrorResponse, + DeleteObjectResult, DeleteObjectsResponse, } from '@insforge/shared-schemas'; From 6d847ef70d8a4bd91920a8f5d7a11982f4f26baf Mon Sep 17 00:00:00 2001 From: Chandranil Bakshi Date: Thu, 16 Jul 2026 15:47:29 +0530 Subject: [PATCH 15/15] test(storage): type batch delete response from shared schemas --- src/modules/__tests__/storage.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/modules/__tests__/storage.test.ts b/src/modules/__tests__/storage.test.ts index e72c2c4..3db8753 100644 --- a/src/modules/__tests__/storage.test.ts +++ b/src/modules/__tests__/storage.test.ts @@ -3,6 +3,7 @@ import { StorageBucket } from '../storage'; import { HttpClient } from '../../lib/http-client'; import { InsForgeError } from '../../types'; import { TokenManager } from '../../lib/token-manager'; +import type { DeleteObjectsResponse } from '@insforge/shared-schemas'; function makeTokenManager(): TokenManager { return { @@ -201,7 +202,7 @@ describe('StorageBucket.remove', () => { { key: 'missing.pdf', status: 'notFound' }, { key: 'locked.pdf', status: 'failed', message: 'Delete denied' }, ], - } as const; + } satisfies DeleteObjectsResponse; const fetchFn = vi.fn().mockResolvedValue(jsonRes(200, response)); const bucket = new StorageBucket('docs', makeHttp(fetchFn));