diff --git a/package.json b/package.json index 00f1d2ae..a523bc57 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@internxt/sdk", "author": "Internxt ", - "version": "1.19.0", + "version": "1.20.0", "description": "An sdk for interacting with Internxt's services", "repository": { "type": "git", diff --git a/src/drive/storage/index.ts b/src/drive/storage/index.ts index 6c5819e8..3205bd85 100644 --- a/src/drive/storage/index.ts +++ b/src/drive/storage/index.ts @@ -31,6 +31,7 @@ import { FileVersion, FolderAncestor, FolderAncestorWorkspace, + GlobalSearchOptions, FolderMeta, FolderStatsResponse, FolderTreeResponse, @@ -660,23 +661,27 @@ export class Storage { * * @param {string} search - The name of the item. * @param {string} workspaceId - The ID of the workspace (optional). - * @param {number} offset - The position of the first item to return (optional). + * @param {number | GlobalSearchOptions} offsetOrOptions - The position of the first item to return, or an options + * object (optional) with the offset and the search filters: `type` (file categories, combined with OR), + * `minSize`/`maxSize` (bytes, files only) and `modifiedAfter`/`modifiedBefore` (ISO 8601 dates). Filters are + * combined with AND. * @returns {[Promise, RequestCanceler]} An array containing a promise to get the API response and a function to cancel the request. */ public getGlobalSearchItems( search: string, workspaceId?: string, - offset?: number, + offsetOrOptions?: number | GlobalSearchOptions, ): [Promise, RequestCanceler] { - const query = new URLSearchParams(); - if (offset !== undefined) query.set('offset', String(offset)); + const options: GlobalSearchOptions = + typeof offsetOrOptions === 'number' ? { offset: offsetOrOptions } : (offsetOrOptions ?? {}); const { promise, requestCanceler } = workspaceId - ? this.client.getCancellable( - `workspaces/${workspaceId}/fuzzy/${search}?${query}`, + ? this.client.postCancellable( + `workspaces/${workspaceId}/fuzzy/${search}`, + options, this.headers(), ) - : this.client.getCancellable(`fuzzy/${search}?${query}`, this.headers()); + : this.client.postCancellable(`fuzzy/${search}`, options, this.headers()); return [promise, requestCanceler]; } diff --git a/src/drive/storage/types.ts b/src/drive/storage/types.ts index 7ba3c959..56a49166 100644 --- a/src/drive/storage/types.ts +++ b/src/drive/storage/types.ts @@ -363,6 +363,8 @@ export interface SearchResultData { data: [SearchResult]; } +export type GlobalSearchOptions = paths['/fuzzy/{search}']['post']['requestBody']['content']['application/json']; + export interface FolderAncestor { bucket: null | string; createdAt: string; diff --git a/src/schema.ts b/src/schema.ts index e74e3563..bf8d72b3 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -2305,10 +2305,10 @@ export interface paths { path?: never; cookie?: never; }; - /** Search by name inside workspace */ - get: operations['WorkspacesController_searchWorkspace']; + get?: never; put?: never; - post?: never; + /** Search by name inside workspace */ + post: operations['WorkspacesController_searchWorkspace']; delete?: never; options?: never; head?: never; @@ -2376,10 +2376,10 @@ export interface paths { path?: never; cookie?: never; }; - /** Search for items from a part of the name */ - get: operations['FuzzySearchController_fuzzySearch']; + get?: never; put?: never; - post?: never; + /** Search for items from a part of the name */ + post: operations['FuzzySearchController_fuzzySearch']; delete?: never; options?: never; head?: never; @@ -4798,6 +4798,42 @@ export interface components { */ phoneNumber: Record; }; + FuzzySearchQueryDto: { + /** + * @description Offset for pagination + * @example 0 + */ + offset?: number; + /** + * @description File extensions to filter by, or the reserved value "folder" to include folders (a single string is also accepted) + * @example [ + * "jpg", + * "pdf", + * "folder" + * ] + */ + type?: string[]; + /** + * @description Minimum file size in bytes (folders are excluded) + * @example 5242880 + */ + minSize?: number; + /** + * @description Maximum file size in bytes (folders are excluded) + * @example 1073741824 + */ + maxSize?: number; + /** + * @description Filter items modified after this date + * @example 2026-01-01T00:00:00.000Z + */ + modifiedAfter?: string; + /** + * @description Filter items modified before this date + * @example 2026-06-30T23:59:59.999Z + */ + modifiedBefore?: string; + }; FuzzySearchResult: { id: string; itemId: string; @@ -8946,9 +8982,7 @@ export interface operations { }; WorkspacesController_searchWorkspace: { parameters: { - query: { - offset: number; - }; + query?: never; header?: never; path: { search: string; @@ -8956,7 +8990,11 @@ export interface operations { }; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + 'application/json': components['schemas']['FuzzySearchQueryDto']; + }; + }; responses: { /** @description Search results */ 200: { @@ -9032,16 +9070,18 @@ export interface operations { }; FuzzySearchController_fuzzySearch: { parameters: { - query: { - offset: number; - }; + query?: never; header?: never; path: { search: string; }; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + 'application/json': components['schemas']['FuzzySearchQueryDto']; + }; + }; responses: { /** @description Elements found */ 200: { diff --git a/test/drive/storage/index.test.ts b/test/drive/storage/index.test.ts index 0e45138b..ff331e38 100644 --- a/test/drive/storage/index.test.ts +++ b/test/drive/storage/index.test.ts @@ -10,6 +10,7 @@ import { FolderAncestorWorkspace, MoveFolderPayload, MoveFolderResponse, + SearchResultData, UpdateFilePayload, } from '../../../src/drive/storage/types'; import { ApiSecurity, AppDetails } from '../../../src/shared'; @@ -1027,6 +1028,72 @@ describe('# storage service tests', () => { }); }); }); + + describe('-> global search', () => { + const emptySearchResponse: SearchResultData = { data: [] as unknown as SearchResultData['data'] }; + + it('Should post to the personal fuzzy endpoint with an empty body, when no options are passed', async () => { + const { client, headers } = clientAndHeaders({}); + const postCancellableStub = vi.spyOn(HttpClient.prototype, 'postCancellable').mockReturnValue({ + promise: Promise.resolve(emptySearchResponse), + requestCanceler: { cancel: () => null }, + }); + + const [promise] = client.getGlobalSearchItems('report'); + await promise; + + expect(postCancellableStub).toHaveBeenCalledWith('fuzzy/report', {}, headers); + }); + + it('Should post to the workspace fuzzy endpoint, when a workspaceId is passed', async () => { + const workspaceId = 'workspace-id'; + const { client, headers } = clientAndHeaders({}); + const postCancellableStub = vi.spyOn(HttpClient.prototype, 'postCancellable').mockReturnValue({ + promise: Promise.resolve(emptySearchResponse), + requestCanceler: { cancel: () => null }, + }); + + const [promise] = client.getGlobalSearchItems('report', workspaceId); + await promise; + + expect(postCancellableStub).toHaveBeenCalledWith(`workspaces/${workspaceId}/fuzzy/report`, {}, headers); + }); + + it('Should keep supporting a numeric offset as third argument', async () => { + const { client, headers } = clientAndHeaders({}); + const postCancellableStub = vi.spyOn(HttpClient.prototype, 'postCancellable').mockReturnValue({ + promise: Promise.resolve(emptySearchResponse), + requestCanceler: { cancel: () => null }, + }); + + const [promise] = client.getGlobalSearchItems('report', undefined, 10); + await promise; + + expect(postCancellableStub).toHaveBeenCalledWith('fuzzy/report', { offset: 10 }, headers); + }); + + it('Should send all search filters in the request body, when options are passed', async () => { + const { client, headers } = clientAndHeaders({}); + const postCancellableStub = vi.spyOn(HttpClient.prototype, 'postCancellable').mockReturnValue({ + promise: Promise.resolve(emptySearchResponse), + requestCanceler: { cancel: () => null }, + }); + + const filters = { + offset: 0, + type: ['pdf', 'image'], + minSize: 1024, + maxSize: 5242880, + modifiedAfter: '2026-01-01T00:00:00.000Z', + modifiedBefore: '2026-06-30T23:59:59.999Z', + }; + + const [promise] = client.getGlobalSearchItems('report', undefined, filters); + await promise; + + expect(postCancellableStub).toHaveBeenCalledWith('fuzzy/report', filters, headers); + }); + }); }); function clientAndHeaders({