@naturalcycles/cloud-storage-lib 1.12.0 → 1.13.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/package.json +2 -2
- package/src/cloudStorage.ts +1 -1
- package/src/commonStorageKeyValueDB.ts +5 -1
- package/src/inMemoryCommonStorage.ts +9 -9
- package/dist/cloudStorage.d.ts +0 -82
- package/dist/cloudStorage.js +0 -248
- package/dist/commonStorage.d.ts +0 -111
- package/dist/commonStorage.js +0 -2
- package/dist/commonStorageBucket.d.ts +0 -69
- package/dist/commonStorageBucket.js +0 -146
- package/dist/commonStorageKeyValueDB.d.ts +0 -33
- package/dist/commonStorageKeyValueDB.js +0 -81
- package/dist/inMemoryCommonStorage.d.ts +0 -34
- package/dist/inMemoryCommonStorage.js +0 -131
- package/dist/index.d.ts +0 -7
- package/dist/index.js +0 -10
- package/dist/model.d.ts +0 -5
- package/dist/model.js +0 -2
- package/dist/testing/commonStorageTest.d.ts +0 -5
- package/dist/testing/commonStorageTest.js +0 -117
package/package.json
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"@naturalcycles/nodejs-lib": "^13.1.0"
|
|
11
11
|
},
|
|
12
12
|
"devDependencies": {
|
|
13
|
-
"@naturalcycles/dev-lib": "^
|
|
13
|
+
"@naturalcycles/dev-lib": "^15.18.0",
|
|
14
14
|
"@types/node": "^20.4.6",
|
|
15
15
|
"firebase-admin": "^12.0.0",
|
|
16
16
|
"jest": "^29.1.2"
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"engines": {
|
|
36
36
|
"node": ">=18.12.0"
|
|
37
37
|
},
|
|
38
|
-
"version": "1.
|
|
38
|
+
"version": "1.13.0",
|
|
39
39
|
"description": "CommonStorage implementation based on Google Cloud Storage",
|
|
40
40
|
"author": "Natural Cycles Team",
|
|
41
41
|
"license": "MIT"
|
package/src/cloudStorage.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CommonDBCreateOptions, CommonKeyValueDB, KeyValueDBTuple } from '@naturalcycles/db-lib'
|
|
2
|
-
import { pMap, StringMap } from '@naturalcycles/js-lib'
|
|
2
|
+
import { AppError, pMap, StringMap } from '@naturalcycles/js-lib'
|
|
3
3
|
import { ReadableTyped } from '@naturalcycles/nodejs-lib'
|
|
4
4
|
import { CommonStorage } from './commonStorage'
|
|
5
5
|
|
|
@@ -100,4 +100,8 @@ export class CommonStorageKeyValueDB implements CommonKeyValueDB {
|
|
|
100
100
|
|
|
101
101
|
return (await this.cfg.storage.getFileNames(bucketName, { prefix })).length
|
|
102
102
|
}
|
|
103
|
+
|
|
104
|
+
async increment(_table: string, _id: string, _by?: number): Promise<number> {
|
|
105
|
+
throw new AppError('CommonStorageKeyValueDB.increment() is not implemented')
|
|
106
|
+
}
|
|
103
107
|
}
|
|
@@ -45,7 +45,7 @@ export class InMemoryCommonStorage implements CommonStorage {
|
|
|
45
45
|
|
|
46
46
|
async saveFile(bucketName: string, filePath: string, content: Buffer): Promise<void> {
|
|
47
47
|
this.data[bucketName] ||= {}
|
|
48
|
-
this.data[bucketName]
|
|
48
|
+
this.data[bucketName][filePath] = content
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
async deletePath(bucketName: string, prefix: string): Promise<void> {
|
|
@@ -112,12 +112,12 @@ export class InMemoryCommonStorage implements CommonStorage {
|
|
|
112
112
|
bucketFilePath: string,
|
|
113
113
|
): Promise<void> {
|
|
114
114
|
this.data[bucketName] ||= {}
|
|
115
|
-
this.data[bucketName]
|
|
115
|
+
this.data[bucketName][bucketFilePath] = await fs2.readBufferAsync(localFilePath)
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
async setFileVisibility(bucketName: string, filePath: string, isPublic: boolean): Promise<void> {
|
|
119
119
|
this.publicMap[bucketName] ||= {}
|
|
120
|
-
this.publicMap[bucketName]
|
|
120
|
+
this.publicMap[bucketName][filePath] = isPublic
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
async getFileVisibility(bucketName: string, filePath: string): Promise<boolean> {
|
|
@@ -133,7 +133,7 @@ export class InMemoryCommonStorage implements CommonStorage {
|
|
|
133
133
|
const tob = toBucket || fromBucket
|
|
134
134
|
this.data[fromBucket] ||= {}
|
|
135
135
|
this.data[tob] ||= {}
|
|
136
|
-
this.data[tob]
|
|
136
|
+
this.data[tob][toPath] = this.data[fromBucket][fromPath]
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
async moveFile(
|
|
@@ -145,8 +145,8 @@ export class InMemoryCommonStorage implements CommonStorage {
|
|
|
145
145
|
const tob = toBucket || fromBucket
|
|
146
146
|
this.data[fromBucket] ||= {}
|
|
147
147
|
this.data[tob] ||= {}
|
|
148
|
-
this.data[tob]
|
|
149
|
-
delete this.data[fromBucket]
|
|
148
|
+
this.data[tob][toPath] = this.data[fromBucket][fromPath]
|
|
149
|
+
delete this.data[fromBucket][fromPath]
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
async movePath(
|
|
@@ -159,7 +159,7 @@ export class InMemoryCommonStorage implements CommonStorage {
|
|
|
159
159
|
this.data[fromBucket] ||= {}
|
|
160
160
|
this.data[tob] ||= {}
|
|
161
161
|
|
|
162
|
-
_stringMapEntries(this.data[fromBucket]
|
|
162
|
+
_stringMapEntries(this.data[fromBucket]).forEach(([filePath, v]) => {
|
|
163
163
|
if (!filePath.startsWith(fromPrefix)) return
|
|
164
164
|
this.data[tob]![toPrefix + filePath.slice(fromPrefix.length)] = v
|
|
165
165
|
delete this.data[fromBucket]![filePath]
|
|
@@ -185,7 +185,7 @@ export class InMemoryCommonStorage implements CommonStorage {
|
|
|
185
185
|
if (!this.data[bucketName]) return
|
|
186
186
|
const tob = toBucket || bucketName
|
|
187
187
|
this.data[tob] ||= {}
|
|
188
|
-
this.data[tob]
|
|
188
|
+
this.data[tob][toPath] = Buffer.concat(
|
|
189
189
|
filePaths.map(p => this.data[bucketName]![p]).filter(_isTruthy),
|
|
190
190
|
)
|
|
191
191
|
|
|
@@ -201,6 +201,6 @@ export class InMemoryCommonStorage implements CommonStorage {
|
|
|
201
201
|
const buf = this.data[bucketName]?.[filePath]
|
|
202
202
|
_assert(buf, `getSignedUrl file not found: ${bucketName}/${filePath}`)
|
|
203
203
|
const signature = md5(buf)
|
|
204
|
-
return `https://testurl.com/${bucketName}/${filePath}?expires=${localTime(expires).unix
|
|
204
|
+
return `https://testurl.com/${bucketName}/${filePath}?expires=${localTime(expires).unix}&signature=${signature}`
|
|
205
205
|
}
|
|
206
206
|
}
|
package/dist/cloudStorage.d.ts
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
import type { Storage, StorageOptions } from '@google-cloud/storage';
|
|
3
|
-
import { CommonLogger, LocalTimeInput } from '@naturalcycles/js-lib';
|
|
4
|
-
import type { ReadableBinary, ReadableTyped, WritableBinary } from '@naturalcycles/nodejs-lib';
|
|
5
|
-
import type { CommonStorage, CommonStorageGetOptions, FileEntry } from './commonStorage';
|
|
6
|
-
import type { GCPServiceAccount } from './model';
|
|
7
|
-
export { type Storage, type StorageOptions, };
|
|
8
|
-
/**
|
|
9
|
-
* This object is intentionally made to NOT extend StorageOptions,
|
|
10
|
-
* because StorageOptions is complicated and provides just too many ways
|
|
11
|
-
* to configure credentials.
|
|
12
|
-
*
|
|
13
|
-
* Here we define the minimum simple set of credentials needed.
|
|
14
|
-
* All of these properties are available from the "service account" json file
|
|
15
|
-
* (either personal one or non-personal).
|
|
16
|
-
*/
|
|
17
|
-
export interface CloudStorageCfg {
|
|
18
|
-
/**
|
|
19
|
-
* Default is console
|
|
20
|
-
*/
|
|
21
|
-
logger?: CommonLogger;
|
|
22
|
-
/**
|
|
23
|
-
* Pass true for extra debugging
|
|
24
|
-
*/
|
|
25
|
-
debug?: boolean;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* CloudStorage implementation of CommonStorage API.
|
|
29
|
-
*
|
|
30
|
-
* API: https://googleapis.dev/nodejs/storage/latest/index.html
|
|
31
|
-
*/
|
|
32
|
-
export declare class CloudStorage implements CommonStorage {
|
|
33
|
-
storage: Storage;
|
|
34
|
-
private constructor();
|
|
35
|
-
cfg: CloudStorageCfg & {
|
|
36
|
-
logger: CommonLogger;
|
|
37
|
-
};
|
|
38
|
-
static createFromGCPServiceAccount(credentials?: GCPServiceAccount, cfg?: CloudStorageCfg): CloudStorage;
|
|
39
|
-
static createFromStorageOptions(storageOptions?: StorageOptions, cfg?: CloudStorageCfg): CloudStorage;
|
|
40
|
-
/**
|
|
41
|
-
* Passing the pre-created Storage allows to instantiate it from both
|
|
42
|
-
* GCP Storage and FirebaseStorage.
|
|
43
|
-
*/
|
|
44
|
-
static createFromStorage(storage: Storage, cfg?: CloudStorageCfg): CloudStorage;
|
|
45
|
-
ping(bucketName?: string): Promise<void>;
|
|
46
|
-
deletePath(bucketName: string, prefix: string): Promise<void>;
|
|
47
|
-
deletePaths(bucketName: string, prefixes: string[]): Promise<void>;
|
|
48
|
-
fileExists(bucketName: string, filePath: string): Promise<boolean>;
|
|
49
|
-
getFileNames(bucketName: string, opt?: CommonStorageGetOptions): Promise<string[]>;
|
|
50
|
-
getFileNamesStream(bucketName: string, opt?: CommonStorageGetOptions): ReadableTyped<string>;
|
|
51
|
-
getFilesStream(bucketName: string, opt?: CommonStorageGetOptions): ReadableTyped<FileEntry>;
|
|
52
|
-
getFile(bucketName: string, filePath: string): Promise<Buffer | null>;
|
|
53
|
-
/**
|
|
54
|
-
* Returns a Readable that is NOT object mode,
|
|
55
|
-
* so you can e.g pipe it to fs.createWriteStream()
|
|
56
|
-
*/
|
|
57
|
-
getFileReadStream(bucketName: string, filePath: string): ReadableBinary;
|
|
58
|
-
saveFile(bucketName: string, filePath: string, content: Buffer): Promise<void>;
|
|
59
|
-
getFileWriteStream(bucketName: string, filePath: string): WritableBinary;
|
|
60
|
-
uploadFile(localFilePath: string, bucketName: string, bucketFilePath: string): Promise<void>;
|
|
61
|
-
setFileVisibility(bucketName: string, filePath: string, isPublic: boolean): Promise<void>;
|
|
62
|
-
getFileVisibility(bucketName: string, filePath: string): Promise<boolean>;
|
|
63
|
-
copyFile(fromBucket: string, fromPath: string, toPath: string, toBucket?: string): Promise<void>;
|
|
64
|
-
moveFile(fromBucket: string, fromPath: string, toPath: string, toBucket?: string): Promise<void>;
|
|
65
|
-
movePath(fromBucket: string, fromPrefix: string, toPrefix: string, toBucket?: string): Promise<void>;
|
|
66
|
-
deleteFiles(bucketName: string, filePaths: string[]): Promise<void>;
|
|
67
|
-
combineFiles(bucketName: string, filePaths: string[], toPath: string, toBucket?: string, currentRecursionDepth?: number): Promise<void>;
|
|
68
|
-
combine(bucketName: string, prefix: string, toPath: string, toBucket?: string): Promise<void>;
|
|
69
|
-
/**
|
|
70
|
-
* Acquires a "signed url", which allows bearer to use it to download ('read') the file.
|
|
71
|
-
*
|
|
72
|
-
* expires: 'v4' supports maximum duration of 7 days from now.
|
|
73
|
-
*
|
|
74
|
-
* @experimental - not tested yet
|
|
75
|
-
*/
|
|
76
|
-
getSignedUrl(bucketName: string, filePath: string, expires: LocalTimeInput): Promise<string>;
|
|
77
|
-
/**
|
|
78
|
-
* Returns SKIP if fileName is a folder.
|
|
79
|
-
* If !fullPaths - strip away the folder prefix.
|
|
80
|
-
*/
|
|
81
|
-
private normalizeFilename;
|
|
82
|
-
}
|
package/dist/cloudStorage.js
DELETED
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CloudStorage = void 0;
|
|
4
|
-
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
5
|
-
const MAX_RECURSION_DEPTH = 10;
|
|
6
|
-
const BATCH_SIZE = 32;
|
|
7
|
-
/**
|
|
8
|
-
* CloudStorage implementation of CommonStorage API.
|
|
9
|
-
*
|
|
10
|
-
* API: https://googleapis.dev/nodejs/storage/latest/index.html
|
|
11
|
-
*/
|
|
12
|
-
class CloudStorage {
|
|
13
|
-
constructor(storage, cfg = {}) {
|
|
14
|
-
this.storage = storage;
|
|
15
|
-
this.cfg = {
|
|
16
|
-
logger: console,
|
|
17
|
-
...cfg,
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
static createFromGCPServiceAccount(credentials, cfg) {
|
|
21
|
-
const storageLib = require('@google-cloud/storage');
|
|
22
|
-
const storage = new storageLib.Storage({
|
|
23
|
-
credentials,
|
|
24
|
-
// Explicitly passing it here to fix this error:
|
|
25
|
-
// Error: Unable to detect a Project Id in the current environment.
|
|
26
|
-
// To learn more about authentication and Google APIs, visit:
|
|
27
|
-
// https://cloud.google.com/docs/authentication/getting-started
|
|
28
|
-
// at /root/repo/node_modules/google-auth-library/build/src/auth/googleauth.js:95:31
|
|
29
|
-
projectId: credentials?.project_id,
|
|
30
|
-
});
|
|
31
|
-
return new CloudStorage(storage, cfg);
|
|
32
|
-
}
|
|
33
|
-
static createFromStorageOptions(storageOptions, cfg) {
|
|
34
|
-
const storageLib = require('@google-cloud/storage');
|
|
35
|
-
const storage = new storageLib.Storage(storageOptions);
|
|
36
|
-
return new CloudStorage(storage, cfg);
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Passing the pre-created Storage allows to instantiate it from both
|
|
40
|
-
* GCP Storage and FirebaseStorage.
|
|
41
|
-
*/
|
|
42
|
-
static createFromStorage(storage, cfg) {
|
|
43
|
-
return new CloudStorage(storage, cfg);
|
|
44
|
-
}
|
|
45
|
-
async ping(bucketName) {
|
|
46
|
-
await this.storage.bucket(bucketName || 'non-existing-for-sure').exists();
|
|
47
|
-
}
|
|
48
|
-
async deletePath(bucketName, prefix) {
|
|
49
|
-
await this.deletePaths(bucketName, [prefix]);
|
|
50
|
-
}
|
|
51
|
-
async deletePaths(bucketName, prefixes) {
|
|
52
|
-
const bucket = this.storage.bucket(bucketName);
|
|
53
|
-
await (0, js_lib_1.pMap)(prefixes, async (prefix) => {
|
|
54
|
-
await bucket.deleteFiles({
|
|
55
|
-
prefix,
|
|
56
|
-
// to keep going in case error occurs, similar to THROW_AGGREGATED
|
|
57
|
-
force: true,
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
async fileExists(bucketName, filePath) {
|
|
62
|
-
const [exists] = await this.storage.bucket(bucketName).file(filePath).exists();
|
|
63
|
-
return exists;
|
|
64
|
-
}
|
|
65
|
-
async getFileNames(bucketName, opt = {}) {
|
|
66
|
-
const { prefix, fullPaths = true } = opt;
|
|
67
|
-
const [files] = await this.storage.bucket(bucketName).getFiles({
|
|
68
|
-
prefix,
|
|
69
|
-
});
|
|
70
|
-
if (fullPaths) {
|
|
71
|
-
// Paths that end with `/` are "folders", which are "virtual" in CloudStorage
|
|
72
|
-
// It doesn't make sense to return or do anything with them
|
|
73
|
-
return files.map(f => f.name).filter(s => !s.endsWith('/'));
|
|
74
|
-
}
|
|
75
|
-
return files.map(f => (0, js_lib_1._substringAfterLast)(f.name, '/')).filter(Boolean);
|
|
76
|
-
}
|
|
77
|
-
getFileNamesStream(bucketName, opt = {}) {
|
|
78
|
-
const { prefix, fullPaths = true } = opt;
|
|
79
|
-
return this.storage.bucket(bucketName).getFilesStream({
|
|
80
|
-
prefix,
|
|
81
|
-
maxResults: opt.limit || undefined,
|
|
82
|
-
}).flatMap(f => {
|
|
83
|
-
const r = this.normalizeFilename(f.name, fullPaths);
|
|
84
|
-
if (r === js_lib_1.SKIP)
|
|
85
|
-
return [];
|
|
86
|
-
return [r];
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
getFilesStream(bucketName, opt = {}) {
|
|
90
|
-
const { prefix, fullPaths = true } = opt;
|
|
91
|
-
return this.storage.bucket(bucketName).getFilesStream({
|
|
92
|
-
prefix,
|
|
93
|
-
maxResults: opt.limit || undefined,
|
|
94
|
-
}).flatMap(async (f) => {
|
|
95
|
-
const filePath = this.normalizeFilename(f.name, fullPaths);
|
|
96
|
-
if (filePath === js_lib_1.SKIP)
|
|
97
|
-
return [];
|
|
98
|
-
const [content] = await f.download();
|
|
99
|
-
return [{ filePath, content }];
|
|
100
|
-
}, {
|
|
101
|
-
concurrency: 16,
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
async getFile(bucketName, filePath) {
|
|
105
|
-
const [buf] = await this.storage
|
|
106
|
-
.bucket(bucketName)
|
|
107
|
-
.file(filePath)
|
|
108
|
-
.download()
|
|
109
|
-
.catch(err => {
|
|
110
|
-
if (err?.code === 404)
|
|
111
|
-
return [null]; // file not found
|
|
112
|
-
throw err; // rethrow otherwise
|
|
113
|
-
});
|
|
114
|
-
return buf;
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* Returns a Readable that is NOT object mode,
|
|
118
|
-
* so you can e.g pipe it to fs.createWriteStream()
|
|
119
|
-
*/
|
|
120
|
-
getFileReadStream(bucketName, filePath) {
|
|
121
|
-
return this.storage.bucket(bucketName).file(filePath).createReadStream();
|
|
122
|
-
}
|
|
123
|
-
async saveFile(bucketName, filePath, content) {
|
|
124
|
-
await this.storage.bucket(bucketName).file(filePath).save(content);
|
|
125
|
-
}
|
|
126
|
-
getFileWriteStream(bucketName, filePath) {
|
|
127
|
-
return this.storage.bucket(bucketName).file(filePath).createWriteStream();
|
|
128
|
-
}
|
|
129
|
-
async uploadFile(localFilePath, bucketName, bucketFilePath) {
|
|
130
|
-
await this.storage.bucket(bucketName).upload(localFilePath, {
|
|
131
|
-
destination: bucketFilePath,
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
async setFileVisibility(bucketName, filePath, isPublic) {
|
|
135
|
-
await this.storage.bucket(bucketName).file(filePath)[isPublic ? 'makePublic' : 'makePrivate']();
|
|
136
|
-
}
|
|
137
|
-
async getFileVisibility(bucketName, filePath) {
|
|
138
|
-
const [isPublic] = await this.storage.bucket(bucketName).file(filePath).isPublic();
|
|
139
|
-
return isPublic;
|
|
140
|
-
}
|
|
141
|
-
async copyFile(fromBucket, fromPath, toPath, toBucket) {
|
|
142
|
-
await this.storage
|
|
143
|
-
.bucket(fromBucket)
|
|
144
|
-
.file(fromPath)
|
|
145
|
-
.copy(this.storage.bucket(toBucket || fromBucket).file(toPath));
|
|
146
|
-
}
|
|
147
|
-
async moveFile(fromBucket, fromPath, toPath, toBucket) {
|
|
148
|
-
await this.storage
|
|
149
|
-
.bucket(fromBucket)
|
|
150
|
-
.file(fromPath)
|
|
151
|
-
.move(this.storage.bucket(toBucket || fromBucket).file(toPath));
|
|
152
|
-
}
|
|
153
|
-
async movePath(fromBucket, fromPrefix, toPrefix, toBucket) {
|
|
154
|
-
(0, js_lib_1._assert)(fromPrefix.endsWith('/'), 'fromPrefix should end with `/`');
|
|
155
|
-
(0, js_lib_1._assert)(toPrefix.endsWith('/'), 'toPrefix should end with `/`');
|
|
156
|
-
await this.storage
|
|
157
|
-
.bucket(fromBucket)
|
|
158
|
-
.getFilesStream({
|
|
159
|
-
prefix: fromPrefix,
|
|
160
|
-
})
|
|
161
|
-
.forEach(async (file) => {
|
|
162
|
-
const { name } = file;
|
|
163
|
-
const newName = toPrefix + name.slice(fromPrefix.length);
|
|
164
|
-
await file.move(this.storage.bucket(toBucket || fromBucket).file(newName));
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
async deleteFiles(bucketName, filePaths) {
|
|
168
|
-
await (0, js_lib_1.pMap)(filePaths, async (filePath) => {
|
|
169
|
-
await this.storage.bucket(bucketName).file(filePath).delete();
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
async combineFiles(bucketName, filePaths, toPath, toBucket, currentRecursionDepth = 0) {
|
|
173
|
-
(0, js_lib_1._assert)(currentRecursionDepth <= MAX_RECURSION_DEPTH, `combineFiles reached max recursion depth of ${MAX_RECURSION_DEPTH}`);
|
|
174
|
-
const { logger, debug } = this.cfg;
|
|
175
|
-
if (filePaths.length === 0) {
|
|
176
|
-
if (debug) {
|
|
177
|
-
logger.log(`[${currentRecursionDepth}] Nothing to compose, returning early!`);
|
|
178
|
-
}
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
if (debug) {
|
|
182
|
-
logger.log(`[${currentRecursionDepth}] Will compose ${filePaths.length} files, by batches of ${BATCH_SIZE}`);
|
|
183
|
-
}
|
|
184
|
-
const intermediateFiles = [];
|
|
185
|
-
if (filePaths.length <= BATCH_SIZE) {
|
|
186
|
-
await this.storage
|
|
187
|
-
.bucket(bucketName)
|
|
188
|
-
.combine(filePaths, this.storage.bucket(toBucket || bucketName).file(toPath));
|
|
189
|
-
if (debug) {
|
|
190
|
-
logger.log(`[${currentRecursionDepth}] Composed into ${toPath}!`);
|
|
191
|
-
}
|
|
192
|
-
await this.deleteFiles(bucketName, filePaths);
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
const started = Date.now();
|
|
196
|
-
await (0, js_lib_1.pMap)((0, js_lib_1._chunk)(filePaths, BATCH_SIZE), async (fileBatch, i) => {
|
|
197
|
-
if (debug) {
|
|
198
|
-
logger.log(`[${currentRecursionDepth}] Composing batch ${i + 1}...`);
|
|
199
|
-
}
|
|
200
|
-
const intermediateFile = `temp_${currentRecursionDepth}_${i}`;
|
|
201
|
-
await this.storage
|
|
202
|
-
.bucket(bucketName)
|
|
203
|
-
.combine(fileBatch, this.storage.bucket(toBucket || bucketName).file(intermediateFile));
|
|
204
|
-
intermediateFiles.push(intermediateFile);
|
|
205
|
-
await this.deleteFiles(bucketName, fileBatch);
|
|
206
|
-
});
|
|
207
|
-
if (debug) {
|
|
208
|
-
logger.log(`[${currentRecursionDepth}] Batch composed into ${intermediateFiles.length} files, in ${(0, js_lib_1._since)(started)}`);
|
|
209
|
-
}
|
|
210
|
-
await this.combineFiles(toBucket || bucketName, intermediateFiles, toPath, toBucket, currentRecursionDepth + 1);
|
|
211
|
-
}
|
|
212
|
-
async combine(bucketName, prefix, toPath, toBucket) {
|
|
213
|
-
const filePaths = await this.getFileNames(bucketName, { prefix });
|
|
214
|
-
await this.combineFiles(bucketName, filePaths, toPath, toBucket);
|
|
215
|
-
}
|
|
216
|
-
/**
|
|
217
|
-
* Acquires a "signed url", which allows bearer to use it to download ('read') the file.
|
|
218
|
-
*
|
|
219
|
-
* expires: 'v4' supports maximum duration of 7 days from now.
|
|
220
|
-
*
|
|
221
|
-
* @experimental - not tested yet
|
|
222
|
-
*/
|
|
223
|
-
async getSignedUrl(bucketName, filePath, expires) {
|
|
224
|
-
const [url] = await this.storage
|
|
225
|
-
.bucket(bucketName)
|
|
226
|
-
.file(filePath)
|
|
227
|
-
.getSignedUrl({
|
|
228
|
-
action: 'read',
|
|
229
|
-
version: 'v4',
|
|
230
|
-
expires: (0, js_lib_1.localTime)(expires).unixMillis(),
|
|
231
|
-
});
|
|
232
|
-
return url;
|
|
233
|
-
}
|
|
234
|
-
/**
|
|
235
|
-
* Returns SKIP if fileName is a folder.
|
|
236
|
-
* If !fullPaths - strip away the folder prefix.
|
|
237
|
-
*/
|
|
238
|
-
normalizeFilename(fileName, fullPaths) {
|
|
239
|
-
if (fullPaths) {
|
|
240
|
-
if (fileName.endsWith('/'))
|
|
241
|
-
return js_lib_1.SKIP; // skip folders
|
|
242
|
-
return fileName;
|
|
243
|
-
}
|
|
244
|
-
fileName = (0, js_lib_1._substringAfterLast)(fileName, '/');
|
|
245
|
-
return fileName || js_lib_1.SKIP; // skip folders
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
exports.CloudStorage = CloudStorage;
|
package/dist/commonStorage.d.ts
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
import type { LocalTimeInput } from '@naturalcycles/js-lib';
|
|
3
|
-
import type { ReadableBinary, ReadableTyped, WritableBinary } 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
|
-
* Defaults to true.
|
|
15
|
-
* Set to false to return file names instead of full paths.
|
|
16
|
-
*/
|
|
17
|
-
fullPaths?: boolean;
|
|
18
|
-
/**
|
|
19
|
-
* Limits the number of results.
|
|
20
|
-
*
|
|
21
|
-
* By default it's unlimited.
|
|
22
|
-
*
|
|
23
|
-
* 0 is treated as `undefined`.
|
|
24
|
-
*/
|
|
25
|
-
limit?: number;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Common denominator interface for File Storage.
|
|
29
|
-
* Modelled after GCP Cloud Storage, Firebase Storage.
|
|
30
|
-
*
|
|
31
|
-
* Uses the concept of Bucket (identified by string name) and Path within the Bucket.
|
|
32
|
-
*
|
|
33
|
-
* Path MUST NOT start with a slash !
|
|
34
|
-
*
|
|
35
|
-
* Similarly to CommonDB, Bucket is like a Table, and Path is like an `id`.
|
|
36
|
-
*/
|
|
37
|
-
export interface CommonStorage {
|
|
38
|
-
/**
|
|
39
|
-
* Ensure that the credentials are correct and the connection is working.
|
|
40
|
-
* Idempotent.
|
|
41
|
-
*
|
|
42
|
-
* Pass `bucketName` in case you only have permissions to operate on that bucket.
|
|
43
|
-
*/
|
|
44
|
-
ping: (bucketName?: string) => Promise<void>;
|
|
45
|
-
/**
|
|
46
|
-
* Creates a new bucket by given name.
|
|
47
|
-
* todo: check what to do if it already exists
|
|
48
|
-
*/
|
|
49
|
-
fileExists: (bucketName: string, filePath: string) => Promise<boolean>;
|
|
50
|
-
getFile: (bucketName: string, filePath: string) => Promise<Buffer | null>;
|
|
51
|
-
saveFile: (bucketName: string, filePath: string, content: Buffer) => Promise<void>;
|
|
52
|
-
/**
|
|
53
|
-
* Should recursively delete all files in a folder, if path is a folder.
|
|
54
|
-
*/
|
|
55
|
-
deletePath: (bucketName: string, prefix: string) => Promise<void>;
|
|
56
|
-
deletePaths: (bucketName: string, prefixes: string[]) => Promise<void>;
|
|
57
|
-
/**
|
|
58
|
-
* Should delete all files by their paths.
|
|
59
|
-
*/
|
|
60
|
-
deleteFiles: (bucketName: string, filePaths: string[]) => Promise<void>;
|
|
61
|
-
/**
|
|
62
|
-
* Returns an array of strings which are file paths.
|
|
63
|
-
* Files that are not found by the path are not present in the map.
|
|
64
|
-
*
|
|
65
|
-
* Second argument is called `prefix` (same as `path`) to explain how
|
|
66
|
-
* listing works (it filters all files by `startsWith`). Also, to match
|
|
67
|
-
* GCP Cloud Storage API.
|
|
68
|
-
*
|
|
69
|
-
* Important difference between `prefix` and `path` is that `prefix` will
|
|
70
|
-
* return all files from sub-directories too!
|
|
71
|
-
*/
|
|
72
|
-
getFileNames: (bucketName: string, opt?: CommonStorageGetOptions) => Promise<string[]>;
|
|
73
|
-
getFileNamesStream: (bucketName: string, opt?: CommonStorageGetOptions) => ReadableTyped<string>;
|
|
74
|
-
getFilesStream: (bucketName: string, opt?: CommonStorageGetOptions) => ReadableTyped<FileEntry>;
|
|
75
|
-
getFileReadStream: (bucketName: string, filePath: string) => ReadableBinary;
|
|
76
|
-
getFileWriteStream: (bucketName: string, filePath: string) => WritableBinary;
|
|
77
|
-
/**
|
|
78
|
-
* Upload local file to the bucket (by streaming it).
|
|
79
|
-
*/
|
|
80
|
-
uploadFile: (localFilePath: string, bucketName: string, bucketFilePath: string) => Promise<void>;
|
|
81
|
-
setFileVisibility: (bucketName: string, filePath: string, isPublic: boolean) => Promise<void>;
|
|
82
|
-
getFileVisibility: (bucketName: string, filePath: string) => Promise<boolean>;
|
|
83
|
-
copyFile: (fromBucket: string, fromPath: string, toPath: string, toBucket?: string) => Promise<void>;
|
|
84
|
-
moveFile: (fromBucket: string, fromPath: string, toPath: string, toBucket?: string) => Promise<void>;
|
|
85
|
-
/**
|
|
86
|
-
* Allows to move "directory" with all its contents.
|
|
87
|
-
*
|
|
88
|
-
* Prefixes should end with `/` to work properly,
|
|
89
|
-
* otherwise some folder that starts with the same prefix will be included.
|
|
90
|
-
*/
|
|
91
|
-
movePath: (fromBucket: string, fromPrefix: string, toPrefix: string, toBucket?: string) => Promise<void>;
|
|
92
|
-
/**
|
|
93
|
-
* Combine (compose) multiple input files into a single output file.
|
|
94
|
-
* Should support unlimited number of input files, using recursive algorithm if necessary.
|
|
95
|
-
*
|
|
96
|
-
* After the output file is created, all input files should be deleted.
|
|
97
|
-
*
|
|
98
|
-
* @experimental
|
|
99
|
-
*/
|
|
100
|
-
combineFiles: (bucketName: string, filePaths: string[], toPath: string, toBucket?: string) => Promise<void>;
|
|
101
|
-
/**
|
|
102
|
-
* Like `combineFiles`, but for a `prefix`.
|
|
103
|
-
*/
|
|
104
|
-
combine: (bucketName: string, prefix: string, toPath: string, toBucket?: string) => Promise<void>;
|
|
105
|
-
/**
|
|
106
|
-
* Acquire a "signed url", which allows bearer to use it to download ('read') the file.
|
|
107
|
-
*
|
|
108
|
-
* @experimental
|
|
109
|
-
*/
|
|
110
|
-
getSignedUrl: (bucketName: string, filePath: string, expires: LocalTimeInput) => Promise<string>;
|
|
111
|
-
}
|
package/dist/commonStorage.js
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
/// <reference types="node" />
|
|
3
|
-
import { Readable, Writable } from 'node:stream';
|
|
4
|
-
import { ReadableTyped } from '@naturalcycles/nodejs-lib';
|
|
5
|
-
import { CommonStorage, CommonStorageGetOptions, FileEntry } from './commonStorage';
|
|
6
|
-
export interface CommonStorageBucketCfg {
|
|
7
|
-
storage: CommonStorage;
|
|
8
|
-
bucketName: string;
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Convenience wrapper around CommonStorage for a given Bucket.
|
|
12
|
-
*
|
|
13
|
-
* Similar to what CommonDao is to CommonDB.
|
|
14
|
-
*/
|
|
15
|
-
export declare class CommonStorageBucket {
|
|
16
|
-
cfg: CommonStorageBucketCfg;
|
|
17
|
-
constructor(cfg: CommonStorageBucketCfg);
|
|
18
|
-
ping(bucketName?: string): Promise<void>;
|
|
19
|
-
fileExists(filePath: string): Promise<boolean>;
|
|
20
|
-
getFile(filePath: string): Promise<Buffer | null>;
|
|
21
|
-
getFileAsString(filePath: string): Promise<string | null>;
|
|
22
|
-
getFileAsJson<T = any>(filePath: string): Promise<T | null>;
|
|
23
|
-
requireFile(filePath: string): Promise<Buffer>;
|
|
24
|
-
requireFileAsString(filePath: string): Promise<string>;
|
|
25
|
-
requireFileAsJson<T = any>(filePath: string): Promise<T>;
|
|
26
|
-
private throwRequiredError;
|
|
27
|
-
getFileContents(paths: string[]): Promise<Buffer[]>;
|
|
28
|
-
getFileContentsAsJson<T = any>(paths: string[]): Promise<T[]>;
|
|
29
|
-
getFileEntries(paths: string[]): Promise<FileEntry[]>;
|
|
30
|
-
getFileEntriesAsJson<T = any>(paths: string[]): Promise<{
|
|
31
|
-
filePath: string;
|
|
32
|
-
content: T;
|
|
33
|
-
}[]>;
|
|
34
|
-
saveFile(filePath: string, content: Buffer): Promise<void>;
|
|
35
|
-
/**
|
|
36
|
-
* Convenience method that does:
|
|
37
|
-
* await saveFile
|
|
38
|
-
* await setFileVisibility
|
|
39
|
-
*/
|
|
40
|
-
savePublicFile(filePath: string, content: Buffer): Promise<void>;
|
|
41
|
-
saveStringFile(filePath: string, content: string): Promise<void>;
|
|
42
|
-
saveJsonFile(filePath: string, content: any): Promise<void>;
|
|
43
|
-
saveFiles(entries: FileEntry[]): Promise<void>;
|
|
44
|
-
/**
|
|
45
|
-
* Should recursively delete all files in a folder, if path is a folder.
|
|
46
|
-
*/
|
|
47
|
-
deletePath(prefix: string): Promise<void>;
|
|
48
|
-
deletePaths(prefixes: string[]): Promise<void>;
|
|
49
|
-
/**
|
|
50
|
-
* Returns an array of strings which are file paths.
|
|
51
|
-
* Files that are not found by the path are not present in the map.
|
|
52
|
-
*
|
|
53
|
-
* Second argument is called `prefix` (same as `path`) to explain how
|
|
54
|
-
* listing works (it filters all files by `startsWith`). Also, to match
|
|
55
|
-
* GCP Cloud Storage API.
|
|
56
|
-
*
|
|
57
|
-
* Important difference between `prefix` and `path` is that `prefix` will
|
|
58
|
-
* return all files from sub-directories too!
|
|
59
|
-
*/
|
|
60
|
-
getFileNames(opt?: CommonStorageGetOptions): Promise<string[]>;
|
|
61
|
-
getFileNamesStream(opt?: CommonStorageGetOptions): ReadableTyped<string>;
|
|
62
|
-
getFilesStream(opt?: CommonStorageGetOptions): ReadableTyped<FileEntry>;
|
|
63
|
-
getFileReadStream(filePath: string): Readable;
|
|
64
|
-
getFileWriteStream(filePath: string): Writable;
|
|
65
|
-
setFileVisibility(filePath: string, isPublic: boolean): Promise<void>;
|
|
66
|
-
getFileVisibility(filePath: string): Promise<boolean>;
|
|
67
|
-
copyFile(fromPath: string, toPath: string, toBucket?: string): Promise<void>;
|
|
68
|
-
moveFile(fromPath: string, toPath: string, toBucket?: string): Promise<void>;
|
|
69
|
-
}
|
|
@@ -1,146 +0,0 @@
|
|
|
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
|
-
/**
|
|
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
|
-
}
|
|
91
|
-
async saveFiles(entries) {
|
|
92
|
-
await (0, js_lib_1.pMap)(entries, async (f) => {
|
|
93
|
-
await this.cfg.storage.saveFile(this.cfg.bucketName, f.filePath, f.content);
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* Should recursively delete all files in a folder, if path is a folder.
|
|
98
|
-
*/
|
|
99
|
-
async deletePath(prefix) {
|
|
100
|
-
return await this.cfg.storage.deletePath(this.cfg.bucketName, prefix);
|
|
101
|
-
}
|
|
102
|
-
async deletePaths(prefixes) {
|
|
103
|
-
await (0, js_lib_1.pMap)(prefixes, async (prefix) => {
|
|
104
|
-
return await this.cfg.storage.deletePath(this.cfg.bucketName, prefix);
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Returns an array of strings which are file paths.
|
|
109
|
-
* Files that are not found by the path are not present in the map.
|
|
110
|
-
*
|
|
111
|
-
* Second argument is called `prefix` (same as `path`) to explain how
|
|
112
|
-
* listing works (it filters all files by `startsWith`). Also, to match
|
|
113
|
-
* GCP Cloud Storage API.
|
|
114
|
-
*
|
|
115
|
-
* Important difference between `prefix` and `path` is that `prefix` will
|
|
116
|
-
* return all files from sub-directories too!
|
|
117
|
-
*/
|
|
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);
|
|
126
|
-
}
|
|
127
|
-
getFileReadStream(filePath) {
|
|
128
|
-
return this.cfg.storage.getFileReadStream(this.cfg.bucketName, filePath);
|
|
129
|
-
}
|
|
130
|
-
getFileWriteStream(filePath) {
|
|
131
|
-
return this.cfg.storage.getFileWriteStream(this.cfg.bucketName, filePath);
|
|
132
|
-
}
|
|
133
|
-
async setFileVisibility(filePath, isPublic) {
|
|
134
|
-
await this.cfg.storage.setFileVisibility(this.cfg.bucketName, filePath, isPublic);
|
|
135
|
-
}
|
|
136
|
-
async getFileVisibility(filePath) {
|
|
137
|
-
return await this.cfg.storage.getFileVisibility(this.cfg.bucketName, filePath);
|
|
138
|
-
}
|
|
139
|
-
async copyFile(fromPath, toPath, toBucket) {
|
|
140
|
-
await this.cfg.storage.copyFile(this.cfg.bucketName, fromPath, toPath, toBucket);
|
|
141
|
-
}
|
|
142
|
-
async moveFile(fromPath, toPath, toBucket) {
|
|
143
|
-
await this.cfg.storage.moveFile(this.cfg.bucketName, fromPath, toPath, toBucket);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
exports.CommonStorageBucket = CommonStorageBucket;
|
|
@@ -1,33 +0,0 @@
|
|
|
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
|
-
count(table: string): Promise<number>;
|
|
33
|
-
}
|
|
@@ -1,81 +0,0 @@
|
|
|
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
|
-
/**
|
|
6
|
-
* CommonKeyValueDB, backed up by a CommonStorage implementation.
|
|
7
|
-
*
|
|
8
|
-
* Each Table is represented as a Folder.
|
|
9
|
-
* Each Item is represented as a File:
|
|
10
|
-
* fileName is ${id} (without extension)
|
|
11
|
-
* file contents is ${v} (Buffer)
|
|
12
|
-
*/
|
|
13
|
-
class CommonStorageKeyValueDB {
|
|
14
|
-
constructor(cfg) {
|
|
15
|
-
this.cfg = cfg;
|
|
16
|
-
}
|
|
17
|
-
async ping() {
|
|
18
|
-
await this.cfg.storage.ping(this.cfg.bucketName);
|
|
19
|
-
}
|
|
20
|
-
async createTable(_table, _opt) {
|
|
21
|
-
// no-op
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Allows to pass `SomeBucket.SomeTable` in `table`, to override a Bucket.
|
|
25
|
-
*/
|
|
26
|
-
getBucketAndPrefix(table) {
|
|
27
|
-
const [part1, part2] = table.split('.');
|
|
28
|
-
if (part2) {
|
|
29
|
-
return {
|
|
30
|
-
bucketName: part1,
|
|
31
|
-
prefix: part2,
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
// As is
|
|
35
|
-
return {
|
|
36
|
-
bucketName: this.cfg.bucketName,
|
|
37
|
-
prefix: table,
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
async deleteByIds(table, ids) {
|
|
41
|
-
const { bucketName, prefix } = this.getBucketAndPrefix(table);
|
|
42
|
-
await (0, js_lib_1.pMap)(ids, async (id) => {
|
|
43
|
-
await this.cfg.storage.deletePath(bucketName, [prefix, id].join('/'));
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
async getByIds(table, ids) {
|
|
47
|
-
const { bucketName, prefix } = this.getBucketAndPrefix(table);
|
|
48
|
-
const map = {};
|
|
49
|
-
await (0, js_lib_1.pMap)(ids, async (id) => {
|
|
50
|
-
const buf = await this.cfg.storage.getFile(bucketName, [prefix, id].join('/'));
|
|
51
|
-
if (buf)
|
|
52
|
-
map[id] = buf;
|
|
53
|
-
});
|
|
54
|
-
return ids.map(id => [id, map[id]]).filter(t => t[1]);
|
|
55
|
-
}
|
|
56
|
-
async saveBatch(table, entries) {
|
|
57
|
-
const { bucketName, prefix } = this.getBucketAndPrefix(table);
|
|
58
|
-
await (0, js_lib_1.pMap)(entries, async ([id, content]) => {
|
|
59
|
-
await this.cfg.storage.saveFile(bucketName, [prefix, id].join('/'), content);
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
streamIds(table, limit) {
|
|
63
|
-
const { bucketName, prefix } = this.getBucketAndPrefix(table);
|
|
64
|
-
return this.cfg.storage.getFileNamesStream(bucketName, { prefix, limit, fullPaths: false });
|
|
65
|
-
}
|
|
66
|
-
streamValues(table, limit) {
|
|
67
|
-
const { bucketName, prefix } = this.getBucketAndPrefix(table);
|
|
68
|
-
return this.cfg.storage.getFilesStream(bucketName, { prefix, limit }).map(f => f.content);
|
|
69
|
-
}
|
|
70
|
-
streamEntries(table, limit) {
|
|
71
|
-
const { bucketName, prefix } = this.getBucketAndPrefix(table);
|
|
72
|
-
return this.cfg.storage
|
|
73
|
-
.getFilesStream(bucketName, { prefix, limit, fullPaths: false })
|
|
74
|
-
.map(f => [f.filePath, f.content]);
|
|
75
|
-
}
|
|
76
|
-
async count(table) {
|
|
77
|
-
const { bucketName, prefix } = this.getBucketAndPrefix(table);
|
|
78
|
-
return (await this.cfg.storage.getFileNames(bucketName, { prefix })).length;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
exports.CommonStorageKeyValueDB = CommonStorageKeyValueDB;
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
import { LocalTimeInput, StringMap } from '@naturalcycles/js-lib';
|
|
3
|
-
import { ReadableBinary, ReadableTyped, WritableBinary } from '@naturalcycles/nodejs-lib';
|
|
4
|
-
import { CommonStorage, CommonStorageGetOptions, FileEntry } from './commonStorage';
|
|
5
|
-
export declare class InMemoryCommonStorage implements CommonStorage {
|
|
6
|
-
/**
|
|
7
|
-
* data[bucketName][filePath] = Buffer
|
|
8
|
-
*/
|
|
9
|
-
data: StringMap<StringMap<Buffer>>;
|
|
10
|
-
publicMap: StringMap<StringMap<boolean>>;
|
|
11
|
-
ping(): Promise<void>;
|
|
12
|
-
getBucketNames(): Promise<string[]>;
|
|
13
|
-
getBucketNamesStream(): ReadableTyped<string>;
|
|
14
|
-
fileExists(bucketName: string, filePath: string): Promise<boolean>;
|
|
15
|
-
getFile(bucketName: string, filePath: string): Promise<Buffer | null>;
|
|
16
|
-
saveFile(bucketName: string, filePath: string, content: Buffer): Promise<void>;
|
|
17
|
-
deletePath(bucketName: string, prefix: string): Promise<void>;
|
|
18
|
-
deletePaths(bucketName: string, prefixes: string[]): Promise<void>;
|
|
19
|
-
deleteFiles(bucketName: string, filePaths: string[]): Promise<void>;
|
|
20
|
-
getFileNames(bucketName: string, opt?: CommonStorageGetOptions): Promise<string[]>;
|
|
21
|
-
getFileNamesStream(bucketName: string, opt?: CommonStorageGetOptions): ReadableTyped<string>;
|
|
22
|
-
getFilesStream(bucketName: string, opt?: CommonStorageGetOptions): ReadableTyped<FileEntry>;
|
|
23
|
-
getFileReadStream(bucketName: string, filePath: string): ReadableBinary;
|
|
24
|
-
getFileWriteStream(_bucketName: string, _filePath: string): WritableBinary;
|
|
25
|
-
uploadFile(localFilePath: string, bucketName: string, bucketFilePath: string): Promise<void>;
|
|
26
|
-
setFileVisibility(bucketName: string, filePath: string, isPublic: boolean): Promise<void>;
|
|
27
|
-
getFileVisibility(bucketName: string, filePath: string): Promise<boolean>;
|
|
28
|
-
copyFile(fromBucket: string, fromPath: string, toPath: string, toBucket?: string): Promise<void>;
|
|
29
|
-
moveFile(fromBucket: string, fromPath: string, toPath: string, toBucket?: string): Promise<void>;
|
|
30
|
-
movePath(fromBucket: string, fromPrefix: string, toPrefix: string, toBucket?: string): Promise<void>;
|
|
31
|
-
combine(bucketName: string, prefix: string, toPath: string, toBucket?: string): Promise<void>;
|
|
32
|
-
combineFiles(bucketName: string, filePaths: string[], toPath: string, toBucket?: string): Promise<void>;
|
|
33
|
-
getSignedUrl(bucketName: string, filePath: string, expires: LocalTimeInput): Promise<string>;
|
|
34
|
-
}
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.InMemoryCommonStorage = void 0;
|
|
4
|
-
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
5
|
-
const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
|
|
6
|
-
class InMemoryCommonStorage {
|
|
7
|
-
constructor() {
|
|
8
|
-
/**
|
|
9
|
-
* data[bucketName][filePath] = Buffer
|
|
10
|
-
*/
|
|
11
|
-
this.data = {};
|
|
12
|
-
this.publicMap = {};
|
|
13
|
-
}
|
|
14
|
-
async ping() { }
|
|
15
|
-
async getBucketNames() {
|
|
16
|
-
return Object.keys(this.data);
|
|
17
|
-
}
|
|
18
|
-
getBucketNamesStream() {
|
|
19
|
-
return (0, nodejs_lib_1.readableFrom)(Object.keys(this.data));
|
|
20
|
-
}
|
|
21
|
-
async fileExists(bucketName, filePath) {
|
|
22
|
-
return !!this.data[bucketName]?.[filePath];
|
|
23
|
-
}
|
|
24
|
-
async getFile(bucketName, filePath) {
|
|
25
|
-
return this.data[bucketName]?.[filePath] || null;
|
|
26
|
-
}
|
|
27
|
-
async saveFile(bucketName, filePath, content) {
|
|
28
|
-
this.data[bucketName] ||= {};
|
|
29
|
-
this.data[bucketName][filePath] = content;
|
|
30
|
-
}
|
|
31
|
-
async deletePath(bucketName, prefix) {
|
|
32
|
-
await this.deletePaths(bucketName, [prefix]);
|
|
33
|
-
}
|
|
34
|
-
async deletePaths(bucketName, prefixes) {
|
|
35
|
-
Object.keys(this.data[bucketName] || {}).forEach(filePath => {
|
|
36
|
-
if (prefixes.some(prefix => filePath.startsWith(prefix))) {
|
|
37
|
-
delete this.data[bucketName][filePath];
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
async deleteFiles(bucketName, filePaths) {
|
|
42
|
-
if (!this.data[bucketName])
|
|
43
|
-
return;
|
|
44
|
-
filePaths.forEach(filePath => delete this.data[bucketName][filePath]);
|
|
45
|
-
}
|
|
46
|
-
async getFileNames(bucketName, opt = {}) {
|
|
47
|
-
const { prefix = '', fullPaths = true } = opt;
|
|
48
|
-
return Object.keys(this.data[bucketName] || {})
|
|
49
|
-
.filter(filePath => filePath.startsWith(prefix))
|
|
50
|
-
.map(f => (fullPaths ? f : (0, js_lib_1._substringAfterLast)(f, '/')));
|
|
51
|
-
}
|
|
52
|
-
getFileNamesStream(bucketName, opt = {}) {
|
|
53
|
-
const { prefix = '', fullPaths = true } = opt;
|
|
54
|
-
return (0, nodejs_lib_1.readableFrom)(Object.keys(this.data[bucketName] || {})
|
|
55
|
-
.filter(filePath => filePath.startsWith(prefix))
|
|
56
|
-
.slice(0, opt.limit)
|
|
57
|
-
.map(n => (fullPaths ? n : (0, js_lib_1._substringAfterLast)(n, '/'))));
|
|
58
|
-
}
|
|
59
|
-
getFilesStream(bucketName, opt = {}) {
|
|
60
|
-
const { prefix = '', fullPaths = true } = opt;
|
|
61
|
-
return (0, nodejs_lib_1.readableFrom)((0, js_lib_1._stringMapEntries)(this.data[bucketName] || {})
|
|
62
|
-
.map(([filePath, content]) => ({
|
|
63
|
-
filePath,
|
|
64
|
-
content,
|
|
65
|
-
}))
|
|
66
|
-
.filter(f => f.filePath.startsWith(prefix))
|
|
67
|
-
.slice(0, opt.limit)
|
|
68
|
-
.map(f => (fullPaths ? f : { ...f, filePath: (0, js_lib_1._substringAfterLast)(f.filePath, '/') })));
|
|
69
|
-
}
|
|
70
|
-
getFileReadStream(bucketName, filePath) {
|
|
71
|
-
return (0, nodejs_lib_1.readableFrom)(this.data[bucketName][filePath]);
|
|
72
|
-
}
|
|
73
|
-
getFileWriteStream(_bucketName, _filePath) {
|
|
74
|
-
throw new Error('Method not implemented.');
|
|
75
|
-
}
|
|
76
|
-
async uploadFile(localFilePath, bucketName, bucketFilePath) {
|
|
77
|
-
this.data[bucketName] ||= {};
|
|
78
|
-
this.data[bucketName][bucketFilePath] = await nodejs_lib_1.fs2.readBufferAsync(localFilePath);
|
|
79
|
-
}
|
|
80
|
-
async setFileVisibility(bucketName, filePath, isPublic) {
|
|
81
|
-
this.publicMap[bucketName] ||= {};
|
|
82
|
-
this.publicMap[bucketName][filePath] = isPublic;
|
|
83
|
-
}
|
|
84
|
-
async getFileVisibility(bucketName, filePath) {
|
|
85
|
-
return !!this.publicMap[bucketName]?.[filePath];
|
|
86
|
-
}
|
|
87
|
-
async copyFile(fromBucket, fromPath, toPath, toBucket) {
|
|
88
|
-
const tob = toBucket || fromBucket;
|
|
89
|
-
this.data[fromBucket] ||= {};
|
|
90
|
-
this.data[tob] ||= {};
|
|
91
|
-
this.data[tob][toPath] = this.data[fromBucket][fromPath];
|
|
92
|
-
}
|
|
93
|
-
async moveFile(fromBucket, fromPath, toPath, toBucket) {
|
|
94
|
-
const tob = toBucket || fromBucket;
|
|
95
|
-
this.data[fromBucket] ||= {};
|
|
96
|
-
this.data[tob] ||= {};
|
|
97
|
-
this.data[tob][toPath] = this.data[fromBucket][fromPath];
|
|
98
|
-
delete this.data[fromBucket][fromPath];
|
|
99
|
-
}
|
|
100
|
-
async movePath(fromBucket, fromPrefix, toPrefix, toBucket) {
|
|
101
|
-
const tob = toBucket || fromBucket;
|
|
102
|
-
this.data[fromBucket] ||= {};
|
|
103
|
-
this.data[tob] ||= {};
|
|
104
|
-
(0, js_lib_1._stringMapEntries)(this.data[fromBucket]).forEach(([filePath, v]) => {
|
|
105
|
-
if (!filePath.startsWith(fromPrefix))
|
|
106
|
-
return;
|
|
107
|
-
this.data[tob][toPrefix + filePath.slice(fromPrefix.length)] = v;
|
|
108
|
-
delete this.data[fromBucket][filePath];
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
async combine(bucketName, prefix, toPath, toBucket) {
|
|
112
|
-
const filePaths = await this.getFileNames(bucketName, { prefix });
|
|
113
|
-
await this.combineFiles(bucketName, filePaths, toPath, toBucket);
|
|
114
|
-
}
|
|
115
|
-
async combineFiles(bucketName, filePaths, toPath, toBucket) {
|
|
116
|
-
if (!this.data[bucketName])
|
|
117
|
-
return;
|
|
118
|
-
const tob = toBucket || bucketName;
|
|
119
|
-
this.data[tob] ||= {};
|
|
120
|
-
this.data[tob][toPath] = Buffer.concat(filePaths.map(p => this.data[bucketName][p]).filter(js_lib_1._isTruthy));
|
|
121
|
-
// delete source files
|
|
122
|
-
filePaths.forEach(p => delete this.data[bucketName][p]);
|
|
123
|
-
}
|
|
124
|
-
async getSignedUrl(bucketName, filePath, expires) {
|
|
125
|
-
const buf = this.data[bucketName]?.[filePath];
|
|
126
|
-
(0, js_lib_1._assert)(buf, `getSignedUrl file not found: ${bucketName}/${filePath}`);
|
|
127
|
-
const signature = (0, nodejs_lib_1.md5)(buf);
|
|
128
|
-
return `https://testurl.com/${bucketName}/${filePath}?expires=${(0, js_lib_1.localTime)(expires).unix()}&signature=${signature}`;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
exports.InMemoryCommonStorage = InMemoryCommonStorage;
|
package/dist/index.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export * from './cloudStorage';
|
|
2
|
-
export * from './commonStorage';
|
|
3
|
-
export * from './commonStorageBucket';
|
|
4
|
-
export * from './commonStorageKeyValueDB';
|
|
5
|
-
export * from './inMemoryCommonStorage';
|
|
6
|
-
export * from './model';
|
|
7
|
-
export * from './testing/commonStorageTest';
|
package/dist/index.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const tslib_1 = require("tslib");
|
|
4
|
-
tslib_1.__exportStar(require("./cloudStorage"), exports);
|
|
5
|
-
tslib_1.__exportStar(require("./commonStorage"), exports);
|
|
6
|
-
tslib_1.__exportStar(require("./commonStorageBucket"), exports);
|
|
7
|
-
tslib_1.__exportStar(require("./commonStorageKeyValueDB"), exports);
|
|
8
|
-
tslib_1.__exportStar(require("./inMemoryCommonStorage"), exports);
|
|
9
|
-
tslib_1.__exportStar(require("./model"), exports);
|
|
10
|
-
tslib_1.__exportStar(require("./testing/commonStorageTest"), exports);
|
package/dist/model.d.ts
DELETED
package/dist/model.js
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.runCommonStorageTest = void 0;
|
|
4
|
-
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
5
|
-
const TEST_FOLDER = 'test/subdir';
|
|
6
|
-
const TEST_ITEMS = (0, js_lib_1._range)(10).map(n => ({
|
|
7
|
-
id: `id_${n + 1}`,
|
|
8
|
-
n,
|
|
9
|
-
even: n % 2 === 0,
|
|
10
|
-
}));
|
|
11
|
-
const TEST_ITEMS2 = (0, js_lib_1._range)(10).map(n => ({
|
|
12
|
-
fileType: 2,
|
|
13
|
-
id: `id_${n + 1}`,
|
|
14
|
-
n,
|
|
15
|
-
even: n % 2 === 0,
|
|
16
|
-
}));
|
|
17
|
-
const TEST_ITEMS3 = (0, js_lib_1._range)(10).map(n => ({
|
|
18
|
-
fileType: 3,
|
|
19
|
-
id: `id_${n + 1}`,
|
|
20
|
-
n,
|
|
21
|
-
even: n % 2 === 0,
|
|
22
|
-
}));
|
|
23
|
-
const TEST_FILES = [TEST_ITEMS, TEST_ITEMS2, TEST_ITEMS3].map((obj, i) => ({
|
|
24
|
-
filePath: `${TEST_FOLDER}/file_${i + 1}.json`,
|
|
25
|
-
content: Buffer.from(JSON.stringify(obj)),
|
|
26
|
-
}));
|
|
27
|
-
/**
|
|
28
|
-
* This test suite must be idempotent.
|
|
29
|
-
*/
|
|
30
|
-
function runCommonStorageTest(storage, bucketName) {
|
|
31
|
-
// test('createBucket', async () => {
|
|
32
|
-
// await storage.createBucket(bucketName)
|
|
33
|
-
// })
|
|
34
|
-
test('ping', async () => {
|
|
35
|
-
await storage.ping(bucketName);
|
|
36
|
-
});
|
|
37
|
-
// test('listBuckets', async () => {
|
|
38
|
-
// const buckets = await storage.getBucketNames()
|
|
39
|
-
// console.log(buckets)
|
|
40
|
-
// })
|
|
41
|
-
//
|
|
42
|
-
// test('streamBuckets', async () => {
|
|
43
|
-
// const buckets = await readableToArray(storage.getBucketNamesStream())
|
|
44
|
-
// console.log(buckets)
|
|
45
|
-
// })
|
|
46
|
-
test('prepare: clear bucket', async () => {
|
|
47
|
-
await (0, js_lib_1.pMap)(TEST_FILES.map(f => f.filePath), async (filePath) => await storage.deletePath(bucketName, filePath));
|
|
48
|
-
});
|
|
49
|
-
// test('listFileNames on root should return empty', async () => {
|
|
50
|
-
// const fileNames = await storage.getFileNames(bucketName)
|
|
51
|
-
// expect(fileNames).toEqual([])
|
|
52
|
-
// })
|
|
53
|
-
test(`listFileNames on ${TEST_FOLDER} should return empty`, async () => {
|
|
54
|
-
const fileNames = await storage.getFileNames(bucketName, { prefix: TEST_FOLDER });
|
|
55
|
-
expect(fileNames).toEqual([]);
|
|
56
|
-
});
|
|
57
|
-
test(`streamFileNames on ${TEST_FOLDER} should return empty`, async () => {
|
|
58
|
-
const fileNames = await storage
|
|
59
|
-
.getFileNamesStream(bucketName, { prefix: TEST_FOLDER })
|
|
60
|
-
.toArray();
|
|
61
|
-
expect(fileNames).toEqual([]);
|
|
62
|
-
});
|
|
63
|
-
test(`exists should return empty array`, async () => {
|
|
64
|
-
await (0, js_lib_1.pMap)(TEST_FILES, async (f) => {
|
|
65
|
-
const exists = await storage.fileExists(bucketName, f.filePath);
|
|
66
|
-
expect(exists).toBe(false);
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
test(`saveFiles, then listFileNames, streamFileNames and getFiles should return just saved files`, async () => {
|
|
70
|
-
const testFilesMap = Object.fromEntries(TEST_FILES.map(f => [f.filePath, f.content]));
|
|
71
|
-
// It's done in the same test to ensure "strong consistency"
|
|
72
|
-
await (0, js_lib_1.pMap)(TEST_FILES, async (f) => await storage.saveFile(bucketName, f.filePath, f.content));
|
|
73
|
-
const fileNamesShort = await storage.getFileNames(bucketName, {
|
|
74
|
-
prefix: TEST_FOLDER,
|
|
75
|
-
fullPaths: false,
|
|
76
|
-
});
|
|
77
|
-
expect(fileNamesShort.sort()).toEqual(TEST_FILES.map(f => (0, js_lib_1._substringAfterLast)(f.filePath, '/')).sort());
|
|
78
|
-
const fileNames = await storage.getFileNames(bucketName, { prefix: TEST_FOLDER });
|
|
79
|
-
expect(fileNames.sort()).toEqual(TEST_FILES.map(f => f.filePath).sort());
|
|
80
|
-
const streamedFileNames = await storage
|
|
81
|
-
.getFileNamesStream(bucketName, { prefix: TEST_FOLDER })
|
|
82
|
-
.toArray();
|
|
83
|
-
expect(streamedFileNames.sort()).toEqual(TEST_FILES.map(f => f.filePath).sort());
|
|
84
|
-
const filesMap = {};
|
|
85
|
-
await (0, js_lib_1.pMap)(fileNames, async (filePath) => {
|
|
86
|
-
filesMap[filePath] = (await storage.getFile(bucketName, filePath));
|
|
87
|
-
});
|
|
88
|
-
expect(filesMap).toEqual(testFilesMap);
|
|
89
|
-
await (0, js_lib_1.pMap)(fileNames, async (filePath) => {
|
|
90
|
-
const exists = await storage.fileExists(bucketName, filePath);
|
|
91
|
-
expect(exists).toBe(true);
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
test('cleanup', async () => {
|
|
95
|
-
await storage.deletePath(bucketName, TEST_FOLDER);
|
|
96
|
-
});
|
|
97
|
-
// Cannot update access control for an object when uniform bucket-level access is enabled. Read more at https://cloud.google.com/storage/docs/uniform-bucket-level-access
|
|
98
|
-
/*
|
|
99
|
-
test(`get/set FilesVisibility`, async () => {
|
|
100
|
-
const fileNames = TEST_FILES.map(f => f.filePath)
|
|
101
|
-
|
|
102
|
-
let map = await storage.getFilesVisibility(bucketName, fileNames)
|
|
103
|
-
expect(map).toEqual(Object.fromEntries(fileNames.map(f => [f, false])))
|
|
104
|
-
|
|
105
|
-
await storage.setFilesVisibility(bucketName, fileNames, true)
|
|
106
|
-
|
|
107
|
-
map = await storage.getFilesVisibility(bucketName, fileNames)
|
|
108
|
-
expect(map).toEqual(Object.fromEntries(fileNames.map(f => [f, true])))
|
|
109
|
-
|
|
110
|
-
await storage.setFilesVisibility(bucketName, fileNames, false)
|
|
111
|
-
|
|
112
|
-
map = await storage.getFilesVisibility(bucketName, fileNames)
|
|
113
|
-
expect(map).toEqual(Object.fromEntries(fileNames.map(f => [f, false])))
|
|
114
|
-
})
|
|
115
|
-
*/
|
|
116
|
-
}
|
|
117
|
-
exports.runCommonStorageTest = runCommonStorageTest;
|