@naturalcycles/cloud-storage-lib 1.0.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.
@@ -0,0 +1,43 @@
1
+ /// <reference types="node" />
2
+ import { Readable, Writable } from 'stream';
3
+ import { Storage } from '@google-cloud/storage';
4
+ import { ReadableTyped } from '@naturalcycles/nodejs-lib';
5
+ import { CommonStorage, CommonStorageGetOptions, FileEntry } from './commonStorage';
6
+ import { GCPServiceAccount } from './model';
7
+ /**
8
+ * This object is intentionally made to NOT extend StorageOptions,
9
+ * because StorageOptions is complicated and provides just too many ways
10
+ * to configure credentials.
11
+ *
12
+ * Here we define the minimum simple set of credentials needed.
13
+ * All of these properties are available from the "service account" json file
14
+ * (either personal one or non-personal).
15
+ */
16
+ export interface CloudStorageCfg {
17
+ credentials: GCPServiceAccount;
18
+ }
19
+ export declare class CloudStorage implements CommonStorage {
20
+ cfg: CloudStorageCfg;
21
+ constructor(cfg: CloudStorageCfg);
22
+ storage: Storage;
23
+ ping(bucketName?: string): Promise<void>;
24
+ getBucketNames(opt?: CommonStorageGetOptions): Promise<string[]>;
25
+ getBucketNamesStream(): ReadableTyped<string>;
26
+ deletePath(bucketName: string, prefix: string): Promise<void>;
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>;
31
+ getFile(bucketName: string, filePath: string): Promise<Buffer | null>;
32
+ /**
33
+ * Returns a Readable that is NOT object mode,
34
+ * so you can e.g pipe it to fs.createWriteStream()
35
+ */
36
+ getFileReadStream(bucketName: string, filePath: string): Readable;
37
+ saveFile(bucketName: string, filePath: string, content: Buffer): Promise<void>;
38
+ getFileWriteStream(bucketName: string, filePath: string): Writable;
39
+ setFileVisibility(bucketName: string, filePath: string, isPublic: boolean): Promise<void>;
40
+ getFileVisibility(bucketName: string, filePath: string): Promise<boolean>;
41
+ copyFile(fromBucket: string, fromPath: string, toPath: string, toBucket?: string): Promise<void>;
42
+ moveFile(fromBucket: string, fromPath: string, toPath: string, toBucket?: string): Promise<void>;
43
+ }
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CloudStorage = void 0;
4
+ const storage_1 = require("@google-cloud/storage");
5
+ const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
6
+ class CloudStorage {
7
+ constructor(cfg) {
8
+ this.cfg = cfg;
9
+ this.storage = new storage_1.Storage({
10
+ credentials: cfg.credentials,
11
+ // Explicitly passing it here to fix this error:
12
+ // Error: Unable to detect a Project Id in the current environment.
13
+ // To learn more about authentication and Google APIs, visit:
14
+ // https://cloud.google.com/docs/authentication/getting-started
15
+ // at /root/repo/node_modules/google-auth-library/build/src/auth/googleauth.js:95:31
16
+ projectId: cfg.credentials.project_id,
17
+ });
18
+ }
19
+ // async createBucket(bucketName: string): Promise<void> {
20
+ // const bucket = await this.storage.createBucket(bucketName)
21
+ // console.log(bucket) // debugging
22
+ // }
23
+ async ping(bucketName) {
24
+ await this.storage.bucket(bucketName || 'non-existing-for-sure').exists();
25
+ }
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
+ async deletePath(bucketName, prefix) {
36
+ await this.storage.bucket(bucketName).deleteFiles({
37
+ prefix,
38
+ // to keep going in case error occurs, similar to THROW_AGGREGATED
39
+ force: true,
40
+ });
41
+ }
42
+ async fileExists(bucketName, filePath) {
43
+ const [exists] = await this.storage.bucket(bucketName).file(filePath).exists();
44
+ return exists;
45
+ }
46
+ async getFileNames(bucketName, prefix) {
47
+ const [files] = await this.storage.bucket(bucketName).getFiles({
48
+ prefix,
49
+ });
50
+ return files.map(f => f.name);
51
+ }
52
+ getFileNamesStream(bucketName, prefix, opt = {}) {
53
+ return this.storage
54
+ .bucket(bucketName)
55
+ .getFilesStream({
56
+ prefix,
57
+ maxResults: opt.limit,
58
+ })
59
+ .pipe((0, nodejs_lib_1.transformMapSimple)(f => f.name));
60
+ }
61
+ getFilesStream(bucketName, prefix, opt = {}) {
62
+ return this.storage
63
+ .bucket(bucketName)
64
+ .getFilesStream({
65
+ prefix,
66
+ maxResults: opt.limit,
67
+ })
68
+ .pipe((0, nodejs_lib_1.transformMap)(async (f) => {
69
+ const [content] = await f.download();
70
+ return { filePath: f.name, content };
71
+ }));
72
+ }
73
+ async getFile(bucketName, filePath) {
74
+ const [buf] = await this.storage
75
+ .bucket(bucketName)
76
+ .file(filePath)
77
+ .download()
78
+ .catch(err => {
79
+ if (err?.code === 404)
80
+ return [null]; // file not found
81
+ throw err; // rethrow otherwise
82
+ });
83
+ return buf;
84
+ }
85
+ /**
86
+ * Returns a Readable that is NOT object mode,
87
+ * so you can e.g pipe it to fs.createWriteStream()
88
+ */
89
+ getFileReadStream(bucketName, filePath) {
90
+ return this.storage.bucket(bucketName).file(filePath).createReadStream();
91
+ }
92
+ async saveFile(bucketName, filePath, content) {
93
+ await this.storage.bucket(bucketName).file(filePath).save(content);
94
+ }
95
+ getFileWriteStream(bucketName, filePath) {
96
+ return this.storage.bucket(bucketName).file(filePath).createWriteStream();
97
+ }
98
+ async setFileVisibility(bucketName, filePath, isPublic) {
99
+ await this.storage.bucket(bucketName).file(filePath)[isPublic ? 'makePublic' : 'makePrivate']();
100
+ }
101
+ async getFileVisibility(bucketName, filePath) {
102
+ const [isPublic] = await this.storage.bucket(bucketName).file(filePath).isPublic();
103
+ return isPublic;
104
+ }
105
+ async copyFile(fromBucket, fromPath, toPath, toBucket) {
106
+ await this.storage
107
+ .bucket(fromBucket)
108
+ .file(fromPath)
109
+ .copy(this.storage.bucket(toBucket || fromBucket).file(toPath));
110
+ }
111
+ async moveFile(fromBucket, fromPath, toPath, toBucket) {
112
+ await this.storage
113
+ .bucket(fromBucket)
114
+ .file(fromPath)
115
+ .move(this.storage.bucket(toBucket || fromBucket).file(toPath));
116
+ }
117
+ }
118
+ exports.CloudStorage = CloudStorage;
@@ -0,0 +1,74 @@
1
+ /// <reference types="node" />
2
+ import { Readable, Writable } from 'stream';
3
+ import { ReadableTyped } from '@naturalcycles/nodejs-lib';
4
+ export interface FileEntry {
5
+ filePath: string;
6
+ content: Buffer;
7
+ }
8
+ export interface CommonStorageGetOptions {
9
+ /**
10
+ * Will filter resulting files based on `prefix`.
11
+ */
12
+ prefix?: string;
13
+ /**
14
+ * Limits the number of results.
15
+ *
16
+ * By default it's unlimited.
17
+ */
18
+ limit?: number;
19
+ }
20
+ /**
21
+ * Common denominator interface for File Storage.
22
+ * Modelled after GCP Cloud Storage, Firebase Storage.
23
+ *
24
+ * Uses the concept of Bucket (identified by string name) and Path within the Bucket.
25
+ *
26
+ * Path MUST NOT start with a slash !
27
+ *
28
+ * Similarly to CommonDB, Bucket is like a Table, and Path is like an `id`.
29
+ */
30
+ export interface CommonStorage {
31
+ /**
32
+ * Ensure that the credentials are correct and the connection is working.
33
+ * Idempotent.
34
+ *
35
+ * Pass `bucketName` in case you only have permissions to operate on that bucket.
36
+ */
37
+ ping(bucketName?: string): Promise<void>;
38
+ /**
39
+ * Often needs a special permission.
40
+ */
41
+ getBucketNames(opt?: CommonStorageGetOptions): Promise<string[]>;
42
+ getBucketNamesStream(): ReadableTyped<string>;
43
+ /**
44
+ * Creates a new bucket by given name.
45
+ * todo: check what to do if it already exists
46
+ */
47
+ fileExists(bucketName: string, filePath: string): Promise<boolean>;
48
+ getFile(bucketName: string, filePath: string): Promise<Buffer | null>;
49
+ saveFile(bucketName: string, filePath: string, content: Buffer): Promise<void>;
50
+ /**
51
+ * Should recursively delete all files in a folder, if path is a folder.
52
+ */
53
+ deletePath(bucketName: string, prefix: string): Promise<void>;
54
+ /**
55
+ * Returns an array of strings which are file paths.
56
+ * Files that are not found by the path are not present in the map.
57
+ *
58
+ * Second argument is called `prefix` (same as `path`) to explain how
59
+ * listing works (it filters all files by `startsWith`). Also, to match
60
+ * GCP Cloud Storage API.
61
+ *
62
+ * Important difference between `prefix` and `path` is that `prefix` will
63
+ * return all files from sub-directories too!
64
+ */
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>;
68
+ getFileReadStream(bucketName: string, filePath: string): Readable;
69
+ getFileWriteStream(bucketName: string, filePath: string): Writable;
70
+ setFileVisibility(bucketName: string, filePath: string, isPublic: boolean): Promise<void>;
71
+ getFileVisibility(bucketName: string, filePath: string): Promise<boolean>;
72
+ copyFile(fromBucket: string, fromPath: string, toPath: string, toBucket?: string): Promise<void>;
73
+ moveFile(fromBucket: string, fromPath: string, toPath: string, toBucket?: string): Promise<void>;
74
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,60 @@
1
+ /// <reference types="node" />
2
+ import { Readable, Writable } from 'stream';
3
+ import { ReadableTyped } from '@naturalcycles/nodejs-lib';
4
+ import { CommonStorage, CommonStorageGetOptions, FileEntry } from './commonStorage';
5
+ export interface CommonStorageBucketCfg {
6
+ storage: CommonStorage;
7
+ bucketName: string;
8
+ }
9
+ /**
10
+ * Convenience wrapper around CommonStorage for a given Bucket.
11
+ *
12
+ * Similar to what CommonDao is to CommonDB.
13
+ */
14
+ export declare class CommonStorageBucket {
15
+ cfg: CommonStorageBucketCfg;
16
+ constructor(cfg: CommonStorageBucketCfg);
17
+ ping(bucketName?: string): Promise<void>;
18
+ fileExists(filePath: string): Promise<boolean>;
19
+ getFile(filePath: string): Promise<Buffer | null>;
20
+ getFileAsString(filePath: string): Promise<string | null>;
21
+ getFileAsJson<T = any>(filePath: string): Promise<T | null>;
22
+ requireFile(filePath: string): Promise<Buffer>;
23
+ requireFileAsString(filePath: string): Promise<string>;
24
+ requireFileAsJson<T = any>(filePath: string): Promise<T>;
25
+ private throwRequiredError;
26
+ getFileContents(paths: string[]): Promise<Buffer[]>;
27
+ getFileContentsAsJson<T = any>(paths: string[]): Promise<T[]>;
28
+ getFileEntries(paths: string[]): Promise<FileEntry[]>;
29
+ getFileEntriesAsJson<T = any>(paths: string[]): Promise<{
30
+ filePath: string;
31
+ content: T;
32
+ }[]>;
33
+ saveFile(filePath: string, content: Buffer): Promise<void>;
34
+ saveFiles(entries: FileEntry[]): Promise<void>;
35
+ /**
36
+ * Should recursively delete all files in a folder, if path is a folder.
37
+ */
38
+ deletePath(prefix: string): Promise<void>;
39
+ deletePaths(prefixes: string[]): Promise<void>;
40
+ /**
41
+ * Returns an array of strings which are file paths.
42
+ * Files that are not found by the path are not present in the map.
43
+ *
44
+ * Second argument is called `prefix` (same as `path`) to explain how
45
+ * listing works (it filters all files by `startsWith`). Also, to match
46
+ * GCP Cloud Storage API.
47
+ *
48
+ * Important difference between `prefix` and `path` is that `prefix` will
49
+ * return all files from sub-directories too!
50
+ */
51
+ getFileNames(prefix: string): Promise<string[]>;
52
+ getFileNamesStream(prefix: string, opt?: CommonStorageGetOptions): ReadableTyped<string>;
53
+ getFilesStream(prefix: string, opt?: CommonStorageGetOptions): ReadableTyped<FileEntry>;
54
+ getFileReadStream(filePath: string): Readable;
55
+ getFileWriteStream(filePath: string): Writable;
56
+ setFileVisibility(filePath: string, isPublic: boolean): Promise<void>;
57
+ getFileVisibility(filePath: string): Promise<boolean>;
58
+ copyFile(fromPath: string, toPath: string, toBucket?: string): Promise<void>;
59
+ moveFile(fromPath: string, toPath: string, toBucket?: string): Promise<void>;
60
+ }
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CommonStorageBucket = void 0;
4
+ const js_lib_1 = require("@naturalcycles/js-lib");
5
+ /**
6
+ * Convenience wrapper around CommonStorage for a given Bucket.
7
+ *
8
+ * Similar to what CommonDao is to CommonDB.
9
+ */
10
+ class CommonStorageBucket {
11
+ constructor(cfg) {
12
+ this.cfg = cfg;
13
+ }
14
+ async ping(bucketName) {
15
+ await this.cfg.storage.ping(bucketName);
16
+ }
17
+ async fileExists(filePath) {
18
+ return await this.cfg.storage.fileExists(this.cfg.bucketName, filePath);
19
+ }
20
+ async getFile(filePath) {
21
+ return await this.cfg.storage.getFile(this.cfg.bucketName, filePath);
22
+ }
23
+ async getFileAsString(filePath) {
24
+ const buf = await this.cfg.storage.getFile(this.cfg.bucketName, filePath);
25
+ return buf?.toString() || null;
26
+ }
27
+ async getFileAsJson(filePath) {
28
+ const buf = await this.cfg.storage.getFile(this.cfg.bucketName, filePath);
29
+ if (!buf)
30
+ return null;
31
+ return JSON.parse(buf.toString());
32
+ }
33
+ async requireFile(filePath) {
34
+ const buf = await this.cfg.storage.getFile(this.cfg.bucketName, filePath);
35
+ if (!buf)
36
+ this.throwRequiredError(filePath);
37
+ return buf;
38
+ }
39
+ async requireFileAsString(filePath) {
40
+ const s = await this.getFileAsString(filePath);
41
+ return s ?? this.throwRequiredError(filePath);
42
+ }
43
+ async requireFileAsJson(filePath) {
44
+ const v = await this.getFileAsJson(filePath);
45
+ return v ?? this.throwRequiredError(filePath);
46
+ }
47
+ throwRequiredError(filePath) {
48
+ throw new js_lib_1.AppError(`File required, but not found: ${this.cfg.bucketName}/${filePath}`, {
49
+ code: 'FILE_REQUIRED',
50
+ });
51
+ }
52
+ async getFileContents(paths) {
53
+ return (await (0, js_lib_1.pMap)(paths, async (filePath) => (await this.cfg.storage.getFile(this.cfg.bucketName, filePath)))).filter(Boolean);
54
+ }
55
+ async getFileContentsAsJson(paths) {
56
+ return (await (0, js_lib_1.pMap)(paths, async (filePath) => {
57
+ const buf = await this.cfg.storage.getFile(this.cfg.bucketName, filePath);
58
+ return buf ? JSON.parse(buf.toString()) : null;
59
+ })).filter(Boolean);
60
+ }
61
+ async getFileEntries(paths) {
62
+ return (await (0, js_lib_1.pMap)(paths, async (filePath) => {
63
+ const content = await this.cfg.storage.getFile(this.cfg.bucketName, filePath);
64
+ return { filePath, content: content };
65
+ })).filter(f => f.content);
66
+ }
67
+ async getFileEntriesAsJson(paths) {
68
+ return (await (0, js_lib_1.pMap)(paths, async (filePath) => {
69
+ const buf = await this.cfg.storage.getFile(this.cfg.bucketName, filePath);
70
+ return buf ? { filePath, content: JSON.parse(buf.toString()) } : null;
71
+ })).filter(Boolean);
72
+ }
73
+ async saveFile(filePath, content) {
74
+ await this.cfg.storage.saveFile(this.cfg.bucketName, filePath, content);
75
+ }
76
+ async saveFiles(entries) {
77
+ await (0, js_lib_1.pMap)(entries, async (f) => {
78
+ await this.cfg.storage.saveFile(this.cfg.bucketName, f.filePath, f.content);
79
+ });
80
+ }
81
+ /**
82
+ * Should recursively delete all files in a folder, if path is a folder.
83
+ */
84
+ async deletePath(prefix) {
85
+ return await this.cfg.storage.deletePath(this.cfg.bucketName, prefix);
86
+ }
87
+ async deletePaths(prefixes) {
88
+ await (0, js_lib_1.pMap)(prefixes, async (prefix) => {
89
+ return await this.cfg.storage.deletePath(this.cfg.bucketName, prefix);
90
+ });
91
+ }
92
+ /**
93
+ * Returns an array of strings which are file paths.
94
+ * Files that are not found by the path are not present in the map.
95
+ *
96
+ * Second argument is called `prefix` (same as `path`) to explain how
97
+ * listing works (it filters all files by `startsWith`). Also, to match
98
+ * GCP Cloud Storage API.
99
+ *
100
+ * Important difference between `prefix` and `path` is that `prefix` will
101
+ * return all files from sub-directories too!
102
+ */
103
+ async getFileNames(prefix) {
104
+ return await this.cfg.storage.getFileNames(this.cfg.bucketName, prefix);
105
+ }
106
+ getFileNamesStream(prefix, opt) {
107
+ return this.cfg.storage.getFileNamesStream(this.cfg.bucketName, prefix, opt);
108
+ }
109
+ getFilesStream(prefix, opt) {
110
+ return this.cfg.storage.getFilesStream(this.cfg.bucketName, prefix, opt);
111
+ }
112
+ getFileReadStream(filePath) {
113
+ return this.cfg.storage.getFileReadStream(this.cfg.bucketName, filePath);
114
+ }
115
+ getFileWriteStream(filePath) {
116
+ return this.cfg.storage.getFileWriteStream(this.cfg.bucketName, filePath);
117
+ }
118
+ async setFileVisibility(filePath, isPublic) {
119
+ await this.cfg.storage.setFileVisibility(this.cfg.bucketName, filePath, isPublic);
120
+ }
121
+ async getFileVisibility(filePath) {
122
+ return await this.cfg.storage.getFileVisibility(this.cfg.bucketName, filePath);
123
+ }
124
+ async copyFile(fromPath, toPath, toBucket) {
125
+ await this.cfg.storage.copyFile(this.cfg.bucketName, fromPath, toPath, toBucket);
126
+ }
127
+ async moveFile(fromPath, toPath, toBucket) {
128
+ await this.cfg.storage.moveFile(this.cfg.bucketName, fromPath, toPath, toBucket);
129
+ }
130
+ }
131
+ exports.CommonStorageBucket = CommonStorageBucket;
@@ -0,0 +1,32 @@
1
+ /// <reference types="node" />
2
+ import { CommonDBCreateOptions, CommonKeyValueDB, KeyValueDBTuple } from '@naturalcycles/db-lib';
3
+ import { ReadableTyped } from '@naturalcycles/nodejs-lib';
4
+ import { CommonStorage } from './commonStorage';
5
+ export interface CommonStorageKeyValueDBCfg {
6
+ storage: CommonStorage;
7
+ bucketName: string;
8
+ }
9
+ /**
10
+ * CommonKeyValueDB, backed up by a CommonStorage implementation.
11
+ *
12
+ * Each Table is represented as a Folder.
13
+ * Each Item is represented as a File:
14
+ * fileName is ${id} (without extension)
15
+ * file contents is ${v} (Buffer)
16
+ */
17
+ export declare class CommonStorageKeyValueDB implements CommonKeyValueDB {
18
+ cfg: CommonStorageKeyValueDBCfg;
19
+ constructor(cfg: CommonStorageKeyValueDBCfg);
20
+ ping(): Promise<void>;
21
+ createTable(_table: string, _opt?: CommonDBCreateOptions): Promise<void>;
22
+ /**
23
+ * Allows to pass `SomeBucket.SomeTable` in `table`, to override a Bucket.
24
+ */
25
+ private getBucketAndPrefix;
26
+ deleteByIds(table: string, ids: string[]): Promise<void>;
27
+ getByIds(table: string, ids: string[]): Promise<KeyValueDBTuple[]>;
28
+ saveBatch(table: string, entries: KeyValueDBTuple[]): Promise<void>;
29
+ streamIds(table: string, limit?: number): ReadableTyped<string>;
30
+ streamValues(table: string, limit?: number): ReadableTyped<Buffer>;
31
+ streamEntries(table: string, limit?: number): ReadableTyped<KeyValueDBTuple>;
32
+ }
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CommonStorageKeyValueDB = void 0;
4
+ const js_lib_1 = require("@naturalcycles/js-lib");
5
+ const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
6
+ /**
7
+ * CommonKeyValueDB, backed up by a CommonStorage implementation.
8
+ *
9
+ * Each Table is represented as a Folder.
10
+ * Each Item is represented as a File:
11
+ * fileName is ${id} (without extension)
12
+ * file contents is ${v} (Buffer)
13
+ */
14
+ class CommonStorageKeyValueDB {
15
+ constructor(cfg) {
16
+ this.cfg = cfg;
17
+ }
18
+ async ping() {
19
+ await this.cfg.storage.ping(this.cfg.bucketName);
20
+ }
21
+ async createTable(_table, _opt) {
22
+ // no-op
23
+ }
24
+ /**
25
+ * Allows to pass `SomeBucket.SomeTable` in `table`, to override a Bucket.
26
+ */
27
+ getBucketAndPrefix(table) {
28
+ const [part1, part2] = table.split('.');
29
+ if (part2) {
30
+ return {
31
+ bucketName: part1,
32
+ prefix: part2,
33
+ };
34
+ }
35
+ // As is
36
+ return {
37
+ bucketName: this.cfg.bucketName,
38
+ prefix: table,
39
+ };
40
+ }
41
+ async deleteByIds(table, ids) {
42
+ const { bucketName, prefix } = this.getBucketAndPrefix(table);
43
+ await (0, js_lib_1.pMap)(ids, async (id) => {
44
+ await this.cfg.storage.deletePath(bucketName, [prefix, id].join('/'));
45
+ });
46
+ }
47
+ async getByIds(table, ids) {
48
+ const { bucketName, prefix } = this.getBucketAndPrefix(table);
49
+ const map = {};
50
+ await (0, js_lib_1.pMap)(ids, async (id) => {
51
+ const buf = await this.cfg.storage.getFile(bucketName, [prefix, id].join('/'));
52
+ if (buf)
53
+ map[id] = buf;
54
+ });
55
+ return ids.map(id => [id, map[id]]).filter(t => t[1]);
56
+ }
57
+ async saveBatch(table, entries) {
58
+ const { bucketName, prefix } = this.getBucketAndPrefix(table);
59
+ await (0, js_lib_1.pMap)(entries, async ([id, content]) => {
60
+ await this.cfg.storage.saveFile(bucketName, [prefix, id].join('/'), content);
61
+ });
62
+ }
63
+ streamIds(table, limit) {
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)));
69
+ }
70
+ streamValues(table, limit) {
71
+ const { bucketName, prefix } = this.getBucketAndPrefix(table);
72
+ return this.cfg.storage
73
+ .getFilesStream(bucketName, prefix, { limit })
74
+ .pipe((0, nodejs_lib_1.transformMapSimple)(f => f.content));
75
+ }
76
+ streamEntries(table, limit) {
77
+ const { bucketName, prefix } = this.getBucketAndPrefix(table);
78
+ const index = prefix.length + 1;
79
+ return this.cfg.storage
80
+ .getFilesStream(bucketName, prefix, { limit })
81
+ .pipe((0, nodejs_lib_1.transformMapSimple)(({ filePath, content }) => [
82
+ filePath.slice(index),
83
+ content,
84
+ ]));
85
+ }
86
+ }
87
+ exports.CommonStorageKeyValueDB = CommonStorageKeyValueDB;
@@ -0,0 +1,28 @@
1
+ /// <reference types="node" />
2
+ import { Readable, Writable } from 'stream';
3
+ import { StringMap } from '@naturalcycles/js-lib';
4
+ import { ReadableTyped } from '@naturalcycles/nodejs-lib';
5
+ import { CommonStorage, CommonStorageGetOptions, FileEntry } from './commonStorage';
6
+ export declare class InMemoryCommonStorage implements CommonStorage {
7
+ /**
8
+ * data[bucketName][filePath] = Buffer
9
+ */
10
+ data: StringMap<StringMap<Buffer>>;
11
+ publicMap: StringMap<StringMap<boolean>>;
12
+ ping(): Promise<void>;
13
+ getBucketNames(): Promise<string[]>;
14
+ getBucketNamesStream(): ReadableTyped<string>;
15
+ fileExists(bucketName: string, filePath: string): Promise<boolean>;
16
+ getFile(bucketName: string, filePath: string): Promise<Buffer | null>;
17
+ saveFile(bucketName: string, filePath: string, content: Buffer): Promise<void>;
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>;
22
+ getFileReadStream(bucketName: string, filePath: string): Readable;
23
+ getFileWriteStream(_bucketName: string, _filePath: string): Writable;
24
+ setFileVisibility(bucketName: string, filePath: string, isPublic: boolean): Promise<void>;
25
+ getFileVisibility(bucketName: string, filePath: string): Promise<boolean>;
26
+ copyFile(fromBucket: string, fromPath: string, toPath: string, toBucket?: string): Promise<void>;
27
+ moveFile(fromBucket: string, fromPath: string, toPath: string, toBucket?: string): Promise<void>;
28
+ }
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.InMemoryCommonStorage = void 0;
4
+ const stream_1 = require("stream");
5
+ class InMemoryCommonStorage {
6
+ constructor() {
7
+ /**
8
+ * data[bucketName][filePath] = Buffer
9
+ */
10
+ this.data = {};
11
+ this.publicMap = {};
12
+ }
13
+ async ping() { }
14
+ async getBucketNames() {
15
+ return Object.keys(this.data);
16
+ }
17
+ getBucketNamesStream() {
18
+ return stream_1.Readable.from(Object.keys(this.data));
19
+ }
20
+ async fileExists(bucketName, filePath) {
21
+ return !!this.data[bucketName]?.[filePath];
22
+ }
23
+ async getFile(bucketName, filePath) {
24
+ return this.data[bucketName]?.[filePath] || null;
25
+ }
26
+ async saveFile(bucketName, filePath, content) {
27
+ var _a;
28
+ (_a = this.data)[bucketName] || (_a[bucketName] = {});
29
+ this.data[bucketName][filePath] = content;
30
+ }
31
+ async deletePath(bucketName, prefix) {
32
+ Object.keys(this.data[bucketName] || {}).forEach(filePath => {
33
+ if (filePath.startsWith(prefix)) {
34
+ delete this.data[bucketName][filePath];
35
+ }
36
+ });
37
+ }
38
+ async getFileNames(bucketName, prefix) {
39
+ return Object.keys(this.data[bucketName] || {}).filter(filePath => filePath.startsWith(prefix));
40
+ }
41
+ getFileNamesStream(bucketName, prefix, opt = {}) {
42
+ return stream_1.Readable.from(Object.keys(this.data[bucketName] || {})
43
+ .filter(filePath => filePath.startsWith(prefix))
44
+ .slice(0, opt.limit));
45
+ }
46
+ getFilesStream(bucketName, prefix, opt = {}) {
47
+ return stream_1.Readable.from(Object.entries(this.data[bucketName] || {})
48
+ .map(([filePath, content]) => ({ filePath, content }))
49
+ .filter(f => f.filePath.startsWith(prefix))
50
+ .slice(0, opt.limit));
51
+ }
52
+ getFileReadStream(bucketName, filePath) {
53
+ return stream_1.Readable.from(this.data[bucketName][filePath]);
54
+ }
55
+ getFileWriteStream(_bucketName, _filePath) {
56
+ throw new Error('Method not implemented.');
57
+ }
58
+ async setFileVisibility(bucketName, filePath, isPublic) {
59
+ var _a;
60
+ (_a = this.publicMap)[bucketName] || (_a[bucketName] = {});
61
+ this.publicMap[bucketName][filePath] = isPublic;
62
+ }
63
+ async getFileVisibility(bucketName, filePath) {
64
+ return !!this.publicMap[bucketName]?.[filePath];
65
+ }
66
+ async copyFile(fromBucket, fromPath, toPath, toBucket) {
67
+ var _a, _b;
68
+ const tob = toBucket || fromBucket;
69
+ (_a = this.data)[fromBucket] || (_a[fromBucket] = {});
70
+ (_b = this.data)[tob] || (_b[tob] = {});
71
+ this.data[tob][toPath] = this.data[fromBucket][fromPath];
72
+ }
73
+ async moveFile(fromBucket, fromPath, toPath, toBucket) {
74
+ var _a, _b;
75
+ const tob = toBucket || fromBucket;
76
+ (_a = this.data)[fromBucket] || (_a[fromBucket] = {});
77
+ (_b = this.data)[tob] || (_b[tob] = {});
78
+ this.data[tob][toPath] = this.data[fromBucket][fromPath];
79
+ delete this.data[fromBucket][fromPath];
80
+ }
81
+ }
82
+ exports.InMemoryCommonStorage = InMemoryCommonStorage;
@@ -0,0 +1,9 @@
1
+ import { CloudStorage, CloudStorageCfg } from './cloudStorage';
2
+ import { CommonStorage, CommonStorageGetOptions } from './commonStorage';
3
+ import { CommonStorageBucket, CommonStorageBucketCfg } from './commonStorageBucket';
4
+ import { CommonStorageKeyValueDB, CommonStorageKeyValueDBCfg } from './commonStorageKeyValueDB';
5
+ import { InMemoryCommonStorage } from './inMemoryCommonStorage';
6
+ import { GCPServiceAccount } from './model';
7
+ import { runCommonStorageTest } from './testing/commonStorageTest';
8
+ export type { CommonStorage, CloudStorageCfg, CommonStorageGetOptions, GCPServiceAccount, CommonStorageBucketCfg, CommonStorageKeyValueDBCfg, };
9
+ export { CloudStorage, CommonStorageKeyValueDB, CommonStorageBucket, InMemoryCommonStorage, runCommonStorageTest, };