@flystorage/file-storage 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.
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checksumFromStream = void 0;
4
+ const crypto_1 = require("crypto");
5
+ async function checksumFromStream(stream, options) {
6
+ return new Promise(async (resolve, reject) => {
7
+ const hash = (0, crypto_1.createHash)(options.algo ?? 'md5');
8
+ stream.on('error', reject);
9
+ stream.pipe(hash, { end: false });
10
+ stream.on('end', () => {
11
+ hash.end();
12
+ resolve(hash.digest(options.encoding ?? 'hex'));
13
+ });
14
+ });
15
+ }
16
+ exports.checksumFromStream = checksumFromStream;
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UnableToListDirectory = exports.UnableToCheckDirectoryExistence = exports.UnableToCheckFileExistence = exports.UnableToDeleteFile = exports.UnableToDeleteDirectory = exports.UnableToCreateDirectory = exports.UnableToGetStat = exports.UnableToGetTemporaryUrl = exports.UnableToGetPublicUrl = exports.UnableToGetVisibility = exports.UnableToSetVisibility = exports.UnableToReadFile = exports.UnableToWriteFile = exports.UnableToGetChecksum = exports.ChecksumIsNotAvailable = exports.FlystorageError = exports.errorToMessage = void 0;
4
+ function errorToMessage(error) {
5
+ return error instanceof Error ? error.message : String(error);
6
+ }
7
+ exports.errorToMessage = errorToMessage;
8
+ class FlystorageError extends Error {
9
+ context;
10
+ code = 'unknown_error';
11
+ constructor(message, context = {}, cause = undefined) {
12
+ const options = cause === undefined ? undefined : { cause };
13
+ // @ts-ignore TS2554
14
+ super(message, options);
15
+ this.context = context;
16
+ }
17
+ }
18
+ exports.FlystorageError = FlystorageError;
19
+ /**
20
+ * Thrown when the checksum algo is not supported or not pre-computed. This error
21
+ * is thrown with the intention of falling back to computing it based on a file read.
22
+ */
23
+ class ChecksumIsNotAvailable extends FlystorageError {
24
+ algo;
25
+ code = 'flystorage.checksum_not_supported';
26
+ constructor(message, algo, context = {}, cause = undefined) {
27
+ super(message, context, cause);
28
+ this.algo = algo;
29
+ }
30
+ static checksumNotSupported = (algo, { context = {}, cause = undefined } = {}) => new ChecksumIsNotAvailable(`Checksum algo "${algo}" is not supported`, algo, { ...context, algo }, cause);
31
+ }
32
+ exports.ChecksumIsNotAvailable = ChecksumIsNotAvailable;
33
+ class UnableToGetChecksum extends FlystorageError {
34
+ code = 'flystorage.unable_to_get_checksum';
35
+ static because = (reason, { context = {}, cause = undefined }) => new UnableToWriteFile(`Unable to write the file. Reason: ${reason}`, context, cause);
36
+ }
37
+ exports.UnableToGetChecksum = UnableToGetChecksum;
38
+ class UnableToWriteFile extends FlystorageError {
39
+ code = 'flystorage.unable_to_write_file';
40
+ static because = (reason, { context = {}, cause = undefined }) => new UnableToWriteFile(`Unable to write the file. Reason: ${reason}`, context, cause);
41
+ }
42
+ exports.UnableToWriteFile = UnableToWriteFile;
43
+ class UnableToReadFile extends FlystorageError {
44
+ code = 'flystorage.unable_to_read_file';
45
+ static because = (reason, { context = {}, cause = undefined }) => new UnableToReadFile(`Unable to read the file. Reason: ${reason}`, context, cause);
46
+ }
47
+ exports.UnableToReadFile = UnableToReadFile;
48
+ class UnableToSetVisibility extends FlystorageError {
49
+ code = 'flystorage.unable_to_set_visibility';
50
+ static because = (reason, { context = {}, cause = undefined }) => new UnableToSetVisibility(`Unable to set visibility. Reason: ${reason}`, context, cause);
51
+ }
52
+ exports.UnableToSetVisibility = UnableToSetVisibility;
53
+ class UnableToGetVisibility extends FlystorageError {
54
+ code = 'flystorage.unable_to_get_visibility';
55
+ static because = (reason, { context = {}, cause = undefined }) => new UnableToGetVisibility(`Unable to get visibility. Reason: ${reason}`, context, cause);
56
+ }
57
+ exports.UnableToGetVisibility = UnableToGetVisibility;
58
+ class UnableToGetPublicUrl extends FlystorageError {
59
+ code = 'flystorage.unable_to_get_public_url';
60
+ static because = (reason, { context = {}, cause = undefined }) => new UnableToGetPublicUrl(`Unable to get public URL. Reason: ${reason}`, context, cause);
61
+ }
62
+ exports.UnableToGetPublicUrl = UnableToGetPublicUrl;
63
+ class UnableToGetTemporaryUrl extends FlystorageError {
64
+ code = 'flystorage.unable_to_get_temporary_url';
65
+ static because = (reason, { context = {}, cause = undefined }) => new UnableToGetTemporaryUrl(`Unable to get temporary URL. Reason: ${reason}`, context, cause);
66
+ }
67
+ exports.UnableToGetTemporaryUrl = UnableToGetTemporaryUrl;
68
+ class UnableToGetStat extends FlystorageError {
69
+ code = 'flystorage.unable_to_get_stat';
70
+ static because = (reason, { context = {}, cause = undefined }) => new UnableToGetStat(`Unable to get stat. Reason: ${reason}`, context, cause);
71
+ static noFileStatResolved = ({ context = {}, cause = undefined }) => new UnableToGetStat(`Stat was not a file.`, context, cause);
72
+ }
73
+ exports.UnableToGetStat = UnableToGetStat;
74
+ class UnableToCreateDirectory extends FlystorageError {
75
+ code = 'flystorage.unable_to_create_directory';
76
+ static because = (reason, { context = {}, cause = undefined }) => new UnableToCreateDirectory(`Unable to create directory. Reason: ${reason}`, context, cause);
77
+ }
78
+ exports.UnableToCreateDirectory = UnableToCreateDirectory;
79
+ class UnableToDeleteDirectory extends FlystorageError {
80
+ code = 'flystorage.unable_to_delete_directory';
81
+ static because = (reason, { context = {}, cause = undefined }) => new UnableToDeleteDirectory(`Unable to delete directory. Reason: ${reason}`, context, cause);
82
+ }
83
+ exports.UnableToDeleteDirectory = UnableToDeleteDirectory;
84
+ class UnableToDeleteFile extends FlystorageError {
85
+ code = 'flystorage.unable_to_delete_file';
86
+ static because = (reason, { context = {}, cause = undefined }) => new UnableToDeleteFile(`Unable to delete file. Reason: ${reason}`, context, cause);
87
+ }
88
+ exports.UnableToDeleteFile = UnableToDeleteFile;
89
+ class UnableToCheckFileExistence extends FlystorageError {
90
+ code = 'flystorage.unable_to_check_file_existence';
91
+ static because = (reason, { context = {}, cause = undefined }) => new UnableToCheckFileExistence(`Unable to check file existence. Reason: ${reason}`, context, cause);
92
+ }
93
+ exports.UnableToCheckFileExistence = UnableToCheckFileExistence;
94
+ class UnableToCheckDirectoryExistence extends FlystorageError {
95
+ code = 'flystorage.unable_to_check_directory_existence';
96
+ static because = (reason, { context = {}, cause = undefined }) => new UnableToCheckDirectoryExistence(`Unable to check directory existence. Reason: ${reason}`, context, cause);
97
+ }
98
+ exports.UnableToCheckDirectoryExistence = UnableToCheckDirectoryExistence;
99
+ class UnableToListDirectory extends FlystorageError {
100
+ code = 'flystorage.unable_to_list_directory_contents';
101
+ static because = (reason, { context = {}, cause = undefined }) => new UnableToListDirectory(`Unable to list directory contents. Reason: ${reason}`, context, cause);
102
+ }
103
+ exports.UnableToListDirectory = UnableToListDirectory;
@@ -0,0 +1,235 @@
1
+ "use strict";
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;
4
+ const stream_1 = require("stream");
5
+ const checksum_from_stream_js_1 = require("./checksum-from-stream.js");
6
+ const errors = require("./errors.js");
7
+ const errors_js_1 = require("./errors.js");
8
+ const path_normalizer_js_1 = require("./path-normalizer.js");
9
+ function isFile(stat) {
10
+ return stat.isFile;
11
+ }
12
+ exports.isFile = isFile;
13
+ function isDirectory(stat) {
14
+ return stat.isDirectory;
15
+ }
16
+ exports.isDirectory = isDirectory;
17
+ function toReadable(contents) {
18
+ if (contents instanceof stream_1.Readable) {
19
+ return contents;
20
+ }
21
+ return stream_1.Readable.from(contents);
22
+ }
23
+ const naturalSorting = new Intl.Collator(undefined, {
24
+ numeric: true,
25
+ sensitivity: 'base'
26
+ });
27
+ class FileStorage {
28
+ adapter;
29
+ pathNormalizer;
30
+ options;
31
+ constructor(adapter, pathNormalizer = new path_normalizer_js_1.PathNormalizerV1(), options = {}) {
32
+ this.adapter = adapter;
33
+ this.pathNormalizer = pathNormalizer;
34
+ this.options = options;
35
+ }
36
+ async write(path, contents, options = {}) {
37
+ try {
38
+ const body = toReadable(contents);
39
+ await this.adapter.write(this.pathNormalizer.normalizePath(path), body, Object.assign({}, this.options.writes || {}, options));
40
+ await closeReadable(body);
41
+ }
42
+ catch (error) {
43
+ throw errors.UnableToWriteFile.because(errors.errorToMessage(error), { cause: error, context: { path, options } });
44
+ }
45
+ }
46
+ async read(path) {
47
+ try {
48
+ return stream_1.Readable.from(await this.adapter.read(this.pathNormalizer.normalizePath(path)));
49
+ }
50
+ catch (error) {
51
+ throw errors.UnableToReadFile.because(errors.errorToMessage(error), { cause: error, context: { path } });
52
+ }
53
+ }
54
+ async readToString(path) {
55
+ return await readableToString(await this.read(path));
56
+ }
57
+ async readToUint8Array(path) {
58
+ return await readableToUint8Array(await this.read(path));
59
+ }
60
+ async deleteFile(path) {
61
+ try {
62
+ await this.adapter.deleteFile(this.pathNormalizer.normalizePath(path));
63
+ }
64
+ catch (error) {
65
+ throw errors.UnableToDeleteFile.because(errors.errorToMessage(error), { cause: error, context: { path } });
66
+ }
67
+ }
68
+ async createDirectory(path, options = {}) {
69
+ try {
70
+ return await this.adapter.createDirectory(this.pathNormalizer.normalizePath(path), options);
71
+ }
72
+ catch (error) {
73
+ throw errors.UnableToCreateDirectory.because(errors.errorToMessage(error), { cause: error, context: { path, options } });
74
+ }
75
+ }
76
+ async deleteDirectory(path) {
77
+ try {
78
+ return await this.adapter.deleteDirectory(this.pathNormalizer.normalizePath(path));
79
+ }
80
+ catch (error) {
81
+ throw errors.UnableToDeleteDirectory.because(errors.errorToMessage(error), { cause: error, context: { path } });
82
+ }
83
+ }
84
+ async stat(path) {
85
+ try {
86
+ return await this.adapter.stat(this.pathNormalizer.normalizePath(path));
87
+ }
88
+ catch (error) {
89
+ throw errors.UnableToGetStat.because(errors.errorToMessage(error), { cause: error, context: { path } });
90
+ }
91
+ }
92
+ async setVisibility(path, visibility) {
93
+ try {
94
+ return await this.adapter.changeVisibility(this.pathNormalizer.normalizePath(path), visibility);
95
+ }
96
+ catch (error) {
97
+ throw errors.UnableToSetVisibility.because(errors.errorToMessage(error), { cause: error, context: { path, visibility } });
98
+ }
99
+ }
100
+ async visibility(path) {
101
+ try {
102
+ return await this.adapter.visibility(this.pathNormalizer.normalizePath(path));
103
+ }
104
+ catch (error) {
105
+ throw errors.UnableToGetVisibility.because(errors.errorToMessage(error), { cause: error, context: { path } });
106
+ }
107
+ }
108
+ async fileExists(path) {
109
+ try {
110
+ return await this.adapter.fileExists(this.pathNormalizer.normalizePath(path));
111
+ }
112
+ catch (error) {
113
+ throw errors.UnableToCheckFileExistence.because(errors.errorToMessage(error), { cause: error, context: { path } });
114
+ }
115
+ }
116
+ 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
+ };
137
+ }
138
+ async statFile(path) {
139
+ const stat = await this.stat(path);
140
+ if (isFile(stat)) {
141
+ return stat;
142
+ }
143
+ throw errors.UnableToGetStat.noFileStatResolved({ context: { path } });
144
+ }
145
+ async directoryExists(path) {
146
+ try {
147
+ return await this.adapter.directoryExists(this.pathNormalizer.normalizePath(path));
148
+ }
149
+ catch (error) {
150
+ throw errors.UnableToCheckDirectoryExistence.because(errors.errorToMessage(error), { cause: error, context: { path } });
151
+ }
152
+ }
153
+ async publicUrl(path, options = {}) {
154
+ try {
155
+ return await this.adapter.publicUrl(this.pathNormalizer.normalizePath(path), options);
156
+ }
157
+ catch (error) {
158
+ throw errors.UnableToGetPublicUrl.because(errors.errorToMessage(error), { cause: error, context: { path, options } });
159
+ }
160
+ }
161
+ async temporaryUrl(path, options) {
162
+ try {
163
+ return await this.adapter.temporaryUrl(this.pathNormalizer.normalizePath(path), options);
164
+ }
165
+ catch (error) {
166
+ throw errors.UnableToGetTemporaryUrl.because(errors.errorToMessage(error), { cause: error, context: { path, options } });
167
+ }
168
+ }
169
+ async checksum(path, options = {}) {
170
+ try {
171
+ return await this.adapter.checksum(this.pathNormalizer.normalizePath(path), options);
172
+ }
173
+ catch (error) {
174
+ if (error instanceof errors_js_1.ChecksumIsNotAvailable) {
175
+ return this.calculateChecksum(path, options);
176
+ }
177
+ throw errors.UnableToGetChecksum.because(errors.errorToMessage(error), { cause: error, context: { path, options } });
178
+ }
179
+ }
180
+ async calculateChecksum(path, options) {
181
+ try {
182
+ return await (0, checksum_from_stream_js_1.checksumFromStream)(await this.read(path), options);
183
+ }
184
+ catch (error) {
185
+ throw errors.UnableToGetChecksum.because(errors.errorToMessage(error), { cause: error, context: { path, options } });
186
+ }
187
+ }
188
+ }
189
+ exports.FileStorage = FileStorage;
190
+ function normalizeExpiryToDate(expiresAt) {
191
+ return expiresAt instanceof Date ? expiresAt : new Date(expiresAt);
192
+ }
193
+ exports.normalizeExpiryToDate = normalizeExpiryToDate;
194
+ function normalizeExpiryToMilliseconds(expiresAt) {
195
+ return expiresAt instanceof Date ? expiresAt.getTime() : expiresAt;
196
+ }
197
+ exports.normalizeExpiryToMilliseconds = normalizeExpiryToMilliseconds;
198
+ async function closeReadable(body) {
199
+ if (body.closed) {
200
+ return;
201
+ }
202
+ await new Promise((resolve, reject) => {
203
+ body.on('close', (err) => {
204
+ err ? reject(err) : resolve();
205
+ });
206
+ body.destroy();
207
+ });
208
+ }
209
+ exports.closeReadable = closeReadable;
210
+ const decoder = new TextDecoder();
211
+ async function readableToString(stream) {
212
+ return decoder.decode(await readableToUint8Array(stream));
213
+ }
214
+ exports.readableToString = readableToString;
215
+ function readableToUint8Array(stream) {
216
+ return new Promise((resolve, reject) => {
217
+ const parts = [];
218
+ stream.on('data', (chunk) => {
219
+ parts.push(chunk);
220
+ });
221
+ stream.on('error', reject);
222
+ stream.on('end', () => resolve(concatUint8Arrays(parts)));
223
+ });
224
+ }
225
+ exports.readableToUint8Array = readableToUint8Array;
226
+ function concatUint8Arrays(input) {
227
+ const length = input.reduce((l, a) => l + a.byteLength, 0);
228
+ const output = new Uint8Array(length);
229
+ let position = 0;
230
+ input.forEach(i => {
231
+ output.set(i, position);
232
+ position += i.byteLength;
233
+ });
234
+ return output;
235
+ }
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./checksum-from-stream.js"), exports);
18
+ __exportStar(require("./file-storage.js"), exports);
19
+ __exportStar(require("./errors.js"), exports);
20
+ __exportStar(require("./path-normalizer.js"), exports);
21
+ __exportStar(require("./path-prefixer.js"), exports);
22
+ __exportStar(require("./portable-visibility.js"), exports);
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PathTraversalDetected = exports.CorruptedPathDetected = exports.PathNormalizerV1 = void 0;
4
+ const node_path_1 = require("node:path");
5
+ const funkyWhiteSpaceRegex = new RegExp('\\p{C}+', 'u');
6
+ class PathNormalizerV1 {
7
+ normalizePath(path) {
8
+ if (funkyWhiteSpaceRegex.test(path)) {
9
+ throw CorruptedPathDetected.unexpectedWhitespace(path);
10
+ }
11
+ const normalized = (0, node_path_1.join)(...(path.split('/')));
12
+ if (normalized.indexOf('../') !== -1 || normalized == '..') {
13
+ throw PathTraversalDetected.forPath(path);
14
+ }
15
+ return normalized === '.' ? '' : normalized;
16
+ }
17
+ }
18
+ exports.PathNormalizerV1 = PathNormalizerV1;
19
+ class CorruptedPathDetected extends Error {
20
+ static unexpectedWhitespace = (path) => new CorruptedPathDetected(`Corrupted path detected with unexpected whitespace: ${path}`);
21
+ }
22
+ exports.CorruptedPathDetected = CorruptedPathDetected;
23
+ class PathTraversalDetected extends Error {
24
+ static forPath = (path) => new PathTraversalDetected(`Path traversal detected for: ${path}`);
25
+ }
26
+ exports.PathTraversalDetected = PathTraversalDetected;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PathPrefixer = void 0;
4
+ const node_path_1 = require("node:path");
5
+ class PathPrefixer {
6
+ prefix = '';
7
+ constructor(prefix = '') {
8
+ if (prefix.length > 0) {
9
+ this.prefix = (0, node_path_1.join)(prefix, '/');
10
+ }
11
+ }
12
+ prefixFilePath(path) {
13
+ return this.prefix.length > 0 ? (0, node_path_1.join)(this.prefix, path) : path;
14
+ }
15
+ prefixDirectoryPath(path) {
16
+ return this.prefix.length > 0 ? (0, node_path_1.join)(this.prefix, path, '/') : (0, node_path_1.join)(path, '/');
17
+ }
18
+ stripFilePath(path) {
19
+ return path.substring(this.prefix.length);
20
+ }
21
+ stripDirectoryPath(path) {
22
+ return this.stripFilePath(path).replace(/\/+$/g, '');
23
+ }
24
+ }
25
+ exports.PathPrefixer = PathPrefixer;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Visibility = void 0;
4
+ var Visibility;
5
+ (function (Visibility) {
6
+ Visibility["PUBLIC"] = "public";
7
+ Visibility["PRIVATE"] = "private";
8
+ })(Visibility || (exports.Visibility = Visibility = {}));
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checksumFromStream = void 0;
4
+ const crypto_1 = require("crypto");
5
+ async function checksumFromStream(stream, options) {
6
+ return new Promise(async (resolve, reject) => {
7
+ const hash = (0, crypto_1.createHash)(options.algo ?? 'md5');
8
+ stream.on('error', reject);
9
+ stream.pipe(hash, { end: false });
10
+ stream.on('end', () => {
11
+ hash.end();
12
+ resolve(hash.digest(options.encoding ?? 'hex'));
13
+ });
14
+ });
15
+ }
16
+ exports.checksumFromStream = checksumFromStream;
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UnableToListDirectory = exports.UnableToCheckDirectoryExistence = exports.UnableToCheckFileExistence = exports.UnableToDeleteFile = exports.UnableToDeleteDirectory = exports.UnableToCreateDirectory = exports.UnableToGetStat = exports.UnableToGetTemporaryUrl = exports.UnableToGetPublicUrl = exports.UnableToGetVisibility = exports.UnableToSetVisibility = exports.UnableToReadFile = exports.UnableToWriteFile = exports.UnableToGetChecksum = exports.ChecksumIsNotAvailable = exports.FlystorageError = exports.errorToMessage = void 0;
4
+ function errorToMessage(error) {
5
+ return error instanceof Error ? error.message : String(error);
6
+ }
7
+ exports.errorToMessage = errorToMessage;
8
+ class FlystorageError extends Error {
9
+ context;
10
+ code = 'unknown_error';
11
+ constructor(message, context = {}, cause = undefined) {
12
+ const options = cause === undefined ? undefined : { cause };
13
+ // @ts-ignore TS2554
14
+ super(message, options);
15
+ this.context = context;
16
+ }
17
+ }
18
+ exports.FlystorageError = FlystorageError;
19
+ /**
20
+ * Thrown when the checksum algo is not supported or not pre-computed. This error
21
+ * is thrown with the intention of falling back to computing it based on a file read.
22
+ */
23
+ class ChecksumIsNotAvailable extends FlystorageError {
24
+ algo;
25
+ code = 'flystorage.checksum_not_supported';
26
+ constructor(message, algo, context = {}, cause = undefined) {
27
+ super(message, context, cause);
28
+ this.algo = algo;
29
+ }
30
+ static checksumNotSupported = (algo, { context = {}, cause = undefined } = {}) => new ChecksumIsNotAvailable(`Checksum algo "${algo}" is not supported`, algo, { ...context, algo }, cause);
31
+ }
32
+ exports.ChecksumIsNotAvailable = ChecksumIsNotAvailable;
33
+ class UnableToGetChecksum extends FlystorageError {
34
+ code = 'flystorage.unable_to_get_checksum';
35
+ static because = (reason, { context = {}, cause = undefined }) => new UnableToWriteFile(`Unable to write the file. Reason: ${reason}`, context, cause);
36
+ }
37
+ exports.UnableToGetChecksum = UnableToGetChecksum;
38
+ class UnableToWriteFile extends FlystorageError {
39
+ code = 'flystorage.unable_to_write_file';
40
+ static because = (reason, { context = {}, cause = undefined }) => new UnableToWriteFile(`Unable to write the file. Reason: ${reason}`, context, cause);
41
+ }
42
+ exports.UnableToWriteFile = UnableToWriteFile;
43
+ class UnableToReadFile extends FlystorageError {
44
+ code = 'flystorage.unable_to_read_file';
45
+ static because = (reason, { context = {}, cause = undefined }) => new UnableToReadFile(`Unable to read the file. Reason: ${reason}`, context, cause);
46
+ }
47
+ exports.UnableToReadFile = UnableToReadFile;
48
+ class UnableToSetVisibility extends FlystorageError {
49
+ code = 'flystorage.unable_to_set_visibility';
50
+ static because = (reason, { context = {}, cause = undefined }) => new UnableToSetVisibility(`Unable to set visibility. Reason: ${reason}`, context, cause);
51
+ }
52
+ exports.UnableToSetVisibility = UnableToSetVisibility;
53
+ class UnableToGetVisibility extends FlystorageError {
54
+ code = 'flystorage.unable_to_get_visibility';
55
+ static because = (reason, { context = {}, cause = undefined }) => new UnableToGetVisibility(`Unable to get visibility. Reason: ${reason}`, context, cause);
56
+ }
57
+ exports.UnableToGetVisibility = UnableToGetVisibility;
58
+ class UnableToGetPublicUrl extends FlystorageError {
59
+ code = 'flystorage.unable_to_get_public_url';
60
+ static because = (reason, { context = {}, cause = undefined }) => new UnableToGetPublicUrl(`Unable to get public URL. Reason: ${reason}`, context, cause);
61
+ }
62
+ exports.UnableToGetPublicUrl = UnableToGetPublicUrl;
63
+ class UnableToGetTemporaryUrl extends FlystorageError {
64
+ code = 'flystorage.unable_to_get_temporary_url';
65
+ static because = (reason, { context = {}, cause = undefined }) => new UnableToGetTemporaryUrl(`Unable to get temporary URL. Reason: ${reason}`, context, cause);
66
+ }
67
+ exports.UnableToGetTemporaryUrl = UnableToGetTemporaryUrl;
68
+ class UnableToGetStat extends FlystorageError {
69
+ code = 'flystorage.unable_to_get_stat';
70
+ static because = (reason, { context = {}, cause = undefined }) => new UnableToGetStat(`Unable to get stat. Reason: ${reason}`, context, cause);
71
+ static noFileStatResolved = ({ context = {}, cause = undefined }) => new UnableToGetStat(`Stat was not a file.`, context, cause);
72
+ }
73
+ exports.UnableToGetStat = UnableToGetStat;
74
+ class UnableToCreateDirectory extends FlystorageError {
75
+ code = 'flystorage.unable_to_create_directory';
76
+ static because = (reason, { context = {}, cause = undefined }) => new UnableToCreateDirectory(`Unable to create directory. Reason: ${reason}`, context, cause);
77
+ }
78
+ exports.UnableToCreateDirectory = UnableToCreateDirectory;
79
+ class UnableToDeleteDirectory extends FlystorageError {
80
+ code = 'flystorage.unable_to_delete_directory';
81
+ static because = (reason, { context = {}, cause = undefined }) => new UnableToDeleteDirectory(`Unable to delete directory. Reason: ${reason}`, context, cause);
82
+ }
83
+ exports.UnableToDeleteDirectory = UnableToDeleteDirectory;
84
+ class UnableToDeleteFile extends FlystorageError {
85
+ code = 'flystorage.unable_to_delete_file';
86
+ static because = (reason, { context = {}, cause = undefined }) => new UnableToDeleteFile(`Unable to delete file. Reason: ${reason}`, context, cause);
87
+ }
88
+ exports.UnableToDeleteFile = UnableToDeleteFile;
89
+ class UnableToCheckFileExistence extends FlystorageError {
90
+ code = 'flystorage.unable_to_check_file_existence';
91
+ static because = (reason, { context = {}, cause = undefined }) => new UnableToCheckFileExistence(`Unable to check file existence. Reason: ${reason}`, context, cause);
92
+ }
93
+ exports.UnableToCheckFileExistence = UnableToCheckFileExistence;
94
+ class UnableToCheckDirectoryExistence extends FlystorageError {
95
+ code = 'flystorage.unable_to_check_directory_existence';
96
+ static because = (reason, { context = {}, cause = undefined }) => new UnableToCheckDirectoryExistence(`Unable to check directory existence. Reason: ${reason}`, context, cause);
97
+ }
98
+ exports.UnableToCheckDirectoryExistence = UnableToCheckDirectoryExistence;
99
+ class UnableToListDirectory extends FlystorageError {
100
+ code = 'flystorage.unable_to_list_directory_contents';
101
+ static because = (reason, { context = {}, cause = undefined }) => new UnableToListDirectory(`Unable to list directory contents. Reason: ${reason}`, context, cause);
102
+ }
103
+ exports.UnableToListDirectory = UnableToListDirectory;
@@ -0,0 +1,235 @@
1
+ "use strict";
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;
4
+ const stream_1 = require("stream");
5
+ const checksum_from_stream_js_1 = require("./checksum-from-stream.js");
6
+ const errors = require("./errors.js");
7
+ const errors_js_1 = require("./errors.js");
8
+ const path_normalizer_js_1 = require("./path-normalizer.js");
9
+ function isFile(stat) {
10
+ return stat.isFile;
11
+ }
12
+ exports.isFile = isFile;
13
+ function isDirectory(stat) {
14
+ return stat.isDirectory;
15
+ }
16
+ exports.isDirectory = isDirectory;
17
+ function toReadable(contents) {
18
+ if (contents instanceof stream_1.Readable) {
19
+ return contents;
20
+ }
21
+ return stream_1.Readable.from(contents);
22
+ }
23
+ const naturalSorting = new Intl.Collator(undefined, {
24
+ numeric: true,
25
+ sensitivity: 'base'
26
+ });
27
+ class FileStorage {
28
+ adapter;
29
+ pathNormalizer;
30
+ options;
31
+ constructor(adapter, pathNormalizer = new path_normalizer_js_1.PathNormalizerV1(), options = {}) {
32
+ this.adapter = adapter;
33
+ this.pathNormalizer = pathNormalizer;
34
+ this.options = options;
35
+ }
36
+ async write(path, contents, options = {}) {
37
+ try {
38
+ const body = toReadable(contents);
39
+ await this.adapter.write(this.pathNormalizer.normalizePath(path), body, Object.assign({}, this.options.writes || {}, options));
40
+ await closeReadable(body);
41
+ }
42
+ catch (error) {
43
+ throw errors.UnableToWriteFile.because(errors.errorToMessage(error), { cause: error, context: { path, options } });
44
+ }
45
+ }
46
+ async read(path) {
47
+ try {
48
+ return stream_1.Readable.from(await this.adapter.read(this.pathNormalizer.normalizePath(path)));
49
+ }
50
+ catch (error) {
51
+ throw errors.UnableToReadFile.because(errors.errorToMessage(error), { cause: error, context: { path } });
52
+ }
53
+ }
54
+ async readToString(path) {
55
+ return await readableToString(await this.read(path));
56
+ }
57
+ async readToUint8Array(path) {
58
+ return await readableToUint8Array(await this.read(path));
59
+ }
60
+ async deleteFile(path) {
61
+ try {
62
+ await this.adapter.deleteFile(this.pathNormalizer.normalizePath(path));
63
+ }
64
+ catch (error) {
65
+ throw errors.UnableToDeleteFile.because(errors.errorToMessage(error), { cause: error, context: { path } });
66
+ }
67
+ }
68
+ async createDirectory(path, options = {}) {
69
+ try {
70
+ return await this.adapter.createDirectory(this.pathNormalizer.normalizePath(path), options);
71
+ }
72
+ catch (error) {
73
+ throw errors.UnableToCreateDirectory.because(errors.errorToMessage(error), { cause: error, context: { path, options } });
74
+ }
75
+ }
76
+ async deleteDirectory(path) {
77
+ try {
78
+ return await this.adapter.deleteDirectory(this.pathNormalizer.normalizePath(path));
79
+ }
80
+ catch (error) {
81
+ throw errors.UnableToDeleteDirectory.because(errors.errorToMessage(error), { cause: error, context: { path } });
82
+ }
83
+ }
84
+ async stat(path) {
85
+ try {
86
+ return await this.adapter.stat(this.pathNormalizer.normalizePath(path));
87
+ }
88
+ catch (error) {
89
+ throw errors.UnableToGetStat.because(errors.errorToMessage(error), { cause: error, context: { path } });
90
+ }
91
+ }
92
+ async setVisibility(path, visibility) {
93
+ try {
94
+ return await this.adapter.changeVisibility(this.pathNormalizer.normalizePath(path), visibility);
95
+ }
96
+ catch (error) {
97
+ throw errors.UnableToSetVisibility.because(errors.errorToMessage(error), { cause: error, context: { path, visibility } });
98
+ }
99
+ }
100
+ async visibility(path) {
101
+ try {
102
+ return await this.adapter.visibility(this.pathNormalizer.normalizePath(path));
103
+ }
104
+ catch (error) {
105
+ throw errors.UnableToGetVisibility.because(errors.errorToMessage(error), { cause: error, context: { path } });
106
+ }
107
+ }
108
+ async fileExists(path) {
109
+ try {
110
+ return await this.adapter.fileExists(this.pathNormalizer.normalizePath(path));
111
+ }
112
+ catch (error) {
113
+ throw errors.UnableToCheckFileExistence.because(errors.errorToMessage(error), { cause: error, context: { path } });
114
+ }
115
+ }
116
+ 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
+ };
137
+ }
138
+ async statFile(path) {
139
+ const stat = await this.stat(path);
140
+ if (isFile(stat)) {
141
+ return stat;
142
+ }
143
+ throw errors.UnableToGetStat.noFileStatResolved({ context: { path } });
144
+ }
145
+ async directoryExists(path) {
146
+ try {
147
+ return await this.adapter.directoryExists(this.pathNormalizer.normalizePath(path));
148
+ }
149
+ catch (error) {
150
+ throw errors.UnableToCheckDirectoryExistence.because(errors.errorToMessage(error), { cause: error, context: { path } });
151
+ }
152
+ }
153
+ async publicUrl(path, options = {}) {
154
+ try {
155
+ return await this.adapter.publicUrl(this.pathNormalizer.normalizePath(path), options);
156
+ }
157
+ catch (error) {
158
+ throw errors.UnableToGetPublicUrl.because(errors.errorToMessage(error), { cause: error, context: { path, options } });
159
+ }
160
+ }
161
+ async temporaryUrl(path, options) {
162
+ try {
163
+ return await this.adapter.temporaryUrl(this.pathNormalizer.normalizePath(path), options);
164
+ }
165
+ catch (error) {
166
+ throw errors.UnableToGetTemporaryUrl.because(errors.errorToMessage(error), { cause: error, context: { path, options } });
167
+ }
168
+ }
169
+ async checksum(path, options = {}) {
170
+ try {
171
+ return await this.adapter.checksum(this.pathNormalizer.normalizePath(path), options);
172
+ }
173
+ catch (error) {
174
+ if (error instanceof errors_js_1.ChecksumIsNotAvailable) {
175
+ return this.calculateChecksum(path, options);
176
+ }
177
+ throw errors.UnableToGetChecksum.because(errors.errorToMessage(error), { cause: error, context: { path, options } });
178
+ }
179
+ }
180
+ async calculateChecksum(path, options) {
181
+ try {
182
+ return await (0, checksum_from_stream_js_1.checksumFromStream)(await this.read(path), options);
183
+ }
184
+ catch (error) {
185
+ throw errors.UnableToGetChecksum.because(errors.errorToMessage(error), { cause: error, context: { path, options } });
186
+ }
187
+ }
188
+ }
189
+ exports.FileStorage = FileStorage;
190
+ function normalizeExpiryToDate(expiresAt) {
191
+ return expiresAt instanceof Date ? expiresAt : new Date(expiresAt);
192
+ }
193
+ exports.normalizeExpiryToDate = normalizeExpiryToDate;
194
+ function normalizeExpiryToMilliseconds(expiresAt) {
195
+ return expiresAt instanceof Date ? expiresAt.getTime() : expiresAt;
196
+ }
197
+ exports.normalizeExpiryToMilliseconds = normalizeExpiryToMilliseconds;
198
+ async function closeReadable(body) {
199
+ if (body.closed) {
200
+ return;
201
+ }
202
+ await new Promise((resolve, reject) => {
203
+ body.on('close', (err) => {
204
+ err ? reject(err) : resolve();
205
+ });
206
+ body.destroy();
207
+ });
208
+ }
209
+ exports.closeReadable = closeReadable;
210
+ const decoder = new TextDecoder();
211
+ async function readableToString(stream) {
212
+ return decoder.decode(await readableToUint8Array(stream));
213
+ }
214
+ exports.readableToString = readableToString;
215
+ function readableToUint8Array(stream) {
216
+ return new Promise((resolve, reject) => {
217
+ const parts = [];
218
+ stream.on('data', (chunk) => {
219
+ parts.push(chunk);
220
+ });
221
+ stream.on('error', reject);
222
+ stream.on('end', () => resolve(concatUint8Arrays(parts)));
223
+ });
224
+ }
225
+ exports.readableToUint8Array = readableToUint8Array;
226
+ function concatUint8Arrays(input) {
227
+ const length = input.reduce((l, a) => l + a.byteLength, 0);
228
+ const output = new Uint8Array(length);
229
+ let position = 0;
230
+ input.forEach(i => {
231
+ output.set(i, position);
232
+ position += i.byteLength;
233
+ });
234
+ return output;
235
+ }
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./checksum-from-stream.js"), exports);
18
+ __exportStar(require("./file-storage.js"), exports);
19
+ __exportStar(require("./errors.js"), exports);
20
+ __exportStar(require("./path-normalizer.js"), exports);
21
+ __exportStar(require("./path-prefixer.js"), exports);
22
+ __exportStar(require("./portable-visibility.js"), exports);
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PathTraversalDetected = exports.CorruptedPathDetected = exports.PathNormalizerV1 = void 0;
4
+ const node_path_1 = require("node:path");
5
+ const funkyWhiteSpaceRegex = new RegExp('\\p{C}+', 'u');
6
+ class PathNormalizerV1 {
7
+ normalizePath(path) {
8
+ if (funkyWhiteSpaceRegex.test(path)) {
9
+ throw CorruptedPathDetected.unexpectedWhitespace(path);
10
+ }
11
+ const normalized = (0, node_path_1.join)(...(path.split('/')));
12
+ if (normalized.indexOf('../') !== -1 || normalized == '..') {
13
+ throw PathTraversalDetected.forPath(path);
14
+ }
15
+ return normalized === '.' ? '' : normalized;
16
+ }
17
+ }
18
+ exports.PathNormalizerV1 = PathNormalizerV1;
19
+ class CorruptedPathDetected extends Error {
20
+ static unexpectedWhitespace = (path) => new CorruptedPathDetected(`Corrupted path detected with unexpected whitespace: ${path}`);
21
+ }
22
+ exports.CorruptedPathDetected = CorruptedPathDetected;
23
+ class PathTraversalDetected extends Error {
24
+ static forPath = (path) => new PathTraversalDetected(`Path traversal detected for: ${path}`);
25
+ }
26
+ exports.PathTraversalDetected = PathTraversalDetected;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PathPrefixer = void 0;
4
+ const node_path_1 = require("node:path");
5
+ class PathPrefixer {
6
+ prefix = '';
7
+ constructor(prefix = '') {
8
+ if (prefix.length > 0) {
9
+ this.prefix = (0, node_path_1.join)(prefix, '/');
10
+ }
11
+ }
12
+ prefixFilePath(path) {
13
+ return this.prefix.length > 0 ? (0, node_path_1.join)(this.prefix, path) : path;
14
+ }
15
+ prefixDirectoryPath(path) {
16
+ return this.prefix.length > 0 ? (0, node_path_1.join)(this.prefix, path, '/') : (0, node_path_1.join)(path, '/');
17
+ }
18
+ stripFilePath(path) {
19
+ return path.substring(this.prefix.length);
20
+ }
21
+ stripDirectoryPath(path) {
22
+ return this.stripFilePath(path).replace(/\/+$/g, '');
23
+ }
24
+ }
25
+ exports.PathPrefixer = PathPrefixer;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Visibility = void 0;
4
+ var Visibility;
5
+ (function (Visibility) {
6
+ Visibility["PUBLIC"] = "public";
7
+ Visibility["PRIVATE"] = "private";
8
+ })(Visibility || (exports.Visibility = Visibility = {}));
@@ -0,0 +1,8 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
3
+ import { BinaryToTextEncoding } from 'crypto';
4
+ import { Readable } from 'node:stream';
5
+ export declare function checksumFromStream(stream: Readable, options: {
6
+ algo?: string;
7
+ encoding?: BinaryToTextEncoding;
8
+ }): Promise<string>;
@@ -0,0 +1,128 @@
1
+ export type ErrorContext = {
2
+ [index: string]: any;
3
+ };
4
+ export type OperationError = Error & {
5
+ readonly code: string;
6
+ readonly context: ErrorContext;
7
+ };
8
+ export declare function errorToMessage(error: unknown): string;
9
+ export declare abstract class FlystorageError extends Error implements OperationError {
10
+ readonly context: ErrorContext;
11
+ readonly code: string;
12
+ constructor(message: string, context?: ErrorContext, cause?: unknown);
13
+ }
14
+ /**
15
+ * Thrown when the checksum algo is not supported or not pre-computed. This error
16
+ * is thrown with the intention of falling back to computing it based on a file read.
17
+ */
18
+ export declare class ChecksumIsNotAvailable extends FlystorageError {
19
+ readonly algo: string;
20
+ readonly code = "flystorage.checksum_not_supported";
21
+ constructor(message: string, algo: string, context?: ErrorContext, cause?: unknown);
22
+ static checksumNotSupported: (algo: string, { context, cause }?: {
23
+ context?: ErrorContext | undefined;
24
+ cause?: unknown;
25
+ }) => ChecksumIsNotAvailable;
26
+ }
27
+ export declare class UnableToGetChecksum extends FlystorageError {
28
+ readonly code = "flystorage.unable_to_get_checksum";
29
+ static because: (reason: string, { context, cause }: {
30
+ context?: ErrorContext | undefined;
31
+ cause?: unknown;
32
+ }) => UnableToWriteFile;
33
+ }
34
+ export declare class UnableToWriteFile extends FlystorageError {
35
+ readonly code = "flystorage.unable_to_write_file";
36
+ static because: (reason: string, { context, cause }: {
37
+ context?: ErrorContext | undefined;
38
+ cause?: unknown;
39
+ }) => UnableToWriteFile;
40
+ }
41
+ export declare class UnableToReadFile extends FlystorageError {
42
+ readonly code = "flystorage.unable_to_read_file";
43
+ static because: (reason: string, { context, cause }: {
44
+ context?: ErrorContext | undefined;
45
+ cause?: unknown;
46
+ }) => UnableToReadFile;
47
+ }
48
+ export declare class UnableToSetVisibility extends FlystorageError {
49
+ readonly code = "flystorage.unable_to_set_visibility";
50
+ static because: (reason: string, { context, cause }: {
51
+ context?: ErrorContext | undefined;
52
+ cause?: unknown;
53
+ }) => UnableToSetVisibility;
54
+ }
55
+ export declare class UnableToGetVisibility extends FlystorageError {
56
+ readonly code = "flystorage.unable_to_get_visibility";
57
+ static because: (reason: string, { context, cause }: {
58
+ context?: ErrorContext | undefined;
59
+ cause?: unknown;
60
+ }) => UnableToGetVisibility;
61
+ }
62
+ export declare class UnableToGetPublicUrl extends FlystorageError {
63
+ readonly code = "flystorage.unable_to_get_public_url";
64
+ static because: (reason: string, { context, cause }: {
65
+ context?: ErrorContext | undefined;
66
+ cause?: unknown;
67
+ }) => UnableToGetPublicUrl;
68
+ }
69
+ export declare class UnableToGetTemporaryUrl extends FlystorageError {
70
+ readonly code = "flystorage.unable_to_get_temporary_url";
71
+ static because: (reason: string, { context, cause }: {
72
+ context?: ErrorContext | undefined;
73
+ cause?: unknown;
74
+ }) => UnableToGetTemporaryUrl;
75
+ }
76
+ export declare class UnableToGetStat extends FlystorageError {
77
+ readonly code = "flystorage.unable_to_get_stat";
78
+ static because: (reason: string, { context, cause }: {
79
+ context?: ErrorContext | undefined;
80
+ cause?: unknown;
81
+ }) => UnableToGetStat;
82
+ static noFileStatResolved: ({ context, cause }: {
83
+ context?: ErrorContext | undefined;
84
+ cause?: unknown;
85
+ }) => UnableToGetStat;
86
+ }
87
+ export declare class UnableToCreateDirectory extends FlystorageError {
88
+ readonly code = "flystorage.unable_to_create_directory";
89
+ static because: (reason: string, { context, cause }: {
90
+ context?: ErrorContext | undefined;
91
+ cause?: unknown;
92
+ }) => UnableToCreateDirectory;
93
+ }
94
+ export declare class UnableToDeleteDirectory extends FlystorageError {
95
+ readonly code = "flystorage.unable_to_delete_directory";
96
+ static because: (reason: string, { context, cause }: {
97
+ context?: ErrorContext | undefined;
98
+ cause?: unknown;
99
+ }) => UnableToDeleteDirectory;
100
+ }
101
+ export declare class UnableToDeleteFile extends FlystorageError {
102
+ readonly code = "flystorage.unable_to_delete_file";
103
+ static because: (reason: string, { context, cause }: {
104
+ context?: ErrorContext | undefined;
105
+ cause?: unknown;
106
+ }) => UnableToDeleteFile;
107
+ }
108
+ export declare class UnableToCheckFileExistence extends FlystorageError {
109
+ readonly code = "flystorage.unable_to_check_file_existence";
110
+ static because: (reason: string, { context, cause }: {
111
+ context?: ErrorContext | undefined;
112
+ cause?: unknown;
113
+ }) => UnableToCheckFileExistence;
114
+ }
115
+ export declare class UnableToCheckDirectoryExistence extends FlystorageError {
116
+ readonly code = "flystorage.unable_to_check_directory_existence";
117
+ static because: (reason: string, { context, cause }: {
118
+ context?: ErrorContext | undefined;
119
+ cause?: unknown;
120
+ }) => UnableToCheckDirectoryExistence;
121
+ }
122
+ export declare class UnableToListDirectory extends FlystorageError {
123
+ readonly code = "flystorage.unable_to_list_directory_contents";
124
+ static because: (reason: string, { context, cause }: {
125
+ context?: ErrorContext | undefined;
126
+ cause?: unknown;
127
+ }) => UnableToListDirectory;
128
+ }
@@ -0,0 +1,105 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
3
+ /// <reference types="node" />
4
+ import { BinaryToTextEncoding } from 'crypto';
5
+ import { Readable } from 'stream';
6
+ import { PathNormalizer } from './path-normalizer.js';
7
+ export type CommonStatInfo = Readonly<{
8
+ path: string;
9
+ lastModifiedMs?: number;
10
+ visibility?: string;
11
+ }>;
12
+ export type FileInfo = Readonly<{
13
+ type: 'file';
14
+ size?: number;
15
+ isFile: true;
16
+ isDirectory: false;
17
+ mimeType?: string;
18
+ } & CommonStatInfo>;
19
+ export type DirectoryInfo = Readonly<{
20
+ type: 'directory';
21
+ isFile: false;
22
+ isDirectory: true;
23
+ } & CommonStatInfo>;
24
+ export declare function isFile(stat: StatEntry): stat is FileInfo;
25
+ export declare function isDirectory(stat: StatEntry): stat is DirectoryInfo;
26
+ export type StatEntry = FileInfo | DirectoryInfo;
27
+ export interface StorageAdapter {
28
+ write(path: string, contents: Readable, options: WriteOptions): Promise<void>;
29
+ read(path: string): Promise<FileContents>;
30
+ deleteFile(path: string): Promise<void>;
31
+ createDirectory(path: string, options: CreateDirectoryOptions): Promise<void>;
32
+ stat(path: string): Promise<StatEntry>;
33
+ list(path: string, options: {
34
+ deep: boolean;
35
+ }): AsyncGenerator<StatEntry>;
36
+ changeVisibility(path: string, visibility: string): Promise<void>;
37
+ visibility(path: string): Promise<string>;
38
+ deleteDirectory(path: string): Promise<void>;
39
+ fileExists(path: string): Promise<boolean>;
40
+ directoryExists(path: string): Promise<boolean>;
41
+ publicUrl(path: string, options: PublicUrlOptions): Promise<string>;
42
+ temporaryUrl(path: string, options: TemporaryUrlOptions): Promise<string>;
43
+ checksum(path: string, options: ChecksumOptions): Promise<string>;
44
+ }
45
+ export interface DirectoryListing extends AsyncIterable<StatEntry> {
46
+ toArray(sorted?: boolean): Promise<StatEntry[]>;
47
+ }
48
+ export type FileContents = Iterable<any> | AsyncIterable<any> | NodeJS.ReadableStream | Readable;
49
+ export type MiscellaneousOptions = {
50
+ [option: string]: any;
51
+ };
52
+ export type VisibilityOptions = {
53
+ visibility?: string;
54
+ directoryVisibility?: string;
55
+ };
56
+ export type WriteOptions = VisibilityOptions & MiscellaneousOptions & {
57
+ mimeType?: string;
58
+ size?: number;
59
+ };
60
+ export type CreateDirectoryOptions = MiscellaneousOptions & Pick<VisibilityOptions, 'directoryVisibility'> & {};
61
+ export type PublicUrlOptions = MiscellaneousOptions & {};
62
+ export type TemporaryUrlOptions = MiscellaneousOptions & {
63
+ expiresAt: ExpiresAt;
64
+ };
65
+ export type ChecksumOptions = MiscellaneousOptions & {
66
+ algo?: string;
67
+ encoding?: BinaryToTextEncoding;
68
+ };
69
+ export type ConfigurationOptions = {
70
+ defaults?: VisibilityOptions;
71
+ writes?: WriteOptions;
72
+ };
73
+ export declare class FileStorage {
74
+ private readonly adapter;
75
+ private readonly pathNormalizer;
76
+ private readonly options;
77
+ constructor(adapter: StorageAdapter, pathNormalizer?: PathNormalizer, options?: ConfigurationOptions);
78
+ write(path: string, contents: FileContents, options?: WriteOptions): Promise<void>;
79
+ read(path: string): Promise<Readable>;
80
+ readToString(path: string): Promise<string>;
81
+ readToUint8Array(path: string): Promise<Uint8Array>;
82
+ deleteFile(path: string): Promise<void>;
83
+ createDirectory(path: string, options?: CreateDirectoryOptions): Promise<void>;
84
+ deleteDirectory(path: string): Promise<void>;
85
+ stat(path: string): Promise<StatEntry>;
86
+ setVisibility(path: string, visibility: string): Promise<void>;
87
+ visibility(path: string): Promise<string>;
88
+ fileExists(path: string): Promise<boolean>;
89
+ list(path: string, { deep }?: {
90
+ deep?: boolean;
91
+ }): DirectoryListing;
92
+ statFile(path: string): Promise<FileInfo>;
93
+ directoryExists(path: string): Promise<boolean>;
94
+ publicUrl(path: string, options?: PublicUrlOptions): Promise<string>;
95
+ temporaryUrl(path: string, options: TemporaryUrlOptions): Promise<string>;
96
+ checksum(path: string, options?: ChecksumOptions): Promise<string>;
97
+ private calculateChecksum;
98
+ }
99
+ export type TimestampMs = number;
100
+ export type ExpiresAt = Date | TimestampMs;
101
+ export declare function normalizeExpiryToDate(expiresAt: ExpiresAt): Date;
102
+ export declare function normalizeExpiryToMilliseconds(expiresAt: ExpiresAt): number;
103
+ export declare function closeReadable(body: Readable): Promise<void>;
104
+ export declare function readableToString(stream: Readable): Promise<string>;
105
+ export declare function readableToUint8Array(stream: Readable): Promise<Uint8Array>;
@@ -0,0 +1,6 @@
1
+ export * from './checksum-from-stream.js';
2
+ export * from './file-storage.js';
3
+ export * from './errors.js';
4
+ export * from './path-normalizer.js';
5
+ export * from './path-prefixer.js';
6
+ export * from './portable-visibility.js';
@@ -0,0 +1,12 @@
1
+ export interface PathNormalizer {
2
+ normalizePath(path: string): string;
3
+ }
4
+ export declare class PathNormalizerV1 implements PathNormalizer {
5
+ normalizePath(path: string): string;
6
+ }
7
+ export declare class CorruptedPathDetected extends Error {
8
+ static unexpectedWhitespace: (path: string) => CorruptedPathDetected;
9
+ }
10
+ export declare class PathTraversalDetected extends Error {
11
+ static forPath: (path: string) => PathTraversalDetected;
12
+ }
@@ -0,0 +1,8 @@
1
+ export declare class PathPrefixer {
2
+ private readonly prefix;
3
+ constructor(prefix?: string);
4
+ prefixFilePath(path: string): string;
5
+ prefixDirectoryPath(path: string): string;
6
+ stripFilePath(path: string): string;
7
+ stripDirectoryPath(path: string): string;
8
+ }
@@ -0,0 +1,4 @@
1
+ export declare enum Visibility {
2
+ PUBLIC = "public",
3
+ PRIVATE = "private"
4
+ }
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@flystorage/file-storage",
3
+ "version": "0.0.1",
4
+ "description": "File-storage abstraction: multiple filesystems, one API.",
5
+ "main": "./dist/cjs/index.js",
6
+ "types": "./dist/types/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "import": {
10
+ "default": "./dist/esm/index.js",
11
+ "types": "./dist/types/index.d.ts"
12
+ },
13
+ "require": {
14
+ "default": "./dist/cjs/index.js",
15
+ "types": "./dist/types/index.d.ts"
16
+ }
17
+ }
18
+ },
19
+ "scripts": {
20
+ "compile": "concurrently npm:compile:*",
21
+ "compile:esm": "tsc --outDir ./dist/esm/ --declaration false",
22
+ "compile:cjs": "tsc --outDir ./dist/cjs/ --declaration false --module commonjs --moduleResolution node",
23
+ "compile:types": "tsc --outDir ./dist/types/ --declaration --emitDeclarationOnly",
24
+ "watch": "tsc --watch"
25
+ },
26
+ "author": "Frank de Jonge (https://frankdejonge.nl)",
27
+ "keywords": ["fs", "file", "files", "filesystem", "filesystems", "storage"],
28
+ "license": "MIT"
29
+ }