Skip to content

Commit

Permalink
feat: added upload byte types, delete()
Browse files Browse the repository at this point in the history
  • Loading branch information
dereekb committed Jul 3, 2022
1 parent e3fe97e commit 655088b
Show file tree
Hide file tree
Showing 8 changed files with 257 additions and 44 deletions.
65 changes: 54 additions & 11 deletions packages/firebase-server/src/lib/storage/driver.accessor.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { StorageServerUploadInput, FirebaseStorageAccessorDriver, FirebaseStorageAccessorFile, FirebaseStorageAccessorFolder, FirebaseStorage, StoragePath } from '@dereekb/firebase';
import { Maybe } from '@dereekb/util';
import { Storage as GoogleCloudStorage, File as GoogleCloudFile, DownloadOptions } from '@google-cloud/storage';
import { StorageUploadOptions, StorageServerUploadInput, FirebaseStorageAccessorDriver, FirebaseStorageAccessorFile, FirebaseStorageAccessorFolder, FirebaseStorage, StoragePath, assertStorageUploadOptionsStringFormat, StorageDeleteFileOptions } from '@dereekb/firebase';
import { Maybe, PromiseOrValue } from '@dereekb/util';
import { SaveOptions, CreateWriteStreamOptions, Storage as GoogleCloudStorage, File as GoogleCloudFile, DownloadOptions } from '@google-cloud/storage';
import { isArrayBuffer, isUint8Array } from 'util/types';

export function googleCloudStorageFileForStorageFilePath(storage: GoogleCloudStorage, path: StoragePath) {
return storage.bucket(path.bucketId).file(path.pathString);
Expand All @@ -22,6 +23,23 @@ export function googleCloudStorageAccessorFile(storage: GoogleCloudStorage, stor
};
}

function makeUploadOptions(options?: StorageUploadOptions): SaveOptions | CreateWriteStreamOptions {
let metadata: object | undefined;

if (options?.contentType) {
metadata = {
contentType: options?.contentType
};
}

return {
// non-resumable
resumable: false,
// add content type
...(metadata ? { metadata } : undefined)
};
}

return {
reference: file,
storagePath,
Expand All @@ -30,14 +48,39 @@ export function googleCloudStorageAccessorFile(storage: GoogleCloudStorage, stor
getMetadata: () => file.getMetadata().then((x) => x[0]),
getBytes: (maxDownloadSizeBytes) => file.download(makeDownloadOptions(maxDownloadSizeBytes)).then((x) => x[0]),
getStream: (maxDownloadSizeBytes) => file.createReadStream(makeDownloadOptions(maxDownloadSizeBytes)),
upload: (input, options) =>
file.save(input as StorageServerUploadInput, {
// non-resumable
resumable: false,
// add content type
...(options?.contentType ? { contentType: options?.contentType } : undefined)
}),
uploadStream: (options) => file.createWriteStream({ ...(options?.contentType ? { contentType: options?.contentType } : undefined) })
upload: async (input, options) => {
let dataToUpload: PromiseOrValue<Buffer>;

if (typeof input === 'string') {
const parsedStringFormat = assertStorageUploadOptionsStringFormat(options);
const stringFormat = parsedStringFormat === 'raw' ? 'utf-8' : parsedStringFormat;

if (stringFormat === 'data_url') {
// todo: support this later if necessary. Server should really never see this type.
throw new Error('"data_url" is unsupported.');
}

dataToUpload = Buffer.from(input, stringFormat);
} else {
if (Buffer.isBuffer(input)) {
dataToUpload = input;
} else if (isUint8Array(input)) {
dataToUpload = Buffer.from(input);
} else {
// NOTE: these values shouldn't ever be encountered in the NodeJS environment. May remove later.
if (isArrayBuffer(input)) {
dataToUpload = Buffer.from(input);
} else {
dataToUpload = input.arrayBuffer().then((x) => Buffer.from(x));
}
}
}

const data = await dataToUpload;
return file.save(data, makeUploadOptions(options));
},
uploadStream: (options) => file.createWriteStream(makeUploadOptions(options)),
delete: (options: StorageDeleteFileOptions) => file.delete(options).then((x) => undefined)
};
}

Expand Down
25 changes: 20 additions & 5 deletions packages/firebase/src/lib/client/storage/driver.accessor.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { FirebaseStorageAccessorDriver, FirebaseStorageAccessorFile, FirebaseStorageAccessorFolder } from '../../common/storage/driver/accessor';
import { StorageReference, getDownloadURL, FirebaseStorage as ClientFirebaseStorage, ref } from '@firebase/storage';
import { firebaseStorageFilePathFromStorageFilePath, StoragePath } from '../../common/storage/storage';
import { FirebaseStorage, StorageClientUploadBytesInput, StorageClientUploadInput, StorageDataString, StorageUploadOptions } from '../../common/storage/types';
import { getBytes, getMetadata, uploadBytes, uploadBytesResumable, UploadMetadata, uploadString } from 'firebase/storage';
import { FirebaseStorage, StorageClientUploadBytesInput, StorageClientUploadInput, StorageDataString, StorageDeleteFileOptions, StorageUploadOptions } from '../../common/storage/types';
import { getBytes, getMetadata, uploadBytes, uploadBytesResumable, UploadMetadata, uploadString, deleteObject, getBlob } from 'firebase/storage';
import { assertStorageUploadOptionsStringFormat } from '../../common';
import { ErrorInput, errorMessageContainsString, Maybe } from '@dereekb/util';

export function isFirebaseStorageObjectNotFoundError(input: Maybe<ErrorInput | string>): boolean {
return errorMessageContainsString(input, 'storage/object-not-found');
}

export function firebaseStorageRefForStorageFilePath(storage: ClientFirebaseStorage, path: StoragePath): StorageReference {
return ref(storage, firebaseStorageFilePathFromStorageFilePath(path));
Expand Down Expand Up @@ -41,19 +47,28 @@ export function firebaseStorageClientAccessorFile(storage: ClientFirebaseStorage
getDownloadUrl: () => getDownloadURL(ref),
getMetadata: () => getMetadata(ref),
upload: (input, options) => {
const inputType = typeof input === 'string';
let metadataOption: UploadMetadata | undefined = asUploadMetadata(options);

if (typeof input === 'string') {
return uploadString(ref, input as StorageDataString, options?.stringFormat ?? 'base64', metadataOption);
if (inputType) {
const stringFormat = assertStorageUploadOptionsStringFormat(options);
return uploadString(ref, input as StorageDataString, stringFormat, metadataOption);
} else {
return uploadBytes(ref, input as StorageClientUploadBytesInput, metadataOption);
}
},
getBytes: (maxDownloadSizeBytes) => getBytes(ref, maxDownloadSizeBytes),
getBlob: (maxDownloadSizeBytes) => getBlob(ref, maxDownloadSizeBytes),
uploadResumable: (input, options) => {
let metadataOption: UploadMetadata | undefined = asUploadMetadata(options);
return uploadBytesResumable(ref, input as StorageClientUploadBytesInput, metadataOption);
}
},
delete: (options: StorageDeleteFileOptions) =>
deleteObject(ref).catch((x) => {
if (!options.ignoreNotFound || !isFirebaseStorageObjectNotFoundError(x)) {
throw x;
}
})
};
}

Expand Down
8 changes: 7 additions & 1 deletion packages/firebase/src/lib/common/storage/driver/accessor.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { StorageBucketId } from './../storage';
import { StoragePath, StoragePathRef } from '../storage';
import { FirebaseStorage, StorageClientUploadBytesInput, StorageDownloadUrl, StorageMetadata, StorageUploadInput, StorageUploadOptions, StorageUploadResult, StorageUploadTask } from '../types';
import { FirebaseStorage, StorageClientUploadBytesInput, StorageDeleteFileOptions, StorageDownloadUrl, StorageMetadata, StorageUploadInput, StorageUploadOptions, StorageUploadResult, StorageUploadTask } from '../types';
import { Maybe } from '@dereekb/util';

/**
Expand Down Expand Up @@ -55,6 +55,12 @@ export interface FirebaseStorageAccessorFile<R extends unknown = unknown> extend
* Optional implementation.
*/
uploadStream?(options?: StorageUploadOptions): NodeJS.WritableStream;
/**
* Deletes the file.
*
* Throws an error if the file does not exist.
*/
delete(options?: StorageDeleteFileOptions): Promise<void>;
}

/**
Expand Down
15 changes: 15 additions & 0 deletions packages/firebase/src/lib/common/storage/driver/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { StorageDataStringType, StorageUploadOptions } from '../types';

export function assertStorageUploadOptionsStringFormat(options?: StorageUploadOptions): StorageDataStringType {
const stringFormat = options?.stringFormat;

if (!stringFormat) {
throw noStringFormatInStorageUploadOptionsError();
}

return stringFormat;
}

export function noStringFormatInStorageUploadOptionsError() {
return new Error('stringFormat was missing a value in the StorageUploadOptions.');
}
1 change: 1 addition & 0 deletions packages/firebase/src/lib/common/storage/driver/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './accessor';
export * from './driver';
export * from './error';
20 changes: 16 additions & 4 deletions packages/firebase/src/lib/common/storage/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export type StorageDownloadUrl = string;

/**
* Example:
* '5b6p5Y+344GX44G+44GX44Gf77yB44GK44KB44Gn44Go44GG77yB';
* 'Hello! \\ud83d\\ude0a';
*/
export type StorageRawDataString = string;

Expand Down Expand Up @@ -103,10 +103,15 @@ export type StorageClientUploadBytesInput = File | Blob | Uint8Array;
*/
export type StorageClientUploadInput = StorageClientUploadBytesInput | StorageDataString;

/**
* Known types that can be uploaded by the client implementation.
*/
export type StorageServerUploadBytesInput = Buffer | Uint8Array;

/**
* Known types that can be uploaded by the server implementation.
*/
export type StorageServerUploadInput = Buffer | StorageDataString;
export type StorageServerUploadInput = StorageServerUploadBytesInput | StorageDataString;

export type StorageUploadInput = StorageClientUploadInput | StorageServerUploadInput;

Expand Down Expand Up @@ -134,7 +139,7 @@ export type StorageUploadResult = StorageClientUploadResult | unknown;
export interface StorageUploadOptions {
resumable?: boolean;
/**
* String format to handle the upload as.
* String format to handle the upload as. Required if the input is a string.
*/
stringFormat?: StorageDataStringType;
/**
Expand All @@ -144,7 +149,7 @@ export interface StorageUploadOptions {
/**
* other metadata to attach to the file.
*/
metadata?: StorageMetadata; // todo: ...
metadata?: StorageMetadata;
}

/**
Expand Down Expand Up @@ -222,3 +227,10 @@ export type StorageMetadata = {
export type StorageCustomMetadata = {
[key: string]: string;
};

export interface StorageDeleteFileOptions {
/**
* Ignores errors related to the file not existing.
*/
ignoreNotFound?: boolean;
}
Loading

0 comments on commit 655088b

Please sign in to comment.