@supabase/storage-js
Version:
Isomorphic storage client for Supabase.
532 lines • 23.5 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { isStorageError, StorageError, StorageUnknownError } from '../lib/errors';
import { get, head, post, put, remove } from '../lib/fetch';
import { recursiveToCamel, resolveFetch } from '../lib/helpers';
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,
};
export default class StorageFileApi {
constructor(url, headers = {}, bucketId, fetch) {
this.url = url;
this.headers = headers;
this.bucketId = bucketId;
this.fetch = resolveFetch(fetch);
}
/**
* 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.
*/
uploadOrUpdate(method, path, fileBody, fileOptions) {
return __awaiter(this, void 0, void 0, function* () {
try {
let body;
const options = Object.assign(Object.assign({}, DEFAULT_FILE_OPTIONS), fileOptions);
let headers = Object.assign(Object.assign({}, 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;
body.append('cacheControl', options.cacheControl);
if (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 (fileOptions === null || fileOptions === void 0 ? void 0 : fileOptions.headers) {
headers = Object.assign(Object.assign({}, headers), fileOptions.headers);
}
const cleanPath = this._removeEmptyFolders(path);
const _path = this._getFinalPath(cleanPath);
const data = yield (method == 'PUT' ? put : post)(this.fetch, `${this.url}/object/${_path}`, body, Object.assign({ 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 (isStorageError(error)) {
return { data: null, error };
}
throw error;
}
});
}
/**
* Uploads a file to an existing bucket.
*
* @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.
*/
upload(path, fileBody, fileOptions) {
return __awaiter(this, void 0, void 0, function* () {
return this.uploadOrUpdate('POST', path, fileBody, fileOptions);
});
}
/**
* Upload a file with a token generated from `createSignedUploadUrl`.
* @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.
*/
uploadToSignedUrl(path, token, fileBody, fileOptions) {
return __awaiter(this, void 0, void 0, function* () {
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 = Object.assign({ upsert: DEFAULT_FILE_OPTIONS.upsert }, fileOptions);
const headers = Object.assign(Object.assign({}, this.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;
}
const data = yield put(this.fetch, url.toString(), body, { headers });
return {
data: { path: cleanPath, fullPath: data.Key },
error: null,
};
}
catch (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.
* @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.
*/
createSignedUploadUrl(path, options) {
return __awaiter(this, void 0, void 0, function* () {
try {
let _path = this._getFinalPath(path);
const headers = Object.assign({}, this.headers);
if (options === null || options === void 0 ? void 0 : options.upsert) {
headers['x-upsert'] = 'true';
}
const data = yield 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 (isStorageError(error)) {
return { data: null, error };
}
throw error;
}
});
}
/**
* Replaces an existing file at the specified path with a new one.
*
* @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.
*/
update(path, fileBody, fileOptions) {
return __awaiter(this, void 0, void 0, function* () {
return this.uploadOrUpdate('PUT', path, fileBody, fileOptions);
});
}
/**
* Moves an existing file to a new path in the same bucket.
*
* @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.
*/
move(fromPath, toPath, options) {
return __awaiter(this, void 0, void 0, function* () {
try {
const data = yield post(this.fetch, `${this.url}/object/move`, {
bucketId: this.bucketId,
sourceKey: fromPath,
destinationKey: toPath,
destinationBucket: options === null || options === void 0 ? void 0 : options.destinationBucket,
}, { headers: this.headers });
return { data, error: null };
}
catch (error) {
if (isStorageError(error)) {
return { data: null, error };
}
throw error;
}
});
}
/**
* Copies an existing file to a new path in the same bucket.
*
* @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.
*/
copy(fromPath, toPath, options) {
return __awaiter(this, void 0, void 0, function* () {
try {
const data = yield post(this.fetch, `${this.url}/object/copy`, {
bucketId: this.bucketId,
sourceKey: fromPath,
destinationKey: toPath,
destinationBucket: options === null || options === void 0 ? void 0 : options.destinationBucket,
}, { headers: this.headers });
return { data: { path: data.Key }, error: null };
}
catch (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.
*
* @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.
*/
createSignedUrl(path, expiresIn, options) {
return __awaiter(this, void 0, void 0, function* () {
try {
let _path = this._getFinalPath(path);
let data = yield post(this.fetch, `${this.url}/object/sign/${_path}`, Object.assign({ expiresIn }, ((options === null || options === void 0 ? void 0 : options.transform) ? { transform: options.transform } : {})), { headers: this.headers });
const downloadQueryParam = (options === null || options === void 0 ? void 0 : 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 (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.
*
* @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.
*/
createSignedUrls(paths, expiresIn, options) {
return __awaiter(this, void 0, void 0, function* () {
try {
const data = yield post(this.fetch, `${this.url}/object/sign/${this.bucketId}`, { expiresIn, paths }, { headers: this.headers });
const downloadQueryParam = (options === null || options === void 0 ? void 0 : options.download)
? `&download=${options.download === true ? '' : options.download}`
: '';
return {
data: data.map((datum) => (Object.assign(Object.assign({}, datum), { signedUrl: datum.signedURL
? encodeURI(`${this.url}${datum.signedURL}${downloadQueryParam}`)
: null }))),
error: null,
};
}
catch (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.
*
* @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.
*/
download(path, options) {
return __awaiter(this, void 0, void 0, function* () {
const wantsTransformation = typeof (options === null || options === void 0 ? void 0 : options.transform) !== 'undefined';
const renderPath = wantsTransformation ? 'render/image/authenticated' : 'object';
const transformationQuery = this.transformOptsToQueryString((options === null || options === void 0 ? void 0 : options.transform) || {});
const queryString = transformationQuery ? `?${transformationQuery}` : '';
try {
const _path = this._getFinalPath(path);
const res = yield get(this.fetch, `${this.url}/${renderPath}/${_path}${queryString}`, {
headers: this.headers,
noResolveJson: true,
});
const data = yield res.blob();
return { data, error: null };
}
catch (error) {
if (isStorageError(error)) {
return { data: null, error };
}
throw error;
}
});
}
/**
* Retrieves the details of an existing file.
* @param path
*/
info(path) {
return __awaiter(this, void 0, void 0, function* () {
const _path = this._getFinalPath(path);
try {
const data = yield get(this.fetch, `${this.url}/object/info/${_path}`, {
headers: this.headers,
});
return { data: recursiveToCamel(data), error: null };
}
catch (error) {
if (isStorageError(error)) {
return { data: null, error };
}
throw error;
}
});
}
/**
* Checks the existence of a file.
* @param path
*/
exists(path) {
return __awaiter(this, void 0, void 0, function* () {
const _path = this._getFinalPath(path);
try {
yield head(this.fetch, `${this.url}/object/${_path}`, {
headers: this.headers,
});
return { data: true, error: null };
}
catch (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.
*
* @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.
*/
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 wantsTransformation = typeof (options === null || options === void 0 ? void 0 : options.transform) !== 'undefined';
const renderPath = wantsTransformation ? '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
*
* @param paths An array of files to delete, including the path and file name. For example [`'folder/image.png'`].
*/
remove(paths) {
return __awaiter(this, void 0, void 0, function* () {
try {
const data = yield remove(this.fetch, `${this.url}/object/${this.bucketId}`, { prefixes: paths }, { headers: this.headers });
return { data, error: null };
}
catch (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 within a bucket.
* @param path The folder path.
*/
list(path, options, parameters) {
return __awaiter(this, void 0, void 0, function* () {
try {
const body = Object.assign(Object.assign(Object.assign({}, DEFAULT_SEARCH_OPTIONS), options), { prefix: path || '' });
const data = yield post(this.fetch, `${this.url}/object/list/${this.bucketId}`, body, { headers: this.headers }, parameters);
return { data, error: null };
}
catch (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}`;
}
_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('&');
}
}
//# sourceMappingURL=StorageFileApi.js.map