Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
</p>

<p align="center">
<a href="https://www.npmjs.com/package/@slynova/flydrive"><img src="https://img.shields.io/npm/dm/@slynova/flydrive.svg?style=flat-square" alt="Download"></a>
<a href="https://www.npmjs.com/package/@slynova/flydrive"><img src="https://img.shields.io/npm/v/@slynova/flydrive.svg?style=flat-square" alt="Version"></a>
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/npm/l/@slynova/flydrive.svg?style=flat-square" alt="License"></a>
<a href="https://www.npmjs.com/package/@pdspicer/flydrive"><img src="https://img.shields.io/npm/dm/@pdspicer/flydrive.svg?style=flat-square" alt="Download"></a>
<a href="https://www.npmjs.com/package/@pdspicer/flydrive"><img src="https://img.shields.io/npm/v/@pdspicer/flydrive.svg?style=flat-square" alt="Version"></a>
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/npm/l/@pdspicer/flydrive.svg?style=flat-square" alt="License"></a>
</p>

`flydrive` is a framework-agnostic package which provides a powerful wrapper to manage file Storage in [Node.js](https://nodejs.org).
Expand All @@ -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);
```

Expand All @@ -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<LocalFileSystem>('local');
```
Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -20,7 +20,8 @@
"contributors": [
"Harminder Virk <virk@adonisjs.com>",
"Michaël Zasso <targos@pm.me>",
"Krzysztof Chrapka <krzysztof.chrapka gamfi pl>"
"Krzysztof Chrapka <krzysztof.chrapka gamfi pl>",
"Paul Spicer <pdspicer@gmail.com>"
],
"files": [
"build"
Expand Down
75 changes: 72 additions & 3 deletions src/Storage/AmazonWebServicesS3Storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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) {
Expand All @@ -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();
}

Expand Down Expand Up @@ -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<AWSCredentials>;
providers: AWSCredentials[]|(() => AWSCredentials)[];
}

28 changes: 18 additions & 10 deletions src/Storage/AzureBlockBlobStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@
* @author Christopher Chrapka <krzysztof.chrapka gamfi pl>
*/
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 {
Expand All @@ -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";

Expand All @@ -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),
);
Expand Down Expand Up @@ -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
},
Expand Down
58 changes: 53 additions & 5 deletions src/Storage/GoogleCloudStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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) {
Expand All @@ -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();
}

Expand Down Expand Up @@ -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;
}
10 changes: 5 additions & 5 deletions src/Storage/LocalStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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 {
Expand Down Expand Up @@ -90,7 +90,7 @@ export class LocalStorage extends Storage {
*/
public async delete(location: string): Promise<DeleteResponse> {
const fullPath = this.dataPath(location);
let wasDeleted: boolean = true;
let wasDeleted = true;

try {
await Promise.all([
Expand Down Expand Up @@ -271,7 +271,7 @@ export class LocalStorage extends Storage {
}

private async *flatListAbsolute(prefix: string): AsyncGenerator<FileListResponse> {
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'})) {
Expand Down
1 change: 0 additions & 1 deletion src/Storage/Storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import {
} from '../types';

export interface StorageConstructor<T extends Storage = Storage> {
new(...args: any[]): T;
fromConfig(config: object): T;
}

Expand Down
4 changes: 3 additions & 1 deletion src/Storage/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Loading