diff --git a/README.md b/README.md
index dd93cf6..c0734a6 100644
--- a/README.md
+++ b/README.md
@@ -3,9 +3,9 @@
-
-
-
+
+
+
`flydrive` is a framework-agnostic package which provides a powerful wrapper to manage file Storage in [Node.js](https://nodejs.org).
@@ -27,16 +27,16 @@ This package is available in the npm registry.
It can easily be installed with `npm` or `yarn`.
```bash
-$ npm i @slynova/flydrive
+$ npm i @pdspicer/flydrive
# or
-$ yarn add @slynova/flydrive
+$ yarn add @pdspicer/flydrive
```
When you require the package in your file, it will give you access to the `StorageManager` class.
This class is a facade for the package and should be instantiated with a [configuration object](https://github.com/Slynova-Org/flydrive/blob/master/test/stubs/config.ts).
```javascript
-const { StorageManager } = require('@slynova/flydrive');
+const { StorageManager } = require('@pdspicer/flydrive');
const storage = new StorageManager(config);
```
@@ -62,7 +62,7 @@ storage.disk('local').getSignedUrl();
Since we are using TypeScript, you can make use of casting to get the real interface:
```typescript
-import { LocalFileSystem } from '@slynova/flydrive';
+import { LocalFileSystem } from '@pdspicer/flydrive';
storage.disk('local');
```
diff --git a/package.json b/package.json
index 4a4c825..8c12644 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
- "name": "@slynova/flydrive",
- "version": "1.0.0-0",
+ "name": "flydrive-manager",
+ "version": "1.0.7",
"description": "Flexible and Fluent way to manage storage in Node.js.",
"main": "./build/index.js",
"license": "MIT",
@@ -20,7 +20,8 @@
"contributors": [
"Harminder Virk ",
"Michaƫl Zasso ",
- "Krzysztof Chrapka "
+ "Krzysztof Chrapka ",
+ "Paul Spicer "
],
"files": [
"build"
diff --git a/src/Storage/AmazonWebServicesS3Storage.ts b/src/Storage/AmazonWebServicesS3Storage.ts
index 08907c8..c7bc34c 100644
--- a/src/Storage/AmazonWebServicesS3Storage.ts
+++ b/src/Storage/AmazonWebServicesS3Storage.ts
@@ -6,7 +6,7 @@
*/
import { Readable } from 'stream';
-import S3, { ClientConfiguration } from 'aws-sdk/clients/s3';
+import S3 from 'aws-sdk/clients/s3';
import {UnknownException, NoSuchBucket, FileNotFound, InvalidInput, PermissionMissing} from '../Exceptions';
import {
ContentResponse,
@@ -22,6 +22,8 @@ import {
import { MetadataConverter } from '../utils/MetadataConverter'
import { isReadableStream } from "../utils";
import { Storage } from "./Storage";
+import {Agent as httpAgent} from "http";
+import {Agent as httpsAgent} from "https";
function handleError(err: Error, path: string, bucket: string): Error {
switch (err.name) {
@@ -37,7 +39,7 @@ function handleError(err: Error, path: string, bucket: string): Error {
}
export class AmazonWebServicesS3Storage extends Storage {
- constructor(protected readonly $driver: S3, protected readonly $bucket: string) {
+ private constructor(private readonly $driver: S3, protected readonly $bucket: string) {
super();
}
@@ -281,8 +283,75 @@ export class AmazonWebServicesS3Storage extends Storage {
}
}
-export interface AWSS3Config extends ClientConfiguration {
+export interface AWSS3Config {
key: string;
secret: string;
bucket: string;
+ endpoint?: string;
+ params?: {
+ [key: string]: any;
+ };
+ computeChecksums?: boolean;
+ convertResponseTypes?: boolean;
+ correctClockSkew?: boolean;
+ customUserAgent?: string;
+ credentials?: AWSCredentials|null;
+ credentialProvider?: AWSCredentialProviderChain;
+ httpOptions?: {
+ proxy?: string;
+ agent?: httpAgent | httpsAgent;
+ connectTimeout?: number;
+ timeout?: number;
+ xhrAsync?: boolean;
+ xhrWithCredentials?: boolean;
+ };
+ logger?: {
+ write?: (...any) => void;
+ log?: (...any) => void;
+ };
+ maxRedirects?: number;
+ maxRetries?: number;
+ paramValidation?: boolean | {
+ min?: boolean;
+ max?: boolean;
+ pattern?: boolean;
+ enum?: boolean;
+ };
+ region?: string;
+ retryDelayOptions?: {
+ base?: number;
+ customBackoff?: (retryCount: number, err?: Error) => number;
+ };
+ s3BucketEndpoint?: boolean;
+ s3DisableBodySigning?: boolean;
+ s3ForcePathStyle?: boolean;
+ s3UsEast1RegionalEndpoint?: "regional"|"legacy";
+ s3UseArnRegion?: boolean;
+ signatureCache?: boolean;
+ signatureVersion?: "v2"|"v3"|"v4"|string;
+ sslEnabled?: boolean;
+ systemClockOffset?: number;
+ useAccelerateEndpoint?: boolean;
+ dynamoDbCrc32?: boolean;
+ endpointDiscoveryEnabled?: boolean;
+ endpointCacheSize?: number;
+ hostPrefixEnabled?: boolean;
+ stsRegionalEndpoints?: "legacy"|"regional";
+ useDualstack?: boolean;
+ apiVersion?: "2006-03-01"|"latest"|string;
}
+
+export interface AWSCredentials {
+ accessKeyId: string
+ secretAccessKey: string
+ sessionToken?: string
+ expired?: boolean;
+ expireTime?: Date;
+}
+
+export interface AWSCredentialProviderChain {
+ resolve(callback:(err: any, credentials: AWSCredentials) => void): AWSCredentialProviderChain;
+ resolvePromise(): Promise;
+ providers: AWSCredentials[]|(() => AWSCredentials)[];
+}
+
diff --git a/src/Storage/AzureBlockBlobStorage.ts b/src/Storage/AzureBlockBlobStorage.ts
index 8425eaf..969da6c 100644
--- a/src/Storage/AzureBlockBlobStorage.ts
+++ b/src/Storage/AzureBlockBlobStorage.ts
@@ -6,12 +6,10 @@
* @author Christopher Chrapka
*/
import { Storage } from "./Storage";
-import {
+import AzureStorageBlob, {
BlobDownloadHeaders,
- BlobSASPermissions,
- BlobServiceClient,
BlockBlobClient, ContainerClient,
- generateBlobSASQueryParameters, RestError, StorageSharedKeyCredential
+ RestError, StorageSharedKeyCredential
} from "@azure/storage-blob";
import {Readable, PassThrough} from "stream";
import {
@@ -24,9 +22,8 @@ import {
SignedUrlResponse
} from "../types";
import {isReadableStream} from "../utils";
-import {InvalidInput} from "../Exceptions/InvalidInput";
import {streamToBuffer} from "../utils/streamToBuffer";
-import {AuthorizationRequired, FileNotFound, UnknownException} from "../Exceptions";
+import {AuthorizationRequired, FileNotFound, UnknownException, InvalidInput} from "../Exceptions";
import {MetadataConverter} from "../utils/MetadataConverter";
import {BlockBlobUploadOptions} from "@azure/storage-blob/src/Clients";
@@ -37,13 +34,24 @@ export interface AzureBlobStorageConfig {
export class AzureBlockBlobStorage extends Storage
{
- constructor(private readonly $containerClient: ContainerClient) {
+ private static _azure: typeof AzureStorageBlob;
+
+ private static get AzureStorageBlob () {
+ if (this._azure) return this._azure;
+ this._azure = require('@azure/storage-blob');
+ return this._azure;
+ }
+ private get AzureStorageBlob () {
+ return AzureBlockBlobStorage.AzureStorageBlob;
+ }
+
+ private constructor(private readonly $containerClient: ContainerClient) {
super();
}
static fromConfig(config: AzureBlobStorageConfig): AzureBlockBlobStorage {
return new AzureBlockBlobStorage(
- BlobServiceClient
+ this.AzureStorageBlob.BlobServiceClient
.fromConnectionString(config.connectionString)
.getContainerClient(config.container),
);
@@ -153,11 +161,11 @@ export class AzureBlockBlobStorage extends Storage
const expiresOn = new Date(new Date().valueOf() + expiry * 1000);
const client = this.blockBlobClient(location);
- const token = await generateBlobSASQueryParameters(
+ const token = await this.AzureStorageBlob.generateBlobSASQueryParameters(
{
containerName: container,
blobName: location,
- permissions: BlobSASPermissions.parse("r"), // Required
+ permissions: this.AzureStorageBlob.BlobSASPermissions.parse("r"), // Required
startsOn, // Required
expiresOn, // Optional
},
diff --git a/src/Storage/GoogleCloudStorage.ts b/src/Storage/GoogleCloudStorage.ts
index a48935e..adf20ba 100644
--- a/src/Storage/GoogleCloudStorage.ts
+++ b/src/Storage/GoogleCloudStorage.ts
@@ -6,7 +6,7 @@
*/
import { Readable } from 'stream';
-import {StorageOptions, Bucket, File, CreateWriteStreamOptions} from '@google-cloud/storage';
+import {Bucket, File, CreateWriteStreamOptions} from '@google-cloud/storage';
import { Storage } from './Storage';
import { isReadableStream, pipeline } from '../utils';
import {
@@ -18,9 +18,15 @@ import {
PropertiesResponse,
FileListResponse, PutOptions, DeleteResponse
} from '../types';
-import { FileNotFound, PermissionMissing, UnknownException, AuthorizationRequired, WrongKeyPath } from '../Exceptions';
+import {
+ FileNotFound,
+ PermissionMissing,
+ UnknownException,
+ AuthorizationRequired,
+ WrongKeyPath,
+ InvalidInput
+} from '../Exceptions';
import {MetadataConverter} from "../utils/MetadataConverter";
-import {InvalidInput} from "../Exceptions/InvalidInput";
function handleError(err: Error & { code?: number | string }, path: string): Error {
switch (err.code) {
@@ -38,7 +44,7 @@ function handleError(err: Error & { code?: number | string }, path: string): Err
}
export class GoogleCloudStorage extends Storage {
- public constructor(private readonly $bucket: Bucket) {
+ private constructor(private readonly $bucket: Bucket) {
super();
}
@@ -260,6 +266,48 @@ export class GoogleCloudStorage extends Storage {
}
}
-export interface GoogleCloudStorageConfig extends StorageOptions {
+export interface GoogleCloudStorageConfig {
bucket: string;
+ autoRetry?: boolean;
+ maxRetries?: number;
+ promise?: typeof Promise;
+ apiEndpoint?: string;
+ keyFilename?: string;
+ keyFile?: string;
+ credentials?: CredentialBody;
+ clientOptions?: JWTOptions | OAuth2ClientOptions | UserRefreshClientOptions;
+ scopes?: string | string[];
+ projectId?: string;
+}
+
+export interface CredentialBody {
+ client_email?: string;
+ private_key?: string;
+}
+
+export interface JWTOptions extends RefreshOptions {
+ email?: string;
+ keyFile?: string;
+ key?: string;
+ keyId?: string;
+ scopes?: string | string[];
+ subject?: string;
+ additionalClaims?: {};
+}
+
+export interface OAuth2ClientOptions extends RefreshOptions {
+ clientId?: string;
+ clientSecret?: string;
+ redirectUri?: string;
+}
+
+export interface UserRefreshClientOptions extends RefreshOptions {
+ clientId?: string;
+ clientSecret?: string;
+ refreshToken?: string;
+}
+
+export interface RefreshOptions {
+ eagerRefreshThresholdMillis?: number;
+ forceRefreshOnFailure?: boolean;
}
diff --git a/src/Storage/LocalStorage.ts b/src/Storage/LocalStorage.ts
index 1f3505e..75a78e1 100644
--- a/src/Storage/LocalStorage.ts
+++ b/src/Storage/LocalStorage.ts
@@ -24,7 +24,7 @@ import {
} from '../types';
import {promisify} from "util";
import {MetadataConverter} from "../utils/MetadataConverter";
-import {InvalidInput} from "../Exceptions/InvalidInput";
+import {InvalidInput} from "../Exceptions";
function handleError(err: Error & { code: string; path?: string }, fullPath: string): Error {
switch (err.code) {
@@ -46,8 +46,8 @@ export class LocalStorage extends Storage {
constructor(config: LocalFileSystemConfig) {
super();
this.$root = resolve(config.root);
- this.$dataDirectory = config.dataDirectory || join(this.$root, 'data');
- this.$metaDirectory = config.metadataDirectory || join(this.$root, 'meta');
+ this.$dataDirectory = config.dataDirectory ? resolve(config.dataDirectory) : join(this.$root, 'data');
+ this.$metaDirectory = config.metadataDirectory ? resolve(config.metadataDirectory) : join(this.$root, 'meta');
}
static fromConfig(config: LocalFileSystemConfig): Storage {
@@ -90,7 +90,7 @@ export class LocalStorage extends Storage {
*/
public async delete(location: string): Promise {
const fullPath = this.dataPath(location);
- let wasDeleted: boolean = true;
+ let wasDeleted = true;
try {
await Promise.all([
@@ -271,7 +271,7 @@ export class LocalStorage extends Storage {
}
private async *flatListAbsolute(prefix: string): AsyncGenerator {
- let prefixDirectory = (prefix[prefix.length-1] === '/') ? prefix : dirname(prefix);
+ const prefixDirectory = (prefix[prefix.length-1] === '/') ? prefix : dirname(prefix);
try {
for (const file of await promisify(fs.readdir)(prefixDirectory, {withFileTypes: true, encoding: 'utf-8'})) {
diff --git a/src/Storage/Storage.ts b/src/Storage/Storage.ts
index 56cb569..9264787 100644
--- a/src/Storage/Storage.ts
+++ b/src/Storage/Storage.ts
@@ -18,7 +18,6 @@ import {
} from '../types';
export interface StorageConstructor {
- new(...args: any[]): T;
fromConfig(config: object): T;
}
diff --git a/src/Storage/index.ts b/src/Storage/index.ts
index dc66d08..f7fb052 100644
--- a/src/Storage/index.ts
+++ b/src/Storage/index.ts
@@ -9,10 +9,12 @@ import { AmazonWebServicesS3Storage } from './AmazonWebServicesS3Storage';
import { AzureBlockBlobStorage } from "./AzureBlockBlobStorage";
import { GoogleCloudStorage } from './GoogleCloudStorage';
import { LocalStorage } from './LocalStorage';
+import { Storage as AbstractStorage } from './Storage';
-export default {
+export {
AmazonWebServicesS3Storage,
AzureBlockBlobStorage,
GoogleCloudStorage,
LocalStorage,
+ AbstractStorage,
};
diff --git a/src/StorageManager.ts b/src/StorageManager.ts
index e172da0..c424e25 100644
--- a/src/StorageManager.ts
+++ b/src/StorageManager.ts
@@ -17,12 +17,12 @@ export class StorageManager {
/**
* Configuration of the storage manager.
*/
- private readonly _config: StorageManagerConfig;
+ private _config: StorageManagerConfig;
/**
* Created disk instances
*/
- private readonly _diskInstances: Map;
+ private _diskInstances: Map;
/**
* List of available storages
@@ -30,14 +30,18 @@ export class StorageManager {
private _storages: Map = new Map();
constructor(config: StorageManagerConfig) {
- this._config = Object.assign({disks: {}}, config);
- this._diskInstances = new Map();
+ this.config(config);
this.registerStorage('azureBlob', AzureBlockBlobStorage);
this.registerStorage('gcs', GoogleCloudStorage);
this.registerStorage('local', LocalStorage);
this.registerStorage('s3', AmazonWebServicesS3Storage);
}
+ config(config: StorageManagerConfig) {
+ this._config = Object.assign({disks: {}}, config);
+ this._diskInstances = new Map();
+ }
+
addDisk(name: string, config: StorageManagerDiskConfig) {
if (this._config.disks.hasOwnProperty(name)) {
throw InvalidConfig.duplicateDiskName(name);
diff --git a/src/index.ts b/src/index.ts
index 09c45d8..f4e3796 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,3 +1,6 @@
-export { StorageManager } from './StorageManager';
+import {StorageManager} from './StorageManager';
+
+export {StorageManager};
export * from './Storage';
export * from './types';
+export const manager = new StorageManager({disks: {}});
diff --git a/test/tsconfig.json b/test/tsconfig.json
new file mode 100644
index 0000000..e8dd22a
--- /dev/null
+++ b/test/tsconfig.json
@@ -0,0 +1,4 @@
+{
+ "extends": "../tsconfig",
+ "include": ["./**/*"]
+}
diff --git a/test/unit/azure-bbs-driver.spec.ts b/test/unit/azure-bbs-driver.spec.ts
index f82b100..0e5b025 100644
--- a/test/unit/azure-bbs-driver.spec.ts
+++ b/test/unit/azure-bbs-driver.spec.ts
@@ -2,7 +2,7 @@ import crypto from 'crypto';
import fs from 'fs';
import uuid from 'uuid';
-import { AzureBlockBlobStorage } from "../../src/Storage/AzureBlockBlobStorage";
+import { AzureBlockBlobStorage } from '../../src/Storage';
import { BlobServiceClient } from "@azure/storage-blob";
import { streamToString } from "../../src/utils/streamToString";
import { AuthorizationRequired } from "../../src/Exceptions";
diff --git a/test/unit/gcs-driver.spec.ts b/test/unit/gcs-driver.spec.ts
index 2fc9372..87cf9ba 100644
--- a/test/unit/gcs-driver.spec.ts
+++ b/test/unit/gcs-driver.spec.ts
@@ -1,6 +1,6 @@
import uuid from 'uuid/v4';
-import { GoogleCloudStorage } from '../../src/Storage/GoogleCloudStorage';
+import { GoogleCloudStorage } from '../../src/Storage';
import { runGenericStorageSpec } from "../stubs/storage.generic";
const testBucket = process.env.GCS_BUCKET || 'flydrive-test';
diff --git a/test/unit/local-driver.spec.ts b/test/unit/local-driver.spec.ts
index 0ae5f4a..b4f8c8a 100644
--- a/test/unit/local-driver.spec.ts
+++ b/test/unit/local-driver.spec.ts
@@ -6,7 +6,7 @@
*/
import path from 'path';
-import { LocalStorage } from "../../src/Storage/LocalStorage";
+import { LocalStorage } from '../../src/Storage';
import { runGenericStorageSpec } from "../stubs/storage.generic";
import { MethodNotSupported } from "../../src/Exceptions";
diff --git a/test/unit/s3-driver.spec.ts b/test/unit/s3-driver.spec.ts
index 9d22bb5..43d056f 100644
--- a/test/unit/s3-driver.spec.ts
+++ b/test/unit/s3-driver.spec.ts
@@ -6,10 +6,10 @@
*/
import S3 from "aws-sdk/clients/s3";
-import { AmazonWebServicesS3Storage, AWSS3Config } from '../../src/Storage/AmazonWebServicesS3Storage';
+import { AmazonWebServicesS3Storage } from '../../src/Storage';
import { runGenericStorageSpec } from "../stubs/storage.generic";
-const config: AWSS3Config = {
+const config = {
key: process.env.S3_KEY || '',
secret: process.env.S3_SECRET || '',
bucket: process.env.S3_BUCKET || '',
@@ -21,13 +21,13 @@ const driver = new S3({
secretAccessKey: config.secret,
...config,
});
-const storage = new AmazonWebServicesS3Storage(driver, config.bucket);
+const storage = AmazonWebServicesS3Storage.fromConfig(config);
describe('Amazon Web Services S3 Storage', () => {
describe('.getUrl', () => {
test('get public url to a file', () => {
const url = storage.getUrl('dummy-file1.txt');
- expect(url).toStrictEqual(`https://${driver.endpoint.host}/${config.bucket}/dummy-file1.txt`);
+ expect(url).toStrictEqual(`https://${config.bucket}.${driver.endpoint.host}/dummy-file1.txt`);
});
test('get public url to a file when region is not defined', () => {
diff --git a/test/unit/storage-manager.spec.ts b/test/unit/storage-manager.spec.ts
index e8011c9..a6b35e2 100644
--- a/test/unit/storage-manager.spec.ts
+++ b/test/unit/storage-manager.spec.ts
@@ -7,8 +7,8 @@
import { resolve } from 'path';
-import { StorageManager } from '../../src/StorageManager';
-import { LocalStorage } from '../../src/Storage/LocalStorage';
+import { StorageManager } from '../../src';
+import { LocalStorage } from '../../src/Storage';
import { Storage } from "../../src/Storage/Storage";
describe('Storage Manager', () => {
diff --git a/tsconfig.json b/tsconfig.json
index cdc65a2..3e9f0ba 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -2,10 +2,9 @@
"include": ["src/**/*"],
"exclude": ["node_modules"],
"compilerOptions": {
- "allowSyntheticDefaultImports": true,
"declaration": true,
"esModuleInterop": true,
- "lib": ["es2017"],
+ "lib": ["es2017", "dom"],
"module": "commonjs",
"moduleResolution": "node",
"noUnusedLocals": true,
@@ -13,6 +12,6 @@
"outDir": "build",
"removeComments": false,
"strictNullChecks": true,
- "target": "es2017",
+ "target": "es2017"
}
}