@flystorage/file-storage 0.0.3 → 0.0.4

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,128 @@
1
+ <img src="https://avatars.githubusercontent.com/u/151840999" width="50px" height="50px" />
2
+
3
+ # Flystorage
4
+ Flystorage is a file storage abstraction for NodeJS and TypeScript. It is an 80/20 solution
5
+ that is built around a set of goals:
6
+
7
+ - Provide a straight-forward API that is easy to use.
8
+ - Allow application code to be unaware WHERE files are stored.
9
+ - Pragmatically smooth over underlying storage differences.
10
+ - Expose an async/await based API, promises all the way.
11
+ - Abstract over file permissions using "visibility".
12
+ - Actually tested using real integrations, _mocks are not welcome_.
13
+ - Stand on the shoulders of giants, use official vendor packages when possible.
14
+
15
+ ### What is Flystorage NOT:
16
+ Flystorage is meant to be used in cases for generic file storage use-cases. It's not an API for
17
+ any specific filesystem. It's a generalised solution and will not implement feature only
18
+ specific to one particular storage implementation. There will be use-cases that are not catered
19
+ to, simply because they cannot be abstracted over in a reasonable manner.
20
+
21
+ ## Capabilities
22
+
23
+ ### Implemented
24
+ - [x] Write files using string | buffer | readable/stream
25
+ - [x] Read files as stream, string, or Uint8Array
26
+ - [x] Set permissions using abstracted visibility
27
+ - [x] List the contents of a directory/prefix, (shallow and deep).
28
+ - [x] Delete files without failing when they don't exist.
29
+ - [x] Delete directories (and any files it contains)
30
+ - [x] Generate public URLs.
31
+ - [x] Generate temporary (signed) URLs.
32
+ - [x] Expose or calculate checksums for files.
33
+ - [x] Mime-type resolving
34
+ - [x] Last modified fetching
35
+ - [x] File size
36
+
37
+ ### Planned
38
+ - [ ] Moving files
39
+ - [ ] Copying files
40
+
41
+ ## Implementations / Adapters
42
+
43
+ ### Implemented
44
+ - [x] Local Filesystem
45
+ - [x] AWS S3 (using the V3 SDK)
46
+
47
+ ### Planned
48
+
49
+ #### Prio 1
50
+ - [ ] Azure Blob Storage
51
+ - [ ] Test implementation (in-memory, with staged errors)
52
+ - [ ] Google Cloud Storage
53
+
54
+ ### Prio 2
55
+ - [ ] FTP (using `basic-ftp`)
56
+ - [ ] SFTP (?)
57
+
58
+ ## Usage
59
+ Install the main package and any adapters you might need:
60
+
61
+ ```bash
62
+ npm i -S @flystorage/file-storage
63
+
64
+ # for using AWS S3
65
+ npm i -S @flystorage/aws-s3
66
+
67
+ # for using the local filesystem
68
+ npm i -S @flystorage/local-fs
69
+ ```
70
+
71
+ ## Local Usage
72
+ ```typescript
73
+ import {resolve} from 'node:path';
74
+ import {createReadStream} from 'node:fs';
75
+ import {FileStorage, Visibility} from '@flystorage/file-storage';
76
+ import {LocalFileStorage} from '@flystorage/local-fs';
77
+
78
+ /**
79
+ * SETUP
80
+ **/
81
+
82
+ const rootDirectory = resolve(process.cwd(), 'my-files');
83
+ const storage = new FileStorage(new LocalFileStorage(rootDirectory));
84
+
85
+ /**
86
+ * USAGE
87
+ **/
88
+
89
+ // Write using a string
90
+ await storage.write('write-from-a-string.txt', 'file contents');
91
+
92
+ // Write using a stream
93
+ const stream = createReadStream(resolve(process.cwd(), 'test-files/picture.png'));
94
+ await storage.write('picture.png', stream);
95
+
96
+ // Write with visibility (permissions).
97
+ await storage.write('public.txt', 'debug', {
98
+ visibility: Visibility.PUBLIC, // mode: 0o644
99
+ });
100
+ await storage.write('private.txt', 'debug', {
101
+ visibility: Visibility.PRIVATE, // mode: 0o600
102
+ });
103
+
104
+ // List directory contents
105
+ const contentsAsAsyncGenerator = storage.list('', {deep: true});
106
+
107
+ for await (const item of contentsAsAsyncGenerator) {
108
+ console.log(item.path);
109
+
110
+ if (item.isFile) {
111
+ // do something with the file
112
+ } else if (item.isDirectory) {
113
+ // do something with the directory
114
+ }
115
+ }
116
+
117
+ // Delete a file
118
+ await storage.deleteFile('some-file.txt');
119
+
120
+ // Delete a directory (with all contents)
121
+ await storage.deleteDirectory('some-directory');
122
+ ```
123
+
124
+ ## Author
125
+ Flystorage is built by the maintainer of [Flysystem](https://flysystem.thephpleague.com), a
126
+ filesystem abstraction for PHP. This brings along more than
127
+ a decade of smoothing over filesystem implementation differences
128
+ and weighing trade-offs to make a usable API.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.readableToUint8Array = exports.readableToString = exports.closeReadable = exports.normalizeExpiryToMilliseconds = exports.normalizeExpiryToDate = exports.FileStorage = exports.isDirectory = exports.isFile = void 0;
3
+ exports.readableToUint8Array = exports.readableToString = exports.closeReadable = exports.normalizeExpiryToMilliseconds = exports.normalizeExpiryToDate = exports.FileStorage = exports.DirectoryListing = exports.isDirectory = exports.isFile = void 0;
4
4
  const stream_1 = require("stream");
5
5
  const checksum_from_stream_js_1 = require("./checksum-from-stream.js");
6
6
  const errors = require("./errors.js");
@@ -14,6 +14,45 @@ function isDirectory(stat) {
14
14
  return stat.isDirectory;
15
15
  }
16
16
  exports.isDirectory = isDirectory;
17
+ class DirectoryListing {
18
+ listing;
19
+ path;
20
+ deep;
21
+ constructor(listing, path, deep) {
22
+ this.listing = listing;
23
+ this.path = path;
24
+ this.deep = deep;
25
+ }
26
+ async toArray(sorted = true) {
27
+ const items = [];
28
+ for await (const item of this.listing) {
29
+ items.push(item);
30
+ }
31
+ return sorted ? items.sort((a, b) => naturalSorting.compare(a.path, b.path)) : items;
32
+ }
33
+ filter(filter) {
34
+ const listing = this.listing;
35
+ const filtered = (async function* () {
36
+ for await (const entry of listing) {
37
+ if (filter(entry)) {
38
+ yield entry;
39
+ }
40
+ }
41
+ })();
42
+ return new DirectoryListing(filtered, this.path, this.deep);
43
+ }
44
+ async *[Symbol.asyncIterator]() {
45
+ try {
46
+ for await (const item of this.listing) {
47
+ yield item;
48
+ }
49
+ }
50
+ catch (error) {
51
+ throw errors.UnableToListDirectory.because(errors.errorToMessage(error), { cause: error, context: { path: this.path, deep: this.deep } });
52
+ }
53
+ }
54
+ }
55
+ exports.DirectoryListing = DirectoryListing;
17
56
  function toReadable(contents) {
18
57
  if (contents instanceof stream_1.Readable) {
19
58
  return contents;
@@ -57,6 +96,9 @@ class FileStorage {
57
96
  async readToUint8Array(path) {
58
97
  return await readableToUint8Array(await this.read(path));
59
98
  }
99
+ async readToBuffer(path) {
100
+ return Buffer.from(await this.readToUint8Array(path));
101
+ }
60
102
  async deleteFile(path) {
61
103
  try {
62
104
  await this.adapter.deleteFile(this.pathNormalizer.normalizePath(path));
@@ -114,26 +156,7 @@ class FileStorage {
114
156
  }
115
157
  }
116
158
  list(path, { deep = false } = {}) {
117
- const listing = this.adapter.list(this.pathNormalizer.normalizePath(path), { deep });
118
- return {
119
- async toArray(sorted = true) {
120
- const items = [];
121
- for await (const item of listing) {
122
- items.push(item);
123
- }
124
- return sorted ? items.sort((a, b) => naturalSorting.compare(a.path, b.path)) : items;
125
- },
126
- async *[Symbol.asyncIterator]() {
127
- try {
128
- for await (const item of listing) {
129
- yield item;
130
- }
131
- }
132
- catch (error) {
133
- throw errors.UnableToListDirectory.because(errors.errorToMessage(error), { cause: error, context: { path, deep } });
134
- }
135
- }
136
- };
159
+ return new DirectoryListing(this.adapter.list(this.pathNormalizer.normalizePath(path), { deep }), path, deep);
137
160
  }
138
161
  async statFile(path) {
139
162
  const stat = await this.stat(path);
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.readableToUint8Array = exports.readableToString = exports.closeReadable = exports.normalizeExpiryToMilliseconds = exports.normalizeExpiryToDate = exports.FileStorage = exports.isDirectory = exports.isFile = void 0;
3
+ exports.readableToUint8Array = exports.readableToString = exports.closeReadable = exports.normalizeExpiryToMilliseconds = exports.normalizeExpiryToDate = exports.FileStorage = exports.DirectoryListing = exports.isDirectory = exports.isFile = void 0;
4
4
  const stream_1 = require("stream");
5
5
  const checksum_from_stream_js_1 = require("./checksum-from-stream.js");
6
6
  const errors = require("./errors.js");
@@ -14,6 +14,45 @@ function isDirectory(stat) {
14
14
  return stat.isDirectory;
15
15
  }
16
16
  exports.isDirectory = isDirectory;
17
+ class DirectoryListing {
18
+ listing;
19
+ path;
20
+ deep;
21
+ constructor(listing, path, deep) {
22
+ this.listing = listing;
23
+ this.path = path;
24
+ this.deep = deep;
25
+ }
26
+ async toArray(sorted = true) {
27
+ const items = [];
28
+ for await (const item of this.listing) {
29
+ items.push(item);
30
+ }
31
+ return sorted ? items.sort((a, b) => naturalSorting.compare(a.path, b.path)) : items;
32
+ }
33
+ filter(filter) {
34
+ const listing = this.listing;
35
+ const filtered = (async function* () {
36
+ for await (const entry of listing) {
37
+ if (filter(entry)) {
38
+ yield entry;
39
+ }
40
+ }
41
+ })();
42
+ return new DirectoryListing(filtered, this.path, this.deep);
43
+ }
44
+ async *[Symbol.asyncIterator]() {
45
+ try {
46
+ for await (const item of this.listing) {
47
+ yield item;
48
+ }
49
+ }
50
+ catch (error) {
51
+ throw errors.UnableToListDirectory.because(errors.errorToMessage(error), { cause: error, context: { path: this.path, deep: this.deep } });
52
+ }
53
+ }
54
+ }
55
+ exports.DirectoryListing = DirectoryListing;
17
56
  function toReadable(contents) {
18
57
  if (contents instanceof stream_1.Readable) {
19
58
  return contents;
@@ -57,6 +96,9 @@ class FileStorage {
57
96
  async readToUint8Array(path) {
58
97
  return await readableToUint8Array(await this.read(path));
59
98
  }
99
+ async readToBuffer(path) {
100
+ return Buffer.from(await this.readToUint8Array(path));
101
+ }
60
102
  async deleteFile(path) {
61
103
  try {
62
104
  await this.adapter.deleteFile(this.pathNormalizer.normalizePath(path));
@@ -114,26 +156,7 @@ class FileStorage {
114
156
  }
115
157
  }
116
158
  list(path, { deep = false } = {}) {
117
- const listing = this.adapter.list(this.pathNormalizer.normalizePath(path), { deep });
118
- return {
119
- async toArray(sorted = true) {
120
- const items = [];
121
- for await (const item of listing) {
122
- items.push(item);
123
- }
124
- return sorted ? items.sort((a, b) => naturalSorting.compare(a.path, b.path)) : items;
125
- },
126
- async *[Symbol.asyncIterator]() {
127
- try {
128
- for await (const item of listing) {
129
- yield item;
130
- }
131
- }
132
- catch (error) {
133
- throw errors.UnableToListDirectory.because(errors.errorToMessage(error), { cause: error, context: { path, deep } });
134
- }
135
- }
136
- };
159
+ return new DirectoryListing(this.adapter.list(this.pathNormalizer.normalizePath(path), { deep }), path, deep);
137
160
  }
138
161
  async statFile(path) {
139
162
  const stat = await this.stat(path);
@@ -1,6 +1,7 @@
1
1
  /// <reference types="node" />
2
2
  /// <reference types="node" />
3
3
  /// <reference types="node" />
4
+ /// <reference types="node" />
4
5
  import { BinaryToTextEncoding } from 'crypto';
5
6
  import { Readable } from 'stream';
6
7
  import { PathNormalizer } from './path-normalizer.js';
@@ -45,8 +46,14 @@ export interface StorageAdapter {
45
46
  lastModified(path: string): Promise<number>;
46
47
  fileSize(path: string): Promise<number>;
47
48
  }
48
- export interface DirectoryListing extends AsyncIterable<StatEntry> {
49
+ export declare class DirectoryListing implements AsyncIterable<StatEntry> {
50
+ private readonly listing;
51
+ private readonly path;
52
+ private readonly deep;
53
+ constructor(listing: AsyncGenerator<StatEntry>, path: string, deep: boolean);
49
54
  toArray(sorted?: boolean): Promise<StatEntry[]>;
55
+ filter(filter: (entry: StatEntry) => boolean): DirectoryListing;
56
+ [Symbol.asyncIterator](): AsyncGenerator<StatEntry, void, unknown>;
50
57
  }
51
58
  export type FileContents = Iterable<any> | AsyncIterable<any> | NodeJS.ReadableStream | Readable;
52
59
  export type MiscellaneousOptions = {
@@ -86,6 +93,7 @@ export declare class FileStorage {
86
93
  read(path: string): Promise<Readable>;
87
94
  readToString(path: string): Promise<string>;
88
95
  readToUint8Array(path: string): Promise<Uint8Array>;
96
+ readToBuffer(path: string): Promise<Buffer>;
89
97
  deleteFile(path: string): Promise<void>;
90
98
  createDirectory(path: string, options?: CreateDirectoryOptions): Promise<void>;
91
99
  deleteDirectory(path: string): Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flystorage/file-storage",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "File-storage abstraction: multiple filesystems, one API.",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "types": "./dist/types/index.d.ts",
@@ -24,6 +24,11 @@
24
24
  "watch": "tsc --watch"
25
25
  },
26
26
  "author": "Frank de Jonge (https://frankdejonge.nl)",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/flystorage/flystorage.git",
30
+ "directory": "packages/file-storage"
31
+ },
27
32
  "keywords": ["fs", "file", "files", "filesystem", "filesystems", "storage"],
28
33
  "license": "MIT"
29
34
  }