@supabase/storage-js
Version:
Isomorphic storage client for Supabase.
1,603 lines (1,588 loc) • 94.7 kB
JavaScript
let iceberg_js = require("iceberg-js");
//#region src/lib/errors.ts
var StorageError = class extends Error {
constructor(message) {
super(message);
this.__isStorageError = true;
this.name = "StorageError";
}
};
function isStorageError(error) {
return typeof error === "object" && error !== null && "__isStorageError" in error;
}
var StorageApiError = class extends StorageError {
constructor(message, status, statusCode) {
super(message);
this.name = "StorageApiError";
this.status = status;
this.statusCode = statusCode;
}
toJSON() {
return {
name: this.name,
message: this.message,
status: this.status,
statusCode: this.statusCode
};
}
};
var StorageUnknownError = class extends StorageError {
constructor(message, originalError) {
super(message);
this.name = "StorageUnknownError";
this.originalError = originalError;
}
};
//#endregion
//#region src/lib/helpers.ts
const resolveFetch$1 = (customFetch) => {
if (customFetch) return (...args) => customFetch(...args);
return (...args) => fetch(...args);
};
const resolveResponse$1 = () => {
return Response;
};
const recursiveToCamel = (item) => {
if (Array.isArray(item)) return item.map((el) => recursiveToCamel(el));
else if (typeof item === "function" || item !== Object(item)) return item;
const result = {};
Object.entries(item).forEach(([key, value]) => {
const newKey = key.replace(/([-_][a-z])/gi, (c) => c.toUpperCase().replace(/[-_]/g, ""));
result[newKey] = recursiveToCamel(value);
});
return result;
};
/**
* Determine if input is a plain object
* An object is plain if it's created by either {}, new Object(), or Object.create(null)
* source: https://github.com/sindresorhus/is-plain-obj
*/
const isPlainObject$1 = (value) => {
if (typeof value !== "object" || value === null) return false;
const prototype = Object.getPrototypeOf(value);
return (prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null) && !(Symbol.toStringTag in value) && !(Symbol.iterator in value);
};
/**
* Validates if a given bucket name is valid according to Supabase Storage API rules
* Mirrors backend validation from: storage/src/storage/limits.ts:isValidBucketName()
*
* Rules:
* - Length: 1-100 characters
* - Allowed characters: alphanumeric (a-z, A-Z, 0-9), underscore (_), and safe special characters
* - Safe special characters: ! - . * ' ( ) space & $ @ = ; : + , ?
* - Forbidden: path separators (/, \), path traversal (..), leading/trailing whitespace
*
* AWS S3 Reference: https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-keys.html
*
* @param bucketName - The bucket name to validate
* @returns true if valid, false otherwise
*/
const isValidBucketName = (bucketName) => {
if (!bucketName || typeof bucketName !== "string") return false;
if (bucketName.length === 0 || bucketName.length > 100) return false;
if (bucketName.trim() !== bucketName) return false;
if (bucketName.includes("/") || bucketName.includes("\\")) return false;
return /^[\w!.\*'() &$@=;:+,?-]+$/.test(bucketName);
};
//#endregion
//#region \0@oxc-project+runtime@0.101.0/helpers/typeof.js
function _typeof(o) {
"@babel/helpers - typeof";
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o$1) {
return typeof o$1;
} : function(o$1) {
return o$1 && "function" == typeof Symbol && o$1.constructor === Symbol && o$1 !== Symbol.prototype ? "symbol" : typeof o$1;
}, _typeof(o);
}
//#endregion
//#region \0@oxc-project+runtime@0.101.0/helpers/toPrimitive.js
function toPrimitive(t, r) {
if ("object" != _typeof(t) || !t) return t;
var e = t[Symbol.toPrimitive];
if (void 0 !== e) {
var i = e.call(t, r || "default");
if ("object" != _typeof(i)) return i;
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return ("string" === r ? String : Number)(t);
}
//#endregion
//#region \0@oxc-project+runtime@0.101.0/helpers/toPropertyKey.js
function toPropertyKey(t) {
var i = toPrimitive(t, "string");
return "symbol" == _typeof(i) ? i : i + "";
}
//#endregion
//#region \0@oxc-project+runtime@0.101.0/helpers/defineProperty.js
function _defineProperty(e, r, t) {
return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
value: t,
enumerable: !0,
configurable: !0,
writable: !0
}) : e[r] = t, e;
}
//#endregion
//#region \0@oxc-project+runtime@0.101.0/helpers/objectSpread2.js
function ownKeys(e, r) {
var t = Object.keys(e);
if (Object.getOwnPropertySymbols) {
var o = Object.getOwnPropertySymbols(e);
r && (o = o.filter(function(r$1) {
return Object.getOwnPropertyDescriptor(e, r$1).enumerable;
})), t.push.apply(t, o);
}
return t;
}
function _objectSpread2(e) {
for (var r = 1; r < arguments.length; r++) {
var t = null != arguments[r] ? arguments[r] : {};
r % 2 ? ownKeys(Object(t), !0).forEach(function(r$1) {
_defineProperty(e, r$1, t[r$1]);
}) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function(r$1) {
Object.defineProperty(e, r$1, Object.getOwnPropertyDescriptor(t, r$1));
});
}
return e;
}
//#endregion
//#region src/lib/fetch.ts
const _getErrorMessage$1 = (err) => {
var _err$error;
return err.msg || err.message || err.error_description || (typeof err.error === "string" ? err.error : (_err$error = err.error) === null || _err$error === void 0 ? void 0 : _err$error.message) || JSON.stringify(err);
};
const handleError$1 = async (error, reject, options) => {
if (error instanceof await resolveResponse$1() && !(options === null || options === void 0 ? void 0 : options.noResolveJson)) error.json().then((err) => {
const status = error.status || 500;
const statusCode = (err === null || err === void 0 ? void 0 : err.statusCode) || status + "";
reject(new StorageApiError(_getErrorMessage$1(err), status, statusCode));
}).catch((err) => {
reject(new StorageUnknownError(_getErrorMessage$1(err), err));
});
else reject(new StorageUnknownError(_getErrorMessage$1(error), error));
};
const _getRequestParams$1 = (method, options, parameters, body) => {
const params = {
method,
headers: (options === null || options === void 0 ? void 0 : options.headers) || {}
};
if (method === "GET" || !body) return params;
if (isPlainObject$1(body)) {
params.headers = _objectSpread2({ "Content-Type": "application/json" }, options === null || options === void 0 ? void 0 : options.headers);
params.body = JSON.stringify(body);
} else params.body = body;
if (options === null || options === void 0 ? void 0 : options.duplex) params.duplex = options.duplex;
return _objectSpread2(_objectSpread2({}, params), parameters);
};
async function _handleRequest$1(fetcher, method, url, options, parameters, body) {
return new Promise((resolve, reject) => {
fetcher(url, _getRequestParams$1(method, options, parameters, body)).then((result) => {
if (!result.ok) throw result;
if (options === null || options === void 0 ? void 0 : options.noResolveJson) return result;
return result.json();
}).then((data) => resolve(data)).catch((error) => handleError$1(error, reject, options));
});
}
async function get(fetcher, url, options, parameters) {
return _handleRequest$1(fetcher, "GET", url, options, parameters);
}
async function post$1(fetcher, url, body, options, parameters) {
return _handleRequest$1(fetcher, "POST", url, options, parameters, body);
}
async function put(fetcher, url, body, options, parameters) {
return _handleRequest$1(fetcher, "PUT", url, options, parameters, body);
}
async function head(fetcher, url, options, parameters) {
return _handleRequest$1(fetcher, "HEAD", url, _objectSpread2(_objectSpread2({}, options), {}, { noResolveJson: true }), parameters);
}
async function remove(fetcher, url, body, options, parameters) {
return _handleRequest$1(fetcher, "DELETE", url, options, parameters, body);
}
//#endregion
//#region src/packages/StreamDownloadBuilder.ts
var StreamDownloadBuilder = class {
constructor(downloadFn, shouldThrowOnError) {
this.downloadFn = downloadFn;
this.shouldThrowOnError = shouldThrowOnError;
}
then(onfulfilled, onrejected) {
return this.execute().then(onfulfilled, onrejected);
}
async execute() {
var _this = this;
try {
return {
data: (await _this.downloadFn()).body,
error: null
};
} catch (error) {
if (_this.shouldThrowOnError) throw error;
if (isStorageError(error)) return {
data: null,
error
};
throw error;
}
}
};
//#endregion
//#region src/packages/BlobDownloadBuilder.ts
let _Symbol$toStringTag;
_Symbol$toStringTag = Symbol.toStringTag;
var BlobDownloadBuilder = class {
constructor(downloadFn, shouldThrowOnError) {
this.downloadFn = downloadFn;
this.shouldThrowOnError = shouldThrowOnError;
this[_Symbol$toStringTag] = "BlobDownloadBuilder";
this.promise = null;
}
asStream() {
return new StreamDownloadBuilder(this.downloadFn, this.shouldThrowOnError);
}
then(onfulfilled, onrejected) {
return this.getPromise().then(onfulfilled, onrejected);
}
catch(onrejected) {
return this.getPromise().catch(onrejected);
}
finally(onfinally) {
return this.getPromise().finally(onfinally);
}
getPromise() {
if (!this.promise) this.promise = this.execute();
return this.promise;
}
async execute() {
var _this = this;
try {
return {
data: await (await _this.downloadFn()).blob(),
error: null
};
} catch (error) {
if (_this.shouldThrowOnError) throw error;
if (isStorageError(error)) return {
data: null,
error
};
throw error;
}
}
};
//#endregion
//#region src/packages/StorageFileApi.ts
const DEFAULT_SEARCH_OPTIONS = {
limit: 100,
offset: 0,
sortBy: {
column: "name",
order: "asc"
}
};
const DEFAULT_FILE_OPTIONS = {
cacheControl: "3600",
contentType: "text/plain;charset=UTF-8",
upsert: false
};
var StorageFileApi = class {
constructor(url, headers = {}, bucketId, fetch$1) {
this.shouldThrowOnError = false;
this.url = url;
this.headers = headers;
this.bucketId = bucketId;
this.fetch = resolveFetch$1(fetch$1);
}
/**
* Enable throwing errors instead of returning them.
*
* @category File Buckets
*/
throwOnError() {
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.
*/
async uploadOrUpdate(method, path, fileBody, fileOptions) {
var _this = this;
try {
let body;
const options = _objectSpread2(_objectSpread2({}, DEFAULT_FILE_OPTIONS), fileOptions);
let headers = _objectSpread2(_objectSpread2({}, _this.headers), method === "POST" && { "x-upsert": String(options.upsert) });
const metadata = options.metadata;
if (typeof Blob !== "undefined" && fileBody instanceof Blob) {
body = new FormData();
body.append("cacheControl", options.cacheControl);
if (metadata) body.append("metadata", _this.encodeMetadata(metadata));
body.append("", fileBody);
} else if (typeof FormData !== "undefined" && fileBody instanceof FormData) {
body = fileBody;
if (!body.has("cacheControl")) body.append("cacheControl", options.cacheControl);
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;
if (metadata) headers["x-metadata"] = _this.toBase64(_this.encodeMetadata(metadata));
if ((typeof ReadableStream !== "undefined" && body instanceof ReadableStream || body && typeof body === "object" && "pipe" in body && typeof body.pipe === "function") && !options.duplex) options.duplex = "half";
}
if (fileOptions === null || fileOptions === void 0 ? void 0 : fileOptions.headers) headers = _objectSpread2(_objectSpread2({}, headers), fileOptions.headers);
const cleanPath = _this._removeEmptyFolders(path);
const _path = _this._getFinalPath(cleanPath);
const data = await (method == "PUT" ? put : post$1)(_this.fetch, `${_this.url}/object/${_path}`, body, _objectSpread2({ headers }, (options === null || options === void 0 ? void 0 : 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, fileBody, fileOptions) {
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, token, fileBody, fileOptions) {
var _this3 = this;
const cleanPath = _this3._removeEmptyFolders(path);
const _path = _this3._getFinalPath(cleanPath);
const url = new URL(_this3.url + `/object/upload/sign/${_path}`);
url.searchParams.set("token", token);
try {
let body;
const options = _objectSpread2({ upsert: DEFAULT_FILE_OPTIONS.upsert }, fileOptions);
const headers = _objectSpread2(_objectSpread2({}, _this3.headers), { "x-upsert": String(options.upsert) });
if (typeof Blob !== "undefined" && fileBody instanceof Blob) {
body = new FormData();
body.append("cacheControl", options.cacheControl);
body.append("", fileBody);
} else if (typeof FormData !== "undefined" && fileBody instanceof FormData) {
body = fileBody;
body.append("cacheControl", options.cacheControl);
} else {
body = fileBody;
headers["cache-control"] = `max-age=${options.cacheControl}`;
headers["content-type"] = options.contentType;
}
return {
data: {
path: cleanPath,
fullPath: (await put(_this3.fetch, url.toString(), body, { headers })).Key
},
error: null
};
} catch (error) {
if (_this3.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, options) {
var _this4 = this;
try {
let _path = _this4._getFinalPath(path);
const headers = _objectSpread2({}, _this4.headers);
if (options === null || options === void 0 ? void 0 : options.upsert) headers["x-upsert"] = "true";
const data = await post$1(_this4.fetch, `${_this4.url}/object/upload/sign/${_path}`, {}, { headers });
const url = new URL(_this4.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 (_this4.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, fileBody, fileOptions) {
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, toPath, options) {
var _this6 = this;
try {
return {
data: await post$1(_this6.fetch, `${_this6.url}/object/move`, {
bucketId: _this6.bucketId,
sourceKey: fromPath,
destinationKey: toPath,
destinationBucket: options === null || options === void 0 ? void 0 : options.destinationBucket
}, { headers: _this6.headers }),
error: null
};
} catch (error) {
if (_this6.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, toPath, options) {
var _this7 = this;
try {
return {
data: { path: (await post$1(_this7.fetch, `${_this7.url}/object/copy`, {
bucketId: _this7.bucketId,
sourceKey: fromPath,
destinationKey: toPath,
destinationBucket: options === null || options === void 0 ? void 0 : options.destinationBucket
}, { headers: _this7.headers })).Key },
error: null
};
} catch (error) {
if (_this7.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, expiresIn, options) {
var _this8 = this;
try {
let _path = _this8._getFinalPath(path);
let data = await post$1(_this8.fetch, `${_this8.url}/object/sign/${_path}`, _objectSpread2({ expiresIn }, (options === null || options === void 0 ? void 0 : options.transform) ? { transform: options.transform } : {}), { headers: _this8.headers });
const downloadQueryParam = (options === null || options === void 0 ? void 0 : options.download) ? `&download=${options.download === true ? "" : options.download}` : "";
data = { signedUrl: encodeURI(`${_this8.url}${data.signedURL}${downloadQueryParam}`) };
return {
data,
error: null
};
} catch (error) {
if (_this8.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, expiresIn, options) {
var _this9 = this;
try {
const data = await post$1(_this9.fetch, `${_this9.url}/object/sign/${_this9.bucketId}`, {
expiresIn,
paths
}, { headers: _this9.headers });
const downloadQueryParam = (options === null || options === void 0 ? void 0 : options.download) ? `&download=${options.download === true ? "" : options.download}` : "";
return {
data: data.map((datum) => _objectSpread2(_objectSpread2({}, datum), {}, { signedUrl: datum.signedURL ? encodeURI(`${_this9.url}${datum.signedURL}${downloadQueryParam}`) : null })),
error: null
};
} catch (error) {
if (_this9.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(path, options) {
const renderPath = typeof (options === null || options === void 0 ? void 0 : options.transform) !== "undefined" ? "render/image/authenticated" : "object";
const transformationQuery = this.transformOptsToQueryString((options === null || options === void 0 ? void 0 : 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) {
var _this10 = this;
const _path = _this10._getFinalPath(path);
try {
return {
data: recursiveToCamel(await get(_this10.fetch, `${_this10.url}/object/info/${_path}`, { headers: _this10.headers })),
error: null
};
} catch (error) {
if (_this10.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) {
var _this11 = this;
const _path = _this11._getFinalPath(path);
try {
await head(_this11.fetch, `${_this11.url}/object/${_path}`, { headers: _this11.headers });
return {
data: true,
error: null
};
} catch (error) {
if (_this11.shouldThrowOnError) throw error;
if (isStorageError(error) && error instanceof StorageUnknownError) {
const originalError = error.originalError;
if ([400, 404].includes(originalError === null || originalError === void 0 ? void 0 : 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, options) {
const _path = this._getFinalPath(path);
const _queryString = [];
const downloadQueryParam = (options === null || options === void 0 ? void 0 : options.download) ? `download=${options.download === true ? "" : options.download}` : "";
if (downloadQueryParam !== "") _queryString.push(downloadQueryParam);
const renderPath = typeof (options === null || options === void 0 ? void 0 : options.transform) !== "undefined" ? "render/image" : "object";
const transformationQuery = this.transformOptsToQueryString((options === null || options === void 0 ? void 0 : 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) {
var _this12 = this;
try {
return {
data: await remove(_this12.fetch, `${_this12.url}/object/${_this12.bucketId}`, { prefixes: paths }, { headers: _this12.headers }),
error: null
};
} catch (error) {
if (_this12.shouldThrowOnError) throw error;
if (isStorageError(error)) return {
data: null,
error
};
throw error;
}
}
/**
* Get file metadata
* @param id the file id to retrieve metadata
*/
/**
* Update file metadata
* @param id the file id to update metadata
* @param meta the new file metadata
*/
/**
* 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, options, parameters) {
var _this13 = this;
try {
const body = _objectSpread2(_objectSpread2(_objectSpread2({}, DEFAULT_SEARCH_OPTIONS), options), {}, { prefix: path || "" });
return {
data: await post$1(_this13.fetch, `${_this13.url}/object/list/${_this13.bucketId}`, body, { headers: _this13.headers }, parameters),
error: null
};
} catch (error) {
if (_this13.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, parameters) {
var _this14 = this;
try {
const body = _objectSpread2({}, options);
return {
data: await post$1(_this14.fetch, `${_this14.url}/object/list-v2/${_this14.bucketId}`, body, { headers: _this14.headers }, parameters),
error: null
};
} catch (error) {
if (_this14.shouldThrowOnError) throw error;
if (isStorageError(error)) return {
data: null,
error
};
throw error;
}
}
encodeMetadata(metadata) {
return JSON.stringify(metadata);
}
toBase64(data) {
if (typeof Buffer !== "undefined") return Buffer.from(data).toString("base64");
return btoa(data);
}
_getFinalPath(path) {
return `${this.bucketId}/${path.replace(/^\/+/, "")}`;
}
_removeEmptyFolders(path) {
return path.replace(/^\/|\/$/g, "").replace(/\/+/g, "/");
}
transformOptsToQueryString(transform) {
const params = [];
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("&");
}
};
//#endregion
//#region src/lib/version.ts
const version = "2.89.0";
//#endregion
//#region src/lib/constants.ts
const DEFAULT_HEADERS$1 = { "X-Client-Info": `storage-js/${version}` };
//#endregion
//#region src/packages/StorageBucketApi.ts
var StorageBucketApi = class {
constructor(url, headers = {}, fetch$1, opts) {
this.shouldThrowOnError = false;
const baseUrl = new URL(url);
if (opts === null || opts === void 0 ? void 0 : opts.useNewHostname) {
if (/supabase\.(co|in|red)$/.test(baseUrl.hostname) && !baseUrl.hostname.includes("storage.supabase.")) baseUrl.hostname = baseUrl.hostname.replace("supabase.", "storage.supabase.");
}
this.url = baseUrl.href.replace(/\/$/, "");
this.headers = _objectSpread2(_objectSpread2({}, DEFAULT_HEADERS$1), headers);
this.fetch = resolveFetch$1(fetch$1);
}
/**
* Enable throwing errors instead of returning them.
*
* @category File Buckets
*/
throwOnError() {
this.shouldThrowOnError = true;
return this;
}
/**
* Retrieves the details of all Storage buckets within an existing project.
*
* @category File Buckets
* @param options Query parameters for listing buckets
* @param options.limit Maximum number of buckets to return
* @param options.offset Number of buckets to skip
* @param options.sortColumn Column to sort by ('id', 'name', 'created_at', 'updated_at')
* @param options.sortOrder Sort order ('asc' or 'desc')
* @param options.search Search term to filter bucket names
* @returns Promise with response containing array of buckets or error
*
* @example List buckets
* ```js
* const { data, error } = await supabase
* .storage
* .listBuckets()
* ```
*
* @example List buckets with options
* ```js
* const { data, error } = await supabase
* .storage
* .listBuckets({
* limit: 10,
* offset: 0,
* sortColumn: 'created_at',
* sortOrder: 'desc',
* search: 'prod'
* })
* ```
*/
async listBuckets(options) {
var _this = this;
try {
const queryString = _this.listBucketOptionsToQueryString(options);
return {
data: await get(_this.fetch, `${_this.url}/bucket${queryString}`, { headers: _this.headers }),
error: null
};
} catch (error) {
if (_this.shouldThrowOnError) throw error;
if (isStorageError(error)) return {
data: null,
error
};
throw error;
}
}
/**
* Retrieves the details of an existing Storage bucket.
*
* @category File Buckets
* @param id The unique identifier of the bucket you would like to retrieve.
* @returns Promise with response containing bucket details or error
*
* @example Get bucket
* ```js
* const { data, error } = await supabase
* .storage
* .getBucket('avatars')
* ```
*
* Response:
* ```json
* {
* "data": {
* "id": "avatars",
* "name": "avatars",
* "owner": "",
* "public": false,
* "file_size_limit": 1024,
* "allowed_mime_types": [
* "image/png"
* ],
* "created_at": "2024-05-22T22:26:05.100Z",
* "updated_at": "2024-05-22T22:26:05.100Z"
* },
* "error": null
* }
* ```
*/
async getBucket(id) {
var _this2 = this;
try {
return {
data: await get(_this2.fetch, `${_this2.url}/bucket/${id}`, { headers: _this2.headers }),
error: null
};
} catch (error) {
if (_this2.shouldThrowOnError) throw error;
if (isStorageError(error)) return {
data: null,
error
};
throw error;
}
}
/**
* Creates a new Storage bucket
*
* @category File Buckets
* @param id A unique identifier for the bucket you are creating.
* @param options.public The visibility of the bucket. Public buckets don't require an authorization token to download objects, but still require a valid token for all other operations. By default, buckets are private.
* @param options.fileSizeLimit specifies the max file size in bytes that can be uploaded to this bucket.
* The global file size limit takes precedence over this value.
* The default value is null, which doesn't set a per bucket file size limit.
* @param options.allowedMimeTypes specifies the allowed mime types that this bucket can accept during upload.
* The default value is null, which allows files with all mime types to be uploaded.
* Each mime type specified can be a wildcard, e.g. image/*, or a specific mime type, e.g. image/png.
* @param options.type (private-beta) specifies the bucket type. see `BucketType` for more details.
* - default bucket type is `STANDARD`
* @returns Promise with response containing newly created bucket name or error
*
* @example Create bucket
* ```js
* const { data, error } = await supabase
* .storage
* .createBucket('avatars', {
* public: false,
* allowedMimeTypes: ['image/png'],
* fileSizeLimit: 1024
* })
* ```
*
* Response:
* ```json
* {
* "data": {
* "name": "avatars"
* },
* "error": null
* }
* ```
*/
async createBucket(id, options = { public: false }) {
var _this3 = this;
try {
return {
data: await post$1(_this3.fetch, `${_this3.url}/bucket`, {
id,
name: id,
type: options.type,
public: options.public,
file_size_limit: options.fileSizeLimit,
allowed_mime_types: options.allowedMimeTypes
}, { headers: _this3.headers }),
error: null
};
} catch (error) {
if (_this3.shouldThrowOnError) throw error;
if (isStorageError(error)) return {
data: null,
error
};
throw error;
}
}
/**
* Updates a Storage bucket
*
* @category File Buckets
* @param id A unique identifier for the bucket you are updating.
* @param options.public The visibility of the bucket. Public buckets don't require an authorization token to download objects, but still require a valid token for all other operations.
* @param options.fileSizeLimit specifies the max file size in bytes that can be uploaded to this bucket.
* The global file size limit takes precedence over this value.
* The default value is null, which doesn't set a per bucket file size limit.
* @param options.allowedMimeTypes specifies the allowed mime types that this bucket can accept during upload.
* The default value is null, which allows files with all mime types to be uploaded.
* Each mime type specified can be a wildcard, e.g. image/*, or a specific mime type, e.g. image/png.
* @returns Promise with response containing success message or error
*
* @example Update bucket
* ```js
* const { data, error } = await supabase
* .storage
* .updateBucket('avatars', {
* public: false,
* allowedMimeTypes: ['image/png'],
* fileSizeLimit: 1024
* })
* ```
*
* Response:
* ```json
* {
* "data": {
* "message": "Successfully updated"
* },
* "error": null
* }
* ```
*/
async updateBucket(id, options) {
var _this4 = this;
try {
return {
data: await put(_this4.fetch, `${_this4.url}/bucket/${id}`, {
id,
name: id,
public: options.public,
file_size_limit: options.fileSizeLimit,
allowed_mime_types: options.allowedMimeTypes
}, { headers: _this4.headers }),
error: null
};
} catch (error) {
if (_this4.shouldThrowOnError) throw error;
if (isStorageError(error)) return {
data: null,
error
};
throw error;
}
}
/**
* Removes all objects inside a single bucket.
*
* @category File Buckets
* @param id The unique identifier of the bucket you would like to empty.
* @returns Promise with success message or error
*
* @example Empty bucket
* ```js
* const { data, error } = await supabase
* .storage
* .emptyBucket('avatars')
* ```
*
* Response:
* ```json
* {
* "data": {
* "message": "Successfully emptied"
* },
* "error": null
* }
* ```
*/
async emptyBucket(id) {
var _this5 = this;
try {
return {
data: await post$1(_this5.fetch, `${_this5.url}/bucket/${id}/empty`, {}, { headers: _this5.headers }),
error: null
};
} catch (error) {
if (_this5.shouldThrowOnError) throw error;
if (isStorageError(error)) return {
data: null,
error
};
throw error;
}
}
/**
* Deletes an existing bucket. A bucket can't be deleted with existing objects inside it.
* You must first `empty()` the bucket.
*
* @category File Buckets
* @param id The unique identifier of the bucket you would like to delete.
* @returns Promise with success message or error
*
* @example Delete bucket
* ```js
* const { data, error } = await supabase
* .storage
* .deleteBucket('avatars')
* ```
*
* Response:
* ```json
* {
* "data": {
* "message": "Successfully deleted"
* },
* "error": null
* }
* ```
*/
async deleteBucket(id) {
var _this6 = this;
try {
return {
data: await remove(_this6.fetch, `${_this6.url}/bucket/${id}`, {}, { headers: _this6.headers }),
error: null
};
} catch (error) {
if (_this6.shouldThrowOnError) throw error;
if (isStorageError(error)) return {
data: null,
error
};
throw error;
}
}
listBucketOptionsToQueryString(options) {
const params = {};
if (options) {
if ("limit" in options) params.limit = String(options.limit);
if ("offset" in options) params.offset = String(options.offset);
if (options.search) params.search = options.search;
if (options.sortColumn) params.sortColumn = options.sortColumn;
if (options.sortOrder) params.sortOrder = options.sortOrder;
}
return Object.keys(params).length > 0 ? "?" + new URLSearchParams(params).toString() : "";
}
};
//#endregion
//#region src/packages/StorageAnalyticsClient.ts
/**
* Client class for managing Analytics Buckets using Iceberg tables
* Provides methods for creating, listing, and deleting analytics buckets
*/
var StorageAnalyticsClient = class {
/**
* @alpha
*
* Creates a new StorageAnalyticsClient instance
*
* **Public alpha:** This API is part of a public alpha release and may not be available to your account type.
*
* @category Analytics Buckets
* @param url - The base URL for the storage API
* @param headers - HTTP headers to include in requests
* @param fetch - Optional custom fetch implementation
*
* @example
* ```typescript
* const client = new StorageAnalyticsClient(url, headers)
* ```
*/
constructor(url, headers = {}, fetch$1) {
this.shouldThrowOnError = false;
this.url = url.replace(/\/$/, "");
this.headers = _objectSpread2(_objectSpread2({}, DEFAULT_HEADERS$1), headers);
this.fetch = resolveFetch$1(fetch$1);
}
/**
* @alpha
*
* Enable throwing errors instead of returning them in the response
* When enabled, failed operations will throw instead of returning { data: null, error }
*
* **Public alpha:** This API is part of a public alpha release and may not be available to your account type.
*
* @category Analytics Buckets
* @returns This instance for method chaining
*/
throwOnError() {
this.shouldThrowOnError = true;
return this;
}
/**
* @alpha
*
* Creates a new analytics bucket using Iceberg tables
* Analytics buckets are optimized for analytical queries and data processing
*
* **Public alpha:** This API is part of a public alpha release and may not be available to your account type.
*
* @category Analytics Buckets
* @param name A unique name for the bucket you are creating
* @returns Promise with response containing newly created analytics bucket or error
*
* @example Create analytics bucket
* ```js
* const { data, error } = await supabase
* .storage
* .analytics
* .createBucket('analytics-data')
* ```
*
* Response:
* ```json
* {
* "data": {
* "name": "analytics-data",
* "type": "ANALYTICS",
* "format": "iceberg",
* "created_at": "2024-05-22T22:26:05.100Z",
* "updated_at": "2024-05-22T22:26:05.100Z"
* },
* "error": null
* }
* ```
*/
async createBucket(name) {
var _this = this;
try {
return {
data: await post$1(_this.fetch, `${_this.url}/bucket`, { name }, { headers: _this.headers }),
error: null
};
} catch (error) {
if (_this.shouldThrowOnError) throw error;
if (isStorageError(error)) return {
data: null,
error
};
throw error;
}
}
/**
* @alpha
*
* Retrieves the details of all Analytics Storage buckets within an existing project
* Only returns buckets of type 'ANALYTICS'
*
* **Public alpha:** This