@naturalcycles/cloud-storage-lib 1.2.0 → 1.3.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 +3 -3
- package/dist/cloudStorage.js +12 -5
- package/dist/commonStorage.d.ts +8 -3
- package/dist/commonStorageBucket.d.ts +9 -7
- package/dist/commonStorageBucket.js +17 -22
- package/dist/commonStorageKeyValueDB.js +5 -9
- package/dist/inMemoryCommonStorage.d.ts +3 -3
- package/dist/inMemoryCommonStorage.js +18 -7
- package/dist/testing/commonStorageTest.js +10 -5
- package/package.json +1 -1
- package/src/cloudStorage.ts +20 -13
- package/src/commonStorage.ts +11 -13
- package/src/commonStorageBucket.ts +17 -27
- package/src/commonStorageKeyValueDB.ts +5 -9
- package/src/inMemoryCommonStorage.ts +20 -16
- package/src/testing/commonStorageTest.ts +14 -6
package/dist/cloudStorage.d.ts
CHANGED
|
@@ -25,9 +25,9 @@ export declare class CloudStorage implements CommonStorage {
|
|
|
25
25
|
getBucketNamesStream(): ReadableTyped<string>;
|
|
26
26
|
deletePath(bucketName: string, prefix: string): Promise<void>;
|
|
27
27
|
fileExists(bucketName: string, filePath: string): Promise<boolean>;
|
|
28
|
-
getFileNames(bucketName: string,
|
|
29
|
-
getFileNamesStream(bucketName: string,
|
|
30
|
-
getFilesStream(bucketName: string,
|
|
28
|
+
getFileNames(bucketName: string, opt?: CommonStorageGetOptions): Promise<string[]>;
|
|
29
|
+
getFileNamesStream(bucketName: string, opt?: CommonStorageGetOptions): ReadableTyped<string>;
|
|
30
|
+
getFilesStream(bucketName: string, opt?: CommonStorageGetOptions): ReadableTyped<FileEntry>;
|
|
31
31
|
getFile(bucketName: string, filePath: string): Promise<Buffer | null>;
|
|
32
32
|
/**
|
|
33
33
|
* Returns a Readable that is NOT object mode,
|
package/dist/cloudStorage.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
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 {
|
|
@@ -43,22 +44,28 @@ class CloudStorage {
|
|
|
43
44
|
const [exists] = await this.storage.bucket(bucketName).file(filePath).exists();
|
|
44
45
|
return exists;
|
|
45
46
|
}
|
|
46
|
-
async getFileNames(bucketName,
|
|
47
|
+
async getFileNames(bucketName, opt = {}) {
|
|
48
|
+
const { prefix, fullPaths = true } = opt;
|
|
47
49
|
const [files] = await this.storage.bucket(bucketName).getFiles({
|
|
48
50
|
prefix,
|
|
49
51
|
});
|
|
50
|
-
|
|
52
|
+
if (fullPaths) {
|
|
53
|
+
return files.map(f => f.name);
|
|
54
|
+
}
|
|
55
|
+
return files.map(f => (0, js_lib_1._substringAfterLast)(f.name, '/'));
|
|
51
56
|
}
|
|
52
|
-
getFileNamesStream(bucketName,
|
|
57
|
+
getFileNamesStream(bucketName, opt = {}) {
|
|
58
|
+
const { prefix, fullPaths = true } = opt;
|
|
53
59
|
return this.storage
|
|
54
60
|
.bucket(bucketName)
|
|
55
61
|
.getFilesStream({
|
|
56
62
|
prefix,
|
|
57
63
|
maxResults: opt.limit,
|
|
58
64
|
})
|
|
59
|
-
.pipe((0, nodejs_lib_1.transformMapSimple)(f => f.name));
|
|
65
|
+
.pipe((0, nodejs_lib_1.transformMapSimple)(f => fullPaths ? f.name : (0, js_lib_1._substringAfterLast)(f.name, '/')));
|
|
60
66
|
}
|
|
61
|
-
getFilesStream(bucketName,
|
|
67
|
+
getFilesStream(bucketName, opt = {}) {
|
|
68
|
+
const { prefix } = opt;
|
|
62
69
|
return this.storage
|
|
63
70
|
.bucket(bucketName)
|
|
64
71
|
.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
|
*
|
|
@@ -62,9 +67,9 @@ export interface CommonStorage {
|
|
|
62
67
|
* Important difference between `prefix` and `path` is that `prefix` will
|
|
63
68
|
* return all files from sub-directories too!
|
|
64
69
|
*/
|
|
65
|
-
getFileNames(bucketName: string,
|
|
66
|
-
getFileNamesStream(bucketName: string,
|
|
67
|
-
getFilesStream(bucketName: string,
|
|
70
|
+
getFileNames(bucketName: string, opt?: CommonStorageGetOptions): Promise<string[]>;
|
|
71
|
+
getFileNamesStream(bucketName: string, opt?: CommonStorageGetOptions): ReadableTyped<string>;
|
|
72
|
+
getFilesStream(bucketName: string, opt?: CommonStorageGetOptions): ReadableTyped<FileEntry>;
|
|
68
73
|
getFileReadStream(bucketName: string, filePath: string): Readable;
|
|
69
74
|
getFileWriteStream(bucketName: string, filePath: string): Writable;
|
|
70
75
|
setFileVisibility(bucketName: string, filePath: string, isPublic: boolean): Promise<void>;
|
|
@@ -31,6 +31,12 @@ 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>;
|
|
34
40
|
saveStringFile(filePath: string, content: string): Promise<void>;
|
|
35
41
|
saveJsonFile(filePath: string, content: any): Promise<void>;
|
|
36
42
|
saveFiles(entries: FileEntry[]): Promise<void>;
|
|
@@ -50,13 +56,9 @@ export declare class CommonStorageBucket {
|
|
|
50
56
|
* Important difference between `prefix` and `path` is that `prefix` will
|
|
51
57
|
* return all files from sub-directories too!
|
|
52
58
|
*/
|
|
53
|
-
getFileNames(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
getFileNamesStream(prefix: string, opt?: CommonStorageGetOptions & {
|
|
57
|
-
fullPath?: boolean;
|
|
58
|
-
}): ReadableTyped<string>;
|
|
59
|
-
getFilesStream(prefix: string, opt?: CommonStorageGetOptions): ReadableTyped<FileEntry>;
|
|
59
|
+
getFileNames(opt?: CommonStorageGetOptions): Promise<string[]>;
|
|
60
|
+
getFileNamesStream(opt?: CommonStorageGetOptions): ReadableTyped<string>;
|
|
61
|
+
getFilesStream(opt?: CommonStorageGetOptions): ReadableTyped<FileEntry>;
|
|
60
62
|
getFileReadStream(filePath: string): Readable;
|
|
61
63
|
getFileWriteStream(filePath: string): Writable;
|
|
62
64
|
setFileVisibility(filePath: string, isPublic: boolean): Promise<void>;
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.CommonStorageBucket = void 0;
|
|
4
4
|
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
5
|
-
const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
|
|
6
5
|
/**
|
|
7
6
|
* Convenience wrapper around CommonStorage for a given Bucket.
|
|
8
7
|
*
|
|
@@ -74,6 +73,15 @@ class CommonStorageBucket {
|
|
|
74
73
|
async saveFile(filePath, content) {
|
|
75
74
|
await this.cfg.storage.saveFile(this.cfg.bucketName, filePath, content);
|
|
76
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
|
+
}
|
|
77
85
|
async saveStringFile(filePath, content) {
|
|
78
86
|
await this.cfg.storage.saveFile(this.cfg.bucketName, filePath, Buffer.from(content));
|
|
79
87
|
}
|
|
@@ -107,27 +115,14 @@ class CommonStorageBucket {
|
|
|
107
115
|
* Important difference between `prefix` and `path` is that `prefix` will
|
|
108
116
|
* return all files from sub-directories too!
|
|
109
117
|
*/
|
|
110
|
-
async getFileNames(
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
return
|
|
118
|
-
}
|
|
119
|
-
getFileNamesStream(prefix, opt = {}) {
|
|
120
|
-
const { fullPath = true } = opt;
|
|
121
|
-
if (fullPath || !prefix) {
|
|
122
|
-
return this.cfg.storage.getFileNamesStream(this.cfg.bucketName, prefix, opt);
|
|
123
|
-
}
|
|
124
|
-
const start = `${prefix}/`.length;
|
|
125
|
-
return this.cfg.storage
|
|
126
|
-
.getFileNamesStream(this.cfg.bucketName, prefix, opt)
|
|
127
|
-
.pipe((0, nodejs_lib_1.transformMapSimple)(f => f.slice(start)));
|
|
128
|
-
}
|
|
129
|
-
getFilesStream(prefix, opt) {
|
|
130
|
-
return this.cfg.storage.getFilesStream(this.cfg.bucketName, prefix, opt);
|
|
118
|
+
async getFileNames(opt) {
|
|
119
|
+
return await this.cfg.storage.getFileNames(this.cfg.bucketName, opt);
|
|
120
|
+
}
|
|
121
|
+
getFileNamesStream(opt) {
|
|
122
|
+
return this.cfg.storage.getFileNamesStream(this.cfg.bucketName, opt);
|
|
123
|
+
}
|
|
124
|
+
getFilesStream(opt) {
|
|
125
|
+
return this.cfg.storage.getFilesStream(this.cfg.bucketName, opt);
|
|
131
126
|
}
|
|
132
127
|
getFileReadStream(filePath) {
|
|
133
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]);
|
|
@@ -47,15 +47,15 @@ function runCommonStorageTest(storage, bucketName) {
|
|
|
47
47
|
await (0, js_lib_1.pMap)(TEST_FILES.map(f => f.filePath), async (filePath) => await storage.deletePath(bucketName, filePath));
|
|
48
48
|
});
|
|
49
49
|
test('listFileNames on root should return empty', async () => {
|
|
50
|
-
const fileNames = await storage.getFileNames(bucketName
|
|
50
|
+
const fileNames = await storage.getFileNames(bucketName);
|
|
51
51
|
expect(fileNames).toEqual([]);
|
|
52
52
|
});
|
|
53
53
|
test(`listFileNames on ${TEST_FOLDER} should return empty`, async () => {
|
|
54
|
-
const fileNames = await storage.getFileNames(bucketName, TEST_FOLDER);
|
|
54
|
+
const fileNames = await storage.getFileNames(bucketName, { prefix: TEST_FOLDER });
|
|
55
55
|
expect(fileNames).toEqual([]);
|
|
56
56
|
});
|
|
57
57
|
test('streamFileNames on root should return empty', async () => {
|
|
58
|
-
const fileNames = await (0, nodejs_lib_1.readableToArray)(storage.getFileNamesStream(bucketName
|
|
58
|
+
const fileNames = await (0, nodejs_lib_1.readableToArray)(storage.getFileNamesStream(bucketName));
|
|
59
59
|
expect(fileNames).toEqual([]);
|
|
60
60
|
});
|
|
61
61
|
test(`exists should return empty array`, async () => {
|
|
@@ -68,9 +68,14 @@ function runCommonStorageTest(storage, bucketName) {
|
|
|
68
68
|
const testFilesMap = Object.fromEntries(TEST_FILES.map(f => [f.filePath, f.content]));
|
|
69
69
|
// It's done in the same test to ensure "strong consistency"
|
|
70
70
|
await (0, js_lib_1.pMap)(TEST_FILES, async (f) => await storage.saveFile(bucketName, f.filePath, f.content));
|
|
71
|
-
const
|
|
71
|
+
const fileNamesShort = await storage.getFileNames(bucketName, {
|
|
72
|
+
prefix: TEST_FOLDER,
|
|
73
|
+
fullPaths: false,
|
|
74
|
+
});
|
|
75
|
+
expect(fileNamesShort.sort()).toEqual(TEST_FILES.map(f => (0, js_lib_1._substringAfterLast)(f.filePath, '/')).sort());
|
|
76
|
+
const fileNames = await storage.getFileNames(bucketName, { prefix: TEST_FOLDER });
|
|
72
77
|
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));
|
|
78
|
+
const streamedFileNames = await (0, nodejs_lib_1.readableToArray)(storage.getFileNamesStream(bucketName, { prefix: TEST_FOLDER }));
|
|
74
79
|
expect(streamedFileNames.sort()).toEqual(TEST_FILES.map(f => f.filePath).sort());
|
|
75
80
|
const filesMap = {};
|
|
76
81
|
await (0, js_lib_1.pMap)(fileNames, async (filePath) => {
|
package/package.json
CHANGED
package/src/cloudStorage.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as Buffer from 'buffer'
|
|
2
2
|
import { Readable, Writable } from 'stream'
|
|
3
|
+
import { _substringAfterLast } from '@naturalcycles/js-lib'
|
|
3
4
|
import { Bucket, File, Storage } from '@google-cloud/storage'
|
|
4
5
|
import { ReadableTyped, transformMap, transformMapSimple } from '@naturalcycles/nodejs-lib'
|
|
5
6
|
import { CommonStorage, CommonStorageGetOptions, FileEntry } from './commonStorage'
|
|
@@ -67,32 +68,38 @@ export class CloudStorage implements CommonStorage {
|
|
|
67
68
|
return exists
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
async getFileNames(bucketName: string,
|
|
71
|
+
async getFileNames(bucketName: string, opt: CommonStorageGetOptions = {}): Promise<string[]> {
|
|
72
|
+
const { prefix, fullPaths = true } = opt
|
|
71
73
|
const [files] = await this.storage.bucket(bucketName).getFiles({
|
|
72
74
|
prefix,
|
|
73
75
|
})
|
|
74
|
-
|
|
76
|
+
|
|
77
|
+
if (fullPaths) {
|
|
78
|
+
return files.map(f => f.name)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return files.map(f => _substringAfterLast(f.name, '/'))
|
|
75
82
|
}
|
|
76
83
|
|
|
77
|
-
getFileNamesStream(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
opt: CommonStorageGetOptions = {},
|
|
81
|
-
): ReadableTyped<string> {
|
|
84
|
+
getFileNamesStream(bucketName: string, opt: CommonStorageGetOptions = {}): ReadableTyped<string> {
|
|
85
|
+
const { prefix, fullPaths = true } = opt
|
|
86
|
+
|
|
82
87
|
return this.storage
|
|
83
88
|
.bucket(bucketName)
|
|
84
89
|
.getFilesStream({
|
|
85
90
|
prefix,
|
|
86
91
|
maxResults: opt.limit,
|
|
87
92
|
})
|
|
88
|
-
.pipe(
|
|
93
|
+
.pipe(
|
|
94
|
+
transformMapSimple<File, string>(f =>
|
|
95
|
+
fullPaths ? f.name : _substringAfterLast(f.name, '/'),
|
|
96
|
+
),
|
|
97
|
+
)
|
|
89
98
|
}
|
|
90
99
|
|
|
91
|
-
getFilesStream(
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
opt: CommonStorageGetOptions = {},
|
|
95
|
-
): ReadableTyped<FileEntry> {
|
|
100
|
+
getFilesStream(bucketName: string, opt: CommonStorageGetOptions = {}): ReadableTyped<FileEntry> {
|
|
101
|
+
const { prefix } = opt
|
|
102
|
+
|
|
96
103
|
return this.storage
|
|
97
104
|
.bucket(bucketName)
|
|
98
105
|
.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
|
*
|
|
@@ -76,19 +82,11 @@ export interface CommonStorage {
|
|
|
76
82
|
* Important difference between `prefix` and `path` is that `prefix` will
|
|
77
83
|
* return all files from sub-directories too!
|
|
78
84
|
*/
|
|
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>
|
|
85
|
+
getFileNames(bucketName: string, opt?: CommonStorageGetOptions): Promise<string[]>
|
|
86
|
+
|
|
87
|
+
getFileNamesStream(bucketName: string, opt?: CommonStorageGetOptions): ReadableTyped<string>
|
|
88
|
+
|
|
89
|
+
getFilesStream(bucketName: string, opt?: CommonStorageGetOptions): ReadableTyped<FileEntry>
|
|
92
90
|
|
|
93
91
|
getFileReadStream(bucketName: string, filePath: string): Readable
|
|
94
92
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Readable, Writable } from 'stream'
|
|
2
2
|
import { AppError, pMap } from '@naturalcycles/js-lib'
|
|
3
|
-
import { ReadableTyped
|
|
3
|
+
import { ReadableTyped } from '@naturalcycles/nodejs-lib'
|
|
4
4
|
import { CommonStorage, CommonStorageGetOptions, FileEntry } from './commonStorage'
|
|
5
5
|
|
|
6
6
|
export interface CommonStorageBucketCfg {
|
|
@@ -103,6 +103,16 @@ 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
|
+
|
|
106
116
|
async saveStringFile(filePath: string, content: string): Promise<void> {
|
|
107
117
|
await this.cfg.storage.saveFile(this.cfg.bucketName, filePath, Buffer.from(content))
|
|
108
118
|
}
|
|
@@ -145,36 +155,16 @@ export class CommonStorageBucket {
|
|
|
145
155
|
* Important difference between `prefix` and `path` is that `prefix` will
|
|
146
156
|
* return all files from sub-directories too!
|
|
147
157
|
*/
|
|
148
|
-
async getFileNames(
|
|
149
|
-
|
|
150
|
-
const names = await this.cfg.storage.getFileNames(this.cfg.bucketName, prefix)
|
|
151
|
-
|
|
152
|
-
if (!fullPath && prefix) {
|
|
153
|
-
const start = `${prefix}/`.length
|
|
154
|
-
return names.map(n => n.slice(start))
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return names
|
|
158
|
+
async getFileNames(opt?: CommonStorageGetOptions): Promise<string[]> {
|
|
159
|
+
return await this.cfg.storage.getFileNames(this.cfg.bucketName, opt)
|
|
158
160
|
}
|
|
159
161
|
|
|
160
|
-
getFileNamesStream(
|
|
161
|
-
|
|
162
|
-
opt: CommonStorageGetOptions & { fullPath?: boolean } = {},
|
|
163
|
-
): ReadableTyped<string> {
|
|
164
|
-
const { fullPath = true } = opt
|
|
165
|
-
|
|
166
|
-
if (fullPath || !prefix) {
|
|
167
|
-
return this.cfg.storage.getFileNamesStream(this.cfg.bucketName, prefix, opt)
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const start = `${prefix}/`.length
|
|
171
|
-
return this.cfg.storage
|
|
172
|
-
.getFileNamesStream(this.cfg.bucketName, prefix, opt)
|
|
173
|
-
.pipe(transformMapSimple<string, string>(f => f.slice(start)))
|
|
162
|
+
getFileNamesStream(opt?: CommonStorageGetOptions): ReadableTyped<string> {
|
|
163
|
+
return this.cfg.storage.getFileNamesStream(this.cfg.bucketName, opt)
|
|
174
164
|
}
|
|
175
165
|
|
|
176
|
-
getFilesStream(
|
|
177
|
-
return this.cfg.storage.getFilesStream(this.cfg.bucketName,
|
|
166
|
+
getFilesStream(opt?: CommonStorageGetOptions): ReadableTyped<FileEntry> {
|
|
167
|
+
return this.cfg.storage.getFilesStream(this.cfg.bucketName, opt)
|
|
178
168
|
}
|
|
179
169
|
|
|
180
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
|
|
|
@@ -59,17 +59,17 @@ export function runCommonStorageTest(storage: CommonStorage, bucketName: string)
|
|
|
59
59
|
})
|
|
60
60
|
|
|
61
61
|
test('listFileNames on root should return empty', async () => {
|
|
62
|
-
const fileNames = await storage.getFileNames(bucketName
|
|
62
|
+
const fileNames = await storage.getFileNames(bucketName)
|
|
63
63
|
expect(fileNames).toEqual([])
|
|
64
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
71
|
test('streamFileNames on root should return empty', async () => {
|
|
72
|
-
const fileNames = await readableToArray(storage.getFileNamesStream(bucketName
|
|
72
|
+
const fileNames = await readableToArray(storage.getFileNamesStream(bucketName))
|
|
73
73
|
expect(fileNames).toEqual([])
|
|
74
74
|
})
|
|
75
75
|
|
|
@@ -86,11 +86,19 @@ export function runCommonStorageTest(storage: CommonStorage, bucketName: string)
|
|
|
86
86
|
// It's done in the same test to ensure "strong consistency"
|
|
87
87
|
await pMap(TEST_FILES, async f => await storage.saveFile(bucketName, f.filePath, f.content))
|
|
88
88
|
|
|
89
|
-
const
|
|
89
|
+
const fileNamesShort = await storage.getFileNames(bucketName, {
|
|
90
|
+
prefix: TEST_FOLDER,
|
|
91
|
+
fullPaths: false,
|
|
92
|
+
})
|
|
93
|
+
expect(fileNamesShort.sort()).toEqual(
|
|
94
|
+
TEST_FILES.map(f => _substringAfterLast(f.filePath, '/')).sort(),
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
const fileNames = await storage.getFileNames(bucketName, { prefix: TEST_FOLDER })
|
|
90
98
|
expect(fileNames.sort()).toEqual(TEST_FILES.map(f => f.filePath).sort())
|
|
91
99
|
|
|
92
100
|
const streamedFileNames = await readableToArray(
|
|
93
|
-
storage.getFileNamesStream(bucketName, TEST_FOLDER),
|
|
101
|
+
storage.getFileNamesStream(bucketName, { prefix: TEST_FOLDER }),
|
|
94
102
|
)
|
|
95
103
|
expect(streamedFileNames.sort()).toEqual(TEST_FILES.map(f => f.filePath).sort())
|
|
96
104
|
|