@flystorage/file-storage 1.0.1 → 1.2.0
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 +22 -29
- package/changelog.md +15 -0
- package/dist/cjs/errors.js +19 -2
- package/dist/cjs/file-storage.js +132 -47
- package/dist/esm/errors.js +16 -1
- package/dist/esm/file-storage.js +133 -49
- package/dist/types/errors.d.ts +12 -0
- package/dist/types/file-storage.d.ts +53 -36
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -20,35 +20,28 @@ to, simply because they cannot be abstracted over in a reasonable manner.
|
|
|
20
20
|
|
|
21
21
|
## Capabilities
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
- [
|
|
43
|
-
- [
|
|
44
|
-
- [
|
|
45
|
-
- [x] [Test implementation (in-memory)](https://www.npmjs.com/package/@flystorage/in-memory)
|
|
46
|
-
- [x] [Google Cloud Storage](https://www.npmjs.com/package/@flystorage/google-cloud-storage)
|
|
47
|
-
- [x] [Chaos adapter decorator](https://www.npmjs.com/package/@flystorage/chaos)
|
|
48
|
-
|
|
49
|
-
### Planned
|
|
50
|
-
- [ ] FTP (using `basic-ftp`)
|
|
51
|
-
- [ ] SFTP (?)
|
|
23
|
+
- Write files using string | buffer | readable/stream
|
|
24
|
+
- Read files as stream, string, or Uint8Array
|
|
25
|
+
- List the contents of a directory/prefix, (shallow and deep).
|
|
26
|
+
- Metadata, such as; mime-type, last modified timestamp, and file size.
|
|
27
|
+
- Expose or calculate checksums for files.
|
|
28
|
+
- Delete files without failing when they don't exist.
|
|
29
|
+
- Set permissions using abstracted visibility
|
|
30
|
+
- Delete directories (and any files it contains)
|
|
31
|
+
- Generate public URLs.
|
|
32
|
+
- Generate temporary (signed) URLs.
|
|
33
|
+
- Secure direct uploads from the browser.
|
|
34
|
+
- Moving files
|
|
35
|
+
- Copying files
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
## Adapters
|
|
39
|
+
- [Local Filesystem](https://www.npmjs.com/package/@flystorage/local-fs)
|
|
40
|
+
- [AWS S3 (using the V3 SDK)](https://www.npmjs.com/package/@flystorage/aws-s3)
|
|
41
|
+
- [Azure Blob Storage](https://www.npmjs.com/package/@flystorage/azure-storage-blob)
|
|
42
|
+
- [Test implementation (in-memory)](https://www.npmjs.com/package/@flystorage/in-memory)
|
|
43
|
+
- [Google Cloud Storage](https://www.npmjs.com/package/@flystorage/google-cloud-storage)
|
|
44
|
+
- [Chaos adapter decorator](https://www.npmjs.com/package/@flystorage/chaos)
|
|
52
45
|
|
|
53
46
|
## Usage
|
|
54
47
|
Install the main package and any adapters you might need:
|
package/changelog.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# `@flystorage/file-storage`
|
|
2
2
|
|
|
3
|
+
## 1.2.0
|
|
4
|
+
|
|
5
|
+
## Added
|
|
6
|
+
|
|
7
|
+
- Ability to ignore visibility using configuration
|
|
8
|
+
- Support Bun runtime
|
|
9
|
+
|
|
10
|
+
## 1.1.0
|
|
11
|
+
|
|
12
|
+
## Added
|
|
13
|
+
|
|
14
|
+
- AbortSignal Support
|
|
15
|
+
- Normalised over stream errors for reads
|
|
16
|
+
- Introduce way to detect failed reads because of missing files.
|
|
17
|
+
|
|
3
18
|
## 1.0.1
|
|
4
19
|
|
|
5
20
|
### Fixes
|
package/dist/cjs/errors.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.UnableToListDirectory = exports.UnableToCheckDirectoryExistence = exports.UnableToCheckFileExistence = exports.UnableToDeleteFile = exports.UnableToDeleteDirectory = exports.UnableToCreateDirectory = exports.UnableToGetStat = exports.UnableToMoveFile = exports.UnableToCopyFile = exports.UnableToPrepareUploadRequest = exports.UnableToGetTemporaryUrl = exports.UnableToGetPublicUrl = exports.UnableToGetVisibility = exports.UnableToSetVisibility = exports.UnableToReadFile = exports.UnableToWriteFile = exports.UnableToGetFileSize = exports.UnableToGetLastModified = exports.UnableToGetMimeType = exports.UnableToGetChecksum = exports.ChecksumIsNotAvailable = exports.FlystorageError = void 0;
|
|
3
|
+
exports.UnableToListDirectory = exports.UnableToCheckDirectoryExistence = exports.UnableToCheckFileExistence = exports.UnableToDeleteFile = exports.UnableToDeleteDirectory = exports.UnableToCreateDirectory = exports.UnableToGetStat = exports.UnableToMoveFile = exports.UnableToCopyFile = exports.UnableToPrepareUploadRequest = exports.UnableToGetTemporaryUrl = exports.UnableToGetPublicUrl = exports.UnableToGetVisibility = exports.UnableToSetVisibility = exports.FileWasNotFound = exports.UnableToReadFile = exports.UnableToWriteFile = exports.UnableToGetFileSize = exports.UnableToGetLastModified = exports.UnableToGetMimeType = exports.UnableToGetChecksum = exports.ChecksumIsNotAvailable = exports.FlystorageError = void 0;
|
|
4
4
|
exports.errorToMessage = errorToMessage;
|
|
5
|
+
exports.isFileWasNotFound = isFileWasNotFound;
|
|
5
6
|
function errorToMessage(error) {
|
|
6
7
|
return error instanceof Error ? error.message : String(error);
|
|
7
8
|
}
|
|
@@ -59,10 +60,26 @@ class UnableToWriteFile extends FlystorageError {
|
|
|
59
60
|
}
|
|
60
61
|
exports.UnableToWriteFile = UnableToWriteFile;
|
|
61
62
|
class UnableToReadFile extends FlystorageError {
|
|
63
|
+
wasFileNotFound;
|
|
64
|
+
context;
|
|
62
65
|
code = 'flystorage.unable_to_read_file';
|
|
63
|
-
|
|
66
|
+
constructor(wasFileNotFound, message, context = {}, cause = undefined) {
|
|
67
|
+
super(message, context, cause);
|
|
68
|
+
this.wasFileNotFound = wasFileNotFound;
|
|
69
|
+
this.context = context;
|
|
70
|
+
}
|
|
71
|
+
static because = (reason, { context = {}, cause = undefined }) => new UnableToReadFile(false, `Unable to read the file. Reason: ${reason}`, context, cause);
|
|
72
|
+
static becauseFileWasNotFound = (error) => new UnableToReadFile(true, `Unable to read the file. Reason: ${error.message}`, error.context, error);
|
|
64
73
|
}
|
|
65
74
|
exports.UnableToReadFile = UnableToReadFile;
|
|
75
|
+
class FileWasNotFound extends FlystorageError {
|
|
76
|
+
code = 'flystorage.file_was_not_found';
|
|
77
|
+
static atLocation = (location, { context = {}, cause = undefined }) => new FileWasNotFound(`File was not found at location: ${location}`, context, cause);
|
|
78
|
+
}
|
|
79
|
+
exports.FileWasNotFound = FileWasNotFound;
|
|
80
|
+
function isFileWasNotFound(error) {
|
|
81
|
+
return (typeof error === 'object' && error.code === 'flystorage.file_was_not_found');
|
|
82
|
+
}
|
|
66
83
|
class UnableToSetVisibility extends FlystorageError {
|
|
67
84
|
code = 'flystorage.unable_to_set_visibility';
|
|
68
85
|
static because = (reason, { context = {}, cause = undefined }) => new UnableToSetVisibility(`Unable to set visibility. Reason: ${reason}`, context, cause);
|
package/dist/cjs/file-storage.js
CHANGED
|
@@ -8,12 +8,14 @@ exports.normalizeExpiryToDate = normalizeExpiryToDate;
|
|
|
8
8
|
exports.normalizeExpiryToMilliseconds = normalizeExpiryToMilliseconds;
|
|
9
9
|
exports.closeReadable = closeReadable;
|
|
10
10
|
exports.readableToString = readableToString;
|
|
11
|
+
exports.readableToBuffer = readableToBuffer;
|
|
11
12
|
exports.readableToUint8Array = readableToUint8Array;
|
|
12
13
|
const stream_1 = require("stream");
|
|
13
14
|
const checksum_from_stream_js_1 = require("./checksum-from-stream.js");
|
|
14
15
|
const path_normalizer_js_1 = require("./path-normalizer.js");
|
|
15
16
|
const util_1 = require("util");
|
|
16
17
|
const errors_js_1 = require("./errors.js");
|
|
18
|
+
const node_stream_1 = require("node:stream");
|
|
17
19
|
function isFile(stat) {
|
|
18
20
|
return stat.isFile;
|
|
19
21
|
}
|
|
@@ -67,8 +69,28 @@ function toReadable(contents) {
|
|
|
67
69
|
}
|
|
68
70
|
const naturalSorting = new Intl.Collator(undefined, {
|
|
69
71
|
numeric: true,
|
|
70
|
-
sensitivity: 'base'
|
|
72
|
+
sensitivity: 'base',
|
|
71
73
|
});
|
|
74
|
+
function instrumentAbortSignal(options) {
|
|
75
|
+
let abortSignal = options.abortSignal;
|
|
76
|
+
if (options.timeout !== undefined) {
|
|
77
|
+
const timeoutAbort = AbortSignal.timeout(options.timeout);
|
|
78
|
+
if (options.abortSignal) {
|
|
79
|
+
const originalAbortSignal = options.abortSignal;
|
|
80
|
+
abortSignal = AbortSignal.any([
|
|
81
|
+
originalAbortSignal,
|
|
82
|
+
timeoutAbort,
|
|
83
|
+
]);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
abortSignal = timeoutAbort;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (abortSignal?.aborted) {
|
|
90
|
+
throw abortSignal.reason;
|
|
91
|
+
}
|
|
92
|
+
return { ...options, abortSignal };
|
|
93
|
+
}
|
|
72
94
|
class FileStorage {
|
|
73
95
|
adapter;
|
|
74
96
|
pathNormalizer;
|
|
@@ -79,145 +101,194 @@ class FileStorage {
|
|
|
79
101
|
this.options = options;
|
|
80
102
|
}
|
|
81
103
|
async write(path, contents, options = {}) {
|
|
104
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...this.options.visibility, ...this.options.writes, ...options });
|
|
82
105
|
try {
|
|
83
106
|
const body = toReadable(contents);
|
|
84
|
-
await this.adapter.write(this.pathNormalizer.normalizePath(path), body,
|
|
107
|
+
await this.adapter.write(this.pathNormalizer.normalizePath(path), body, options);
|
|
85
108
|
await closeReadable(body);
|
|
86
109
|
}
|
|
87
110
|
catch (error) {
|
|
88
111
|
throw errors_js_1.UnableToWriteFile.because((0, errors_js_1.errorToMessage)(error), { cause: error, context: { path, options } });
|
|
89
112
|
}
|
|
90
113
|
}
|
|
91
|
-
async read(path) {
|
|
114
|
+
async read(path, options = {}) {
|
|
115
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...options });
|
|
92
116
|
try {
|
|
93
|
-
|
|
117
|
+
const stream = stream_1.Readable.from(await this.adapter.read(this.pathNormalizer.normalizePath(path), options));
|
|
118
|
+
const streamOut = new node_stream_1.PassThrough();
|
|
119
|
+
stream.on('error', (error) => {
|
|
120
|
+
stream.unpipe(streamOut);
|
|
121
|
+
streamOut.destroy((0, errors_js_1.isFileWasNotFound)(error)
|
|
122
|
+
? errors_js_1.UnableToReadFile.becauseFileWasNotFound(error)
|
|
123
|
+
: error);
|
|
124
|
+
});
|
|
125
|
+
stream.pipe(streamOut);
|
|
126
|
+
return streamOut;
|
|
94
127
|
}
|
|
95
128
|
catch (error) {
|
|
129
|
+
if ((0, errors_js_1.isFileWasNotFound)(error)) {
|
|
130
|
+
throw errors_js_1.UnableToReadFile.becauseFileWasNotFound(error);
|
|
131
|
+
}
|
|
96
132
|
throw errors_js_1.UnableToReadFile.because((0, errors_js_1.errorToMessage)(error), { cause: error, context: { path } });
|
|
97
133
|
}
|
|
98
134
|
}
|
|
99
|
-
async readToString(path) {
|
|
100
|
-
return await readableToString(await this.read(path));
|
|
135
|
+
async readToString(path, options = {}) {
|
|
136
|
+
return await readableToString(await this.read(path, options));
|
|
101
137
|
}
|
|
102
|
-
async readToUint8Array(path) {
|
|
103
|
-
return await readableToUint8Array(await this.read(path));
|
|
138
|
+
async readToUint8Array(path, options = {}) {
|
|
139
|
+
return await readableToUint8Array(await this.read(path, options));
|
|
104
140
|
}
|
|
105
|
-
async readToBuffer(path) {
|
|
106
|
-
return Buffer.from(await this.readToUint8Array(path));
|
|
141
|
+
async readToBuffer(path, options = {}) {
|
|
142
|
+
return Buffer.from(await this.readToUint8Array(path, options));
|
|
107
143
|
}
|
|
108
|
-
async deleteFile(path) {
|
|
144
|
+
async deleteFile(path, options = {}) {
|
|
145
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...options });
|
|
109
146
|
try {
|
|
110
|
-
await this.adapter.deleteFile(this.pathNormalizer.normalizePath(path));
|
|
147
|
+
await this.adapter.deleteFile(this.pathNormalizer.normalizePath(path), options);
|
|
111
148
|
}
|
|
112
149
|
catch (error) {
|
|
113
150
|
throw errors_js_1.UnableToDeleteFile.because((0, errors_js_1.errorToMessage)(error), { cause: error, context: { path } });
|
|
114
151
|
}
|
|
115
152
|
}
|
|
116
153
|
async createDirectory(path, options = {}) {
|
|
154
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...this.options.visibility, ...options });
|
|
117
155
|
try {
|
|
118
|
-
return await this.adapter.createDirectory(this.pathNormalizer.normalizePath(path),
|
|
156
|
+
return await this.adapter.createDirectory(this.pathNormalizer.normalizePath(path), options);
|
|
119
157
|
}
|
|
120
158
|
catch (error) {
|
|
121
159
|
throw errors_js_1.UnableToCreateDirectory.because((0, errors_js_1.errorToMessage)(error), { cause: error, context: { path, options } });
|
|
122
160
|
}
|
|
123
161
|
}
|
|
124
|
-
async deleteDirectory(path) {
|
|
162
|
+
async deleteDirectory(path, options = {}) {
|
|
163
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...options });
|
|
125
164
|
try {
|
|
126
|
-
return await this.adapter.deleteDirectory(this.pathNormalizer.normalizePath(path));
|
|
165
|
+
return await this.adapter.deleteDirectory(this.pathNormalizer.normalizePath(path), options);
|
|
127
166
|
}
|
|
128
167
|
catch (error) {
|
|
129
168
|
throw errors_js_1.UnableToDeleteDirectory.because((0, errors_js_1.errorToMessage)(error), { cause: error, context: { path } });
|
|
130
169
|
}
|
|
131
170
|
}
|
|
132
|
-
async stat(path) {
|
|
171
|
+
async stat(path, options = {}) {
|
|
172
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...options });
|
|
133
173
|
try {
|
|
134
|
-
return await this.adapter.stat(this.pathNormalizer.normalizePath(path));
|
|
174
|
+
return await this.adapter.stat(this.pathNormalizer.normalizePath(path), options);
|
|
135
175
|
}
|
|
136
176
|
catch (error) {
|
|
137
177
|
throw errors_js_1.UnableToGetStat.because((0, errors_js_1.errorToMessage)(error), { cause: error, context: { path } });
|
|
138
178
|
}
|
|
139
179
|
}
|
|
140
180
|
async moveFile(from, to, options = {}) {
|
|
181
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...this.options.visibility, ...this.options.moves, ...options });
|
|
141
182
|
try {
|
|
142
|
-
await this.adapter.moveFile(this.pathNormalizer.normalizePath(from), this.pathNormalizer.normalizePath(to),
|
|
183
|
+
await this.adapter.moveFile(this.pathNormalizer.normalizePath(from), this.pathNormalizer.normalizePath(to), options);
|
|
143
184
|
}
|
|
144
185
|
catch (error) {
|
|
145
186
|
throw errors_js_1.UnableToMoveFile.because((0, errors_js_1.errorToMessage)(error), { cause: error, context: { from, to } });
|
|
146
187
|
}
|
|
147
188
|
}
|
|
148
189
|
async copyFile(from, to, options = {}) {
|
|
190
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...this.options.visibility, ...this.options.copies, ...options });
|
|
149
191
|
try {
|
|
150
|
-
await this.adapter.copyFile(this.pathNormalizer.normalizePath(from), this.pathNormalizer.normalizePath(to),
|
|
192
|
+
await this.adapter.copyFile(this.pathNormalizer.normalizePath(from), this.pathNormalizer.normalizePath(to), options);
|
|
151
193
|
}
|
|
152
194
|
catch (error) {
|
|
153
195
|
throw errors_js_1.UnableToCopyFile.because((0, errors_js_1.errorToMessage)(error), { cause: error, context: { from, to } });
|
|
154
196
|
}
|
|
155
197
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
198
|
+
async changeVisibility(path, visibility, options = {}) {
|
|
199
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...options });
|
|
200
|
+
if (options.useVisibility === false) {
|
|
201
|
+
const fallback = options.visibilityFallback;
|
|
202
|
+
if (fallback.strategy === 'ignore') {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
else if (fallback.strategy === 'error') {
|
|
206
|
+
throw errors_js_1.UnableToSetVisibility.because(fallback.errorMessage ?? 'Configured not to use visibility', {
|
|
207
|
+
context: { path, visibility },
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
163
211
|
try {
|
|
164
|
-
return await this.adapter.changeVisibility(this.pathNormalizer.normalizePath(path), visibility);
|
|
212
|
+
return await this.adapter.changeVisibility(this.pathNormalizer.normalizePath(path), visibility, options);
|
|
165
213
|
}
|
|
166
214
|
catch (error) {
|
|
167
215
|
throw errors_js_1.UnableToSetVisibility.because((0, errors_js_1.errorToMessage)(error), { cause: error, context: { path, visibility } });
|
|
168
216
|
}
|
|
169
217
|
}
|
|
170
|
-
async visibility(path) {
|
|
218
|
+
async visibility(path, options = {}) {
|
|
219
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...this.options.visibility, ...options });
|
|
220
|
+
if (options.useVisibility === false) {
|
|
221
|
+
const fallback = options.visibilityFallback;
|
|
222
|
+
if (fallback.strategy === 'ignore') {
|
|
223
|
+
return fallback.stagedVisibilityResponse ?? 'unknown';
|
|
224
|
+
}
|
|
225
|
+
else if (fallback.strategy === 'error') {
|
|
226
|
+
throw errors_js_1.UnableToGetVisibility.because(fallback.errorMessage ?? 'Configured not to use visibility', {
|
|
227
|
+
context: { path },
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
171
231
|
try {
|
|
172
|
-
return await this.adapter.visibility(this.pathNormalizer.normalizePath(path));
|
|
232
|
+
return await this.adapter.visibility(this.pathNormalizer.normalizePath(path), options);
|
|
173
233
|
}
|
|
174
234
|
catch (error) {
|
|
175
235
|
throw errors_js_1.UnableToGetVisibility.because((0, errors_js_1.errorToMessage)(error), { cause: error, context: { path } });
|
|
176
236
|
}
|
|
177
237
|
}
|
|
178
|
-
async fileExists(path) {
|
|
238
|
+
async fileExists(path, options = {}) {
|
|
239
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...options });
|
|
179
240
|
try {
|
|
180
|
-
return await this.adapter.fileExists(this.pathNormalizer.normalizePath(path));
|
|
241
|
+
return await this.adapter.fileExists(this.pathNormalizer.normalizePath(path), options);
|
|
181
242
|
}
|
|
182
243
|
catch (error) {
|
|
183
244
|
throw errors_js_1.UnableToCheckFileExistence.because((0, errors_js_1.errorToMessage)(error), { cause: error, context: { path } });
|
|
184
245
|
}
|
|
185
246
|
}
|
|
186
|
-
list(path,
|
|
187
|
-
|
|
247
|
+
list(path, options = {}) {
|
|
248
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...this.options.list, ...options });
|
|
249
|
+
const adapterOptions = {
|
|
250
|
+
...options,
|
|
251
|
+
deep: options.deep ?? false,
|
|
252
|
+
};
|
|
253
|
+
return new DirectoryListing(this.adapter.list(this.pathNormalizer.normalizePath(path), adapterOptions), path, adapterOptions.deep);
|
|
188
254
|
}
|
|
189
|
-
async statFile(path) {
|
|
190
|
-
|
|
255
|
+
async statFile(path, options = {}) {
|
|
256
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...options });
|
|
257
|
+
const stat = await this.stat(path, options);
|
|
191
258
|
if (isFile(stat)) {
|
|
192
259
|
return stat;
|
|
193
260
|
}
|
|
194
261
|
throw errors_js_1.UnableToGetStat.noFileStatResolved({ context: { path } });
|
|
195
262
|
}
|
|
196
|
-
async directoryExists(path) {
|
|
263
|
+
async directoryExists(path, options = {}) {
|
|
264
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...options });
|
|
197
265
|
try {
|
|
198
|
-
return await this.adapter.directoryExists(this.pathNormalizer.normalizePath(path));
|
|
266
|
+
return await this.adapter.directoryExists(this.pathNormalizer.normalizePath(path), options);
|
|
199
267
|
}
|
|
200
268
|
catch (error) {
|
|
201
269
|
throw errors_js_1.UnableToCheckDirectoryExistence.because((0, errors_js_1.errorToMessage)(error), { cause: error, context: { path } });
|
|
202
270
|
}
|
|
203
271
|
}
|
|
204
272
|
async publicUrl(path, options = {}) {
|
|
273
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...this.options.publicUrls, ...options });
|
|
205
274
|
try {
|
|
206
|
-
return await this.adapter.publicUrl(this.pathNormalizer.normalizePath(path),
|
|
275
|
+
return await this.adapter.publicUrl(this.pathNormalizer.normalizePath(path), options);
|
|
207
276
|
}
|
|
208
277
|
catch (error) {
|
|
209
278
|
throw errors_js_1.UnableToGetPublicUrl.because((0, errors_js_1.errorToMessage)(error), { cause: error, context: { path, options } });
|
|
210
279
|
}
|
|
211
280
|
}
|
|
212
281
|
async temporaryUrl(path, options) {
|
|
282
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...this.options.temporaryUrls, ...options });
|
|
213
283
|
try {
|
|
214
|
-
return await this.adapter.temporaryUrl(this.pathNormalizer.normalizePath(path),
|
|
284
|
+
return await this.adapter.temporaryUrl(this.pathNormalizer.normalizePath(path), options);
|
|
215
285
|
}
|
|
216
286
|
catch (error) {
|
|
217
287
|
throw errors_js_1.UnableToGetTemporaryUrl.because((0, errors_js_1.errorToMessage)(error), { cause: error, context: { path, options } });
|
|
218
288
|
}
|
|
219
289
|
}
|
|
220
290
|
async prepareUpload(path, options) {
|
|
291
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...this.options.uploadRequest, ...options });
|
|
221
292
|
if (this.options.preparedUploadStrategy !== undefined) {
|
|
222
293
|
try {
|
|
223
294
|
return this.options.preparedUploadStrategy.prepareUpload(path, options);
|
|
@@ -230,15 +301,16 @@ class FileStorage {
|
|
|
230
301
|
throw new Error('The used adapter does not support prepared uploads.');
|
|
231
302
|
}
|
|
232
303
|
try {
|
|
233
|
-
return await this.adapter.prepareUpload(this.pathNormalizer.normalizePath(path),
|
|
304
|
+
return await this.adapter.prepareUpload(this.pathNormalizer.normalizePath(path), options);
|
|
234
305
|
}
|
|
235
306
|
catch (error) {
|
|
236
307
|
throw errors_js_1.UnableToPrepareUploadRequest.because((0, errors_js_1.errorToMessage)(error), { cause: error, context: { path, options } });
|
|
237
308
|
}
|
|
238
309
|
}
|
|
239
310
|
async checksum(path, options = {}) {
|
|
311
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...this.options.checksums, ...options });
|
|
240
312
|
try {
|
|
241
|
-
return await this.adapter.checksum(this.pathNormalizer.normalizePath(path),
|
|
313
|
+
return await this.adapter.checksum(this.pathNormalizer.normalizePath(path), options);
|
|
242
314
|
}
|
|
243
315
|
catch (error) {
|
|
244
316
|
if (errors_js_1.ChecksumIsNotAvailable.isErrorOfType(error)) {
|
|
@@ -248,24 +320,27 @@ class FileStorage {
|
|
|
248
320
|
}
|
|
249
321
|
}
|
|
250
322
|
async mimeType(path, options = {}) {
|
|
323
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...this.options.mimeTypes, ...options });
|
|
251
324
|
try {
|
|
252
|
-
return await this.adapter.mimeType(this.pathNormalizer.normalizePath(path),
|
|
325
|
+
return await this.adapter.mimeType(this.pathNormalizer.normalizePath(path), options);
|
|
253
326
|
}
|
|
254
327
|
catch (error) {
|
|
255
328
|
throw errors_js_1.UnableToGetMimeType.because((0, errors_js_1.errorToMessage)(error), { cause: error, context: { path, options } });
|
|
256
329
|
}
|
|
257
330
|
}
|
|
258
|
-
async lastModified(path) {
|
|
331
|
+
async lastModified(path, options = {}) {
|
|
332
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...options });
|
|
259
333
|
try {
|
|
260
|
-
return await this.adapter.lastModified(this.pathNormalizer.normalizePath(path));
|
|
334
|
+
return await this.adapter.lastModified(this.pathNormalizer.normalizePath(path), options);
|
|
261
335
|
}
|
|
262
336
|
catch (error) {
|
|
263
337
|
throw errors_js_1.UnableToGetLastModified.because((0, errors_js_1.errorToMessage)(error), { cause: error, context: { path } });
|
|
264
338
|
}
|
|
265
339
|
}
|
|
266
|
-
async fileSize(path) {
|
|
340
|
+
async fileSize(path, options = {}) {
|
|
341
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...options });
|
|
267
342
|
try {
|
|
268
|
-
return await this.adapter.fileSize(this.pathNormalizer.normalizePath(path));
|
|
343
|
+
return await this.adapter.fileSize(this.pathNormalizer.normalizePath(path), options);
|
|
269
344
|
}
|
|
270
345
|
catch (error) {
|
|
271
346
|
throw errors_js_1.UnableToGetFileSize.because((0, errors_js_1.errorToMessage)(error), { cause: error, context: { path } });
|
|
@@ -273,7 +348,7 @@ class FileStorage {
|
|
|
273
348
|
}
|
|
274
349
|
async calculateChecksum(path, options) {
|
|
275
350
|
try {
|
|
276
|
-
return await (0, checksum_from_stream_js_1.checksumFromStream)(await this.read(path), options);
|
|
351
|
+
return await (0, checksum_from_stream_js_1.checksumFromStream)(await this.read(path, options), options);
|
|
277
352
|
}
|
|
278
353
|
catch (error) {
|
|
279
354
|
throw errors_js_1.UnableToGetChecksum.because((0, errors_js_1.errorToMessage)(error), { cause: error, context: { path, options } });
|
|
@@ -292,6 +367,7 @@ async function closeReadable(body) {
|
|
|
292
367
|
return;
|
|
293
368
|
}
|
|
294
369
|
await new Promise((resolve, reject) => {
|
|
370
|
+
body.on('error', reject);
|
|
295
371
|
body.on('close', (err) => {
|
|
296
372
|
err ? reject(err) : resolve();
|
|
297
373
|
});
|
|
@@ -304,6 +380,15 @@ async function readableToString(stream) {
|
|
|
304
380
|
await closeReadable(stream);
|
|
305
381
|
return contents;
|
|
306
382
|
}
|
|
383
|
+
async function readableToBuffer(stream) {
|
|
384
|
+
return new Promise((resolve, reject) => {
|
|
385
|
+
const buffers = [];
|
|
386
|
+
stream.on('data', chunk => buffers.push(Buffer.from(chunk)));
|
|
387
|
+
stream.on('end', () => resolve(Buffer.concat(buffers)));
|
|
388
|
+
stream.on('finish', () => resolve(Buffer.concat(buffers)));
|
|
389
|
+
stream.on('error', err => reject(err));
|
|
390
|
+
});
|
|
391
|
+
}
|
|
307
392
|
const encoder = new util_1.TextEncoder();
|
|
308
393
|
function readableToUint8Array(stream) {
|
|
309
394
|
return new Promise((resolve, reject) => {
|
package/dist/esm/errors.js
CHANGED
|
@@ -48,8 +48,23 @@ export class UnableToWriteFile extends FlystorageError {
|
|
|
48
48
|
static because = (reason, { context = {}, cause = undefined }) => new UnableToWriteFile(`Unable to write the file. Reason: ${reason}`, context, cause);
|
|
49
49
|
}
|
|
50
50
|
export class UnableToReadFile extends FlystorageError {
|
|
51
|
+
wasFileNotFound;
|
|
52
|
+
context;
|
|
51
53
|
code = 'flystorage.unable_to_read_file';
|
|
52
|
-
|
|
54
|
+
constructor(wasFileNotFound, message, context = {}, cause = undefined) {
|
|
55
|
+
super(message, context, cause);
|
|
56
|
+
this.wasFileNotFound = wasFileNotFound;
|
|
57
|
+
this.context = context;
|
|
58
|
+
}
|
|
59
|
+
static because = (reason, { context = {}, cause = undefined }) => new UnableToReadFile(false, `Unable to read the file. Reason: ${reason}`, context, cause);
|
|
60
|
+
static becauseFileWasNotFound = (error) => new UnableToReadFile(true, `Unable to read the file. Reason: ${error.message}`, error.context, error);
|
|
61
|
+
}
|
|
62
|
+
export class FileWasNotFound extends FlystorageError {
|
|
63
|
+
code = 'flystorage.file_was_not_found';
|
|
64
|
+
static atLocation = (location, { context = {}, cause = undefined }) => new FileWasNotFound(`File was not found at location: ${location}`, context, cause);
|
|
65
|
+
}
|
|
66
|
+
export function isFileWasNotFound(error) {
|
|
67
|
+
return (typeof error === 'object' && error.code === 'flystorage.file_was_not_found');
|
|
53
68
|
}
|
|
54
69
|
export class UnableToSetVisibility extends FlystorageError {
|
|
55
70
|
code = 'flystorage.unable_to_set_visibility';
|
package/dist/esm/file-storage.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Readable } from 'stream';
|
|
2
2
|
import { checksumFromStream } from './checksum-from-stream.js';
|
|
3
3
|
import { PathNormalizerV1 } from './path-normalizer.js';
|
|
4
|
-
import { TextEncoder } from
|
|
5
|
-
import { ChecksumIsNotAvailable, errorToMessage, UnableToCheckDirectoryExistence, UnableToCheckFileExistence, UnableToCopyFile, UnableToCreateDirectory, UnableToDeleteDirectory, UnableToDeleteFile, UnableToGetChecksum, UnableToGetFileSize, UnableToGetLastModified, UnableToGetMimeType, UnableToGetPublicUrl, UnableToGetStat, UnableToGetTemporaryUrl, UnableToGetVisibility, UnableToListDirectory, UnableToMoveFile, UnableToPrepareUploadRequest, UnableToReadFile, UnableToSetVisibility, UnableToWriteFile, } from './errors.js';
|
|
4
|
+
import { TextEncoder } from 'util';
|
|
5
|
+
import { ChecksumIsNotAvailable, errorToMessage, isFileWasNotFound, UnableToCheckDirectoryExistence, UnableToCheckFileExistence, UnableToCopyFile, UnableToCreateDirectory, UnableToDeleteDirectory, UnableToDeleteFile, UnableToGetChecksum, UnableToGetFileSize, UnableToGetLastModified, UnableToGetMimeType, UnableToGetPublicUrl, UnableToGetStat, UnableToGetTemporaryUrl, UnableToGetVisibility, UnableToListDirectory, UnableToMoveFile, UnableToPrepareUploadRequest, UnableToReadFile, UnableToSetVisibility, UnableToWriteFile, } from './errors.js';
|
|
6
|
+
import { PassThrough } from 'node:stream';
|
|
6
7
|
export function isFile(stat) {
|
|
7
8
|
return stat.isFile;
|
|
8
9
|
}
|
|
@@ -55,8 +56,28 @@ export function toReadable(contents) {
|
|
|
55
56
|
}
|
|
56
57
|
const naturalSorting = new Intl.Collator(undefined, {
|
|
57
58
|
numeric: true,
|
|
58
|
-
sensitivity: 'base'
|
|
59
|
+
sensitivity: 'base',
|
|
59
60
|
});
|
|
61
|
+
function instrumentAbortSignal(options) {
|
|
62
|
+
let abortSignal = options.abortSignal;
|
|
63
|
+
if (options.timeout !== undefined) {
|
|
64
|
+
const timeoutAbort = AbortSignal.timeout(options.timeout);
|
|
65
|
+
if (options.abortSignal) {
|
|
66
|
+
const originalAbortSignal = options.abortSignal;
|
|
67
|
+
abortSignal = AbortSignal.any([
|
|
68
|
+
originalAbortSignal,
|
|
69
|
+
timeoutAbort,
|
|
70
|
+
]);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
abortSignal = timeoutAbort;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (abortSignal?.aborted) {
|
|
77
|
+
throw abortSignal.reason;
|
|
78
|
+
}
|
|
79
|
+
return { ...options, abortSignal };
|
|
80
|
+
}
|
|
60
81
|
export class FileStorage {
|
|
61
82
|
adapter;
|
|
62
83
|
pathNormalizer;
|
|
@@ -67,145 +88,194 @@ export class FileStorage {
|
|
|
67
88
|
this.options = options;
|
|
68
89
|
}
|
|
69
90
|
async write(path, contents, options = {}) {
|
|
91
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...this.options.visibility, ...this.options.writes, ...options });
|
|
70
92
|
try {
|
|
71
93
|
const body = toReadable(contents);
|
|
72
|
-
await this.adapter.write(this.pathNormalizer.normalizePath(path), body,
|
|
94
|
+
await this.adapter.write(this.pathNormalizer.normalizePath(path), body, options);
|
|
73
95
|
await closeReadable(body);
|
|
74
96
|
}
|
|
75
97
|
catch (error) {
|
|
76
98
|
throw UnableToWriteFile.because(errorToMessage(error), { cause: error, context: { path, options } });
|
|
77
99
|
}
|
|
78
100
|
}
|
|
79
|
-
async read(path) {
|
|
101
|
+
async read(path, options = {}) {
|
|
102
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...options });
|
|
80
103
|
try {
|
|
81
|
-
|
|
104
|
+
const stream = Readable.from(await this.adapter.read(this.pathNormalizer.normalizePath(path), options));
|
|
105
|
+
const streamOut = new PassThrough();
|
|
106
|
+
stream.on('error', (error) => {
|
|
107
|
+
stream.unpipe(streamOut);
|
|
108
|
+
streamOut.destroy(isFileWasNotFound(error)
|
|
109
|
+
? UnableToReadFile.becauseFileWasNotFound(error)
|
|
110
|
+
: error);
|
|
111
|
+
});
|
|
112
|
+
stream.pipe(streamOut);
|
|
113
|
+
return streamOut;
|
|
82
114
|
}
|
|
83
115
|
catch (error) {
|
|
116
|
+
if (isFileWasNotFound(error)) {
|
|
117
|
+
throw UnableToReadFile.becauseFileWasNotFound(error);
|
|
118
|
+
}
|
|
84
119
|
throw UnableToReadFile.because(errorToMessage(error), { cause: error, context: { path } });
|
|
85
120
|
}
|
|
86
121
|
}
|
|
87
|
-
async readToString(path) {
|
|
88
|
-
return await readableToString(await this.read(path));
|
|
122
|
+
async readToString(path, options = {}) {
|
|
123
|
+
return await readableToString(await this.read(path, options));
|
|
89
124
|
}
|
|
90
|
-
async readToUint8Array(path) {
|
|
91
|
-
return await readableToUint8Array(await this.read(path));
|
|
125
|
+
async readToUint8Array(path, options = {}) {
|
|
126
|
+
return await readableToUint8Array(await this.read(path, options));
|
|
92
127
|
}
|
|
93
|
-
async readToBuffer(path) {
|
|
94
|
-
return Buffer.from(await this.readToUint8Array(path));
|
|
128
|
+
async readToBuffer(path, options = {}) {
|
|
129
|
+
return Buffer.from(await this.readToUint8Array(path, options));
|
|
95
130
|
}
|
|
96
|
-
async deleteFile(path) {
|
|
131
|
+
async deleteFile(path, options = {}) {
|
|
132
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...options });
|
|
97
133
|
try {
|
|
98
|
-
await this.adapter.deleteFile(this.pathNormalizer.normalizePath(path));
|
|
134
|
+
await this.adapter.deleteFile(this.pathNormalizer.normalizePath(path), options);
|
|
99
135
|
}
|
|
100
136
|
catch (error) {
|
|
101
137
|
throw UnableToDeleteFile.because(errorToMessage(error), { cause: error, context: { path } });
|
|
102
138
|
}
|
|
103
139
|
}
|
|
104
140
|
async createDirectory(path, options = {}) {
|
|
141
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...this.options.visibility, ...options });
|
|
105
142
|
try {
|
|
106
|
-
return await this.adapter.createDirectory(this.pathNormalizer.normalizePath(path),
|
|
143
|
+
return await this.adapter.createDirectory(this.pathNormalizer.normalizePath(path), options);
|
|
107
144
|
}
|
|
108
145
|
catch (error) {
|
|
109
146
|
throw UnableToCreateDirectory.because(errorToMessage(error), { cause: error, context: { path, options } });
|
|
110
147
|
}
|
|
111
148
|
}
|
|
112
|
-
async deleteDirectory(path) {
|
|
149
|
+
async deleteDirectory(path, options = {}) {
|
|
150
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...options });
|
|
113
151
|
try {
|
|
114
|
-
return await this.adapter.deleteDirectory(this.pathNormalizer.normalizePath(path));
|
|
152
|
+
return await this.adapter.deleteDirectory(this.pathNormalizer.normalizePath(path), options);
|
|
115
153
|
}
|
|
116
154
|
catch (error) {
|
|
117
155
|
throw UnableToDeleteDirectory.because(errorToMessage(error), { cause: error, context: { path } });
|
|
118
156
|
}
|
|
119
157
|
}
|
|
120
|
-
async stat(path) {
|
|
158
|
+
async stat(path, options = {}) {
|
|
159
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...options });
|
|
121
160
|
try {
|
|
122
|
-
return await this.adapter.stat(this.pathNormalizer.normalizePath(path));
|
|
161
|
+
return await this.adapter.stat(this.pathNormalizer.normalizePath(path), options);
|
|
123
162
|
}
|
|
124
163
|
catch (error) {
|
|
125
164
|
throw UnableToGetStat.because(errorToMessage(error), { cause: error, context: { path } });
|
|
126
165
|
}
|
|
127
166
|
}
|
|
128
167
|
async moveFile(from, to, options = {}) {
|
|
168
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...this.options.visibility, ...this.options.moves, ...options });
|
|
129
169
|
try {
|
|
130
|
-
await this.adapter.moveFile(this.pathNormalizer.normalizePath(from), this.pathNormalizer.normalizePath(to),
|
|
170
|
+
await this.adapter.moveFile(this.pathNormalizer.normalizePath(from), this.pathNormalizer.normalizePath(to), options);
|
|
131
171
|
}
|
|
132
172
|
catch (error) {
|
|
133
173
|
throw UnableToMoveFile.because(errorToMessage(error), { cause: error, context: { from, to } });
|
|
134
174
|
}
|
|
135
175
|
}
|
|
136
176
|
async copyFile(from, to, options = {}) {
|
|
177
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...this.options.visibility, ...this.options.copies, ...options });
|
|
137
178
|
try {
|
|
138
|
-
await this.adapter.copyFile(this.pathNormalizer.normalizePath(from), this.pathNormalizer.normalizePath(to),
|
|
179
|
+
await this.adapter.copyFile(this.pathNormalizer.normalizePath(from), this.pathNormalizer.normalizePath(to), options);
|
|
139
180
|
}
|
|
140
181
|
catch (error) {
|
|
141
182
|
throw UnableToCopyFile.because(errorToMessage(error), { cause: error, context: { from, to } });
|
|
142
183
|
}
|
|
143
184
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
185
|
+
async changeVisibility(path, visibility, options = {}) {
|
|
186
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...options });
|
|
187
|
+
if (options.useVisibility === false) {
|
|
188
|
+
const fallback = options.visibilityFallback;
|
|
189
|
+
if (fallback.strategy === 'ignore') {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
else if (fallback.strategy === 'error') {
|
|
193
|
+
throw UnableToSetVisibility.because(fallback.errorMessage ?? 'Configured not to use visibility', {
|
|
194
|
+
context: { path, visibility },
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
151
198
|
try {
|
|
152
|
-
return await this.adapter.changeVisibility(this.pathNormalizer.normalizePath(path), visibility);
|
|
199
|
+
return await this.adapter.changeVisibility(this.pathNormalizer.normalizePath(path), visibility, options);
|
|
153
200
|
}
|
|
154
201
|
catch (error) {
|
|
155
202
|
throw UnableToSetVisibility.because(errorToMessage(error), { cause: error, context: { path, visibility } });
|
|
156
203
|
}
|
|
157
204
|
}
|
|
158
|
-
async visibility(path) {
|
|
205
|
+
async visibility(path, options = {}) {
|
|
206
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...this.options.visibility, ...options });
|
|
207
|
+
if (options.useVisibility === false) {
|
|
208
|
+
const fallback = options.visibilityFallback;
|
|
209
|
+
if (fallback.strategy === 'ignore') {
|
|
210
|
+
return fallback.stagedVisibilityResponse ?? 'unknown';
|
|
211
|
+
}
|
|
212
|
+
else if (fallback.strategy === 'error') {
|
|
213
|
+
throw UnableToGetVisibility.because(fallback.errorMessage ?? 'Configured not to use visibility', {
|
|
214
|
+
context: { path },
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
159
218
|
try {
|
|
160
|
-
return await this.adapter.visibility(this.pathNormalizer.normalizePath(path));
|
|
219
|
+
return await this.adapter.visibility(this.pathNormalizer.normalizePath(path), options);
|
|
161
220
|
}
|
|
162
221
|
catch (error) {
|
|
163
222
|
throw UnableToGetVisibility.because(errorToMessage(error), { cause: error, context: { path } });
|
|
164
223
|
}
|
|
165
224
|
}
|
|
166
|
-
async fileExists(path) {
|
|
225
|
+
async fileExists(path, options = {}) {
|
|
226
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...options });
|
|
167
227
|
try {
|
|
168
|
-
return await this.adapter.fileExists(this.pathNormalizer.normalizePath(path));
|
|
228
|
+
return await this.adapter.fileExists(this.pathNormalizer.normalizePath(path), options);
|
|
169
229
|
}
|
|
170
230
|
catch (error) {
|
|
171
231
|
throw UnableToCheckFileExistence.because(errorToMessage(error), { cause: error, context: { path } });
|
|
172
232
|
}
|
|
173
233
|
}
|
|
174
|
-
list(path,
|
|
175
|
-
|
|
234
|
+
list(path, options = {}) {
|
|
235
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...this.options.list, ...options });
|
|
236
|
+
const adapterOptions = {
|
|
237
|
+
...options,
|
|
238
|
+
deep: options.deep ?? false,
|
|
239
|
+
};
|
|
240
|
+
return new DirectoryListing(this.adapter.list(this.pathNormalizer.normalizePath(path), adapterOptions), path, adapterOptions.deep);
|
|
176
241
|
}
|
|
177
|
-
async statFile(path) {
|
|
178
|
-
|
|
242
|
+
async statFile(path, options = {}) {
|
|
243
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...options });
|
|
244
|
+
const stat = await this.stat(path, options);
|
|
179
245
|
if (isFile(stat)) {
|
|
180
246
|
return stat;
|
|
181
247
|
}
|
|
182
248
|
throw UnableToGetStat.noFileStatResolved({ context: { path } });
|
|
183
249
|
}
|
|
184
|
-
async directoryExists(path) {
|
|
250
|
+
async directoryExists(path, options = {}) {
|
|
251
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...options });
|
|
185
252
|
try {
|
|
186
|
-
return await this.adapter.directoryExists(this.pathNormalizer.normalizePath(path));
|
|
253
|
+
return await this.adapter.directoryExists(this.pathNormalizer.normalizePath(path), options);
|
|
187
254
|
}
|
|
188
255
|
catch (error) {
|
|
189
256
|
throw UnableToCheckDirectoryExistence.because(errorToMessage(error), { cause: error, context: { path } });
|
|
190
257
|
}
|
|
191
258
|
}
|
|
192
259
|
async publicUrl(path, options = {}) {
|
|
260
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...this.options.publicUrls, ...options });
|
|
193
261
|
try {
|
|
194
|
-
return await this.adapter.publicUrl(this.pathNormalizer.normalizePath(path),
|
|
262
|
+
return await this.adapter.publicUrl(this.pathNormalizer.normalizePath(path), options);
|
|
195
263
|
}
|
|
196
264
|
catch (error) {
|
|
197
265
|
throw UnableToGetPublicUrl.because(errorToMessage(error), { cause: error, context: { path, options } });
|
|
198
266
|
}
|
|
199
267
|
}
|
|
200
268
|
async temporaryUrl(path, options) {
|
|
269
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...this.options.temporaryUrls, ...options });
|
|
201
270
|
try {
|
|
202
|
-
return await this.adapter.temporaryUrl(this.pathNormalizer.normalizePath(path),
|
|
271
|
+
return await this.adapter.temporaryUrl(this.pathNormalizer.normalizePath(path), options);
|
|
203
272
|
}
|
|
204
273
|
catch (error) {
|
|
205
274
|
throw UnableToGetTemporaryUrl.because(errorToMessage(error), { cause: error, context: { path, options } });
|
|
206
275
|
}
|
|
207
276
|
}
|
|
208
277
|
async prepareUpload(path, options) {
|
|
278
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...this.options.uploadRequest, ...options });
|
|
209
279
|
if (this.options.preparedUploadStrategy !== undefined) {
|
|
210
280
|
try {
|
|
211
281
|
return this.options.preparedUploadStrategy.prepareUpload(path, options);
|
|
@@ -218,15 +288,16 @@ export class FileStorage {
|
|
|
218
288
|
throw new Error('The used adapter does not support prepared uploads.');
|
|
219
289
|
}
|
|
220
290
|
try {
|
|
221
|
-
return await this.adapter.prepareUpload(this.pathNormalizer.normalizePath(path),
|
|
291
|
+
return await this.adapter.prepareUpload(this.pathNormalizer.normalizePath(path), options);
|
|
222
292
|
}
|
|
223
293
|
catch (error) {
|
|
224
294
|
throw UnableToPrepareUploadRequest.because(errorToMessage(error), { cause: error, context: { path, options } });
|
|
225
295
|
}
|
|
226
296
|
}
|
|
227
297
|
async checksum(path, options = {}) {
|
|
298
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...this.options.checksums, ...options });
|
|
228
299
|
try {
|
|
229
|
-
return await this.adapter.checksum(this.pathNormalizer.normalizePath(path),
|
|
300
|
+
return await this.adapter.checksum(this.pathNormalizer.normalizePath(path), options);
|
|
230
301
|
}
|
|
231
302
|
catch (error) {
|
|
232
303
|
if (ChecksumIsNotAvailable.isErrorOfType(error)) {
|
|
@@ -236,24 +307,27 @@ export class FileStorage {
|
|
|
236
307
|
}
|
|
237
308
|
}
|
|
238
309
|
async mimeType(path, options = {}) {
|
|
310
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...this.options.mimeTypes, ...options });
|
|
239
311
|
try {
|
|
240
|
-
return await this.adapter.mimeType(this.pathNormalizer.normalizePath(path),
|
|
312
|
+
return await this.adapter.mimeType(this.pathNormalizer.normalizePath(path), options);
|
|
241
313
|
}
|
|
242
314
|
catch (error) {
|
|
243
315
|
throw UnableToGetMimeType.because(errorToMessage(error), { cause: error, context: { path, options } });
|
|
244
316
|
}
|
|
245
317
|
}
|
|
246
|
-
async lastModified(path) {
|
|
318
|
+
async lastModified(path, options = {}) {
|
|
319
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...options });
|
|
247
320
|
try {
|
|
248
|
-
return await this.adapter.lastModified(this.pathNormalizer.normalizePath(path));
|
|
321
|
+
return await this.adapter.lastModified(this.pathNormalizer.normalizePath(path), options);
|
|
249
322
|
}
|
|
250
323
|
catch (error) {
|
|
251
324
|
throw UnableToGetLastModified.because(errorToMessage(error), { cause: error, context: { path } });
|
|
252
325
|
}
|
|
253
326
|
}
|
|
254
|
-
async fileSize(path) {
|
|
327
|
+
async fileSize(path, options = {}) {
|
|
328
|
+
options = instrumentAbortSignal({ ...this.options.timeout, ...options });
|
|
255
329
|
try {
|
|
256
|
-
return await this.adapter.fileSize(this.pathNormalizer.normalizePath(path));
|
|
330
|
+
return await this.adapter.fileSize(this.pathNormalizer.normalizePath(path), options);
|
|
257
331
|
}
|
|
258
332
|
catch (error) {
|
|
259
333
|
throw UnableToGetFileSize.because(errorToMessage(error), { cause: error, context: { path } });
|
|
@@ -261,7 +335,7 @@ export class FileStorage {
|
|
|
261
335
|
}
|
|
262
336
|
async calculateChecksum(path, options) {
|
|
263
337
|
try {
|
|
264
|
-
return await checksumFromStream(await this.read(path), options);
|
|
338
|
+
return await checksumFromStream(await this.read(path, options), options);
|
|
265
339
|
}
|
|
266
340
|
catch (error) {
|
|
267
341
|
throw UnableToGetChecksum.because(errorToMessage(error), { cause: error, context: { path, options } });
|
|
@@ -279,6 +353,7 @@ export async function closeReadable(body) {
|
|
|
279
353
|
return;
|
|
280
354
|
}
|
|
281
355
|
await new Promise((resolve, reject) => {
|
|
356
|
+
body.on('error', reject);
|
|
282
357
|
body.on('close', (err) => {
|
|
283
358
|
err ? reject(err) : resolve();
|
|
284
359
|
});
|
|
@@ -291,6 +366,15 @@ export async function readableToString(stream) {
|
|
|
291
366
|
await closeReadable(stream);
|
|
292
367
|
return contents;
|
|
293
368
|
}
|
|
369
|
+
export async function readableToBuffer(stream) {
|
|
370
|
+
return new Promise((resolve, reject) => {
|
|
371
|
+
const buffers = [];
|
|
372
|
+
stream.on('data', chunk => buffers.push(Buffer.from(chunk)));
|
|
373
|
+
stream.on('end', () => resolve(Buffer.concat(buffers)));
|
|
374
|
+
stream.on('finish', () => resolve(Buffer.concat(buffers)));
|
|
375
|
+
stream.on('error', err => reject(err));
|
|
376
|
+
});
|
|
377
|
+
}
|
|
294
378
|
const encoder = new TextEncoder();
|
|
295
379
|
export function readableToUint8Array(stream) {
|
|
296
380
|
return new Promise((resolve, reject) => {
|
package/dist/types/errors.d.ts
CHANGED
|
@@ -61,12 +61,24 @@ export declare class UnableToWriteFile extends FlystorageError {
|
|
|
61
61
|
}) => UnableToWriteFile;
|
|
62
62
|
}
|
|
63
63
|
export declare class UnableToReadFile extends FlystorageError {
|
|
64
|
+
readonly wasFileNotFound: boolean;
|
|
65
|
+
readonly context: ErrorContext;
|
|
64
66
|
readonly code = "flystorage.unable_to_read_file";
|
|
67
|
+
constructor(wasFileNotFound: boolean, message: string, context?: ErrorContext, cause?: unknown);
|
|
65
68
|
static because: (reason: string, { context, cause }: {
|
|
66
69
|
context?: ErrorContext;
|
|
67
70
|
cause?: unknown;
|
|
68
71
|
}) => UnableToReadFile;
|
|
72
|
+
static becauseFileWasNotFound: (error: FileWasNotFound) => UnableToReadFile;
|
|
73
|
+
}
|
|
74
|
+
export declare class FileWasNotFound extends FlystorageError {
|
|
75
|
+
readonly code = "flystorage.file_was_not_found";
|
|
76
|
+
static atLocation: (location: string, { context, cause }: {
|
|
77
|
+
context?: ErrorContext;
|
|
78
|
+
cause?: unknown;
|
|
79
|
+
}) => FileWasNotFound;
|
|
69
80
|
}
|
|
81
|
+
export declare function isFileWasNotFound(error: unknown): error is FileWasNotFound;
|
|
70
82
|
export declare class UnableToSetVisibility extends FlystorageError {
|
|
71
83
|
readonly code = "flystorage.unable_to_set_visibility";
|
|
72
84
|
static because: (reason: string, { context, cause }: {
|
|
@@ -21,29 +21,30 @@ export type DirectoryInfo = Readonly<{
|
|
|
21
21
|
export declare function isFile(stat: StatEntry): stat is FileInfo;
|
|
22
22
|
export declare function isDirectory(stat: StatEntry): stat is DirectoryInfo;
|
|
23
23
|
export type StatEntry = FileInfo | DirectoryInfo;
|
|
24
|
+
export type AdapterListOptions = ListOptions & {
|
|
25
|
+
deep: boolean;
|
|
26
|
+
};
|
|
24
27
|
export interface StorageAdapter {
|
|
25
28
|
write(path: string, contents: Readable, options: WriteOptions): Promise<void>;
|
|
26
|
-
read(path: string): Promise<FileContents>;
|
|
27
|
-
deleteFile(path: string): Promise<void>;
|
|
29
|
+
read(path: string, options: MiscellaneousOptions): Promise<FileContents>;
|
|
30
|
+
deleteFile(path: string, options: MiscellaneousOptions): Promise<void>;
|
|
28
31
|
createDirectory(path: string, options: CreateDirectoryOptions): Promise<void>;
|
|
29
32
|
copyFile(from: string, to: string, options: CopyFileOptions): Promise<void>;
|
|
30
33
|
moveFile(from: string, to: string, options: MoveFileOptions): Promise<void>;
|
|
31
|
-
stat(path: string): Promise<StatEntry>;
|
|
32
|
-
list(path: string, options:
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
fileExists(path: string): Promise<boolean>;
|
|
39
|
-
directoryExists(path: string): Promise<boolean>;
|
|
34
|
+
stat(path: string, options: MiscellaneousOptions): Promise<StatEntry>;
|
|
35
|
+
list(path: string, options: AdapterListOptions): AsyncGenerator<StatEntry>;
|
|
36
|
+
changeVisibility(path: string, visibility: string, options: MiscellaneousOptions): Promise<void>;
|
|
37
|
+
visibility(path: string, options: MiscellaneousOptions): Promise<string>;
|
|
38
|
+
deleteDirectory(path: string, options: MiscellaneousOptions): Promise<void>;
|
|
39
|
+
fileExists(path: string, options: MiscellaneousOptions): Promise<boolean>;
|
|
40
|
+
directoryExists(path: string, options: MiscellaneousOptions): Promise<boolean>;
|
|
40
41
|
publicUrl(path: string, options: PublicUrlOptions): Promise<string>;
|
|
41
42
|
temporaryUrl(path: string, options: TemporaryUrlOptions): Promise<string>;
|
|
42
43
|
prepareUpload?(path: string, options: UploadRequestOptions): Promise<UploadRequest>;
|
|
43
44
|
checksum(path: string, options: ChecksumOptions): Promise<string>;
|
|
44
45
|
mimeType(path: string, options: MimeTypeOptions): Promise<string>;
|
|
45
|
-
lastModified(path: string): Promise<number>;
|
|
46
|
-
fileSize(path: string): Promise<number>;
|
|
46
|
+
lastModified(path: string, options: MiscellaneousOptions): Promise<number>;
|
|
47
|
+
fileSize(path: string, options: MiscellaneousOptions): Promise<number>;
|
|
47
48
|
}
|
|
48
49
|
export declare class DirectoryListing implements AsyncIterable<StatEntry> {
|
|
49
50
|
private readonly listing;
|
|
@@ -55,17 +56,34 @@ export declare class DirectoryListing implements AsyncIterable<StatEntry> {
|
|
|
55
56
|
[Symbol.asyncIterator](): AsyncGenerator<StatEntry, void, unknown>;
|
|
56
57
|
}
|
|
57
58
|
export type FileContents = Iterable<any> | AsyncIterable<any> | NodeJS.ReadableStream | Readable | string;
|
|
58
|
-
export type
|
|
59
|
+
export type TimeoutOptions = {
|
|
60
|
+
timout?: number;
|
|
61
|
+
};
|
|
62
|
+
export type MiscellaneousOptions = TimeoutOptions & {
|
|
59
63
|
[option: string]: any;
|
|
64
|
+
abortSignal?: AbortSignal;
|
|
60
65
|
};
|
|
61
66
|
export type MimeTypeOptions = MiscellaneousOptions & {
|
|
62
67
|
disallowFallback?: boolean;
|
|
63
68
|
fallbackMethod?: 'contents' | 'path';
|
|
64
69
|
};
|
|
65
|
-
export type
|
|
70
|
+
export type VisibilityFallback = {
|
|
71
|
+
strategy: 'ignore';
|
|
72
|
+
stagedVisibilityResponse?: string;
|
|
73
|
+
} | {
|
|
74
|
+
strategy: 'error';
|
|
75
|
+
errorMessage?: string;
|
|
76
|
+
};
|
|
77
|
+
export type VisibilityOptions = MiscellaneousOptions & {
|
|
66
78
|
visibility?: string;
|
|
67
79
|
directoryVisibility?: string;
|
|
68
|
-
|
|
80
|
+
retainVisibility?: boolean;
|
|
81
|
+
} & ({
|
|
82
|
+
useVisibility: true;
|
|
83
|
+
} | {
|
|
84
|
+
useVisibility: false;
|
|
85
|
+
visibilityFallback: VisibilityFallback;
|
|
86
|
+
} | {});
|
|
69
87
|
export type WriteOptions = VisibilityOptions & MiscellaneousOptions & {
|
|
70
88
|
mimeType?: string;
|
|
71
89
|
size?: number;
|
|
@@ -84,7 +102,7 @@ export type CopyFileOptions = MiscellaneousOptions & VisibilityOptions & {
|
|
|
84
102
|
export type MoveFileOptions = MiscellaneousOptions & VisibilityOptions & {
|
|
85
103
|
retainVisibility?: boolean;
|
|
86
104
|
};
|
|
87
|
-
export type ListOptions = {
|
|
105
|
+
export type ListOptions = MiscellaneousOptions & {
|
|
88
106
|
deep?: boolean;
|
|
89
107
|
};
|
|
90
108
|
export type TemporaryUrlOptions = MiscellaneousOptions & {
|
|
@@ -108,6 +126,8 @@ export type ConfigurationOptions = {
|
|
|
108
126
|
checksums?: ChecksumOptions;
|
|
109
127
|
mimeTypes?: MimeTypeOptions;
|
|
110
128
|
preparedUploadStrategy?: PreparedUploadStrategy;
|
|
129
|
+
timeout?: TimeoutOptions;
|
|
130
|
+
list?: ListOptions;
|
|
111
131
|
};
|
|
112
132
|
export declare function toReadable(contents: FileContents): Readable;
|
|
113
133
|
export declare class FileStorage {
|
|
@@ -116,33 +136,29 @@ export declare class FileStorage {
|
|
|
116
136
|
private readonly options;
|
|
117
137
|
constructor(adapter: StorageAdapter, pathNormalizer?: PathNormalizer, options?: ConfigurationOptions);
|
|
118
138
|
write(path: string, contents: FileContents, options?: WriteOptions): Promise<void>;
|
|
119
|
-
read(path: string): Promise<Readable>;
|
|
120
|
-
readToString(path: string): Promise<string>;
|
|
121
|
-
readToUint8Array(path: string): Promise<Uint8Array>;
|
|
122
|
-
readToBuffer(path: string): Promise<Buffer>;
|
|
123
|
-
deleteFile(path: string): Promise<void>;
|
|
139
|
+
read(path: string, options?: MiscellaneousOptions): Promise<Readable>;
|
|
140
|
+
readToString(path: string, options?: MiscellaneousOptions): Promise<string>;
|
|
141
|
+
readToUint8Array(path: string, options?: MiscellaneousOptions): Promise<Uint8Array>;
|
|
142
|
+
readToBuffer(path: string, options?: MiscellaneousOptions): Promise<Buffer>;
|
|
143
|
+
deleteFile(path: string, options?: MiscellaneousOptions): Promise<void>;
|
|
124
144
|
createDirectory(path: string, options?: CreateDirectoryOptions): Promise<void>;
|
|
125
|
-
deleteDirectory(path: string): Promise<void>;
|
|
126
|
-
stat(path: string): Promise<StatEntry>;
|
|
145
|
+
deleteDirectory(path: string, options?: MiscellaneousOptions): Promise<void>;
|
|
146
|
+
stat(path: string, options?: MiscellaneousOptions): Promise<StatEntry>;
|
|
127
147
|
moveFile(from: string, to: string, options?: MoveFileOptions): Promise<void>;
|
|
128
148
|
copyFile(from: string, to: string, options?: CopyFileOptions): Promise<void>;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
fileExists(path: string): Promise<boolean>;
|
|
136
|
-
list(path: string, { deep }?: ListOptions): DirectoryListing;
|
|
137
|
-
statFile(path: string): Promise<FileInfo>;
|
|
138
|
-
directoryExists(path: string): Promise<boolean>;
|
|
149
|
+
changeVisibility(path: string, visibility: string, options?: VisibilityOptions): Promise<void>;
|
|
150
|
+
visibility(path: string, options?: VisibilityOptions): Promise<string>;
|
|
151
|
+
fileExists(path: string, options?: MiscellaneousOptions): Promise<boolean>;
|
|
152
|
+
list(path: string, options?: ListOptions): DirectoryListing;
|
|
153
|
+
statFile(path: string, options?: MiscellaneousOptions): Promise<FileInfo>;
|
|
154
|
+
directoryExists(path: string, options?: MiscellaneousOptions): Promise<boolean>;
|
|
139
155
|
publicUrl(path: string, options?: PublicUrlOptions): Promise<string>;
|
|
140
156
|
temporaryUrl(path: string, options: TemporaryUrlOptions): Promise<string>;
|
|
141
157
|
prepareUpload(path: string, options: UploadRequestOptions): Promise<UploadRequest>;
|
|
142
158
|
checksum(path: string, options?: ChecksumOptions): Promise<string>;
|
|
143
159
|
mimeType(path: string, options?: MimeTypeOptions): Promise<string>;
|
|
144
|
-
lastModified(path: string): Promise<number>;
|
|
145
|
-
fileSize(path: string): Promise<number>;
|
|
160
|
+
lastModified(path: string, options?: MiscellaneousOptions): Promise<number>;
|
|
161
|
+
fileSize(path: string, options?: MiscellaneousOptions): Promise<number>;
|
|
146
162
|
private calculateChecksum;
|
|
147
163
|
}
|
|
148
164
|
export type TimestampMs = number;
|
|
@@ -151,6 +167,7 @@ export declare function normalizeExpiryToDate(expiresAt: ExpiresAt): Date;
|
|
|
151
167
|
export declare function normalizeExpiryToMilliseconds(expiresAt: ExpiresAt): number;
|
|
152
168
|
export declare function closeReadable(body: Readable): Promise<void>;
|
|
153
169
|
export declare function readableToString(stream: Readable): Promise<string>;
|
|
170
|
+
export declare function readableToBuffer(stream: Readable): Promise<Buffer>;
|
|
154
171
|
export declare function readableToUint8Array(stream: Readable): Promise<Uint8Array>;
|
|
155
172
|
export type UploadRequestHeaders = Record<string, string | ReadonlyArray<string>>;
|
|
156
173
|
export type UploadRequest = {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flystorage/file-storage",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0
|
|
4
|
+
"version": "1.2.0",
|
|
5
5
|
"description": "File-storage abstraction: multiple filesystems, one API.",
|
|
6
6
|
"main": "./dist/cjs/index.js",
|
|
7
7
|
"types": "./dist/types/index.d.ts",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"url": "git+https://github.com/duna-oss/flystorage.git",
|
|
31
31
|
"directory": "packages/file-storage"
|
|
32
32
|
},
|
|
33
|
+
"homepage": "https://flystorage.dev/",
|
|
33
34
|
"keywords": [
|
|
34
35
|
"fs",
|
|
35
36
|
"file",
|