@naturalcycles/cloud-storage-lib 1.6.5 → 1.8.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/dist/cloudStorage.d.ts +21 -0
- package/dist/cloudStorage.js +63 -4
- package/dist/commonStorage.d.ts +11 -0
- package/dist/inMemoryCommonStorage.d.ts +2 -0
- package/dist/inMemoryCommonStorage.js +16 -0
- package/package.json +3 -3
- package/src/cloudStorage.ts +96 -10
- package/src/commonStorage.ts +19 -0
- package/src/inMemoryCommonStorage.ts +28 -2
package/dist/cloudStorage.d.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
/// <reference types="node" />
|
|
3
3
|
import { Readable, Writable } from 'node:stream';
|
|
4
4
|
import { Storage, StorageOptions } from '@google-cloud/storage';
|
|
5
|
+
import { LocalTimeInput } from '@naturalcycles/js-lib';
|
|
5
6
|
import { ReadableTyped } from '@naturalcycles/nodejs-lib';
|
|
6
7
|
import { CommonStorage, CommonStorageGetOptions, FileEntry } from './commonStorage';
|
|
7
8
|
import { GCPServiceAccount } from './model';
|
|
@@ -21,6 +22,11 @@ export interface CloudStorageCfg {
|
|
|
21
22
|
*/
|
|
22
23
|
credentials?: GCPServiceAccount;
|
|
23
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* CloudStorage implementation of CommonStorage API.
|
|
27
|
+
*
|
|
28
|
+
* API: https://googleapis.dev/nodejs/storage/latest/index.html
|
|
29
|
+
*/
|
|
24
30
|
export declare class CloudStorage implements CommonStorage {
|
|
25
31
|
storage: Storage;
|
|
26
32
|
/**
|
|
@@ -44,8 +50,23 @@ export declare class CloudStorage implements CommonStorage {
|
|
|
44
50
|
getFileReadStream(bucketName: string, filePath: string): Readable;
|
|
45
51
|
saveFile(bucketName: string, filePath: string, content: Buffer): Promise<void>;
|
|
46
52
|
getFileWriteStream(bucketName: string, filePath: string): Writable;
|
|
53
|
+
uploadFile(localFilePath: string, bucketName: string, bucketFilePath: string): Promise<void>;
|
|
47
54
|
setFileVisibility(bucketName: string, filePath: string, isPublic: boolean): Promise<void>;
|
|
48
55
|
getFileVisibility(bucketName: string, filePath: string): Promise<boolean>;
|
|
49
56
|
copyFile(fromBucket: string, fromPath: string, toPath: string, toBucket?: string): Promise<void>;
|
|
50
57
|
moveFile(fromBucket: string, fromPath: string, toPath: string, toBucket?: string): Promise<void>;
|
|
58
|
+
movePath(fromBucket: string, fromPrefix: string, toPrefix: string, toBucket?: string): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Acquires a "signed url", which allows bearer to use it to download ('read') the file.
|
|
61
|
+
*
|
|
62
|
+
* expires: 'v4' supports maximum duration of 7 days from now.
|
|
63
|
+
*
|
|
64
|
+
* @experimental - not tested yet
|
|
65
|
+
*/
|
|
66
|
+
getSignedUrl(bucketName: string, filePath: string, expires: LocalTimeInput): Promise<string>;
|
|
67
|
+
/**
|
|
68
|
+
* Returns SKIP if fileName is a folder.
|
|
69
|
+
* If !fullPaths - strip away the folder prefix.
|
|
70
|
+
*/
|
|
71
|
+
private normalizeFilename;
|
|
51
72
|
}
|
package/dist/cloudStorage.js
CHANGED
|
@@ -5,6 +5,11 @@ const storage_1 = require("@google-cloud/storage");
|
|
|
5
5
|
Object.defineProperty(exports, "Storage", { enumerable: true, get: function () { return storage_1.Storage; } });
|
|
6
6
|
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
7
7
|
const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
|
|
8
|
+
/**
|
|
9
|
+
* CloudStorage implementation of CommonStorage API.
|
|
10
|
+
*
|
|
11
|
+
* API: https://googleapis.dev/nodejs/storage/latest/index.html
|
|
12
|
+
*/
|
|
8
13
|
class CloudStorage {
|
|
9
14
|
/**
|
|
10
15
|
* Passing the pre-created Storage allows to instantiate it from both
|
|
@@ -49,9 +54,11 @@ class CloudStorage {
|
|
|
49
54
|
prefix,
|
|
50
55
|
});
|
|
51
56
|
if (fullPaths) {
|
|
52
|
-
|
|
57
|
+
// Paths that end with `/` are "folders", which are "virtual" in CloudStorage
|
|
58
|
+
// It doesn't make sense to return or do anything with them
|
|
59
|
+
return files.map(f => f.name).filter(s => !s.endsWith('/'));
|
|
53
60
|
}
|
|
54
|
-
return files.map(f => (0, js_lib_1._substringAfterLast)(f.name, '/'));
|
|
61
|
+
return files.map(f => (0, js_lib_1._substringAfterLast)(f.name, '/')).filter(Boolean);
|
|
55
62
|
}
|
|
56
63
|
getFileNamesStream(bucketName, opt = {}) {
|
|
57
64
|
const { prefix, fullPaths = true } = opt;
|
|
@@ -61,7 +68,7 @@ class CloudStorage {
|
|
|
61
68
|
prefix,
|
|
62
69
|
maxResults: opt.limit || undefined,
|
|
63
70
|
})
|
|
64
|
-
.pipe((0, nodejs_lib_1.
|
|
71
|
+
.pipe((0, nodejs_lib_1.transformMapSync)(f => this.normalizeFilename(f.name, fullPaths)));
|
|
65
72
|
}
|
|
66
73
|
getFilesStream(bucketName, opt = {}) {
|
|
67
74
|
const { prefix, fullPaths = true } = opt;
|
|
@@ -72,8 +79,11 @@ class CloudStorage {
|
|
|
72
79
|
maxResults: opt.limit || undefined,
|
|
73
80
|
})
|
|
74
81
|
.pipe((0, nodejs_lib_1.transformMap)(async (f) => {
|
|
82
|
+
const filePath = this.normalizeFilename(f.name, fullPaths);
|
|
83
|
+
if (filePath === js_lib_1.SKIP)
|
|
84
|
+
return js_lib_1.SKIP;
|
|
75
85
|
const [content] = await f.download();
|
|
76
|
-
return { filePath
|
|
86
|
+
return { filePath, content };
|
|
77
87
|
}));
|
|
78
88
|
}
|
|
79
89
|
async getFile(bucketName, filePath) {
|
|
@@ -101,6 +111,11 @@ class CloudStorage {
|
|
|
101
111
|
getFileWriteStream(bucketName, filePath) {
|
|
102
112
|
return this.storage.bucket(bucketName).file(filePath).createWriteStream();
|
|
103
113
|
}
|
|
114
|
+
async uploadFile(localFilePath, bucketName, bucketFilePath) {
|
|
115
|
+
await this.storage.bucket(bucketName).upload(localFilePath, {
|
|
116
|
+
destination: bucketFilePath,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
104
119
|
async setFileVisibility(bucketName, filePath, isPublic) {
|
|
105
120
|
await this.storage.bucket(bucketName).file(filePath)[isPublic ? 'makePublic' : 'makePrivate']();
|
|
106
121
|
}
|
|
@@ -120,5 +135,49 @@ class CloudStorage {
|
|
|
120
135
|
.file(fromPath)
|
|
121
136
|
.move(this.storage.bucket(toBucket || fromBucket).file(toPath));
|
|
122
137
|
}
|
|
138
|
+
async movePath(fromBucket, fromPrefix, toPrefix, toBucket) {
|
|
139
|
+
(0, js_lib_1._assert)(fromPrefix.endsWith('/'), 'fromPrefix should end with `/`');
|
|
140
|
+
(0, js_lib_1._assert)(toPrefix.endsWith('/'), 'toPrefix should end with `/`');
|
|
141
|
+
await (0, nodejs_lib_1._pipeline)([
|
|
142
|
+
this.storage.bucket(fromBucket).getFilesStream({
|
|
143
|
+
prefix: fromPrefix,
|
|
144
|
+
}),
|
|
145
|
+
(0, nodejs_lib_1.writableForEach)(async (file) => {
|
|
146
|
+
const { name } = file;
|
|
147
|
+
const newName = toPrefix + name.slice(fromPrefix.length);
|
|
148
|
+
await file.move(this.storage.bucket(toBucket || fromBucket).file(newName));
|
|
149
|
+
}),
|
|
150
|
+
]);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Acquires a "signed url", which allows bearer to use it to download ('read') the file.
|
|
154
|
+
*
|
|
155
|
+
* expires: 'v4' supports maximum duration of 7 days from now.
|
|
156
|
+
*
|
|
157
|
+
* @experimental - not tested yet
|
|
158
|
+
*/
|
|
159
|
+
async getSignedUrl(bucketName, filePath, expires) {
|
|
160
|
+
const [url] = await this.storage
|
|
161
|
+
.bucket(bucketName)
|
|
162
|
+
.file(filePath)
|
|
163
|
+
.getSignedUrl({
|
|
164
|
+
action: 'read',
|
|
165
|
+
expires: (0, js_lib_1.localTime)(expires).unixMillis(),
|
|
166
|
+
});
|
|
167
|
+
return url;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Returns SKIP if fileName is a folder.
|
|
171
|
+
* If !fullPaths - strip away the folder prefix.
|
|
172
|
+
*/
|
|
173
|
+
normalizeFilename(fileName, fullPaths) {
|
|
174
|
+
if (fullPaths) {
|
|
175
|
+
if (fileName.endsWith('/'))
|
|
176
|
+
return js_lib_1.SKIP; // skip folders
|
|
177
|
+
return fileName;
|
|
178
|
+
}
|
|
179
|
+
fileName = (0, js_lib_1._substringAfterLast)(fileName, '/');
|
|
180
|
+
return fileName || js_lib_1.SKIP; // skip folders
|
|
181
|
+
}
|
|
123
182
|
}
|
|
124
183
|
exports.CloudStorage = CloudStorage;
|
package/dist/commonStorage.d.ts
CHANGED
|
@@ -70,8 +70,19 @@ export interface CommonStorage {
|
|
|
70
70
|
getFilesStream: (bucketName: string, opt?: CommonStorageGetOptions) => ReadableTyped<FileEntry>;
|
|
71
71
|
getFileReadStream: (bucketName: string, filePath: string) => Readable;
|
|
72
72
|
getFileWriteStream: (bucketName: string, filePath: string) => Writable;
|
|
73
|
+
/**
|
|
74
|
+
* Upload local file to the bucket (by streaming it).
|
|
75
|
+
*/
|
|
76
|
+
uploadFile: (localFilePath: string, bucketName: string, bucketFilePath: string) => Promise<void>;
|
|
73
77
|
setFileVisibility: (bucketName: string, filePath: string, isPublic: boolean) => Promise<void>;
|
|
74
78
|
getFileVisibility: (bucketName: string, filePath: string) => Promise<boolean>;
|
|
75
79
|
copyFile: (fromBucket: string, fromPath: string, toPath: string, toBucket?: string) => Promise<void>;
|
|
76
80
|
moveFile: (fromBucket: string, fromPath: string, toPath: string, toBucket?: string) => Promise<void>;
|
|
81
|
+
/**
|
|
82
|
+
* Allows to move "directory" with all its contents.
|
|
83
|
+
*
|
|
84
|
+
* Prefixes should end with `/` to work properly,
|
|
85
|
+
* otherwise some folder that starts with the same prefix will be included.
|
|
86
|
+
*/
|
|
87
|
+
movePath: (fromBucket: string, fromPrefix: string, toPrefix: string, toBucket?: string) => Promise<void>;
|
|
77
88
|
}
|
|
@@ -22,8 +22,10 @@ export declare class InMemoryCommonStorage implements CommonStorage {
|
|
|
22
22
|
getFilesStream(bucketName: string, opt?: CommonStorageGetOptions): ReadableTyped<FileEntry>;
|
|
23
23
|
getFileReadStream(bucketName: string, filePath: string): Readable;
|
|
24
24
|
getFileWriteStream(_bucketName: string, _filePath: string): Writable;
|
|
25
|
+
uploadFile(localFilePath: string, bucketName: string, bucketFilePath: string): Promise<void>;
|
|
25
26
|
setFileVisibility(bucketName: string, filePath: string, isPublic: boolean): Promise<void>;
|
|
26
27
|
getFileVisibility(bucketName: string, filePath: string): Promise<boolean>;
|
|
27
28
|
copyFile(fromBucket: string, fromPath: string, toPath: string, toBucket?: string): Promise<void>;
|
|
28
29
|
moveFile(fromBucket: string, fromPath: string, toPath: string, toBucket?: string): Promise<void>;
|
|
30
|
+
movePath(fromBucket: string, fromPrefix: string, toPrefix: string, toBucket?: string): Promise<void>;
|
|
29
31
|
}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.InMemoryCommonStorage = void 0;
|
|
4
4
|
const node_stream_1 = require("node:stream");
|
|
5
5
|
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
6
|
+
const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
|
|
6
7
|
class InMemoryCommonStorage {
|
|
7
8
|
constructor() {
|
|
8
9
|
/**
|
|
@@ -65,6 +66,10 @@ class InMemoryCommonStorage {
|
|
|
65
66
|
getFileWriteStream(_bucketName, _filePath) {
|
|
66
67
|
throw new Error('Method not implemented.');
|
|
67
68
|
}
|
|
69
|
+
async uploadFile(localFilePath, bucketName, bucketFilePath) {
|
|
70
|
+
this.data[bucketName] ||= {};
|
|
71
|
+
this.data[bucketName][bucketFilePath] = await nodejs_lib_1.fs2.readBufferAsync(localFilePath);
|
|
72
|
+
}
|
|
68
73
|
async setFileVisibility(bucketName, filePath, isPublic) {
|
|
69
74
|
this.publicMap[bucketName] ||= {};
|
|
70
75
|
this.publicMap[bucketName][filePath] = isPublic;
|
|
@@ -85,5 +90,16 @@ class InMemoryCommonStorage {
|
|
|
85
90
|
this.data[tob][toPath] = this.data[fromBucket][fromPath];
|
|
86
91
|
delete this.data[fromBucket][fromPath];
|
|
87
92
|
}
|
|
93
|
+
async movePath(fromBucket, fromPrefix, toPrefix, toBucket) {
|
|
94
|
+
const tob = toBucket || fromBucket;
|
|
95
|
+
this.data[fromBucket] ||= {};
|
|
96
|
+
this.data[tob] ||= {};
|
|
97
|
+
(0, js_lib_1._stringMapEntries)(this.data[fromBucket]).forEach(([filePath, v]) => {
|
|
98
|
+
if (!filePath.startsWith(fromPrefix))
|
|
99
|
+
return;
|
|
100
|
+
this.data[tob][toPrefix + filePath.slice(fromPrefix.length)] = v;
|
|
101
|
+
delete this.data[fromBucket][filePath];
|
|
102
|
+
});
|
|
103
|
+
}
|
|
88
104
|
}
|
|
89
105
|
exports.InMemoryCommonStorage = InMemoryCommonStorage;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@naturalcycles/cloud-storage-lib",
|
|
3
3
|
"scripts": {
|
|
4
|
-
"prepare": "husky
|
|
4
|
+
"prepare": "husky"
|
|
5
5
|
},
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"@google-cloud/storage": "^7.0.0",
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
"engines": {
|
|
36
36
|
"node": ">=18.12.0"
|
|
37
37
|
},
|
|
38
|
-
"version": "1.
|
|
39
|
-
"description": "",
|
|
38
|
+
"version": "1.8.0",
|
|
39
|
+
"description": "CommonStorage implementation based on Google Cloud Storage",
|
|
40
40
|
"author": "Natural Cycles Team",
|
|
41
41
|
"license": "MIT"
|
|
42
42
|
}
|
package/src/cloudStorage.ts
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
import { Readable, Writable } from 'node:stream'
|
|
2
2
|
import { File, Storage, StorageOptions } from '@google-cloud/storage'
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
_assert,
|
|
5
|
+
_substringAfterLast,
|
|
6
|
+
localTime,
|
|
7
|
+
LocalTimeInput,
|
|
8
|
+
SKIP,
|
|
9
|
+
} from '@naturalcycles/js-lib'
|
|
10
|
+
import {
|
|
11
|
+
_pipeline,
|
|
12
|
+
ReadableTyped,
|
|
13
|
+
transformMap,
|
|
14
|
+
transformMapSync,
|
|
15
|
+
writableForEach,
|
|
16
|
+
} from '@naturalcycles/nodejs-lib'
|
|
5
17
|
import { CommonStorage, CommonStorageGetOptions, FileEntry } from './commonStorage'
|
|
6
18
|
import { GCPServiceAccount } from './model'
|
|
7
19
|
|
|
@@ -27,6 +39,11 @@ export interface CloudStorageCfg {
|
|
|
27
39
|
credentials?: GCPServiceAccount
|
|
28
40
|
}
|
|
29
41
|
|
|
42
|
+
/**
|
|
43
|
+
* CloudStorage implementation of CommonStorage API.
|
|
44
|
+
*
|
|
45
|
+
* API: https://googleapis.dev/nodejs/storage/latest/index.html
|
|
46
|
+
*/
|
|
30
47
|
export class CloudStorage implements CommonStorage {
|
|
31
48
|
/**
|
|
32
49
|
* Passing the pre-created Storage allows to instantiate it from both
|
|
@@ -77,10 +94,12 @@ export class CloudStorage implements CommonStorage {
|
|
|
77
94
|
})
|
|
78
95
|
|
|
79
96
|
if (fullPaths) {
|
|
80
|
-
|
|
97
|
+
// Paths that end with `/` are "folders", which are "virtual" in CloudStorage
|
|
98
|
+
// It doesn't make sense to return or do anything with them
|
|
99
|
+
return files.map(f => f.name).filter(s => !s.endsWith('/'))
|
|
81
100
|
}
|
|
82
101
|
|
|
83
|
-
return files.map(f => _substringAfterLast(f.name, '/'))
|
|
102
|
+
return files.map(f => _substringAfterLast(f.name, '/')).filter(Boolean)
|
|
84
103
|
}
|
|
85
104
|
|
|
86
105
|
getFileNamesStream(bucketName: string, opt: CommonStorageGetOptions = {}): ReadableTyped<string> {
|
|
@@ -92,11 +111,7 @@ export class CloudStorage implements CommonStorage {
|
|
|
92
111
|
prefix,
|
|
93
112
|
maxResults: opt.limit || undefined,
|
|
94
113
|
})
|
|
95
|
-
.pipe(
|
|
96
|
-
transformMapSimple<File, string>(f =>
|
|
97
|
-
fullPaths ? f.name : _substringAfterLast(f.name, '/'),
|
|
98
|
-
),
|
|
99
|
-
)
|
|
114
|
+
.pipe(transformMapSync<File, string>(f => this.normalizeFilename(f.name, fullPaths)))
|
|
100
115
|
}
|
|
101
116
|
|
|
102
117
|
getFilesStream(bucketName: string, opt: CommonStorageGetOptions = {}): ReadableTyped<FileEntry> {
|
|
@@ -110,8 +125,11 @@ export class CloudStorage implements CommonStorage {
|
|
|
110
125
|
})
|
|
111
126
|
.pipe(
|
|
112
127
|
transformMap<File, FileEntry>(async f => {
|
|
128
|
+
const filePath = this.normalizeFilename(f.name, fullPaths)
|
|
129
|
+
if (filePath === SKIP) return SKIP
|
|
130
|
+
|
|
113
131
|
const [content] = await f.download()
|
|
114
|
-
return { filePath
|
|
132
|
+
return { filePath, content }
|
|
115
133
|
}),
|
|
116
134
|
)
|
|
117
135
|
}
|
|
@@ -145,6 +163,16 @@ export class CloudStorage implements CommonStorage {
|
|
|
145
163
|
return this.storage.bucket(bucketName).file(filePath).createWriteStream()
|
|
146
164
|
}
|
|
147
165
|
|
|
166
|
+
async uploadFile(
|
|
167
|
+
localFilePath: string,
|
|
168
|
+
bucketName: string,
|
|
169
|
+
bucketFilePath: string,
|
|
170
|
+
): Promise<void> {
|
|
171
|
+
await this.storage.bucket(bucketName).upload(localFilePath, {
|
|
172
|
+
destination: bucketFilePath,
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
|
|
148
176
|
async setFileVisibility(bucketName: string, filePath: string, isPublic: boolean): Promise<void> {
|
|
149
177
|
await this.storage.bucket(bucketName).file(filePath)[isPublic ? 'makePublic' : 'makePrivate']()
|
|
150
178
|
}
|
|
@@ -177,4 +205,62 @@ export class CloudStorage implements CommonStorage {
|
|
|
177
205
|
.file(fromPath)
|
|
178
206
|
.move(this.storage.bucket(toBucket || fromBucket).file(toPath))
|
|
179
207
|
}
|
|
208
|
+
|
|
209
|
+
async movePath(
|
|
210
|
+
fromBucket: string,
|
|
211
|
+
fromPrefix: string,
|
|
212
|
+
toPrefix: string,
|
|
213
|
+
toBucket?: string,
|
|
214
|
+
): Promise<void> {
|
|
215
|
+
_assert(fromPrefix.endsWith('/'), 'fromPrefix should end with `/`')
|
|
216
|
+
_assert(toPrefix.endsWith('/'), 'toPrefix should end with `/`')
|
|
217
|
+
|
|
218
|
+
await _pipeline([
|
|
219
|
+
this.storage.bucket(fromBucket).getFilesStream({
|
|
220
|
+
prefix: fromPrefix,
|
|
221
|
+
}),
|
|
222
|
+
writableForEach<File>(async file => {
|
|
223
|
+
const { name } = file
|
|
224
|
+
const newName = toPrefix + name.slice(fromPrefix.length)
|
|
225
|
+
await file.move(this.storage.bucket(toBucket || fromBucket).file(newName))
|
|
226
|
+
}),
|
|
227
|
+
])
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Acquires a "signed url", which allows bearer to use it to download ('read') the file.
|
|
232
|
+
*
|
|
233
|
+
* expires: 'v4' supports maximum duration of 7 days from now.
|
|
234
|
+
*
|
|
235
|
+
* @experimental - not tested yet
|
|
236
|
+
*/
|
|
237
|
+
async getSignedUrl(
|
|
238
|
+
bucketName: string,
|
|
239
|
+
filePath: string,
|
|
240
|
+
expires: LocalTimeInput,
|
|
241
|
+
): Promise<string> {
|
|
242
|
+
const [url] = await this.storage
|
|
243
|
+
.bucket(bucketName)
|
|
244
|
+
.file(filePath)
|
|
245
|
+
.getSignedUrl({
|
|
246
|
+
action: 'read',
|
|
247
|
+
expires: localTime(expires).unixMillis(),
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
return url
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Returns SKIP if fileName is a folder.
|
|
255
|
+
* If !fullPaths - strip away the folder prefix.
|
|
256
|
+
*/
|
|
257
|
+
private normalizeFilename(fileName: string, fullPaths: boolean): string | typeof SKIP {
|
|
258
|
+
if (fullPaths) {
|
|
259
|
+
if (fileName.endsWith('/')) return SKIP // skip folders
|
|
260
|
+
return fileName
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
fileName = _substringAfterLast(fileName, '/')
|
|
264
|
+
return fileName || SKIP // skip folders
|
|
265
|
+
}
|
|
180
266
|
}
|
package/src/commonStorage.ts
CHANGED
|
@@ -87,6 +87,11 @@ export interface CommonStorage {
|
|
|
87
87
|
|
|
88
88
|
getFileWriteStream: (bucketName: string, filePath: string) => Writable
|
|
89
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Upload local file to the bucket (by streaming it).
|
|
92
|
+
*/
|
|
93
|
+
uploadFile: (localFilePath: string, bucketName: string, bucketFilePath: string) => Promise<void>
|
|
94
|
+
|
|
90
95
|
setFileVisibility: (bucketName: string, filePath: string, isPublic: boolean) => Promise<void>
|
|
91
96
|
|
|
92
97
|
getFileVisibility: (bucketName: string, filePath: string) => Promise<boolean>
|
|
@@ -97,10 +102,24 @@ export interface CommonStorage {
|
|
|
97
102
|
toPath: string,
|
|
98
103
|
toBucket?: string,
|
|
99
104
|
) => Promise<void>
|
|
105
|
+
|
|
100
106
|
moveFile: (
|
|
101
107
|
fromBucket: string,
|
|
102
108
|
fromPath: string,
|
|
103
109
|
toPath: string,
|
|
104
110
|
toBucket?: string,
|
|
105
111
|
) => Promise<void>
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Allows to move "directory" with all its contents.
|
|
115
|
+
*
|
|
116
|
+
* Prefixes should end with `/` to work properly,
|
|
117
|
+
* otherwise some folder that starts with the same prefix will be included.
|
|
118
|
+
*/
|
|
119
|
+
movePath: (
|
|
120
|
+
fromBucket: string,
|
|
121
|
+
fromPrefix: string,
|
|
122
|
+
toPrefix: string,
|
|
123
|
+
toBucket?: string,
|
|
124
|
+
) => Promise<void>
|
|
106
125
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Readable, Writable } from 'node:stream'
|
|
2
|
-
import { _substringAfterLast, StringMap } from '@naturalcycles/js-lib'
|
|
3
|
-
import { ReadableTyped } from '@naturalcycles/nodejs-lib'
|
|
2
|
+
import { _stringMapEntries, _substringAfterLast, StringMap } from '@naturalcycles/js-lib'
|
|
3
|
+
import { fs2, ReadableTyped } from '@naturalcycles/nodejs-lib'
|
|
4
4
|
import { CommonStorage, CommonStorageGetOptions, FileEntry } from './commonStorage'
|
|
5
5
|
|
|
6
6
|
export class InMemoryCommonStorage implements CommonStorage {
|
|
@@ -83,6 +83,15 @@ export class InMemoryCommonStorage implements CommonStorage {
|
|
|
83
83
|
throw new Error('Method not implemented.')
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
async uploadFile(
|
|
87
|
+
localFilePath: string,
|
|
88
|
+
bucketName: string,
|
|
89
|
+
bucketFilePath: string,
|
|
90
|
+
): Promise<void> {
|
|
91
|
+
this.data[bucketName] ||= {}
|
|
92
|
+
this.data[bucketName]![bucketFilePath] = await fs2.readBufferAsync(localFilePath)
|
|
93
|
+
}
|
|
94
|
+
|
|
86
95
|
async setFileVisibility(bucketName: string, filePath: string, isPublic: boolean): Promise<void> {
|
|
87
96
|
this.publicMap[bucketName] ||= {}
|
|
88
97
|
this.publicMap[bucketName]![filePath] = isPublic
|
|
@@ -116,4 +125,21 @@ export class InMemoryCommonStorage implements CommonStorage {
|
|
|
116
125
|
this.data[tob]![toPath] = this.data[fromBucket]![fromPath]
|
|
117
126
|
delete this.data[fromBucket]![fromPath]
|
|
118
127
|
}
|
|
128
|
+
|
|
129
|
+
async movePath(
|
|
130
|
+
fromBucket: string,
|
|
131
|
+
fromPrefix: string,
|
|
132
|
+
toPrefix: string,
|
|
133
|
+
toBucket?: string,
|
|
134
|
+
): Promise<void> {
|
|
135
|
+
const tob = toBucket || fromBucket
|
|
136
|
+
this.data[fromBucket] ||= {}
|
|
137
|
+
this.data[tob] ||= {}
|
|
138
|
+
|
|
139
|
+
_stringMapEntries(this.data[fromBucket]!).forEach(([filePath, v]) => {
|
|
140
|
+
if (!filePath.startsWith(fromPrefix)) return
|
|
141
|
+
this.data[tob]![toPrefix + filePath.slice(fromPrefix.length)] = v
|
|
142
|
+
delete this.data[fromBucket]![filePath]
|
|
143
|
+
})
|
|
144
|
+
}
|
|
119
145
|
}
|