@theshelf/filestore 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,72 @@
1
+
2
+ # File Store | The Shelf
3
+
4
+ The file store package provides a universal interaction layer with an actual file storage solution.
5
+
6
+ ## Installation
7
+
8
+ ```bash
9
+ npm install @theshelf/filestore
10
+ ```
11
+
12
+ ## Implementations
13
+
14
+ Currently, there are two implementations:
15
+
16
+ * **Memory** - non-persistent in memory storage (suited for testing).
17
+ * **Minio** - persistent S3 compatible object storage.
18
+
19
+ ## Configuration
20
+
21
+ The used implementation needs to be configured in the `.env` file.
22
+
23
+ ```env
24
+ FILE_STORE_IMPLEMENTATION="minio" # (memory | minio)
25
+ ```
26
+
27
+ In case of Minio, additional configuration is required.
28
+
29
+ ```env
30
+ MINIO_END_POINT="address"
31
+ MINIO_PORT_NUMBER=9000
32
+ MINIO_USE_SSL=true
33
+ MINIO_ACCESS_KEY="development"
34
+ MINIO_SECRET_KEY="secret"
35
+ ```
36
+
37
+ ## How to use
38
+
39
+ An instance of the configured file storage implementation can be imported for performing file operations.
40
+
41
+ ```ts
42
+ import fileStorage from '@theshelf/filestorage';
43
+
44
+ // Perform operations with the fileStorage instance
45
+ ```
46
+
47
+ ### Operations
48
+
49
+ ```ts
50
+ import fileStorage from '@theshelf/filestorage';
51
+
52
+ // Open connection
53
+ await fileStorage.connect();
54
+
55
+ // Close connection
56
+ await fileStorage.disconnect();
57
+
58
+ // Check if a file exists
59
+ const exists: boolean = await fileStorage.hasFile('path/to/file.txt');
60
+
61
+ // Write a file to the storage
62
+ const data: Buffer = Buffer.from('Something interesting');
63
+ await fileStorage.writeFile('path/to/file.txt', data);
64
+
65
+ // Read a file from storage
66
+ // Throws FileNotFound if not found
67
+ const data: Buffer = await fileStorage.readFile('path/to/file.txt');
68
+
69
+ // Delete a file from storage
70
+ // Throws FileNotFound if not found
71
+ await fileStorage.deleteFile('path/to/file.txt');
72
+ ```
@@ -0,0 +1,10 @@
1
+ export interface FileStore {
2
+ get connected(): boolean;
3
+ connect(): Promise<void>;
4
+ disconnect(): Promise<void>;
5
+ hasFile(path: string): Promise<boolean>;
6
+ writeFile(path: string, data: Buffer): Promise<void>;
7
+ readFile(path: string): Promise<Buffer>;
8
+ deleteFile(path: string): Promise<void>;
9
+ clear(): Promise<void>;
10
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ import FileSystemError from './FileSystemError';
2
+ export default class FileNotFound extends FileSystemError {
3
+ constructor(path?: string);
4
+ }
@@ -0,0 +1,6 @@
1
+ import FileSystemError from './FileSystemError';
2
+ export default class FileNotFound extends FileSystemError {
3
+ constructor(path) {
4
+ super(path ? `File not found: ${path}` : 'File not found');
5
+ }
6
+ }
@@ -0,0 +1,2 @@
1
+ export default class FileSystemError extends Error {
2
+ }
@@ -0,0 +1,2 @@
1
+ export default class FileSystemError extends Error {
2
+ }
@@ -0,0 +1,4 @@
1
+ import FileSystemError from './FileSystemError';
2
+ export default class NotConnected extends FileSystemError {
3
+ constructor(message?: string);
4
+ }
@@ -0,0 +1,6 @@
1
+ import FileSystemError from './FileSystemError';
2
+ export default class NotConnected extends FileSystemError {
3
+ constructor(message) {
4
+ super(message ?? 'File system not connected');
5
+ }
6
+ }
@@ -0,0 +1,4 @@
1
+ import FileSystemError from './FileSystemError';
2
+ export default class UnknownImplementation extends FileSystemError {
3
+ constructor(name: string);
4
+ }
@@ -0,0 +1,6 @@
1
+ import FileSystemError from './FileSystemError';
2
+ export default class UnknownImplementation extends FileSystemError {
3
+ constructor(name) {
4
+ super(`Unknown file system implementation: ${name}`);
5
+ }
6
+ }
@@ -0,0 +1,3 @@
1
+ import type { FileStore } from './definitions/interfaces';
2
+ declare const _default: FileStore;
3
+ export default _default;
@@ -0,0 +1,14 @@
1
+ import UnknownImplementation from './errors/UnknownImplementation';
2
+ import createMemoryFS from './implementations/memory/create';
3
+ import createMinioFS from './implementations/minio/create';
4
+ const implementations = new Map([
5
+ ['memory', createMemoryFS],
6
+ ['minio', createMinioFS],
7
+ ]);
8
+ const DEFAULT_FILE_STORE_IMPLEMENTATION = 'memory';
9
+ const implementationName = process.env.FILE_STORE_IMPLEMENTATION ?? DEFAULT_FILE_STORE_IMPLEMENTATION;
10
+ const creator = implementations.get(implementationName.toLowerCase());
11
+ if (creator === undefined) {
12
+ throw new UnknownImplementation(implementationName);
13
+ }
14
+ export default creator();
@@ -0,0 +1,12 @@
1
+ import type { FileStore } from '../../definitions/interfaces';
2
+ export default class Memory implements FileStore {
3
+ #private;
4
+ get connected(): boolean;
5
+ connect(): Promise<void>;
6
+ disconnect(): Promise<void>;
7
+ hasFile(path: string): Promise<boolean>;
8
+ writeFile(path: string, data: Buffer): Promise<void>;
9
+ readFile(path: string): Promise<Buffer>;
10
+ deleteFile(path: string): Promise<void>;
11
+ clear(): Promise<void>;
12
+ }
@@ -0,0 +1,45 @@
1
+ import FileNotFound from '../../errors/FileNotFound';
2
+ import NotConnected from '../../errors/NotConnected';
3
+ export default class Memory {
4
+ #files = new Map();
5
+ #connected = false;
6
+ get connected() { return this.#connected; }
7
+ async connect() {
8
+ this.#connected = true;
9
+ }
10
+ async disconnect() {
11
+ this.#connected = false;
12
+ }
13
+ async hasFile(path) {
14
+ const files = this.#getFiles();
15
+ return files.has(path);
16
+ }
17
+ async writeFile(path, data) {
18
+ const files = this.#getFiles();
19
+ files.set(path, data);
20
+ }
21
+ async readFile(path) {
22
+ const files = this.#getFiles();
23
+ const data = files.get(path);
24
+ if (data === undefined) {
25
+ throw new FileNotFound(path);
26
+ }
27
+ return data;
28
+ }
29
+ async deleteFile(path) {
30
+ const files = this.#getFiles();
31
+ if (files.has(path) === false) {
32
+ throw new FileNotFound(path);
33
+ }
34
+ files.delete(path);
35
+ }
36
+ #getFiles() {
37
+ if (this.#files === undefined) {
38
+ throw new NotConnected();
39
+ }
40
+ return this.#files;
41
+ }
42
+ async clear() {
43
+ this.#files.clear();
44
+ }
45
+ }
@@ -0,0 +1,2 @@
1
+ import Memory from './Memory';
2
+ export default function create(): Memory;
@@ -0,0 +1,4 @@
1
+ import Memory from './Memory';
2
+ export default function create() {
3
+ return new Memory();
4
+ }
@@ -0,0 +1,14 @@
1
+ import type { ClientOptions } from 'minio';
2
+ import type { FileStore } from '../../definitions/interfaces';
3
+ export default class MinioFS implements FileStore {
4
+ #private;
5
+ constructor(configuration: ClientOptions);
6
+ get connected(): boolean;
7
+ connect(): Promise<void>;
8
+ disconnect(): Promise<void>;
9
+ hasFile(path: string): Promise<boolean>;
10
+ writeFile(path: string, data: Buffer): Promise<void>;
11
+ readFile(path: string): Promise<Buffer>;
12
+ deleteFile(path: string): Promise<void>;
13
+ clear(): Promise<void>;
14
+ }
@@ -0,0 +1,88 @@
1
+ import { Client } from 'minio';
2
+ import FileNotFound from '../../errors/FileNotFound';
3
+ import NotConnected from '../../errors/NotConnected';
4
+ const BUCKET_NAME = 'comify';
5
+ export default class MinioFS {
6
+ #configuration;
7
+ #client;
8
+ constructor(configuration) {
9
+ this.#configuration = configuration;
10
+ }
11
+ get connected() {
12
+ return this.#client !== undefined;
13
+ }
14
+ async connect() {
15
+ this.#client = new Client(this.#configuration);
16
+ if (await this.#client.bucketExists(BUCKET_NAME) === false) {
17
+ await this.#client.makeBucket(BUCKET_NAME);
18
+ }
19
+ }
20
+ async disconnect() {
21
+ if (this.#client === undefined) {
22
+ throw new NotConnected();
23
+ }
24
+ this.#client = undefined;
25
+ }
26
+ async hasFile(path) {
27
+ const client = this.#getClient();
28
+ try {
29
+ await client.statObject(BUCKET_NAME, path);
30
+ return true;
31
+ }
32
+ catch (error) {
33
+ const customError = this.#handleError(error, path);
34
+ if (customError instanceof FileNotFound) {
35
+ return false;
36
+ }
37
+ throw error;
38
+ }
39
+ }
40
+ async writeFile(path, data) {
41
+ const client = this.#getClient();
42
+ try {
43
+ await client.putObject(BUCKET_NAME, path, data);
44
+ }
45
+ catch (error) {
46
+ throw this.#handleError(error, path);
47
+ }
48
+ }
49
+ async readFile(path) {
50
+ const client = this.#getClient();
51
+ try {
52
+ const stream = await client.getObject(BUCKET_NAME, path);
53
+ const chunks = await stream.toArray();
54
+ return Buffer.concat(chunks);
55
+ }
56
+ catch (error) {
57
+ throw this.#handleError(error, path);
58
+ }
59
+ }
60
+ async deleteFile(path) {
61
+ const client = this.#getClient();
62
+ try {
63
+ await client.removeObject(BUCKET_NAME, path);
64
+ }
65
+ catch (error) {
66
+ throw this.#handleError(error, path);
67
+ }
68
+ }
69
+ async clear() {
70
+ return; // Deliberately not implemented
71
+ }
72
+ #getClient() {
73
+ if (this.#client === undefined) {
74
+ throw new NotConnected();
75
+ }
76
+ return this.#client;
77
+ }
78
+ #handleError(error, path) {
79
+ if (error instanceof Error && this.#isNotFoundError(error)) {
80
+ return new FileNotFound(path);
81
+ }
82
+ return error;
83
+ }
84
+ #isNotFoundError(error) {
85
+ return error.message.startsWith('The specified key does not exist')
86
+ || error.message.startsWith('Not Found');
87
+ }
88
+ }
@@ -0,0 +1,2 @@
1
+ import MinioFS from './MinioFS';
2
+ export default function create(): MinioFS;
@@ -0,0 +1,9 @@
1
+ import MinioFS from './MinioFS';
2
+ export default function create() {
3
+ const endPoint = process.env.MINIO_END_POINT ?? 'undefined';
4
+ const port = Number(process.env.MINIO_PORT_NUMBER ?? 9000);
5
+ const useSSL = process.env.MINIO_USE_SSL === 'true';
6
+ const accessKey = process.env.MINIO_ROOT_USER ?? 'undefined';
7
+ const secretKey = process.env.MINIO_ROOT_PASSWORD ?? 'undefined';
8
+ return new MinioFS({ endPoint, port, useSSL, accessKey, secretKey });
9
+ }
@@ -0,0 +1,6 @@
1
+ export type { FileStore } from './definitions/interfaces';
2
+ export { default as FileNotFound } from './errors/FileNotFound';
3
+ export { default as FileSystemError } from './errors/FileSystemError';
4
+ export { default as NotConnected } from './errors/NotConnected';
5
+ export { default as UnknownImplementation } from './errors/UnknownImplementation';
6
+ export { default } from './implementation';
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { default as FileNotFound } from './errors/FileNotFound';
2
+ export { default as FileSystemError } from './errors/FileSystemError';
3
+ export { default as NotConnected } from './errors/NotConnected';
4
+ export { default as UnknownImplementation } from './errors/UnknownImplementation';
5
+ export { default } from './implementation';
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@theshelf/filestore",
3
+ "private": false,
4
+ "version": "0.0.1",
5
+ "type": "module",
6
+ "scripts": {
7
+ "build": "tsc",
8
+ "clean": "rimraf dist",
9
+ "test": "vitest run",
10
+ "test-coverage": "vitest run --coverage",
11
+ "lint": "eslint",
12
+ "review": "npm run build && npm run lint && npm run test",
13
+ "prepublishOnly": "npm run clean && npm run build"
14
+ },
15
+ "files": [
16
+ "README.md",
17
+ "dist"
18
+ ],
19
+ "types": "dist/index.d.ts",
20
+ "exports": "./dist/index.js",
21
+ "dependencies": {
22
+ "minio": "8.0.6"
23
+ }
24
+ }