@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 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
- ### Implemented
24
- - [x] Write files using string | buffer | readable/stream
25
- - [x] Read files as stream, string, or Uint8Array
26
- - [x] Set permissions using abstracted visibility
27
- - [x] List the contents of a directory/prefix, (shallow and deep).
28
- - [x] Delete files without failing when they don't exist.
29
- - [x] Delete directories (and any files it contains)
30
- - [x] Generate public URLs.
31
- - [x] Generate temporary (signed) URLs.
32
- - [x] Expose or calculate checksums for files.
33
- - [x] Mime-type resolving
34
- - [x] Last modified fetching
35
- - [x] File size
36
- - [x] Moving files
37
- - [x] Copying files
38
-
39
- ## Implementations / Adapters
40
-
41
- ### Implemented
42
- - [x] [Local Filesystem](https://www.npmjs.com/package/@flystorage/local-fs)
43
- - [x] [AWS S3 (using the V3 SDK)](https://www.npmjs.com/package/@flystorage/aws-s3)
44
- - [x] [Azure Blob Storage](https://www.npmjs.com/package/@flystorage/azure-storage-blob)
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
@@ -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
- static because = (reason, { context = {}, cause = undefined }) => new UnableToReadFile(`Unable to read the file. Reason: ${reason}`, context, cause);
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);
@@ -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, { ...this.options.visibility, ...this.options.writes, ...options });
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
- return stream_1.Readable.from(await this.adapter.read(this.pathNormalizer.normalizePath(path)));
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), { ...this.options.visibility, ...options });
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), { ...this.options.visibility, ...this.options.moves, ...options });
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), { ...this.options.visibility, ...this.options.copies, ...options });
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
- * @deprecated use changeVisibility instead
158
- */
159
- async setVisibility(path, visibility) {
160
- return this.changeVisibility(path, visibility);
161
- }
162
- async changeVisibility(path, visibility) {
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, { deep = false } = {}) {
187
- return new DirectoryListing(this.adapter.list(this.pathNormalizer.normalizePath(path), { deep }), path, deep);
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
- const stat = await this.stat(path);
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), { ...this.options.publicUrls, ...options });
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), { ...this.options.temporaryUrls, ...options });
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), { ...this.options.uploadRequest, ...options });
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), { ...this.options.checksums, ...options });
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), { ...this.options.mimeTypes, ...options });
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) => {
@@ -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
- static because = (reason, { context = {}, cause = undefined }) => new UnableToReadFile(`Unable to read the file. Reason: ${reason}`, context, cause);
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';
@@ -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 "util";
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, { ...this.options.visibility, ...this.options.writes, ...options });
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
- return Readable.from(await this.adapter.read(this.pathNormalizer.normalizePath(path)));
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), { ...this.options.visibility, ...options });
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), { ...this.options.visibility, ...this.options.moves, ...options });
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), { ...this.options.visibility, ...this.options.copies, ...options });
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
- * @deprecated use changeVisibility instead
146
- */
147
- async setVisibility(path, visibility) {
148
- return this.changeVisibility(path, visibility);
149
- }
150
- async changeVisibility(path, visibility) {
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, { deep = false } = {}) {
175
- return new DirectoryListing(this.adapter.list(this.pathNormalizer.normalizePath(path), { deep }), path, deep);
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
- const stat = await this.stat(path);
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), { ...this.options.publicUrls, ...options });
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), { ...this.options.temporaryUrls, ...options });
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), { ...this.options.uploadRequest, ...options });
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), { ...this.options.checksums, ...options });
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), { ...this.options.mimeTypes, ...options });
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) => {
@@ -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
- deep: boolean;
34
- }): AsyncGenerator<StatEntry>;
35
- changeVisibility(path: string, visibility: string): Promise<void>;
36
- visibility(path: string): Promise<string>;
37
- deleteDirectory(path: string): Promise<void>;
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 MiscellaneousOptions = {
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 VisibilityOptions = {
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
- * @deprecated use changeVisibility instead
131
- */
132
- setVisibility(path: string, visibility: string): Promise<void>;
133
- changeVisibility(path: string, visibility: string): Promise<void>;
134
- visibility(path: string): Promise<string>;
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.1",
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",