@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.
@@ -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, prefix: string): Promise<string[]>;
29
- getFileNamesStream(bucketName: string, prefix: string, opt?: CommonStorageGetOptions): ReadableTyped<string>;
30
- getFilesStream(bucketName: string, prefix: string, opt?: CommonStorageGetOptions): ReadableTyped<FileEntry>;
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,
@@ -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, prefix) {
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
- return files.map(f => f.name);
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, prefix, opt = {}) {
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, prefix, opt = {}) {
67
+ getFilesStream(bucketName, opt = {}) {
68
+ const { prefix } = opt;
62
69
  return this.storage
63
70
  .bucket(bucketName)
64
71
  .getFilesStream({
@@ -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, prefix: string): Promise<string[]>;
66
- getFileNamesStream(bucketName: string, prefix: string, opt?: CommonStorageGetOptions): ReadableTyped<string>;
67
- getFilesStream(bucketName: string, prefix: string, opt?: CommonStorageGetOptions): ReadableTyped<FileEntry>;
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(prefix: string, opt?: {
54
- fullPath?: boolean;
55
- }): Promise<string[]>;
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(prefix, opt = {}) {
111
- const { fullPath = true } = opt;
112
- const names = await this.cfg.storage.getFileNames(this.cfg.bucketName, prefix);
113
- if (!fullPath && prefix) {
114
- const start = `${prefix}/`.length;
115
- return names.map(n => n.slice(start));
116
- }
117
- return names;
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
- const index = prefix.length + 1;
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, { limit })
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, { limit })
76
+ .getFilesStream(bucketName, { prefix, limit, fullPaths: false })
81
77
  .pipe((0, nodejs_lib_1.transformMapSimple)(({ filePath, content }) => [
82
- filePath.slice(index),
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, prefix: string): Promise<string[]>;
20
- getFileNamesStream(bucketName: string, prefix: string, opt?: CommonStorageGetOptions): ReadableTyped<string>;
21
- getFilesStream(bucketName: string, prefix: string, opt?: CommonStorageGetOptions): ReadableTyped<FileEntry>;
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, prefix) {
39
- return Object.keys(this.data[bucketName] || {}).filter(filePath => filePath.startsWith(prefix));
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, prefix, opt = {}) {
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, prefix, opt = {}) {
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]) => ({ 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 fileNames = await storage.getFileNames(bucketName, TEST_FOLDER);
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
@@ -34,7 +34,7 @@
34
34
  "engines": {
35
35
  "node": ">=14.16.0"
36
36
  },
37
- "version": "1.2.0",
37
+ "version": "1.3.0",
38
38
  "description": "",
39
39
  "author": "Natural Cycles Team",
40
40
  "license": "MIT"
@@ -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, prefix: string): Promise<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
- return files.map(f => f.name)
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
- bucketName: string,
79
- prefix: string,
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(transformMapSimple<File, string>(f => f.name))
93
+ .pipe(
94
+ transformMapSimple<File, string>(f =>
95
+ fullPaths ? f.name : _substringAfterLast(f.name, '/'),
96
+ ),
97
+ )
89
98
  }
90
99
 
91
- getFilesStream(
92
- bucketName: string,
93
- prefix: string,
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({
@@ -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, prefix: string): Promise<string[]>
80
-
81
- getFileNamesStream(
82
- bucketName: string,
83
- prefix: string,
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, transformMapSimple } from '@naturalcycles/nodejs-lib'
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(prefix: string, opt: { fullPath?: boolean } = {}): Promise<string[]> {
149
- const { fullPath = true } = opt
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
- prefix: string,
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(prefix: string, opt?: CommonStorageGetOptions): ReadableTyped<FileEntry> {
177
- return this.cfg.storage.getFilesStream(this.cfg.bucketName, prefix, opt)
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, { limit })
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, { limit })
97
+ .getFilesStream(bucketName, { prefix, limit, fullPaths: false })
102
98
  .pipe(
103
99
  transformMapSimple<FileEntry, KeyValueDBTuple>(({ filePath, content }) => [
104
- filePath.slice(index),
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, prefix: string): Promise<string[]> {
46
- return Object.keys(this.data[bucketName] || {}).filter(filePath => filePath.startsWith(prefix))
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
- bucketName: string,
51
- prefix: string,
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
- bucketName: string,
63
- prefix: string,
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]) => ({ 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 fileNames = await storage.getFileNames(bucketName, TEST_FOLDER)
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