UNPKG

@supabase/storage-js

Version:
1,302 lines (1,220 loc) 35.3 kB
import { isStorageError, StorageError, StorageUnknownError } from '../lib/errors' import { Fetch, get, head, post, put, remove } from '../lib/fetch' import { recursiveToCamel, resolveFetch } from '../lib/helpers' import { FileObject, FileOptions, SearchOptions, FetchParameters, TransformOptions, DestinationOptions, FileObjectV2, Camelize, SearchV2Options, SearchV2Result, } from '../lib/types' import BlobDownloadBuilder from './BlobDownloadBuilder' const DEFAULT_SEARCH_OPTIONS = { limit: 100, offset: 0, sortBy: { column: 'name', order: 'asc', }, } const DEFAULT_FILE_OPTIONS: FileOptions = { cacheControl: '3600', contentType: 'text/plain;charset=UTF-8', upsert: false, } type FileBody = | ArrayBuffer | ArrayBufferView | Blob | Buffer | File | FormData | NodeJS.ReadableStream | ReadableStream<Uint8Array> | URLSearchParams | string export default class StorageFileApi { protected url: string protected headers: { [key: string]: string } protected bucketId?: string protected fetch: Fetch protected shouldThrowOnError = false constructor( url: string, headers: { [key: string]: string } = {}, bucketId?: string, fetch?: Fetch ) { this.url = url this.headers = headers this.bucketId = bucketId this.fetch = resolveFetch(fetch) } /** * Enable throwing errors instead of returning them. * * @category File Buckets */ public throwOnError(): this { this.shouldThrowOnError = true return this } /** * Uploads a file to an existing bucket or replaces an existing file at the specified path with a new one. * * @param method HTTP method. * @param path The relative file path. Should be of the format `folder/subfolder/filename.png`. The bucket must already exist before attempting to upload. * @param fileBody The body of the file to be stored in the bucket. */ private async uploadOrUpdate( method: 'POST' | 'PUT', path: string, fileBody: FileBody, fileOptions?: FileOptions ): Promise< | { data: { id: string; path: string; fullPath: string } error: null } | { data: null error: StorageError } > { try { let body const options = { ...DEFAULT_FILE_OPTIONS, ...fileOptions } let headers: Record<string, string> = { ...this.headers, ...(method === 'POST' && { 'x-upsert': String(options.upsert as boolean) }), } const metadata = options.metadata if (typeof Blob !== 'undefined' && fileBody instanceof Blob) { body = new FormData() body.append('cacheControl', options.cacheControl as string) if (metadata) { body.append('metadata', this.encodeMetadata(metadata)) } body.append('', fileBody) } else if (typeof FormData !== 'undefined' && fileBody instanceof FormData) { body = fileBody // Only append if not already present if (!body.has('cacheControl')) { body.append('cacheControl', options.cacheControl as string) } if (metadata && !body.has('metadata')) { body.append('metadata', this.encodeMetadata(metadata)) } } else { body = fileBody headers['cache-control'] = `max-age=${options.cacheControl}` headers['content-type'] = options.contentType as string if (metadata) { headers['x-metadata'] = this.toBase64(this.encodeMetadata(metadata)) } // Node.js streams require duplex option for fetch in Node 20+ // Check for both web ReadableStream and Node.js streams const isStream = (typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) || (body && typeof body === 'object' && 'pipe' in body && typeof body.pipe === 'function') if (isStream && !options.duplex) { options.duplex = 'half' } } if (fileOptions?.headers) { headers = { ...headers, ...fileOptions.headers } } const cleanPath = this._removeEmptyFolders(path) const _path = this._getFinalPath(cleanPath) const data = await (method == 'PUT' ? put : post)( this.fetch, `${this.url}/object/${_path}`, body as object, { headers, ...(options?.duplex ? { duplex: options.duplex } : {}) } ) return { data: { path: cleanPath, id: data.Id, fullPath: data.Key }, error: null, } } catch (error) { if (this.shouldThrowOnError) { throw error } if (isStorageError(error)) { return { data: null, error } } throw error } } /** * Uploads a file to an existing bucket. * * @category File Buckets * @param path The file path, including the file name. Should be of the format `folder/subfolder/filename.png`. The bucket must already exist before attempting to upload. * @param fileBody The body of the file to be stored in the bucket. * @param fileOptions Optional file upload options including cacheControl, contentType, upsert, and metadata. * @returns Promise with response containing file path, id, and fullPath or error * * @example Upload file * ```js * const avatarFile = event.target.files[0] * const { data, error } = await supabase * .storage * .from('avatars') * .upload('public/avatar1.png', avatarFile, { * cacheControl: '3600', * upsert: false * }) * ``` * * Response: * ```json * { * "data": { * "path": "public/avatar1.png", * "fullPath": "avatars/public/avatar1.png" * }, * "error": null * } * ``` * * @example Upload file using `ArrayBuffer` from base64 file data * ```js * import { decode } from 'base64-arraybuffer' * * const { data, error } = await supabase * .storage * .from('avatars') * .upload('public/avatar1.png', decode('base64FileData'), { * contentType: 'image/png' * }) * ``` */ async upload( path: string, fileBody: FileBody, fileOptions?: FileOptions ): Promise< | { data: { id: string; path: string; fullPath: string } error: null } | { data: null error: StorageError } > { return this.uploadOrUpdate('POST', path, fileBody, fileOptions) } /** * Upload a file with a token generated from `createSignedUploadUrl`. * * @category File Buckets * @param path The file path, including the file name. Should be of the format `folder/subfolder/filename.png`. The bucket must already exist before attempting to upload. * @param token The token generated from `createSignedUploadUrl` * @param fileBody The body of the file to be stored in the bucket. * @param fileOptions HTTP headers (cacheControl, contentType, etc.). * **Note:** The `upsert` option has no effect here. To enable upsert behavior, * pass `{ upsert: true }` when calling `createSignedUploadUrl()` instead. * @returns Promise with response containing file path and fullPath or error * * @example Upload to a signed URL * ```js * const { data, error } = await supabase * .storage * .from('avatars') * .uploadToSignedUrl('folder/cat.jpg', 'token-from-createSignedUploadUrl', file) * ``` * * Response: * ```json * { * "data": { * "path": "folder/cat.jpg", * "fullPath": "avatars/folder/cat.jpg" * }, * "error": null * } * ``` */ async uploadToSignedUrl( path: string, token: string, fileBody: FileBody, fileOptions?: FileOptions ) { const cleanPath = this._removeEmptyFolders(path) const _path = this._getFinalPath(cleanPath) const url = new URL(this.url + `/object/upload/sign/${_path}`) url.searchParams.set('token', token) try { let body const options = { upsert: DEFAULT_FILE_OPTIONS.upsert, ...fileOptions } const headers: Record<string, string> = { ...this.headers, ...{ 'x-upsert': String(options.upsert as boolean) }, } if (typeof Blob !== 'undefined' && fileBody instanceof Blob) { body = new FormData() body.append('cacheControl', options.cacheControl as string) body.append('', fileBody) } else if (typeof FormData !== 'undefined' && fileBody instanceof FormData) { body = fileBody body.append('cacheControl', options.cacheControl as string) } else { body = fileBody headers['cache-control'] = `max-age=${options.cacheControl}` headers['content-type'] = options.contentType as string } const data = await put(this.fetch, url.toString(), body as object, { headers }) return { data: { path: cleanPath, fullPath: data.Key }, error: null, } } catch (error) { if (this.shouldThrowOnError) { throw error } if (isStorageError(error)) { return { data: null, error } } throw error } } /** * Creates a signed upload URL. * Signed upload URLs can be used to upload files to the bucket without further authentication. * They are valid for 2 hours. * * @category File Buckets * @param path The file path, including the current file name. For example `folder/image.png`. * @param options.upsert If set to true, allows the file to be overwritten if it already exists. * @returns Promise with response containing signed upload URL, token, and path or error * * @example Create Signed Upload URL * ```js * const { data, error } = await supabase * .storage * .from('avatars') * .createSignedUploadUrl('folder/cat.jpg') * ``` * * Response: * ```json * { * "data": { * "signedUrl": "https://example.supabase.co/storage/v1/object/upload/sign/avatars/folder/cat.jpg?token=<TOKEN>", * "path": "folder/cat.jpg", * "token": "<TOKEN>" * }, * "error": null * } * ``` */ async createSignedUploadUrl( path: string, options?: { upsert: boolean } ): Promise< | { data: { signedUrl: string; token: string; path: string } error: null } | { data: null error: StorageError } > { try { let _path = this._getFinalPath(path) const headers = { ...this.headers } if (options?.upsert) { headers['x-upsert'] = 'true' } const data = await post( this.fetch, `${this.url}/object/upload/sign/${_path}`, {}, { headers } ) const url = new URL(this.url + data.url) const token = url.searchParams.get('token') if (!token) { throw new StorageError('No token returned by API') } return { data: { signedUrl: url.toString(), path, token }, error: null } } catch (error) { if (this.shouldThrowOnError) { throw error } if (isStorageError(error)) { return { data: null, error } } throw error } } /** * Replaces an existing file at the specified path with a new one. * * @category File Buckets * @param path The relative file path. Should be of the format `folder/subfolder/filename.png`. The bucket must already exist before attempting to update. * @param fileBody The body of the file to be stored in the bucket. * @param fileOptions Optional file upload options including cacheControl, contentType, upsert, and metadata. * @returns Promise with response containing file path, id, and fullPath or error * * @example Update file * ```js * const avatarFile = event.target.files[0] * const { data, error } = await supabase * .storage * .from('avatars') * .update('public/avatar1.png', avatarFile, { * cacheControl: '3600', * upsert: true * }) * ``` * * Response: * ```json * { * "data": { * "path": "public/avatar1.png", * "fullPath": "avatars/public/avatar1.png" * }, * "error": null * } * ``` * * @example Update file using `ArrayBuffer` from base64 file data * ```js * import {decode} from 'base64-arraybuffer' * * const { data, error } = await supabase * .storage * .from('avatars') * .update('public/avatar1.png', decode('base64FileData'), { * contentType: 'image/png' * }) * ``` */ async update( path: string, fileBody: | ArrayBuffer | ArrayBufferView | Blob | Buffer | File | FormData | NodeJS.ReadableStream | ReadableStream<Uint8Array> | URLSearchParams | string, fileOptions?: FileOptions ): Promise< | { data: { id: string; path: string; fullPath: string } error: null } | { data: null error: StorageError } > { return this.uploadOrUpdate('PUT', path, fileBody, fileOptions) } /** * Moves an existing file to a new path in the same bucket. * * @category File Buckets * @param fromPath The original file path, including the current file name. For example `folder/image.png`. * @param toPath The new file path, including the new file name. For example `folder/image-new.png`. * @param options The destination options. * @returns Promise with response containing success message or error * * @example Move file * ```js * const { data, error } = await supabase * .storage * .from('avatars') * .move('public/avatar1.png', 'private/avatar2.png') * ``` * * Response: * ```json * { * "data": { * "message": "Successfully moved" * }, * "error": null * } * ``` */ async move( fromPath: string, toPath: string, options?: DestinationOptions ): Promise< | { data: { message: string } error: null } | { data: null error: StorageError } > { try { const data = await post( this.fetch, `${this.url}/object/move`, { bucketId: this.bucketId, sourceKey: fromPath, destinationKey: toPath, destinationBucket: options?.destinationBucket, }, { headers: this.headers } ) return { data, error: null } } catch (error) { if (this.shouldThrowOnError) { throw error } if (isStorageError(error)) { return { data: null, error } } throw error } } /** * Copies an existing file to a new path in the same bucket. * * @category File Buckets * @param fromPath The original file path, including the current file name. For example `folder/image.png`. * @param toPath The new file path, including the new file name. For example `folder/image-copy.png`. * @param options The destination options. * @returns Promise with response containing copied file path or error * * @example Copy file * ```js * const { data, error } = await supabase * .storage * .from('avatars') * .copy('public/avatar1.png', 'private/avatar2.png') * ``` * * Response: * ```json * { * "data": { * "path": "avatars/private/avatar2.png" * }, * "error": null * } * ``` */ async copy( fromPath: string, toPath: string, options?: DestinationOptions ): Promise< | { data: { path: string } error: null } | { data: null error: StorageError } > { try { const data = await post( this.fetch, `${this.url}/object/copy`, { bucketId: this.bucketId, sourceKey: fromPath, destinationKey: toPath, destinationBucket: options?.destinationBucket, }, { headers: this.headers } ) return { data: { path: data.Key }, error: null } } catch (error) { if (this.shouldThrowOnError) { throw error } if (isStorageError(error)) { return { data: null, error } } throw error } } /** * Creates a signed URL. Use a signed URL to share a file for a fixed amount of time. * * @category File Buckets * @param path The file path, including the current file name. For example `folder/image.png`. * @param expiresIn The number of seconds until the signed URL expires. For example, `60` for a URL which is valid for one minute. * @param options.download triggers the file as a download if set to true. Set this parameter as the name of the file if you want to trigger the download with a different filename. * @param options.transform Transform the asset before serving it to the client. * @returns Promise with response containing signed URL or error * * @example Create Signed URL * ```js * const { data, error } = await supabase * .storage * .from('avatars') * .createSignedUrl('folder/avatar1.png', 60) * ``` * * Response: * ```json * { * "data": { * "signedUrl": "https://example.supabase.co/storage/v1/object/sign/avatars/folder/avatar1.png?token=<TOKEN>" * }, * "error": null * } * ``` * * @example Create a signed URL for an asset with transformations * ```js * const { data } = await supabase * .storage * .from('avatars') * .createSignedUrl('folder/avatar1.png', 60, { * transform: { * width: 100, * height: 100, * } * }) * ``` * * @example Create a signed URL which triggers the download of the asset * ```js * const { data } = await supabase * .storage * .from('avatars') * .createSignedUrl('folder/avatar1.png', 60, { * download: true, * }) * ``` */ async createSignedUrl( path: string, expiresIn: number, options?: { download?: string | boolean; transform?: TransformOptions } ): Promise< | { data: { signedUrl: string } error: null } | { data: null error: StorageError } > { try { let _path = this._getFinalPath(path) let data = await post( this.fetch, `${this.url}/object/sign/${_path}`, { expiresIn, ...(options?.transform ? { transform: options.transform } : {}) }, { headers: this.headers } ) const downloadQueryParam = options?.download ? `&download=${options.download === true ? '' : options.download}` : '' const signedUrl = encodeURI(`${this.url}${data.signedURL}${downloadQueryParam}`) data = { signedUrl } return { data, error: null } } catch (error) { if (this.shouldThrowOnError) { throw error } if (isStorageError(error)) { return { data: null, error } } throw error } } /** * Creates multiple signed URLs. Use a signed URL to share a file for a fixed amount of time. * * @category File Buckets * @param paths The file paths to be downloaded, including the current file names. For example `['folder/image.png', 'folder2/image2.png']`. * @param expiresIn The number of seconds until the signed URLs expire. For example, `60` for URLs which are valid for one minute. * @param options.download triggers the file as a download if set to true. Set this parameter as the name of the file if you want to trigger the download with a different filename. * @returns Promise with response containing array of objects with signedUrl, path, and error or error * * @example Create Signed URLs * ```js * const { data, error } = await supabase * .storage * .from('avatars') * .createSignedUrls(['folder/avatar1.png', 'folder/avatar2.png'], 60) * ``` * * Response: * ```json * { * "data": [ * { * "error": null, * "path": "folder/avatar1.png", * "signedURL": "/object/sign/avatars/folder/avatar1.png?token=<TOKEN>", * "signedUrl": "https://example.supabase.co/storage/v1/object/sign/avatars/folder/avatar1.png?token=<TOKEN>" * }, * { * "error": null, * "path": "folder/avatar2.png", * "signedURL": "/object/sign/avatars/folder/avatar2.png?token=<TOKEN>", * "signedUrl": "https://example.supabase.co/storage/v1/object/sign/avatars/folder/avatar2.png?token=<TOKEN>" * } * ], * "error": null * } * ``` */ async createSignedUrls( paths: string[], expiresIn: number, options?: { download: string | boolean } ): Promise< | { data: { error: string | null; path: string | null; signedUrl: string }[] error: null } | { data: null error: StorageError } > { try { const data = await post( this.fetch, `${this.url}/object/sign/${this.bucketId}`, { expiresIn, paths }, { headers: this.headers } ) const downloadQueryParam = options?.download ? `&download=${options.download === true ? '' : options.download}` : '' return { data: data.map((datum: { signedURL: string }) => ({ ...datum, signedUrl: datum.signedURL ? encodeURI(`${this.url}${datum.signedURL}${downloadQueryParam}`) : null, })), error: null, } } catch (error) { if (this.shouldThrowOnError) { throw error } if (isStorageError(error)) { return { data: null, error } } throw error } } /** * Downloads a file from a private bucket. For public buckets, make a request to the URL returned from `getPublicUrl` instead. * * @category File Buckets * @param path The full path and file name of the file to be downloaded. For example `folder/image.png`. * @param options.transform Transform the asset before serving it to the client. * @returns BlobDownloadBuilder instance for downloading the file * * @example Download file * ```js * const { data, error } = await supabase * .storage * .from('avatars') * .download('folder/avatar1.png') * ``` * * Response: * ```json * { * "data": <BLOB>, * "error": null * } * ``` * * @example Download file with transformations * ```js * const { data, error } = await supabase * .storage * .from('avatars') * .download('folder/avatar1.png', { * transform: { * width: 100, * height: 100, * quality: 80 * } * }) * ``` */ download<Options extends { transform?: TransformOptions }>( path: string, options?: Options ): BlobDownloadBuilder { const wantsTransformation = typeof options?.transform !== 'undefined' const renderPath = wantsTransformation ? 'render/image/authenticated' : 'object' const transformationQuery = this.transformOptsToQueryString(options?.transform || {}) const queryString = transformationQuery ? `?${transformationQuery}` : '' const _path = this._getFinalPath(path) const downloadFn = () => get(this.fetch, `${this.url}/${renderPath}/${_path}${queryString}`, { headers: this.headers, noResolveJson: true, }) return new BlobDownloadBuilder(downloadFn, this.shouldThrowOnError) } /** * Retrieves the details of an existing file. * * @category File Buckets * @param path The file path, including the file name. For example `folder/image.png`. * @returns Promise with response containing file metadata or error * * @example Get file info * ```js * const { data, error } = await supabase * .storage * .from('avatars') * .info('folder/avatar1.png') * ``` */ async info(path: string): Promise< | { data: Camelize<FileObjectV2> error: null } | { data: null error: StorageError } > { const _path = this._getFinalPath(path) try { const data = await get(this.fetch, `${this.url}/object/info/${_path}`, { headers: this.headers, }) return { data: recursiveToCamel(data) as Camelize<FileObjectV2>, error: null } } catch (error) { if (this.shouldThrowOnError) { throw error } if (isStorageError(error)) { return { data: null, error } } throw error } } /** * Checks the existence of a file. * * @category File Buckets * @param path The file path, including the file name. For example `folder/image.png`. * @returns Promise with response containing boolean indicating file existence or error * * @example Check file existence * ```js * const { data, error } = await supabase * .storage * .from('avatars') * .exists('folder/avatar1.png') * ``` */ async exists(path: string): Promise< | { data: boolean error: null } | { data: boolean error: StorageError } > { const _path = this._getFinalPath(path) try { await head(this.fetch, `${this.url}/object/${_path}`, { headers: this.headers, }) return { data: true, error: null } } catch (error) { if (this.shouldThrowOnError) { throw error } if (isStorageError(error) && error instanceof StorageUnknownError) { const originalError = error.originalError as unknown as { status: number } if ([400, 404].includes(originalError?.status)) { return { data: false, error } } } throw error } } /** * A simple convenience function to get the URL for an asset in a public bucket. If you do not want to use this function, you can construct the public URL by concatenating the bucket URL with the path to the asset. * This function does not verify if the bucket is public. If a public URL is created for a bucket which is not public, you will not be able to download the asset. * * @category File Buckets * @param path The path and name of the file to generate the public URL for. For example `folder/image.png`. * @param options.download Triggers the file as a download if set to true. Set this parameter as the name of the file if you want to trigger the download with a different filename. * @param options.transform Transform the asset before serving it to the client. * @returns Object with public URL * * @example Returns the URL for an asset in a public bucket * ```js * const { data } = supabase * .storage * .from('public-bucket') * .getPublicUrl('folder/avatar1.png') * ``` * * Response: * ```json * { * "data": { * "publicUrl": "https://example.supabase.co/storage/v1/object/public/public-bucket/folder/avatar1.png" * } * } * ``` * * @example Returns the URL for an asset in a public bucket with transformations * ```js * const { data } = supabase * .storage * .from('public-bucket') * .getPublicUrl('folder/avatar1.png', { * transform: { * width: 100, * height: 100, * } * }) * ``` * * @example Returns the URL which triggers the download of an asset in a public bucket * ```js * const { data } = supabase * .storage * .from('public-bucket') * .getPublicUrl('folder/avatar1.png', { * download: true, * }) * ``` */ getPublicUrl( path: string, options?: { download?: string | boolean; transform?: TransformOptions } ): { data: { publicUrl: string } } { const _path = this._getFinalPath(path) const _queryString: string[] = [] const downloadQueryParam = options?.download ? `download=${options.download === true ? '' : options.download}` : '' if (downloadQueryParam !== '') { _queryString.push(downloadQueryParam) } const wantsTransformation = typeof options?.transform !== 'undefined' const renderPath = wantsTransformation ? 'render/image' : 'object' const transformationQuery = this.transformOptsToQueryString(options?.transform || {}) if (transformationQuery !== '') { _queryString.push(transformationQuery) } let queryString = _queryString.join('&') if (queryString !== '') { queryString = `?${queryString}` } return { data: { publicUrl: encodeURI(`${this.url}/${renderPath}/public/${_path}${queryString}`) }, } } /** * Deletes files within the same bucket * * @category File Buckets * @param paths An array of files to delete, including the path and file name. For example [`'folder/image.png'`]. * @returns Promise with response containing array of deleted file objects or error * * @example Delete file * ```js * const { data, error } = await supabase * .storage * .from('avatars') * .remove(['folder/avatar1.png']) * ``` * * Response: * ```json * { * "data": [], * "error": null * } * ``` */ async remove(paths: string[]): Promise< | { data: FileObject[] error: null } | { data: null error: StorageError } > { try { const data = await remove( this.fetch, `${this.url}/object/${this.bucketId}`, { prefixes: paths }, { headers: this.headers } ) return { data, error: null } } catch (error) { if (this.shouldThrowOnError) { throw error } if (isStorageError(error)) { return { data: null, error } } throw error } } /** * Get file metadata * @param id the file id to retrieve metadata */ // async getMetadata( // id: string // ): Promise< // | { // data: Metadata // error: null // } // | { // data: null // error: StorageError // } // > { // try { // const data = await get(this.fetch, `${this.url}/metadata/${id}`, { headers: this.headers }) // return { data, error: null } // } catch (error) { // if (isStorageError(error)) { // return { data: null, error } // } // throw error // } // } /** * Update file metadata * @param id the file id to update metadata * @param meta the new file metadata */ // async updateMetadata( // id: string, // meta: Metadata // ): Promise< // | { // data: Metadata // error: null // } // | { // data: null // error: StorageError // } // > { // try { // const data = await post( // this.fetch, // `${this.url}/metadata/${id}`, // { ...meta }, // { headers: this.headers } // ) // return { data, error: null } // } catch (error) { // if (isStorageError(error)) { // return { data: null, error } // } // throw error // } // } /** * Lists all the files and folders within a path of the bucket. * * @category File Buckets * @param path The folder path. * @param options Search options including limit (defaults to 100), offset, sortBy, and search * @param parameters Optional fetch parameters including signal for cancellation * @returns Promise with response containing array of files or error * * @example List files in a bucket * ```js * const { data, error } = await supabase * .storage * .from('avatars') * .list('folder', { * limit: 100, * offset: 0, * sortBy: { column: 'name', order: 'asc' }, * }) * ``` * * Response: * ```json * { * "data": [ * { * "name": "avatar1.png", * "id": "e668cf7f-821b-4a2f-9dce-7dfa5dd1cfd2", * "updated_at": "2024-05-22T23:06:05.580Z", * "created_at": "2024-05-22T23:04:34.443Z", * "last_accessed_at": "2024-05-22T23:04:34.443Z", * "metadata": { * "eTag": "\"c5e8c553235d9af30ef4f6e280790b92\"", * "size": 32175, * "mimetype": "image/png", * "cacheControl": "max-age=3600", * "lastModified": "2024-05-22T23:06:05.574Z", * "contentLength": 32175, * "httpStatusCode": 200 * } * } * ], * "error": null * } * ``` * * @example Search files in a bucket * ```js * const { data, error } = await supabase * .storage * .from('avatars') * .list('folder', { * limit: 100, * offset: 0, * sortBy: { column: 'name', order: 'asc' }, * search: 'jon' * }) * ``` */ async list( path?: string, options?: SearchOptions, parameters?: FetchParameters ): Promise< | { data: FileObject[] error: null } | { data: null error: StorageError } > { try { const body = { ...DEFAULT_SEARCH_OPTIONS, ...options, prefix: path || '' } const data = await post( this.fetch, `${this.url}/object/list/${this.bucketId}`, body, { headers: this.headers }, parameters ) return { data, error: null } } catch (error) { if (this.shouldThrowOnError) { throw error } if (isStorageError(error)) { return { data: null, error } } throw error } } /** * @experimental this method signature might change in the future * * @category File Buckets * @param options search options * @param parameters */ async listV2( options?: SearchV2Options, parameters?: FetchParameters ): Promise< | { data: SearchV2Result error: null } | { data: null error: StorageError } > { try { const body = { ...options } const data = await post( this.fetch, `${this.url}/object/list-v2/${this.bucketId}`, body, { headers: this.headers }, parameters ) return { data, error: null } } catch (error) { if (this.shouldThrowOnError) { throw error } if (isStorageError(error)) { return { data: null, error } } throw error } } protected encodeMetadata(metadata: Record<string, any>) { return JSON.stringify(metadata) } toBase64(data: string) { if (typeof Buffer !== 'undefined') { return Buffer.from(data).toString('base64') } return btoa(data) } private _getFinalPath(path: string) { return `${this.bucketId}/${path.replace(/^\/+/, '')}` } private _removeEmptyFolders(path: string) { return path.replace(/^\/|\/$/g, '').replace(/\/+/g, '/') } private transformOptsToQueryString(transform: TransformOptions) { const params: string[] = [] if (transform.width) { params.push(`width=${transform.width}`) } if (transform.height) { params.push(`height=${transform.height}`) } if (transform.resize) { params.push(`resize=${transform.resize}`) } if (transform.format) { params.push(`format=${transform.format}`) } if (transform.quality) { params.push(`quality=${transform.quality}`) } return params.join('&') } }