@naturalcycles/cloud-storage-lib 1.0.1 → 1.4.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 +9 -7
- package/dist/cloudStorage.js +22 -21
- package/dist/commonStorage.d.ts +8 -8
- package/dist/commonStorageBucket.d.ts +11 -3
- package/dist/commonStorageBucket.js +21 -6
- package/dist/commonStorageKeyValueDB.js +5 -9
- package/dist/inMemoryCommonStorage.d.ts +3 -3
- package/dist/inMemoryCommonStorage.js +18 -7
- package/dist/testing/commonStorageTest.js +25 -19
- package/package.json +2 -1
- package/readme.md +0 -1
- package/src/cloudStorage.ts +31 -35
- package/src/commonStorage.ts +11 -20
- package/src/commonStorageBucket.ts +28 -6
- package/src/commonStorageKeyValueDB.ts +5 -9
- package/src/inMemoryCommonStorage.ts +20 -16
- package/src/testing/commonStorageTest.ts +31 -21
package/dist/cloudStorage.d.ts
CHANGED
|
@@ -17,17 +17,19 @@ export interface CloudStorageCfg {
|
|
|
17
17
|
credentials: GCPServiceAccount;
|
|
18
18
|
}
|
|
19
19
|
export declare class CloudStorage implements CommonStorage {
|
|
20
|
-
cfg: CloudStorageCfg;
|
|
21
|
-
constructor(cfg: CloudStorageCfg);
|
|
22
20
|
storage: Storage;
|
|
21
|
+
/**
|
|
22
|
+
* Passing the pre-created Storage allows to instantiate it from both
|
|
23
|
+
* GCP Storage and FirebaseStorage.
|
|
24
|
+
*/
|
|
25
|
+
constructor(storage: Storage);
|
|
26
|
+
static createFromGCPServiceAccount(cfg: CloudStorageCfg): CloudStorage;
|
|
23
27
|
ping(bucketName?: string): Promise<void>;
|
|
24
|
-
getBucketNames(opt?: CommonStorageGetOptions): Promise<string[]>;
|
|
25
|
-
getBucketNamesStream(): ReadableTyped<string>;
|
|
26
28
|
deletePath(bucketName: string, prefix: string): Promise<void>;
|
|
27
29
|
fileExists(bucketName: string, filePath: string): Promise<boolean>;
|
|
28
|
-
getFileNames(bucketName: string,
|
|
29
|
-
getFileNamesStream(bucketName: string,
|
|
30
|
-
getFilesStream(bucketName: string,
|
|
30
|
+
getFileNames(bucketName: string, opt?: CommonStorageGetOptions): Promise<string[]>;
|
|
31
|
+
getFileNamesStream(bucketName: string, opt?: CommonStorageGetOptions): ReadableTyped<string>;
|
|
32
|
+
getFilesStream(bucketName: string, opt?: CommonStorageGetOptions): ReadableTyped<FileEntry>;
|
|
31
33
|
getFile(bucketName: string, filePath: string): Promise<Buffer | null>;
|
|
32
34
|
/**
|
|
33
35
|
* Returns a Readable that is NOT object mode,
|
package/dist/cloudStorage.js
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.CloudStorage = void 0;
|
|
4
|
+
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
4
5
|
const storage_1 = require("@google-cloud/storage");
|
|
5
6
|
const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
|
|
6
7
|
class CloudStorage {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Passing the pre-created Storage allows to instantiate it from both
|
|
10
|
+
* GCP Storage and FirebaseStorage.
|
|
11
|
+
*/
|
|
12
|
+
constructor(storage) {
|
|
13
|
+
this.storage = storage;
|
|
14
|
+
}
|
|
15
|
+
static createFromGCPServiceAccount(cfg) {
|
|
16
|
+
const storage = new storage_1.Storage({
|
|
10
17
|
credentials: cfg.credentials,
|
|
11
18
|
// Explicitly passing it here to fix this error:
|
|
12
19
|
// Error: Unable to detect a Project Id in the current environment.
|
|
@@ -15,23 +22,11 @@ class CloudStorage {
|
|
|
15
22
|
// at /root/repo/node_modules/google-auth-library/build/src/auth/googleauth.js:95:31
|
|
16
23
|
projectId: cfg.credentials.project_id,
|
|
17
24
|
});
|
|
25
|
+
return new CloudStorage(storage);
|
|
18
26
|
}
|
|
19
|
-
// async createBucket(bucketName: string): Promise<void> {
|
|
20
|
-
// const bucket = await this.storage.createBucket(bucketName)
|
|
21
|
-
// console.log(bucket) // debugging
|
|
22
|
-
// }
|
|
23
27
|
async ping(bucketName) {
|
|
24
28
|
await this.storage.bucket(bucketName || 'non-existing-for-sure').exists();
|
|
25
29
|
}
|
|
26
|
-
async getBucketNames(opt = {}) {
|
|
27
|
-
const [buckets] = await this.storage.getBuckets({
|
|
28
|
-
maxResults: opt.limit,
|
|
29
|
-
});
|
|
30
|
-
return buckets.map(b => b.name);
|
|
31
|
-
}
|
|
32
|
-
getBucketNamesStream() {
|
|
33
|
-
return this.storage.getBucketsStream().pipe((0, nodejs_lib_1.transformMapSimple)(b => b.name));
|
|
34
|
-
}
|
|
35
30
|
async deletePath(bucketName, prefix) {
|
|
36
31
|
await this.storage.bucket(bucketName).deleteFiles({
|
|
37
32
|
prefix,
|
|
@@ -43,22 +38,28 @@ class CloudStorage {
|
|
|
43
38
|
const [exists] = await this.storage.bucket(bucketName).file(filePath).exists();
|
|
44
39
|
return exists;
|
|
45
40
|
}
|
|
46
|
-
async getFileNames(bucketName,
|
|
41
|
+
async getFileNames(bucketName, opt = {}) {
|
|
42
|
+
const { prefix, fullPaths = true } = opt;
|
|
47
43
|
const [files] = await this.storage.bucket(bucketName).getFiles({
|
|
48
44
|
prefix,
|
|
49
45
|
});
|
|
50
|
-
|
|
46
|
+
if (fullPaths) {
|
|
47
|
+
return files.map(f => f.name);
|
|
48
|
+
}
|
|
49
|
+
return files.map(f => (0, js_lib_1._substringAfterLast)(f.name, '/'));
|
|
51
50
|
}
|
|
52
|
-
getFileNamesStream(bucketName,
|
|
51
|
+
getFileNamesStream(bucketName, opt = {}) {
|
|
52
|
+
const { prefix, fullPaths = true } = opt;
|
|
53
53
|
return this.storage
|
|
54
54
|
.bucket(bucketName)
|
|
55
55
|
.getFilesStream({
|
|
56
56
|
prefix,
|
|
57
57
|
maxResults: opt.limit,
|
|
58
58
|
})
|
|
59
|
-
.pipe((0, nodejs_lib_1.transformMapSimple)(f => f.name));
|
|
59
|
+
.pipe((0, nodejs_lib_1.transformMapSimple)(f => fullPaths ? f.name : (0, js_lib_1._substringAfterLast)(f.name, '/')));
|
|
60
60
|
}
|
|
61
|
-
getFilesStream(bucketName,
|
|
61
|
+
getFilesStream(bucketName, opt = {}) {
|
|
62
|
+
const { prefix } = opt;
|
|
62
63
|
return this.storage
|
|
63
64
|
.bucket(bucketName)
|
|
64
65
|
.getFilesStream({
|
package/dist/commonStorage.d.ts
CHANGED
|
@@ -10,6 +10,11 @@ export interface CommonStorageGetOptions {
|
|
|
10
10
|
* Will filter resulting files based on `prefix`.
|
|
11
11
|
*/
|
|
12
12
|
prefix?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Defaults to true.
|
|
15
|
+
* Set to false to return file names instead of full paths.
|
|
16
|
+
*/
|
|
17
|
+
fullPaths?: boolean;
|
|
13
18
|
/**
|
|
14
19
|
* Limits the number of results.
|
|
15
20
|
*
|
|
@@ -35,11 +40,6 @@ export interface CommonStorage {
|
|
|
35
40
|
* Pass `bucketName` in case you only have permissions to operate on that bucket.
|
|
36
41
|
*/
|
|
37
42
|
ping(bucketName?: string): Promise<void>;
|
|
38
|
-
/**
|
|
39
|
-
* Often needs a special permission.
|
|
40
|
-
*/
|
|
41
|
-
getBucketNames(opt?: CommonStorageGetOptions): Promise<string[]>;
|
|
42
|
-
getBucketNamesStream(): ReadableTyped<string>;
|
|
43
43
|
/**
|
|
44
44
|
* Creates a new bucket by given name.
|
|
45
45
|
* todo: check what to do if it already exists
|
|
@@ -62,9 +62,9 @@ export interface CommonStorage {
|
|
|
62
62
|
* Important difference between `prefix` and `path` is that `prefix` will
|
|
63
63
|
* return all files from sub-directories too!
|
|
64
64
|
*/
|
|
65
|
-
getFileNames(bucketName: string,
|
|
66
|
-
getFileNamesStream(bucketName: string,
|
|
67
|
-
getFilesStream(bucketName: string,
|
|
65
|
+
getFileNames(bucketName: string, opt?: CommonStorageGetOptions): Promise<string[]>;
|
|
66
|
+
getFileNamesStream(bucketName: string, opt?: CommonStorageGetOptions): ReadableTyped<string>;
|
|
67
|
+
getFilesStream(bucketName: string, opt?: CommonStorageGetOptions): ReadableTyped<FileEntry>;
|
|
68
68
|
getFileReadStream(bucketName: string, filePath: string): Readable;
|
|
69
69
|
getFileWriteStream(bucketName: string, filePath: string): Writable;
|
|
70
70
|
setFileVisibility(bucketName: string, filePath: string, isPublic: boolean): Promise<void>;
|
|
@@ -31,6 +31,14 @@ export declare class CommonStorageBucket {
|
|
|
31
31
|
content: T;
|
|
32
32
|
}[]>;
|
|
33
33
|
saveFile(filePath: string, content: Buffer): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Convenience method that does:
|
|
36
|
+
* await saveFile
|
|
37
|
+
* await setFileVisibility
|
|
38
|
+
*/
|
|
39
|
+
savePublicFile(filePath: string, content: Buffer): Promise<void>;
|
|
40
|
+
saveStringFile(filePath: string, content: string): Promise<void>;
|
|
41
|
+
saveJsonFile(filePath: string, content: any): Promise<void>;
|
|
34
42
|
saveFiles(entries: FileEntry[]): Promise<void>;
|
|
35
43
|
/**
|
|
36
44
|
* Should recursively delete all files in a folder, if path is a folder.
|
|
@@ -48,9 +56,9 @@ export declare class CommonStorageBucket {
|
|
|
48
56
|
* Important difference between `prefix` and `path` is that `prefix` will
|
|
49
57
|
* return all files from sub-directories too!
|
|
50
58
|
*/
|
|
51
|
-
getFileNames(
|
|
52
|
-
getFileNamesStream(
|
|
53
|
-
getFilesStream(
|
|
59
|
+
getFileNames(opt?: CommonStorageGetOptions): Promise<string[]>;
|
|
60
|
+
getFileNamesStream(opt?: CommonStorageGetOptions): ReadableTyped<string>;
|
|
61
|
+
getFilesStream(opt?: CommonStorageGetOptions): ReadableTyped<FileEntry>;
|
|
54
62
|
getFileReadStream(filePath: string): Readable;
|
|
55
63
|
getFileWriteStream(filePath: string): Writable;
|
|
56
64
|
setFileVisibility(filePath: string, isPublic: boolean): Promise<void>;
|
|
@@ -73,6 +73,21 @@ class CommonStorageBucket {
|
|
|
73
73
|
async saveFile(filePath, content) {
|
|
74
74
|
await this.cfg.storage.saveFile(this.cfg.bucketName, filePath, content);
|
|
75
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Convenience method that does:
|
|
78
|
+
* await saveFile
|
|
79
|
+
* await setFileVisibility
|
|
80
|
+
*/
|
|
81
|
+
async savePublicFile(filePath, content) {
|
|
82
|
+
await this.cfg.storage.saveFile(this.cfg.bucketName, filePath, content);
|
|
83
|
+
await this.cfg.storage.setFileVisibility(this.cfg.bucketName, filePath, true);
|
|
84
|
+
}
|
|
85
|
+
async saveStringFile(filePath, content) {
|
|
86
|
+
await this.cfg.storage.saveFile(this.cfg.bucketName, filePath, Buffer.from(content));
|
|
87
|
+
}
|
|
88
|
+
async saveJsonFile(filePath, content) {
|
|
89
|
+
await this.cfg.storage.saveFile(this.cfg.bucketName, filePath, Buffer.from(JSON.stringify(content)));
|
|
90
|
+
}
|
|
76
91
|
async saveFiles(entries) {
|
|
77
92
|
await (0, js_lib_1.pMap)(entries, async (f) => {
|
|
78
93
|
await this.cfg.storage.saveFile(this.cfg.bucketName, f.filePath, f.content);
|
|
@@ -100,14 +115,14 @@ class CommonStorageBucket {
|
|
|
100
115
|
* Important difference between `prefix` and `path` is that `prefix` will
|
|
101
116
|
* return all files from sub-directories too!
|
|
102
117
|
*/
|
|
103
|
-
async getFileNames(
|
|
104
|
-
return await this.cfg.storage.getFileNames(this.cfg.bucketName,
|
|
118
|
+
async getFileNames(opt) {
|
|
119
|
+
return await this.cfg.storage.getFileNames(this.cfg.bucketName, opt);
|
|
105
120
|
}
|
|
106
|
-
getFileNamesStream(
|
|
107
|
-
return this.cfg.storage.getFileNamesStream(this.cfg.bucketName,
|
|
121
|
+
getFileNamesStream(opt) {
|
|
122
|
+
return this.cfg.storage.getFileNamesStream(this.cfg.bucketName, opt);
|
|
108
123
|
}
|
|
109
|
-
getFilesStream(
|
|
110
|
-
return this.cfg.storage.getFilesStream(this.cfg.bucketName,
|
|
124
|
+
getFilesStream(opt) {
|
|
125
|
+
return this.cfg.storage.getFilesStream(this.cfg.bucketName, opt);
|
|
111
126
|
}
|
|
112
127
|
getFileReadStream(filePath) {
|
|
113
128
|
return this.cfg.storage.getFileReadStream(this.cfg.bucketName, filePath);
|
|
@@ -62,30 +62,26 @@ class CommonStorageKeyValueDB {
|
|
|
62
62
|
}
|
|
63
63
|
streamIds(table, limit) {
|
|
64
64
|
const { bucketName, prefix } = this.getBucketAndPrefix(table);
|
|
65
|
-
|
|
66
|
-
return this.cfg.storage
|
|
67
|
-
.getFileNamesStream(bucketName, prefix, { limit })
|
|
68
|
-
.pipe((0, nodejs_lib_1.transformMapSimple)(f => f.slice(index)));
|
|
65
|
+
return this.cfg.storage.getFileNamesStream(bucketName, { prefix, limit, fullPaths: false });
|
|
69
66
|
}
|
|
70
67
|
streamValues(table, limit) {
|
|
71
68
|
const { bucketName, prefix } = this.getBucketAndPrefix(table);
|
|
72
69
|
return this.cfg.storage
|
|
73
|
-
.getFilesStream(bucketName, prefix,
|
|
70
|
+
.getFilesStream(bucketName, { prefix, limit })
|
|
74
71
|
.pipe((0, nodejs_lib_1.transformMapSimple)(f => f.content));
|
|
75
72
|
}
|
|
76
73
|
streamEntries(table, limit) {
|
|
77
74
|
const { bucketName, prefix } = this.getBucketAndPrefix(table);
|
|
78
|
-
const index = prefix.length + 1;
|
|
79
75
|
return this.cfg.storage
|
|
80
|
-
.getFilesStream(bucketName, prefix,
|
|
76
|
+
.getFilesStream(bucketName, { prefix, limit, fullPaths: false })
|
|
81
77
|
.pipe((0, nodejs_lib_1.transformMapSimple)(({ filePath, content }) => [
|
|
82
|
-
filePath
|
|
78
|
+
filePath,
|
|
83
79
|
content,
|
|
84
80
|
]));
|
|
85
81
|
}
|
|
86
82
|
async count(table) {
|
|
87
83
|
const { bucketName, prefix } = this.getBucketAndPrefix(table);
|
|
88
|
-
return (await this.cfg.storage.getFileNames(bucketName, prefix)).length;
|
|
84
|
+
return (await this.cfg.storage.getFileNames(bucketName, { prefix })).length;
|
|
89
85
|
}
|
|
90
86
|
}
|
|
91
87
|
exports.CommonStorageKeyValueDB = CommonStorageKeyValueDB;
|
|
@@ -16,9 +16,9 @@ export declare class InMemoryCommonStorage implements CommonStorage {
|
|
|
16
16
|
getFile(bucketName: string, filePath: string): Promise<Buffer | null>;
|
|
17
17
|
saveFile(bucketName: string, filePath: string, content: Buffer): Promise<void>;
|
|
18
18
|
deletePath(bucketName: string, prefix: string): Promise<void>;
|
|
19
|
-
getFileNames(bucketName: string,
|
|
20
|
-
getFileNamesStream(bucketName: string,
|
|
21
|
-
getFilesStream(bucketName: string,
|
|
19
|
+
getFileNames(bucketName: string, opt?: CommonStorageGetOptions): Promise<string[]>;
|
|
20
|
+
getFileNamesStream(bucketName: string, opt?: CommonStorageGetOptions): ReadableTyped<string>;
|
|
21
|
+
getFilesStream(bucketName: string, opt?: CommonStorageGetOptions): ReadableTyped<FileEntry>;
|
|
22
22
|
getFileReadStream(bucketName: string, filePath: string): Readable;
|
|
23
23
|
getFileWriteStream(_bucketName: string, _filePath: string): Writable;
|
|
24
24
|
setFileVisibility(bucketName: string, filePath: string, isPublic: boolean): Promise<void>;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.InMemoryCommonStorage = void 0;
|
|
4
4
|
const stream_1 = require("stream");
|
|
5
|
+
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
5
6
|
class InMemoryCommonStorage {
|
|
6
7
|
constructor() {
|
|
7
8
|
/**
|
|
@@ -35,19 +36,29 @@ class InMemoryCommonStorage {
|
|
|
35
36
|
}
|
|
36
37
|
});
|
|
37
38
|
}
|
|
38
|
-
async getFileNames(bucketName,
|
|
39
|
-
|
|
39
|
+
async getFileNames(bucketName, opt = {}) {
|
|
40
|
+
const { prefix = '', fullPaths = true } = opt;
|
|
41
|
+
return Object.keys(this.data[bucketName] || {})
|
|
42
|
+
.filter(filePath => filePath.startsWith(prefix))
|
|
43
|
+
.map(f => (fullPaths ? f : (0, js_lib_1._substringAfterLast)(f, '/')));
|
|
40
44
|
}
|
|
41
|
-
getFileNamesStream(bucketName,
|
|
45
|
+
getFileNamesStream(bucketName, opt = {}) {
|
|
46
|
+
const { prefix = '', fullPaths = true } = opt;
|
|
42
47
|
return stream_1.Readable.from(Object.keys(this.data[bucketName] || {})
|
|
43
48
|
.filter(filePath => filePath.startsWith(prefix))
|
|
44
|
-
.slice(0, opt.limit)
|
|
49
|
+
.slice(0, opt.limit)
|
|
50
|
+
.map(n => (fullPaths ? n : (0, js_lib_1._substringAfterLast)(n, '/'))));
|
|
45
51
|
}
|
|
46
|
-
getFilesStream(bucketName,
|
|
52
|
+
getFilesStream(bucketName, opt = {}) {
|
|
53
|
+
const { prefix = '', fullPaths = true } = opt;
|
|
47
54
|
return stream_1.Readable.from(Object.entries(this.data[bucketName] || {})
|
|
48
|
-
.map(([filePath, content]) => ({
|
|
55
|
+
.map(([filePath, content]) => ({
|
|
56
|
+
filePath,
|
|
57
|
+
content,
|
|
58
|
+
}))
|
|
49
59
|
.filter(f => f.filePath.startsWith(prefix))
|
|
50
|
-
.slice(0, opt.limit)
|
|
60
|
+
.slice(0, opt.limit)
|
|
61
|
+
.map(f => (fullPaths ? f : { ...f, filePath: (0, js_lib_1._substringAfterLast)(f.filePath, '/') })));
|
|
51
62
|
}
|
|
52
63
|
getFileReadStream(bucketName, filePath) {
|
|
53
64
|
return stream_1.Readable.from(this.data[bucketName][filePath]);
|
|
@@ -33,29 +33,30 @@ function runCommonStorageTest(storage, bucketName) {
|
|
|
33
33
|
// await storage.createBucket(bucketName)
|
|
34
34
|
// })
|
|
35
35
|
test('ping', async () => {
|
|
36
|
-
await storage.ping();
|
|
37
|
-
});
|
|
38
|
-
test('listBuckets', async () => {
|
|
39
|
-
const buckets = await storage.getBucketNames();
|
|
40
|
-
console.log(buckets);
|
|
41
|
-
});
|
|
42
|
-
test('streamBuckets', async () => {
|
|
43
|
-
const buckets = await (0, nodejs_lib_1.readableToArray)(storage.getBucketNamesStream());
|
|
44
|
-
console.log(buckets);
|
|
36
|
+
await storage.ping(bucketName);
|
|
45
37
|
});
|
|
38
|
+
// test('listBuckets', async () => {
|
|
39
|
+
// const buckets = await storage.getBucketNames()
|
|
40
|
+
// console.log(buckets)
|
|
41
|
+
// })
|
|
42
|
+
//
|
|
43
|
+
// test('streamBuckets', async () => {
|
|
44
|
+
// const buckets = await readableToArray(storage.getBucketNamesStream())
|
|
45
|
+
// console.log(buckets)
|
|
46
|
+
// })
|
|
46
47
|
test('prepare: clear bucket', async () => {
|
|
47
48
|
await (0, js_lib_1.pMap)(TEST_FILES.map(f => f.filePath), async (filePath) => await storage.deletePath(bucketName, filePath));
|
|
48
49
|
});
|
|
49
|
-
test('listFileNames on root should return empty', async () => {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
})
|
|
50
|
+
// test('listFileNames on root should return empty', async () => {
|
|
51
|
+
// const fileNames = await storage.getFileNames(bucketName)
|
|
52
|
+
// expect(fileNames).toEqual([])
|
|
53
|
+
// })
|
|
53
54
|
test(`listFileNames on ${TEST_FOLDER} should return empty`, async () => {
|
|
54
|
-
const fileNames = await storage.getFileNames(bucketName, TEST_FOLDER);
|
|
55
|
+
const fileNames = await storage.getFileNames(bucketName, { prefix: TEST_FOLDER });
|
|
55
56
|
expect(fileNames).toEqual([]);
|
|
56
57
|
});
|
|
57
|
-
test(
|
|
58
|
-
const fileNames = await (0, nodejs_lib_1.readableToArray)(storage.getFileNamesStream(bucketName,
|
|
58
|
+
test(`streamFileNames on ${TEST_FOLDER} should return empty`, async () => {
|
|
59
|
+
const fileNames = await (0, nodejs_lib_1.readableToArray)(storage.getFileNamesStream(bucketName, { prefix: TEST_FOLDER }));
|
|
59
60
|
expect(fileNames).toEqual([]);
|
|
60
61
|
});
|
|
61
62
|
test(`exists should return empty array`, async () => {
|
|
@@ -68,9 +69,14 @@ function runCommonStorageTest(storage, bucketName) {
|
|
|
68
69
|
const testFilesMap = Object.fromEntries(TEST_FILES.map(f => [f.filePath, f.content]));
|
|
69
70
|
// It's done in the same test to ensure "strong consistency"
|
|
70
71
|
await (0, js_lib_1.pMap)(TEST_FILES, async (f) => await storage.saveFile(bucketName, f.filePath, f.content));
|
|
71
|
-
const
|
|
72
|
+
const fileNamesShort = await storage.getFileNames(bucketName, {
|
|
73
|
+
prefix: TEST_FOLDER,
|
|
74
|
+
fullPaths: false,
|
|
75
|
+
});
|
|
76
|
+
expect(fileNamesShort.sort()).toEqual(TEST_FILES.map(f => (0, js_lib_1._substringAfterLast)(f.filePath, '/')).sort());
|
|
77
|
+
const fileNames = await storage.getFileNames(bucketName, { prefix: TEST_FOLDER });
|
|
72
78
|
expect(fileNames.sort()).toEqual(TEST_FILES.map(f => f.filePath).sort());
|
|
73
|
-
const streamedFileNames = await (0, nodejs_lib_1.readableToArray)(storage.getFileNamesStream(bucketName, TEST_FOLDER));
|
|
79
|
+
const streamedFileNames = await (0, nodejs_lib_1.readableToArray)(storage.getFileNamesStream(bucketName, { prefix: TEST_FOLDER }));
|
|
74
80
|
expect(streamedFileNames.sort()).toEqual(TEST_FILES.map(f => f.filePath).sort());
|
|
75
81
|
const filesMap = {};
|
|
76
82
|
await (0, js_lib_1.pMap)(fileNames, async (filePath) => {
|
|
@@ -83,7 +89,7 @@ function runCommonStorageTest(storage, bucketName) {
|
|
|
83
89
|
});
|
|
84
90
|
});
|
|
85
91
|
test('cleanup', async () => {
|
|
86
|
-
await storage.deletePath(bucketName,
|
|
92
|
+
await storage.deletePath(bucketName, TEST_FOLDER);
|
|
87
93
|
});
|
|
88
94
|
// Cannot update access control for an object when uniform bucket-level access is enabled. Read more at https://cloud.google.com/storage/docs/uniform-bucket-level-access
|
|
89
95
|
/*
|
package/package.json
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"devDependencies": {
|
|
13
13
|
"@naturalcycles/dev-lib": "^12.1.3",
|
|
14
14
|
"@types/node": "^17.0.5",
|
|
15
|
+
"firebase-admin": "^10.0.1",
|
|
15
16
|
"jest": "^27.1.0"
|
|
16
17
|
},
|
|
17
18
|
"files": [
|
|
@@ -34,7 +35,7 @@
|
|
|
34
35
|
"engines": {
|
|
35
36
|
"node": ">=14.16.0"
|
|
36
37
|
},
|
|
37
|
-
"version": "1.0
|
|
38
|
+
"version": "1.4.0",
|
|
38
39
|
"description": "",
|
|
39
40
|
"author": "Natural Cycles Team",
|
|
40
41
|
"license": "MIT"
|
package/readme.md
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
> CommonStorage implementation based on Google Cloud Storage
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@naturalcycles/cloud-storage-lib)
|
|
6
|
-
[](https://bundlephobia.com/result?p=@naturalcycles/cloud-storage-lib)
|
|
7
6
|
[](https://github.com/prettier/prettier)
|
|
8
7
|
|
|
9
8
|
Implements:
|
package/src/cloudStorage.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as Buffer from 'buffer'
|
|
2
2
|
import { Readable, Writable } from 'stream'
|
|
3
|
-
import {
|
|
3
|
+
import { _substringAfterLast } from '@naturalcycles/js-lib'
|
|
4
|
+
import { File, Storage } from '@google-cloud/storage'
|
|
4
5
|
import { ReadableTyped, transformMap, transformMapSimple } from '@naturalcycles/nodejs-lib'
|
|
5
6
|
import { CommonStorage, CommonStorageGetOptions, FileEntry } from './commonStorage'
|
|
6
7
|
import { GCPServiceAccount } from './model'
|
|
@@ -19,8 +20,14 @@ export interface CloudStorageCfg {
|
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
export class CloudStorage implements CommonStorage {
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Passing the pre-created Storage allows to instantiate it from both
|
|
25
|
+
* GCP Storage and FirebaseStorage.
|
|
26
|
+
*/
|
|
27
|
+
constructor(public storage: Storage) {}
|
|
28
|
+
|
|
29
|
+
static createFromGCPServiceAccount(cfg: CloudStorageCfg): CloudStorage {
|
|
30
|
+
const storage = new Storage({
|
|
24
31
|
credentials: cfg.credentials,
|
|
25
32
|
// Explicitly passing it here to fix this error:
|
|
26
33
|
// Error: Unable to detect a Project Id in the current environment.
|
|
@@ -29,31 +36,14 @@ export class CloudStorage implements CommonStorage {
|
|
|
29
36
|
// at /root/repo/node_modules/google-auth-library/build/src/auth/googleauth.js:95:31
|
|
30
37
|
projectId: cfg.credentials.project_id,
|
|
31
38
|
})
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
storage: Storage
|
|
35
39
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
// console.log(bucket) // debugging
|
|
39
|
-
// }
|
|
40
|
+
return new CloudStorage(storage)
|
|
41
|
+
}
|
|
40
42
|
|
|
41
43
|
async ping(bucketName?: string): Promise<void> {
|
|
42
44
|
await this.storage.bucket(bucketName || 'non-existing-for-sure').exists()
|
|
43
45
|
}
|
|
44
46
|
|
|
45
|
-
async getBucketNames(opt: CommonStorageGetOptions = {}): Promise<string[]> {
|
|
46
|
-
const [buckets] = await this.storage.getBuckets({
|
|
47
|
-
maxResults: opt.limit,
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
return buckets.map(b => b.name)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
getBucketNamesStream(): ReadableTyped<string> {
|
|
54
|
-
return this.storage.getBucketsStream().pipe(transformMapSimple<Bucket, string>(b => b.name))
|
|
55
|
-
}
|
|
56
|
-
|
|
57
47
|
async deletePath(bucketName: string, prefix: string): Promise<void> {
|
|
58
48
|
await this.storage.bucket(bucketName).deleteFiles({
|
|
59
49
|
prefix,
|
|
@@ -67,32 +57,38 @@ export class CloudStorage implements CommonStorage {
|
|
|
67
57
|
return exists
|
|
68
58
|
}
|
|
69
59
|
|
|
70
|
-
async getFileNames(bucketName: string,
|
|
60
|
+
async getFileNames(bucketName: string, opt: CommonStorageGetOptions = {}): Promise<string[]> {
|
|
61
|
+
const { prefix, fullPaths = true } = opt
|
|
71
62
|
const [files] = await this.storage.bucket(bucketName).getFiles({
|
|
72
63
|
prefix,
|
|
73
64
|
})
|
|
74
|
-
|
|
65
|
+
|
|
66
|
+
if (fullPaths) {
|
|
67
|
+
return files.map(f => f.name)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return files.map(f => _substringAfterLast(f.name, '/'))
|
|
75
71
|
}
|
|
76
72
|
|
|
77
|
-
getFileNamesStream(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
opt: CommonStorageGetOptions = {},
|
|
81
|
-
): ReadableTyped<string> {
|
|
73
|
+
getFileNamesStream(bucketName: string, opt: CommonStorageGetOptions = {}): ReadableTyped<string> {
|
|
74
|
+
const { prefix, fullPaths = true } = opt
|
|
75
|
+
|
|
82
76
|
return this.storage
|
|
83
77
|
.bucket(bucketName)
|
|
84
78
|
.getFilesStream({
|
|
85
79
|
prefix,
|
|
86
80
|
maxResults: opt.limit,
|
|
87
81
|
})
|
|
88
|
-
.pipe(
|
|
82
|
+
.pipe(
|
|
83
|
+
transformMapSimple<File, string>(f =>
|
|
84
|
+
fullPaths ? f.name : _substringAfterLast(f.name, '/'),
|
|
85
|
+
),
|
|
86
|
+
)
|
|
89
87
|
}
|
|
90
88
|
|
|
91
|
-
getFilesStream(
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
opt: CommonStorageGetOptions = {},
|
|
95
|
-
): ReadableTyped<FileEntry> {
|
|
89
|
+
getFilesStream(bucketName: string, opt: CommonStorageGetOptions = {}): ReadableTyped<FileEntry> {
|
|
90
|
+
const { prefix } = opt
|
|
91
|
+
|
|
96
92
|
return this.storage
|
|
97
93
|
.bucket(bucketName)
|
|
98
94
|
.getFilesStream({
|
package/src/commonStorage.ts
CHANGED
|
@@ -14,6 +14,12 @@ export interface CommonStorageGetOptions {
|
|
|
14
14
|
*/
|
|
15
15
|
prefix?: string
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Defaults to true.
|
|
19
|
+
* Set to false to return file names instead of full paths.
|
|
20
|
+
*/
|
|
21
|
+
fullPaths?: boolean
|
|
22
|
+
|
|
17
23
|
/**
|
|
18
24
|
* Limits the number of results.
|
|
19
25
|
*
|
|
@@ -41,13 +47,6 @@ export interface CommonStorage {
|
|
|
41
47
|
*/
|
|
42
48
|
ping(bucketName?: string): Promise<void>
|
|
43
49
|
|
|
44
|
-
/**
|
|
45
|
-
* Often needs a special permission.
|
|
46
|
-
*/
|
|
47
|
-
getBucketNames(opt?: CommonStorageGetOptions): Promise<string[]>
|
|
48
|
-
|
|
49
|
-
getBucketNamesStream(): ReadableTyped<string>
|
|
50
|
-
|
|
51
50
|
/**
|
|
52
51
|
* Creates a new bucket by given name.
|
|
53
52
|
* todo: check what to do if it already exists
|
|
@@ -76,19 +75,11 @@ export interface CommonStorage {
|
|
|
76
75
|
* Important difference between `prefix` and `path` is that `prefix` will
|
|
77
76
|
* return all files from sub-directories too!
|
|
78
77
|
*/
|
|
79
|
-
getFileNames(bucketName: string,
|
|
80
|
-
|
|
81
|
-
getFileNamesStream(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
opt?: CommonStorageGetOptions,
|
|
85
|
-
): ReadableTyped<string>
|
|
86
|
-
|
|
87
|
-
getFilesStream(
|
|
88
|
-
bucketName: string,
|
|
89
|
-
prefix: string,
|
|
90
|
-
opt?: CommonStorageGetOptions,
|
|
91
|
-
): ReadableTyped<FileEntry>
|
|
78
|
+
getFileNames(bucketName: string, opt?: CommonStorageGetOptions): Promise<string[]>
|
|
79
|
+
|
|
80
|
+
getFileNamesStream(bucketName: string, opt?: CommonStorageGetOptions): ReadableTyped<string>
|
|
81
|
+
|
|
82
|
+
getFilesStream(bucketName: string, opt?: CommonStorageGetOptions): ReadableTyped<FileEntry>
|
|
92
83
|
|
|
93
84
|
getFileReadStream(bucketName: string, filePath: string): Readable
|
|
94
85
|
|
|
@@ -103,6 +103,28 @@ export class CommonStorageBucket {
|
|
|
103
103
|
await this.cfg.storage.saveFile(this.cfg.bucketName, filePath, content)
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Convenience method that does:
|
|
108
|
+
* await saveFile
|
|
109
|
+
* await setFileVisibility
|
|
110
|
+
*/
|
|
111
|
+
async savePublicFile(filePath: string, content: Buffer): Promise<void> {
|
|
112
|
+
await this.cfg.storage.saveFile(this.cfg.bucketName, filePath, content)
|
|
113
|
+
await this.cfg.storage.setFileVisibility(this.cfg.bucketName, filePath, true)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async saveStringFile(filePath: string, content: string): Promise<void> {
|
|
117
|
+
await this.cfg.storage.saveFile(this.cfg.bucketName, filePath, Buffer.from(content))
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async saveJsonFile(filePath: string, content: any): Promise<void> {
|
|
121
|
+
await this.cfg.storage.saveFile(
|
|
122
|
+
this.cfg.bucketName,
|
|
123
|
+
filePath,
|
|
124
|
+
Buffer.from(JSON.stringify(content)),
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
|
|
106
128
|
async saveFiles(entries: FileEntry[]): Promise<void> {
|
|
107
129
|
await pMap(entries, async f => {
|
|
108
130
|
await this.cfg.storage.saveFile(this.cfg.bucketName, f.filePath, f.content)
|
|
@@ -133,16 +155,16 @@ export class CommonStorageBucket {
|
|
|
133
155
|
* Important difference between `prefix` and `path` is that `prefix` will
|
|
134
156
|
* return all files from sub-directories too!
|
|
135
157
|
*/
|
|
136
|
-
async getFileNames(
|
|
137
|
-
return await this.cfg.storage.getFileNames(this.cfg.bucketName,
|
|
158
|
+
async getFileNames(opt?: CommonStorageGetOptions): Promise<string[]> {
|
|
159
|
+
return await this.cfg.storage.getFileNames(this.cfg.bucketName, opt)
|
|
138
160
|
}
|
|
139
161
|
|
|
140
|
-
getFileNamesStream(
|
|
141
|
-
return this.cfg.storage.getFileNamesStream(this.cfg.bucketName,
|
|
162
|
+
getFileNamesStream(opt?: CommonStorageGetOptions): ReadableTyped<string> {
|
|
163
|
+
return this.cfg.storage.getFileNamesStream(this.cfg.bucketName, opt)
|
|
142
164
|
}
|
|
143
165
|
|
|
144
|
-
getFilesStream(
|
|
145
|
-
return this.cfg.storage.getFilesStream(this.cfg.bucketName,
|
|
166
|
+
getFilesStream(opt?: CommonStorageGetOptions): ReadableTyped<FileEntry> {
|
|
167
|
+
return this.cfg.storage.getFilesStream(this.cfg.bucketName, opt)
|
|
146
168
|
}
|
|
147
169
|
|
|
148
170
|
getFileReadStream(filePath: string): Readable {
|
|
@@ -78,30 +78,26 @@ export class CommonStorageKeyValueDB implements CommonKeyValueDB {
|
|
|
78
78
|
|
|
79
79
|
streamIds(table: string, limit?: number): ReadableTyped<string> {
|
|
80
80
|
const { bucketName, prefix } = this.getBucketAndPrefix(table)
|
|
81
|
-
const index = prefix.length + 1
|
|
82
81
|
|
|
83
|
-
return this.cfg.storage
|
|
84
|
-
.getFileNamesStream(bucketName, prefix, { limit })
|
|
85
|
-
.pipe(transformMapSimple<string, string>(f => f.slice(index)))
|
|
82
|
+
return this.cfg.storage.getFileNamesStream(bucketName, { prefix, limit, fullPaths: false })
|
|
86
83
|
}
|
|
87
84
|
|
|
88
85
|
streamValues(table: string, limit?: number): ReadableTyped<Buffer> {
|
|
89
86
|
const { bucketName, prefix } = this.getBucketAndPrefix(table)
|
|
90
87
|
|
|
91
88
|
return this.cfg.storage
|
|
92
|
-
.getFilesStream(bucketName, prefix,
|
|
89
|
+
.getFilesStream(bucketName, { prefix, limit })
|
|
93
90
|
.pipe(transformMapSimple<FileEntry, Buffer>(f => f.content))
|
|
94
91
|
}
|
|
95
92
|
|
|
96
93
|
streamEntries(table: string, limit?: number): ReadableTyped<KeyValueDBTuple> {
|
|
97
94
|
const { bucketName, prefix } = this.getBucketAndPrefix(table)
|
|
98
|
-
const index = prefix.length + 1
|
|
99
95
|
|
|
100
96
|
return this.cfg.storage
|
|
101
|
-
.getFilesStream(bucketName, prefix,
|
|
97
|
+
.getFilesStream(bucketName, { prefix, limit, fullPaths: false })
|
|
102
98
|
.pipe(
|
|
103
99
|
transformMapSimple<FileEntry, KeyValueDBTuple>(({ filePath, content }) => [
|
|
104
|
-
filePath
|
|
100
|
+
filePath,
|
|
105
101
|
content,
|
|
106
102
|
]),
|
|
107
103
|
)
|
|
@@ -110,6 +106,6 @@ export class CommonStorageKeyValueDB implements CommonKeyValueDB {
|
|
|
110
106
|
async count(table: string): Promise<number> {
|
|
111
107
|
const { bucketName, prefix } = this.getBucketAndPrefix(table)
|
|
112
108
|
|
|
113
|
-
return (await this.cfg.storage.getFileNames(bucketName, prefix)).length
|
|
109
|
+
return (await this.cfg.storage.getFileNames(bucketName, { prefix })).length
|
|
114
110
|
}
|
|
115
111
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Readable, Writable } from 'stream'
|
|
2
|
-
import { StringMap } from '@naturalcycles/js-lib'
|
|
2
|
+
import { _substringAfterLast, StringMap } from '@naturalcycles/js-lib'
|
|
3
3
|
import { ReadableTyped } from '@naturalcycles/nodejs-lib'
|
|
4
4
|
import { CommonStorage, CommonStorageGetOptions, FileEntry } from './commonStorage'
|
|
5
5
|
|
|
@@ -42,32 +42,36 @@ export class InMemoryCommonStorage implements CommonStorage {
|
|
|
42
42
|
})
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
async getFileNames(bucketName: string,
|
|
46
|
-
|
|
45
|
+
async getFileNames(bucketName: string, opt: CommonStorageGetOptions = {}): Promise<string[]> {
|
|
46
|
+
const { prefix = '', fullPaths = true } = opt
|
|
47
|
+
return Object.keys(this.data[bucketName] || {})
|
|
48
|
+
.filter(filePath => filePath.startsWith(prefix))
|
|
49
|
+
.map(f => (fullPaths ? f : _substringAfterLast(f, '/')))
|
|
47
50
|
}
|
|
48
51
|
|
|
49
|
-
getFileNamesStream(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
opt: CommonStorageGetOptions = {},
|
|
53
|
-
): ReadableTyped<string> {
|
|
52
|
+
getFileNamesStream(bucketName: string, opt: CommonStorageGetOptions = {}): ReadableTyped<string> {
|
|
53
|
+
const { prefix = '', fullPaths = true } = opt
|
|
54
|
+
|
|
54
55
|
return Readable.from(
|
|
55
56
|
Object.keys(this.data[bucketName] || {})
|
|
56
57
|
.filter(filePath => filePath.startsWith(prefix))
|
|
57
|
-
.slice(0, opt.limit)
|
|
58
|
+
.slice(0, opt.limit)
|
|
59
|
+
.map(n => (fullPaths ? n : _substringAfterLast(n, '/'))),
|
|
58
60
|
)
|
|
59
61
|
}
|
|
60
62
|
|
|
61
|
-
getFilesStream(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
opt: CommonStorageGetOptions = {},
|
|
65
|
-
): ReadableTyped<FileEntry> {
|
|
63
|
+
getFilesStream(bucketName: string, opt: CommonStorageGetOptions = {}): ReadableTyped<FileEntry> {
|
|
64
|
+
const { prefix = '', fullPaths = true } = opt
|
|
65
|
+
|
|
66
66
|
return Readable.from(
|
|
67
67
|
Object.entries(this.data[bucketName] || {})
|
|
68
|
-
.map(([filePath, content]) => ({
|
|
68
|
+
.map(([filePath, content]) => ({
|
|
69
|
+
filePath,
|
|
70
|
+
content,
|
|
71
|
+
}))
|
|
69
72
|
.filter(f => f.filePath.startsWith(prefix))
|
|
70
|
-
.slice(0, opt.limit)
|
|
73
|
+
.slice(0, opt.limit)
|
|
74
|
+
.map(f => (fullPaths ? f : { ...f, filePath: _substringAfterLast(f.filePath, '/') })),
|
|
71
75
|
)
|
|
72
76
|
}
|
|
73
77
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _range, pMap, StringMap } from '@naturalcycles/js-lib'
|
|
1
|
+
import { _range, _substringAfterLast, pMap, StringMap } from '@naturalcycles/js-lib'
|
|
2
2
|
import { readableToArray } from '@naturalcycles/nodejs-lib'
|
|
3
3
|
import { CommonStorage, FileEntry } from '../commonStorage'
|
|
4
4
|
|
|
@@ -38,18 +38,18 @@ export function runCommonStorageTest(storage: CommonStorage, bucketName: string)
|
|
|
38
38
|
// })
|
|
39
39
|
|
|
40
40
|
test('ping', async () => {
|
|
41
|
-
await storage.ping()
|
|
41
|
+
await storage.ping(bucketName)
|
|
42
42
|
})
|
|
43
43
|
|
|
44
|
-
test('listBuckets', async () => {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
test('streamBuckets', async () => {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
})
|
|
44
|
+
// test('listBuckets', async () => {
|
|
45
|
+
// const buckets = await storage.getBucketNames()
|
|
46
|
+
// console.log(buckets)
|
|
47
|
+
// })
|
|
48
|
+
//
|
|
49
|
+
// test('streamBuckets', async () => {
|
|
50
|
+
// const buckets = await readableToArray(storage.getBucketNamesStream())
|
|
51
|
+
// console.log(buckets)
|
|
52
|
+
// })
|
|
53
53
|
|
|
54
54
|
test('prepare: clear bucket', async () => {
|
|
55
55
|
await pMap(
|
|
@@ -58,18 +58,20 @@ export function runCommonStorageTest(storage: CommonStorage, bucketName: string)
|
|
|
58
58
|
)
|
|
59
59
|
})
|
|
60
60
|
|
|
61
|
-
test('listFileNames on root should return empty', async () => {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
})
|
|
61
|
+
// test('listFileNames on root should return empty', async () => {
|
|
62
|
+
// const fileNames = await storage.getFileNames(bucketName)
|
|
63
|
+
// expect(fileNames).toEqual([])
|
|
64
|
+
// })
|
|
65
65
|
|
|
66
66
|
test(`listFileNames on ${TEST_FOLDER} should return empty`, async () => {
|
|
67
|
-
const fileNames = await storage.getFileNames(bucketName, TEST_FOLDER)
|
|
67
|
+
const fileNames = await storage.getFileNames(bucketName, { prefix: TEST_FOLDER })
|
|
68
68
|
expect(fileNames).toEqual([])
|
|
69
69
|
})
|
|
70
70
|
|
|
71
|
-
test(
|
|
72
|
-
const fileNames = await readableToArray(
|
|
71
|
+
test(`streamFileNames on ${TEST_FOLDER} should return empty`, async () => {
|
|
72
|
+
const fileNames = await readableToArray(
|
|
73
|
+
storage.getFileNamesStream(bucketName, { prefix: TEST_FOLDER }),
|
|
74
|
+
)
|
|
73
75
|
expect(fileNames).toEqual([])
|
|
74
76
|
})
|
|
75
77
|
|
|
@@ -86,11 +88,19 @@ export function runCommonStorageTest(storage: CommonStorage, bucketName: string)
|
|
|
86
88
|
// It's done in the same test to ensure "strong consistency"
|
|
87
89
|
await pMap(TEST_FILES, async f => await storage.saveFile(bucketName, f.filePath, f.content))
|
|
88
90
|
|
|
89
|
-
const
|
|
91
|
+
const fileNamesShort = await storage.getFileNames(bucketName, {
|
|
92
|
+
prefix: TEST_FOLDER,
|
|
93
|
+
fullPaths: false,
|
|
94
|
+
})
|
|
95
|
+
expect(fileNamesShort.sort()).toEqual(
|
|
96
|
+
TEST_FILES.map(f => _substringAfterLast(f.filePath, '/')).sort(),
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
const fileNames = await storage.getFileNames(bucketName, { prefix: TEST_FOLDER })
|
|
90
100
|
expect(fileNames.sort()).toEqual(TEST_FILES.map(f => f.filePath).sort())
|
|
91
101
|
|
|
92
102
|
const streamedFileNames = await readableToArray(
|
|
93
|
-
storage.getFileNamesStream(bucketName, TEST_FOLDER),
|
|
103
|
+
storage.getFileNamesStream(bucketName, { prefix: TEST_FOLDER }),
|
|
94
104
|
)
|
|
95
105
|
expect(streamedFileNames.sort()).toEqual(TEST_FILES.map(f => f.filePath).sort())
|
|
96
106
|
|
|
@@ -109,7 +119,7 @@ export function runCommonStorageTest(storage: CommonStorage, bucketName: string)
|
|
|
109
119
|
})
|
|
110
120
|
|
|
111
121
|
test('cleanup', async () => {
|
|
112
|
-
await storage.deletePath(bucketName,
|
|
122
|
+
await storage.deletePath(bucketName, TEST_FOLDER)
|
|
113
123
|
})
|
|
114
124
|
|
|
115
125
|
// Cannot update access control for an object when uniform bucket-level access is enabled. Read more at https://cloud.google.com/storage/docs/uniform-bucket-level-access
|