@naturalcycles/cloud-storage-lib 1.0.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 +11 -3
- package/dist/commonStorageBucket.js +21 -6
- package/dist/commonStorageKeyValueDB.d.ts +1 -0
- package/dist/commonStorageKeyValueDB.js +8 -8
- 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/readme.md +0 -1
- package/src/cloudStorage.ts +20 -13
- package/src/commonStorage.ts +11 -13
- package/src/commonStorageBucket.ts +28 -6
- package/src/commonStorageKeyValueDB.ts +10 -8
- 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,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);
|
|
@@ -29,4 +29,5 @@ export declare class CommonStorageKeyValueDB implements CommonKeyValueDB {
|
|
|
29
29
|
streamIds(table: string, limit?: number): ReadableTyped<string>;
|
|
30
30
|
streamValues(table: string, limit?: number): ReadableTyped<Buffer>;
|
|
31
31
|
streamEntries(table: string, limit?: number): ReadableTyped<KeyValueDBTuple>;
|
|
32
|
+
count(table: string): Promise<number>;
|
|
32
33
|
}
|
|
@@ -62,26 +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
|
}
|
|
82
|
+
async count(table) {
|
|
83
|
+
const { bucketName, prefix } = this.getBucketAndPrefix(table);
|
|
84
|
+
return (await this.cfg.storage.getFileNames(bucketName, { prefix })).length;
|
|
85
|
+
}
|
|
86
86
|
}
|
|
87
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/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,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
|
|
|
@@ -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,32 +78,34 @@ 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
|
)
|
|
108
104
|
}
|
|
105
|
+
|
|
106
|
+
async count(table: string): Promise<number> {
|
|
107
|
+
const { bucketName, prefix } = this.getBucketAndPrefix(table)
|
|
108
|
+
|
|
109
|
+
return (await this.cfg.storage.getFileNames(bucketName, { prefix })).length
|
|
110
|
+
}
|
|
109
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
|
|