@naturalcycles/cloud-storage-lib 1.8.0 → 1.9.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.
@@ -38,6 +38,7 @@ export declare class CloudStorage implements CommonStorage {
38
38
  static createFromStorageOptions(storageOptions?: StorageOptions): CloudStorage;
39
39
  ping(bucketName?: string): Promise<void>;
40
40
  deletePath(bucketName: string, prefix: string): Promise<void>;
41
+ deletePaths(bucketName: string, prefixes: string[]): Promise<void>;
41
42
  fileExists(bucketName: string, filePath: string): Promise<boolean>;
42
43
  getFileNames(bucketName: string, opt?: CommonStorageGetOptions): Promise<string[]>;
43
44
  getFileNamesStream(bucketName: string, opt?: CommonStorageGetOptions): ReadableTyped<string>;
@@ -56,6 +57,7 @@ export declare class CloudStorage implements CommonStorage {
56
57
  copyFile(fromBucket: string, fromPath: string, toPath: string, toBucket?: string): Promise<void>;
57
58
  moveFile(fromBucket: string, fromPath: string, toPath: string, toBucket?: string): Promise<void>;
58
59
  movePath(fromBucket: string, fromPrefix: string, toPrefix: string, toBucket?: string): Promise<void>;
60
+ combine(bucketName: string, filePaths: string[], toPath: string, toBucket?: string): Promise<void>;
59
61
  /**
60
62
  * Acquires a "signed url", which allows bearer to use it to download ('read') the file.
61
63
  *
@@ -38,10 +38,16 @@ class CloudStorage {
38
38
  await this.storage.bucket(bucketName || 'non-existing-for-sure').exists();
39
39
  }
40
40
  async deletePath(bucketName, prefix) {
41
- await this.storage.bucket(bucketName).deleteFiles({
42
- prefix,
43
- // to keep going in case error occurs, similar to THROW_AGGREGATED
44
- force: true,
41
+ await this.deletePaths(bucketName, [prefix]);
42
+ }
43
+ async deletePaths(bucketName, prefixes) {
44
+ const bucket = this.storage.bucket(bucketName);
45
+ await (0, js_lib_1.pMap)(prefixes, async (prefix) => {
46
+ await bucket.deleteFiles({
47
+ prefix,
48
+ // to keep going in case error occurs, similar to THROW_AGGREGATED
49
+ force: true,
50
+ });
45
51
  });
46
52
  }
47
53
  async fileExists(bucketName, filePath) {
@@ -149,6 +155,15 @@ class CloudStorage {
149
155
  }),
150
156
  ]);
151
157
  }
158
+ async combine(bucketName, filePaths, toPath, toBucket) {
159
+ // todo: if (filePaths.length > 32) - use recursive algorithm
160
+ (0, js_lib_1._assert)(filePaths.length <= 32, 'combine supports up to 32 input files');
161
+ await this.storage
162
+ .bucket(bucketName)
163
+ .combine(filePaths, this.storage.bucket(toBucket || bucketName).file(toPath));
164
+ // Delete original files
165
+ await this.deletePaths(bucketName, filePaths);
166
+ }
152
167
  /**
153
168
  * Acquires a "signed url", which allows bearer to use it to download ('read') the file.
154
169
  *
@@ -162,6 +177,7 @@ class CloudStorage {
162
177
  .file(filePath)
163
178
  .getSignedUrl({
164
179
  action: 'read',
180
+ version: 'v4',
165
181
  expires: (0, js_lib_1.localTime)(expires).unixMillis(),
166
182
  });
167
183
  return url;
@@ -1,6 +1,7 @@
1
1
  /// <reference types="node" />
2
2
  /// <reference types="node" />
3
3
  import { Readable, Writable } from 'node:stream';
4
+ import { LocalTimeInput } from '@naturalcycles/js-lib';
4
5
  import { ReadableTyped } from '@naturalcycles/nodejs-lib';
5
6
  export interface FileEntry {
6
7
  filePath: string;
@@ -54,6 +55,7 @@ export interface CommonStorage {
54
55
  * Should recursively delete all files in a folder, if path is a folder.
55
56
  */
56
57
  deletePath: (bucketName: string, prefix: string) => Promise<void>;
58
+ deletePaths: (bucketName: string, prefixes: string[]) => Promise<void>;
57
59
  /**
58
60
  * Returns an array of strings which are file paths.
59
61
  * Files that are not found by the path are not present in the map.
@@ -85,4 +87,19 @@ export interface CommonStorage {
85
87
  * otherwise some folder that starts with the same prefix will be included.
86
88
  */
87
89
  movePath: (fromBucket: string, fromPrefix: string, toPrefix: string, toBucket?: string) => Promise<void>;
90
+ /**
91
+ * Combine (compose) multiple input files into a single output file.
92
+ * Should support unlimited number of input files, using recursive algorithm if necessary.
93
+ *
94
+ * After the output file is created, all input files should be deleted.
95
+ *
96
+ * @experimental
97
+ */
98
+ combine: (bucketName: string, filePaths: string[], toPath: string, toBucket?: string) => Promise<void>;
99
+ /**
100
+ * Acquire a "signed url", which allows bearer to use it to download ('read') the file.
101
+ *
102
+ * @experimental
103
+ */
104
+ getSignedUrl: (bucketName: string, filePath: string, expires: LocalTimeInput) => Promise<string>;
88
105
  }
@@ -1,7 +1,7 @@
1
1
  /// <reference types="node" />
2
2
  /// <reference types="node" />
3
3
  import { Readable, Writable } from 'node:stream';
4
- import { StringMap } from '@naturalcycles/js-lib';
4
+ import { LocalTimeInput, StringMap } from '@naturalcycles/js-lib';
5
5
  import { ReadableTyped } from '@naturalcycles/nodejs-lib';
6
6
  import { CommonStorage, CommonStorageGetOptions, FileEntry } from './commonStorage';
7
7
  export declare class InMemoryCommonStorage implements CommonStorage {
@@ -17,6 +17,7 @@ export declare class InMemoryCommonStorage implements CommonStorage {
17
17
  getFile(bucketName: string, filePath: string): Promise<Buffer | null>;
18
18
  saveFile(bucketName: string, filePath: string, content: Buffer): Promise<void>;
19
19
  deletePath(bucketName: string, prefix: string): Promise<void>;
20
+ deletePaths(bucketName: string, prefixes: string[]): Promise<void>;
20
21
  getFileNames(bucketName: string, opt?: CommonStorageGetOptions): Promise<string[]>;
21
22
  getFileNamesStream(bucketName: string, opt?: CommonStorageGetOptions): ReadableTyped<string>;
22
23
  getFilesStream(bucketName: string, opt?: CommonStorageGetOptions): ReadableTyped<FileEntry>;
@@ -28,4 +29,6 @@ export declare class InMemoryCommonStorage implements CommonStorage {
28
29
  copyFile(fromBucket: string, fromPath: string, toPath: string, toBucket?: string): Promise<void>;
29
30
  moveFile(fromBucket: string, fromPath: string, toPath: string, toBucket?: string): Promise<void>;
30
31
  movePath(fromBucket: string, fromPrefix: string, toPrefix: string, toBucket?: string): Promise<void>;
32
+ combine(bucketName: string, filePaths: string[], toPath: string, toBucket?: string): Promise<void>;
33
+ getSignedUrl(bucketName: string, filePath: string, expires: LocalTimeInput): Promise<string>;
31
34
  }
@@ -30,8 +30,11 @@ class InMemoryCommonStorage {
30
30
  this.data[bucketName][filePath] = content;
31
31
  }
32
32
  async deletePath(bucketName, prefix) {
33
+ await this.deletePaths(bucketName, [prefix]);
34
+ }
35
+ async deletePaths(bucketName, prefixes) {
33
36
  Object.keys(this.data[bucketName] || {}).forEach(filePath => {
34
- if (filePath.startsWith(prefix)) {
37
+ if (prefixes.some(prefix => filePath.startsWith(prefix))) {
35
38
  delete this.data[bucketName][filePath];
36
39
  }
37
40
  });
@@ -101,5 +104,20 @@ class InMemoryCommonStorage {
101
104
  delete this.data[fromBucket][filePath];
102
105
  });
103
106
  }
107
+ async combine(bucketName, filePaths, toPath, toBucket) {
108
+ if (!this.data[bucketName])
109
+ return;
110
+ const tob = toBucket || bucketName;
111
+ this.data[tob] ||= {};
112
+ this.data[tob][toPath] = Buffer.concat(filePaths.map(p => this.data[bucketName][p]).filter(js_lib_1._isTruthy));
113
+ // delete source files
114
+ filePaths.forEach(p => delete this.data[bucketName][p]);
115
+ }
116
+ async getSignedUrl(bucketName, filePath, expires) {
117
+ const buf = this.data[bucketName]?.[filePath];
118
+ (0, js_lib_1._assert)(buf, `getSignedUrl file not found: ${bucketName}/${filePath}`);
119
+ const signature = (0, nodejs_lib_1.md5)(buf);
120
+ return `https://testurl.com/${bucketName}/${filePath}?expires=${(0, js_lib_1.localTime)(expires).unix()}&signature=${signature}`;
121
+ }
104
122
  }
105
123
  exports.InMemoryCommonStorage = InMemoryCommonStorage;
package/package.json CHANGED
@@ -35,7 +35,7 @@
35
35
  "engines": {
36
36
  "node": ">=18.12.0"
37
37
  },
38
- "version": "1.8.0",
38
+ "version": "1.9.0",
39
39
  "description": "CommonStorage implementation based on Google Cloud Storage",
40
40
  "author": "Natural Cycles Team",
41
41
  "license": "MIT"
@@ -5,6 +5,7 @@ import {
5
5
  _substringAfterLast,
6
6
  localTime,
7
7
  LocalTimeInput,
8
+ pMap,
8
9
  SKIP,
9
10
  } from '@naturalcycles/js-lib'
10
11
  import {
@@ -75,10 +76,18 @@ export class CloudStorage implements CommonStorage {
75
76
  }
76
77
 
77
78
  async deletePath(bucketName: string, prefix: string): Promise<void> {
78
- await this.storage.bucket(bucketName).deleteFiles({
79
- prefix,
80
- // to keep going in case error occurs, similar to THROW_AGGREGATED
81
- force: true,
79
+ await this.deletePaths(bucketName, [prefix])
80
+ }
81
+
82
+ async deletePaths(bucketName: string, prefixes: string[]): Promise<void> {
83
+ const bucket = this.storage.bucket(bucketName)
84
+
85
+ await pMap(prefixes, async prefix => {
86
+ await bucket.deleteFiles({
87
+ prefix,
88
+ // to keep going in case error occurs, similar to THROW_AGGREGATED
89
+ force: true,
90
+ })
82
91
  })
83
92
  }
84
93
 
@@ -227,6 +236,23 @@ export class CloudStorage implements CommonStorage {
227
236
  ])
228
237
  }
229
238
 
239
+ async combine(
240
+ bucketName: string,
241
+ filePaths: string[],
242
+ toPath: string,
243
+ toBucket?: string,
244
+ ): Promise<void> {
245
+ // todo: if (filePaths.length > 32) - use recursive algorithm
246
+ _assert(filePaths.length <= 32, 'combine supports up to 32 input files')
247
+
248
+ await this.storage
249
+ .bucket(bucketName)
250
+ .combine(filePaths, this.storage.bucket(toBucket || bucketName).file(toPath))
251
+
252
+ // Delete original files
253
+ await this.deletePaths(bucketName, filePaths)
254
+ }
255
+
230
256
  /**
231
257
  * Acquires a "signed url", which allows bearer to use it to download ('read') the file.
232
258
  *
@@ -244,6 +270,7 @@ export class CloudStorage implements CommonStorage {
244
270
  .file(filePath)
245
271
  .getSignedUrl({
246
272
  action: 'read',
273
+ version: 'v4',
247
274
  expires: localTime(expires).unixMillis(),
248
275
  })
249
276
 
@@ -1,4 +1,5 @@
1
1
  import { Readable, Writable } from 'node:stream'
2
+ import { LocalTimeInput } from '@naturalcycles/js-lib'
2
3
  import { ReadableTyped } from '@naturalcycles/nodejs-lib'
3
4
 
4
5
  export interface FileEntry {
@@ -66,6 +67,8 @@ export interface CommonStorage {
66
67
  */
67
68
  deletePath: (bucketName: string, prefix: string) => Promise<void>
68
69
 
70
+ deletePaths: (bucketName: string, prefixes: string[]) => Promise<void>
71
+
69
72
  /**
70
73
  * Returns an array of strings which are file paths.
71
74
  * Files that are not found by the path are not present in the map.
@@ -122,4 +125,26 @@ export interface CommonStorage {
122
125
  toPrefix: string,
123
126
  toBucket?: string,
124
127
  ) => Promise<void>
128
+
129
+ /**
130
+ * Combine (compose) multiple input files into a single output file.
131
+ * Should support unlimited number of input files, using recursive algorithm if necessary.
132
+ *
133
+ * After the output file is created, all input files should be deleted.
134
+ *
135
+ * @experimental
136
+ */
137
+ combine: (
138
+ bucketName: string,
139
+ filePaths: string[],
140
+ toPath: string,
141
+ toBucket?: string,
142
+ ) => Promise<void>
143
+
144
+ /**
145
+ * Acquire a "signed url", which allows bearer to use it to download ('read') the file.
146
+ *
147
+ * @experimental
148
+ */
149
+ getSignedUrl: (bucketName: string, filePath: string, expires: LocalTimeInput) => Promise<string>
125
150
  }
@@ -1,6 +1,14 @@
1
1
  import { Readable, Writable } from 'node:stream'
2
- import { _stringMapEntries, _substringAfterLast, StringMap } from '@naturalcycles/js-lib'
3
- import { fs2, ReadableTyped } from '@naturalcycles/nodejs-lib'
2
+ import {
3
+ _assert,
4
+ _isTruthy,
5
+ _stringMapEntries,
6
+ _substringAfterLast,
7
+ localTime,
8
+ LocalTimeInput,
9
+ StringMap,
10
+ } from '@naturalcycles/js-lib'
11
+ import { fs2, md5, ReadableTyped } from '@naturalcycles/nodejs-lib'
4
12
  import { CommonStorage, CommonStorageGetOptions, FileEntry } from './commonStorage'
5
13
 
6
14
  export class InMemoryCommonStorage implements CommonStorage {
@@ -35,8 +43,12 @@ export class InMemoryCommonStorage implements CommonStorage {
35
43
  }
36
44
 
37
45
  async deletePath(bucketName: string, prefix: string): Promise<void> {
46
+ await this.deletePaths(bucketName, [prefix])
47
+ }
48
+
49
+ async deletePaths(bucketName: string, prefixes: string[]): Promise<void> {
38
50
  Object.keys(this.data[bucketName] || {}).forEach(filePath => {
39
- if (filePath.startsWith(prefix)) {
51
+ if (prefixes.some(prefix => filePath.startsWith(prefix))) {
40
52
  delete this.data[bucketName]![filePath]
41
53
  }
42
54
  })
@@ -142,4 +154,32 @@ export class InMemoryCommonStorage implements CommonStorage {
142
154
  delete this.data[fromBucket]![filePath]
143
155
  })
144
156
  }
157
+
158
+ async combine(
159
+ bucketName: string,
160
+ filePaths: string[],
161
+ toPath: string,
162
+ toBucket?: string,
163
+ ): Promise<void> {
164
+ if (!this.data[bucketName]) return
165
+ const tob = toBucket || bucketName
166
+ this.data[tob] ||= {}
167
+ this.data[tob]![toPath] = Buffer.concat(
168
+ filePaths.map(p => this.data[bucketName]![p]).filter(_isTruthy),
169
+ )
170
+
171
+ // delete source files
172
+ filePaths.forEach(p => delete this.data[bucketName]![p])
173
+ }
174
+
175
+ async getSignedUrl(
176
+ bucketName: string,
177
+ filePath: string,
178
+ expires: LocalTimeInput,
179
+ ): Promise<string> {
180
+ const buf = this.data[bucketName]?.[filePath]
181
+ _assert(buf, `getSignedUrl file not found: ${bucketName}/${filePath}`)
182
+ const signature = md5(buf)
183
+ return `https://testurl.com/${bucketName}/${filePath}?expires=${localTime(expires).unix()}&signature=${signature}`
184
+ }
145
185
  }