@naturalcycles/datastore-lib 3.16.5 → 3.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/DatastoreStreamReadable.d.ts +3 -2
- package/dist/DatastoreStreamReadable.js +8 -11
- package/dist/datastore.db.d.ts +4 -2
- package/dist/datastore.db.js +28 -15
- package/dist/datastore.model.d.ts +5 -1
- package/dist/datastoreKeyValueDB.d.ts +1 -0
- package/dist/datastoreKeyValueDB.js +4 -0
- package/package.json +2 -2
- package/src/DatastoreStreamReadable.ts +8 -13
- package/src/datastore.db.ts +45 -15
- package/src/datastore.model.ts +6 -1
- package/src/datastoreKeyValueDB.ts +5 -0
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import { Readable } from 'stream';
|
|
3
3
|
import { Query } from '@google-cloud/datastore';
|
|
4
|
+
import { CommonLogger } from '@naturalcycles/js-lib';
|
|
4
5
|
import type { ReadableTyped } from '@naturalcycles/nodejs-lib';
|
|
5
6
|
import type { DatastoreDBStreamOptions } from './datastore.model';
|
|
6
7
|
export declare class DatastoreStreamReadable<T = any> extends Readable implements ReadableTyped<T> {
|
|
7
8
|
private q;
|
|
9
|
+
private logger;
|
|
8
10
|
private originalLimit;
|
|
9
11
|
private rowsRetrieved;
|
|
10
12
|
private endCursor?;
|
|
@@ -12,9 +14,8 @@ export declare class DatastoreStreamReadable<T = any> extends Readable implement
|
|
|
12
14
|
private done;
|
|
13
15
|
private lastQueryDone?;
|
|
14
16
|
private totalWait;
|
|
15
|
-
private log;
|
|
16
17
|
private opt;
|
|
17
|
-
constructor(q: Query, opt: DatastoreDBStreamOptions);
|
|
18
|
+
constructor(q: Query, opt: DatastoreDBStreamOptions, logger: CommonLogger);
|
|
18
19
|
private runNextQuery;
|
|
19
20
|
count: number;
|
|
20
21
|
_read(): void;
|
|
@@ -4,25 +4,22 @@ exports.DatastoreStreamReadable = void 0;
|
|
|
4
4
|
const stream_1 = require("stream");
|
|
5
5
|
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
6
6
|
class DatastoreStreamReadable extends stream_1.Readable {
|
|
7
|
-
constructor(q, opt) {
|
|
7
|
+
constructor(q, opt, logger) {
|
|
8
8
|
super({ objectMode: true });
|
|
9
9
|
this.q = q;
|
|
10
|
+
this.logger = logger;
|
|
10
11
|
this.rowsRetrieved = 0;
|
|
11
12
|
this.running = false;
|
|
12
13
|
this.done = false;
|
|
13
14
|
this.totalWait = 0;
|
|
14
|
-
// private log = (...args: any[]): void => console.log(...args)
|
|
15
|
-
this.log = console.log.bind(console);
|
|
16
15
|
this.count = 0; // use for debugging
|
|
17
16
|
this.opt = {
|
|
18
17
|
rssLimitMB: 1000,
|
|
19
18
|
batchSize: 1000,
|
|
20
19
|
...opt,
|
|
21
20
|
};
|
|
22
|
-
if (!opt.debug)
|
|
23
|
-
this.log = () => { };
|
|
24
21
|
this.originalLimit = q.limitVal;
|
|
25
|
-
|
|
22
|
+
logger.log(`!! using experimentalCursorStream !! batchSize: ${opt.batchSize}`);
|
|
26
23
|
}
|
|
27
24
|
async runNextQuery() {
|
|
28
25
|
if (this.done)
|
|
@@ -45,7 +42,7 @@ class DatastoreStreamReadable extends stream_1.Readable {
|
|
|
45
42
|
try {
|
|
46
43
|
const [rows, info] = await q.run();
|
|
47
44
|
this.rowsRetrieved += rows.length;
|
|
48
|
-
this.log(`got ${rows.length} rows, ${this.rowsRetrieved} rowsRetrieved, totalWait: ${(0, js_lib_1._ms)(this.totalWait)}`, info.moreResults);
|
|
45
|
+
this.logger.log(`got ${rows.length} rows, ${this.rowsRetrieved} rowsRetrieved, totalWait: ${(0, js_lib_1._ms)(this.totalWait)}`, info.moreResults);
|
|
49
46
|
this.endCursor = info.endCursor;
|
|
50
47
|
this.running = false; // ready to take more _reads
|
|
51
48
|
this.lastQueryDone = Date.now();
|
|
@@ -53,7 +50,7 @@ class DatastoreStreamReadable extends stream_1.Readable {
|
|
|
53
50
|
if (!info.endCursor ||
|
|
54
51
|
info.moreResults === 'NO_MORE_RESULTS' ||
|
|
55
52
|
(this.originalLimit && this.rowsRetrieved >= this.originalLimit)) {
|
|
56
|
-
this.log(`!!!! DONE! ${this.rowsRetrieved} rowsRetrieved, totalWait: ${(0, js_lib_1._ms)(this.totalWait)}`);
|
|
53
|
+
this.logger.log(`!!!! DONE! ${this.rowsRetrieved} rowsRetrieved, totalWait: ${(0, js_lib_1._ms)(this.totalWait)}`);
|
|
57
54
|
this.push(null);
|
|
58
55
|
this.done = true;
|
|
59
56
|
}
|
|
@@ -63,7 +60,7 @@ class DatastoreStreamReadable extends stream_1.Readable {
|
|
|
63
60
|
void this.runNextQuery();
|
|
64
61
|
}
|
|
65
62
|
else {
|
|
66
|
-
this.log(`rssLimitMB reached ${rssMB} > ${this.opt.rssLimitMB}, pausing stream`);
|
|
63
|
+
this.logger.log(`rssLimitMB reached ${rssMB} > ${this.opt.rssLimitMB}, pausing stream`);
|
|
67
64
|
}
|
|
68
65
|
}
|
|
69
66
|
}
|
|
@@ -76,10 +73,10 @@ class DatastoreStreamReadable extends stream_1.Readable {
|
|
|
76
73
|
// console.log(`_read called ${++this.count}, wasRunning: ${this.running}`) // debugging
|
|
77
74
|
this.count++;
|
|
78
75
|
if (this.running) {
|
|
79
|
-
this.log(`_read ${this.count}, wasRunning: true`);
|
|
76
|
+
this.logger.log(`_read ${this.count}, wasRunning: true`);
|
|
80
77
|
}
|
|
81
78
|
if (this.done) {
|
|
82
|
-
|
|
79
|
+
this.logger.warn(`!!! _read was called, but done==true`);
|
|
83
80
|
return;
|
|
84
81
|
}
|
|
85
82
|
if (!this.running) {
|
package/dist/datastore.db.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Datastore, Key, Query } from '@google-cloud/datastore';
|
|
2
2
|
import { BaseCommonDB, CommonDB, DBQuery, DBTransaction, RunQueryResult } from '@naturalcycles/db-lib';
|
|
3
|
-
import { ObjectWithId, JsonSchemaRootObject } from '@naturalcycles/js-lib';
|
|
3
|
+
import { ObjectWithId, JsonSchemaRootObject, CommonLogger } from '@naturalcycles/js-lib';
|
|
4
4
|
import { ReadableTyped } from '@naturalcycles/nodejs-lib';
|
|
5
5
|
import { DatastoreDBCfg, DatastoreDBOptions, DatastoreDBSaveOptions, DatastoreDBStreamOptions, DatastorePayload, DatastorePropertyStats, DatastoreStats } from './datastore.model';
|
|
6
6
|
/**
|
|
@@ -9,8 +9,10 @@ import { DatastoreDBCfg, DatastoreDBOptions, DatastoreDBSaveOptions, DatastoreDB
|
|
|
9
9
|
* https://cloud.google.com/datastore/docs/datastore-api-tutorial
|
|
10
10
|
*/
|
|
11
11
|
export declare class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
12
|
-
cfg: DatastoreDBCfg;
|
|
13
12
|
constructor(cfg?: DatastoreDBCfg);
|
|
13
|
+
cfg: DatastoreDBCfg & {
|
|
14
|
+
logger: CommonLogger;
|
|
15
|
+
};
|
|
14
16
|
private cachedDatastore?;
|
|
15
17
|
/**
|
|
16
18
|
* Datastore.KEY
|
package/dist/datastore.db.js
CHANGED
|
@@ -21,27 +21,27 @@ const RETRY_ON = ['GOAWAY', 'UNAVAILABLE', 'UNKNOWN'];
|
|
|
21
21
|
class DatastoreDB extends db_lib_1.BaseCommonDB {
|
|
22
22
|
constructor(cfg = {}) {
|
|
23
23
|
super();
|
|
24
|
-
this.cfg =
|
|
24
|
+
this.cfg = {
|
|
25
|
+
logger: console,
|
|
26
|
+
...cfg,
|
|
27
|
+
};
|
|
25
28
|
}
|
|
26
29
|
// @memo() // not used to be able to connect to many DBs in the same server instance
|
|
27
30
|
ds() {
|
|
31
|
+
var _a;
|
|
28
32
|
if (!this.cachedDatastore) {
|
|
29
|
-
|
|
30
|
-
throw new Error('DatastoreDB cannot be used in Test env, please use InMemoryDB');
|
|
31
|
-
}
|
|
33
|
+
(0, js_lib_1._assert)(process.env['APP_ENV'] !== 'test', 'DatastoreDB cannot be used in Test env, please use InMemoryDB');
|
|
32
34
|
// Lazy-loading
|
|
33
35
|
const datastoreLib = require('@google-cloud/datastore');
|
|
34
36
|
const DS = datastoreLib.Datastore;
|
|
35
|
-
this.cfg.projectId =
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
process.env['GOOGLE_CLOUD_PROJECT'];
|
|
39
|
-
console.log(`DatastoreDB connected to ${(0, colors_1.boldWhite)(this.cfg.projectId)}`);
|
|
37
|
+
(_a = this.cfg).projectId || (_a.projectId = this.cfg.credentials?.project_id || process.env['GOOGLE_CLOUD_PROJECT']);
|
|
38
|
+
(0, js_lib_1._assert)(this.cfg.projectId, '"projectId" is not set for DatastoreDB');
|
|
39
|
+
this.cfg.logger.log(`DatastoreDB connected to ${(0, colors_1.boldWhite)(this.cfg.projectId)}`);
|
|
40
40
|
if (this.cfg.useLegacyGRPC) {
|
|
41
41
|
this.cfg.grpc = require('grpc');
|
|
42
42
|
}
|
|
43
43
|
if (this.cfg.grpc) {
|
|
44
|
-
|
|
44
|
+
this.cfg.logger.log('!!! DatastoreDB using custom grpc !!!');
|
|
45
45
|
}
|
|
46
46
|
this.cachedDatastore = new DS(this.cfg);
|
|
47
47
|
this.KEY = this.cachedDatastore.KEY;
|
|
@@ -55,9 +55,20 @@ class DatastoreDB extends db_lib_1.BaseCommonDB {
|
|
|
55
55
|
if (!ids.length)
|
|
56
56
|
return [];
|
|
57
57
|
const keys = ids.map(id => this.key(table, id));
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
let rows;
|
|
59
|
+
try {
|
|
60
|
+
rows = (await this.ds().get(keys))[0];
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
this.cfg.logger.log('datastore recreated on error');
|
|
64
|
+
// This is to debug "GCP Datastore Timeout issue"
|
|
65
|
+
const datastoreLib = require('@google-cloud/datastore');
|
|
66
|
+
const DS = datastoreLib.Datastore;
|
|
67
|
+
this.cachedDatastore = new DS(this.cfg);
|
|
68
|
+
throw err;
|
|
69
|
+
}
|
|
70
|
+
return (rows
|
|
71
|
+
.map(r => this.mapId(r))
|
|
61
72
|
// Seems like datastore .get() method doesn't return items properly sorted by input ids, so we gonna sort them here
|
|
62
73
|
// same ids are not expected here
|
|
63
74
|
.sort((a, b) => (a.id > b.id ? 1 : -1)));
|
|
@@ -95,7 +106,7 @@ class DatastoreDB extends db_lib_1.BaseCommonDB {
|
|
|
95
106
|
..._opt,
|
|
96
107
|
};
|
|
97
108
|
return (opt.experimentalCursorStream
|
|
98
|
-
? new DatastoreStreamReadable_1.DatastoreStreamReadable(q, opt)
|
|
109
|
+
? new DatastoreStreamReadable_1.DatastoreStreamReadable(q, opt, (0, js_lib_1.commonLoggerMinLevel)(this.cfg.logger, opt.debug ? 'log' : 'warn'))
|
|
99
110
|
: this.ds().runQueryStream(q)).pipe(new stream_1.Transform({
|
|
100
111
|
objectMode: true,
|
|
101
112
|
transform: (chunk, _enc, cb) => {
|
|
@@ -119,19 +130,21 @@ class DatastoreDB extends db_lib_1.BaseCommonDB {
|
|
|
119
130
|
// Here we retry the GOAWAY errors that are somewhat common for Datastore
|
|
120
131
|
// Currently only retrying them here in .saveBatch(), cause probably they're only thrown when saving
|
|
121
132
|
predicate: err => RETRY_ON.some(s => err?.message.includes(s)),
|
|
133
|
+
name: `DatastoreLib.saveBatch(${table})`,
|
|
122
134
|
maxAttempts: 5,
|
|
123
135
|
delay: 5000,
|
|
124
136
|
delayMultiplier: 2,
|
|
125
137
|
logFirstAttempt: false,
|
|
126
138
|
logFailures: true,
|
|
127
139
|
// logAll: true,
|
|
140
|
+
logger: this.cfg.logger,
|
|
128
141
|
});
|
|
129
142
|
try {
|
|
130
143
|
await (0, js_lib_1.pMap)((0, js_lib_1._chunk)(entities, MAX_ITEMS), async (batch) => await save(batch));
|
|
131
144
|
}
|
|
132
145
|
catch (err) {
|
|
133
146
|
// console.log(`datastore.save ${kind}`, { obj, entity })
|
|
134
|
-
|
|
147
|
+
this.cfg.logger.error(`error in DatastoreLib.saveBatch for ${table} (${rows.length} rows)`, err);
|
|
135
148
|
// don't throw, because datastore SDK makes it in separate thread, so exception will be unhandled otherwise
|
|
136
149
|
return await Promise.reject(err);
|
|
137
150
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { DatastoreOptions, Key, Transaction } from '@google-cloud/datastore';
|
|
2
2
|
import { CommonDBOptions, CommonDBSaveOptions } from '@naturalcycles/db-lib';
|
|
3
|
-
import { AnyObjectWithId, ObjectWithId } from '@naturalcycles/js-lib';
|
|
3
|
+
import { AnyObjectWithId, CommonLogger, ObjectWithId } from '@naturalcycles/js-lib';
|
|
4
4
|
export interface DatastorePayload<T = any> {
|
|
5
5
|
key: Key;
|
|
6
6
|
data: T;
|
|
@@ -30,6 +30,10 @@ export interface DatastoreDBCfg extends DatastoreOptions {
|
|
|
30
30
|
* e.g you can globally enable `experimentalCursorStream` here, set the batchSize, etc.
|
|
31
31
|
*/
|
|
32
32
|
streamOptions?: DatastoreDBStreamOptions;
|
|
33
|
+
/**
|
|
34
|
+
* Default to `console`
|
|
35
|
+
*/
|
|
36
|
+
logger?: CommonLogger;
|
|
33
37
|
}
|
|
34
38
|
export interface DatastoreCredentials {
|
|
35
39
|
type?: string;
|
|
@@ -17,4 +17,5 @@ export declare class DatastoreKeyValueDB implements CommonKeyValueDB {
|
|
|
17
17
|
streamIds(table: string, limit?: number): ReadableTyped<string>;
|
|
18
18
|
streamValues(table: string, limit?: number): ReadableTyped<Buffer>;
|
|
19
19
|
streamEntries(table: string, limit?: number): ReadableTyped<KeyValueDBTuple>;
|
|
20
|
+
count(_table: string): Promise<number>;
|
|
20
21
|
}
|
|
@@ -47,5 +47,9 @@ class DatastoreKeyValueDB {
|
|
|
47
47
|
errorMode: js_lib_1.ErrorMode.SUPPRESS, // cause .pipe() cannot propagate errors
|
|
48
48
|
}));
|
|
49
49
|
}
|
|
50
|
+
async count(_table) {
|
|
51
|
+
this.db.cfg.logger.warn(`DatastoreKeyValueDB.count is not supported`);
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
50
54
|
}
|
|
51
55
|
exports.DatastoreKeyValueDB = DatastoreKeyValueDB;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@naturalcycles/datastore-lib",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.18.0",
|
|
4
4
|
"description": "Opinionated library to work with Google Datastore",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"prepare": "husky install"
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"@google-cloud/datastore": "^6.0.0",
|
|
19
19
|
"@naturalcycles/dev-lib": "^12.0.1",
|
|
20
|
-
"@types/node": "^
|
|
20
|
+
"@types/node": "^17.0.8",
|
|
21
21
|
"jest": "^27.0.4"
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Readable } from 'stream'
|
|
2
2
|
import { Query } from '@google-cloud/datastore'
|
|
3
|
-
import { _ms } from '@naturalcycles/js-lib'
|
|
3
|
+
import { _ms, CommonLogger } from '@naturalcycles/js-lib'
|
|
4
4
|
import type { ReadableTyped } from '@naturalcycles/nodejs-lib'
|
|
5
5
|
import type { DatastoreDBStreamOptions } from './datastore.model'
|
|
6
6
|
|
|
@@ -13,12 +13,9 @@ export class DatastoreStreamReadable<T = any> extends Readable implements Readab
|
|
|
13
13
|
private lastQueryDone?: number
|
|
14
14
|
private totalWait = 0
|
|
15
15
|
|
|
16
|
-
// private log = (...args: any[]): void => console.log(...args)
|
|
17
|
-
private log = console.log.bind(console)
|
|
18
|
-
|
|
19
16
|
private opt: DatastoreDBStreamOptions & { batchSize: number }
|
|
20
17
|
|
|
21
|
-
constructor(private q: Query, opt: DatastoreDBStreamOptions) {
|
|
18
|
+
constructor(private q: Query, opt: DatastoreDBStreamOptions, private logger: CommonLogger) {
|
|
22
19
|
super({ objectMode: true })
|
|
23
20
|
|
|
24
21
|
this.opt = {
|
|
@@ -27,11 +24,9 @@ export class DatastoreStreamReadable<T = any> extends Readable implements Readab
|
|
|
27
24
|
...opt,
|
|
28
25
|
}
|
|
29
26
|
|
|
30
|
-
if (!opt.debug) this.log = () => {}
|
|
31
|
-
|
|
32
27
|
this.originalLimit = q.limitVal
|
|
33
28
|
|
|
34
|
-
|
|
29
|
+
logger.log(`!! using experimentalCursorStream !! batchSize: ${opt.batchSize}`)
|
|
35
30
|
}
|
|
36
31
|
|
|
37
32
|
private async runNextQuery(): Promise<void> {
|
|
@@ -61,7 +56,7 @@ export class DatastoreStreamReadable<T = any> extends Readable implements Readab
|
|
|
61
56
|
const [rows, info] = await q.run()
|
|
62
57
|
|
|
63
58
|
this.rowsRetrieved += rows.length
|
|
64
|
-
this.log(
|
|
59
|
+
this.logger.log(
|
|
65
60
|
`got ${rows.length} rows, ${this.rowsRetrieved} rowsRetrieved, totalWait: ${_ms(
|
|
66
61
|
this.totalWait,
|
|
67
62
|
)}`,
|
|
@@ -79,7 +74,7 @@ export class DatastoreStreamReadable<T = any> extends Readable implements Readab
|
|
|
79
74
|
info.moreResults === 'NO_MORE_RESULTS' ||
|
|
80
75
|
(this.originalLimit && this.rowsRetrieved >= this.originalLimit)
|
|
81
76
|
) {
|
|
82
|
-
this.log(
|
|
77
|
+
this.logger.log(
|
|
83
78
|
`!!!! DONE! ${this.rowsRetrieved} rowsRetrieved, totalWait: ${_ms(this.totalWait)}`,
|
|
84
79
|
)
|
|
85
80
|
this.push(null)
|
|
@@ -90,7 +85,7 @@ export class DatastoreStreamReadable<T = any> extends Readable implements Readab
|
|
|
90
85
|
if (rssMB <= this.opt.rssLimitMB) {
|
|
91
86
|
void this.runNextQuery()
|
|
92
87
|
} else {
|
|
93
|
-
this.log(`rssLimitMB reached ${rssMB} > ${this.opt.rssLimitMB}, pausing stream`)
|
|
88
|
+
this.logger.log(`rssLimitMB reached ${rssMB} > ${this.opt.rssLimitMB}, pausing stream`)
|
|
94
89
|
}
|
|
95
90
|
}
|
|
96
91
|
} catch (err) {
|
|
@@ -105,11 +100,11 @@ export class DatastoreStreamReadable<T = any> extends Readable implements Readab
|
|
|
105
100
|
// console.log(`_read called ${++this.count}, wasRunning: ${this.running}`) // debugging
|
|
106
101
|
this.count++
|
|
107
102
|
if (this.running) {
|
|
108
|
-
this.log(`_read ${this.count}, wasRunning: true`)
|
|
103
|
+
this.logger.log(`_read ${this.count}, wasRunning: true`)
|
|
109
104
|
}
|
|
110
105
|
|
|
111
106
|
if (this.done) {
|
|
112
|
-
|
|
107
|
+
this.logger.warn(`!!! _read was called, but done==true`)
|
|
113
108
|
return
|
|
114
109
|
}
|
|
115
110
|
|
package/src/datastore.db.ts
CHANGED
|
@@ -22,6 +22,8 @@ import {
|
|
|
22
22
|
_chunk,
|
|
23
23
|
_omit,
|
|
24
24
|
JsonSchemaRootObject,
|
|
25
|
+
CommonLogger,
|
|
26
|
+
commonLoggerMinLevel,
|
|
25
27
|
} from '@naturalcycles/js-lib'
|
|
26
28
|
import { ReadableTyped } from '@naturalcycles/nodejs-lib'
|
|
27
29
|
import { boldWhite } from '@naturalcycles/nodejs-lib/dist/colors'
|
|
@@ -51,10 +53,16 @@ const RETRY_ON = ['GOAWAY', 'UNAVAILABLE', 'UNKNOWN']
|
|
|
51
53
|
* https://cloud.google.com/datastore/docs/datastore-api-tutorial
|
|
52
54
|
*/
|
|
53
55
|
export class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
54
|
-
constructor(
|
|
56
|
+
constructor(cfg: DatastoreDBCfg = {}) {
|
|
55
57
|
super()
|
|
58
|
+
this.cfg = {
|
|
59
|
+
logger: console,
|
|
60
|
+
...cfg,
|
|
61
|
+
}
|
|
56
62
|
}
|
|
57
63
|
|
|
64
|
+
public cfg: DatastoreDBCfg & { logger: CommonLogger }
|
|
65
|
+
|
|
58
66
|
private cachedDatastore?: Datastore
|
|
59
67
|
|
|
60
68
|
/**
|
|
@@ -65,26 +73,26 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
|
65
73
|
// @memo() // not used to be able to connect to many DBs in the same server instance
|
|
66
74
|
ds(): Datastore {
|
|
67
75
|
if (!this.cachedDatastore) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
76
|
+
_assert(
|
|
77
|
+
process.env['APP_ENV'] !== 'test',
|
|
78
|
+
'DatastoreDB cannot be used in Test env, please use InMemoryDB',
|
|
79
|
+
)
|
|
71
80
|
|
|
72
81
|
// Lazy-loading
|
|
73
82
|
const datastoreLib = require('@google-cloud/datastore')
|
|
74
83
|
const DS = datastoreLib.Datastore as typeof Datastore
|
|
75
|
-
this.cfg.projectId
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
process.env['GOOGLE_CLOUD_PROJECT']!
|
|
84
|
+
this.cfg.projectId ||= this.cfg.credentials?.project_id || process.env['GOOGLE_CLOUD_PROJECT']
|
|
85
|
+
|
|
86
|
+
_assert(this.cfg.projectId, '"projectId" is not set for DatastoreDB')
|
|
79
87
|
|
|
80
|
-
|
|
88
|
+
this.cfg.logger.log(`DatastoreDB connected to ${boldWhite(this.cfg.projectId)}`)
|
|
81
89
|
|
|
82
90
|
if (this.cfg.useLegacyGRPC) {
|
|
83
91
|
this.cfg.grpc = require('grpc')
|
|
84
92
|
}
|
|
85
93
|
|
|
86
94
|
if (this.cfg.grpc) {
|
|
87
|
-
|
|
95
|
+
this.cfg.logger.log('!!! DatastoreDB using custom grpc !!!')
|
|
88
96
|
}
|
|
89
97
|
|
|
90
98
|
this.cachedDatastore = new DS(this.cfg)
|
|
@@ -105,11 +113,24 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
|
105
113
|
): Promise<ROW[]> {
|
|
106
114
|
if (!ids.length) return []
|
|
107
115
|
const keys = ids.map(id => this.key(table, id))
|
|
108
|
-
|
|
116
|
+
let rows: any[]
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
rows = (await this.ds().get(keys))[0]
|
|
120
|
+
} catch (err) {
|
|
121
|
+
this.cfg.logger.log('datastore recreated on error')
|
|
122
|
+
|
|
123
|
+
// This is to debug "GCP Datastore Timeout issue"
|
|
124
|
+
const datastoreLib = require('@google-cloud/datastore')
|
|
125
|
+
const DS = datastoreLib.Datastore as typeof Datastore
|
|
126
|
+
this.cachedDatastore = new DS(this.cfg)
|
|
127
|
+
|
|
128
|
+
throw err
|
|
129
|
+
}
|
|
109
130
|
|
|
110
131
|
return (
|
|
111
|
-
|
|
112
|
-
.map(
|
|
132
|
+
rows
|
|
133
|
+
.map(r => this.mapId<ROW>(r))
|
|
113
134
|
// Seems like datastore .get() method doesn't return items properly sorted by input ids, so we gonna sort them here
|
|
114
135
|
// same ids are not expected here
|
|
115
136
|
.sort((a, b) => (a.id > b.id ? 1 : -1))
|
|
@@ -167,7 +188,11 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
|
167
188
|
|
|
168
189
|
return (
|
|
169
190
|
opt.experimentalCursorStream
|
|
170
|
-
? new DatastoreStreamReadable(
|
|
191
|
+
? new DatastoreStreamReadable(
|
|
192
|
+
q,
|
|
193
|
+
opt,
|
|
194
|
+
commonLoggerMinLevel(this.cfg.logger, opt.debug ? 'log' : 'warn'),
|
|
195
|
+
)
|
|
171
196
|
: this.ds().runQueryStream(q)
|
|
172
197
|
).pipe(
|
|
173
198
|
new Transform({
|
|
@@ -209,12 +234,14 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
|
209
234
|
// Here we retry the GOAWAY errors that are somewhat common for Datastore
|
|
210
235
|
// Currently only retrying them here in .saveBatch(), cause probably they're only thrown when saving
|
|
211
236
|
predicate: err => RETRY_ON.some(s => (err as Error)?.message.includes(s)),
|
|
237
|
+
name: `DatastoreLib.saveBatch(${table})`,
|
|
212
238
|
maxAttempts: 5,
|
|
213
239
|
delay: 5000,
|
|
214
240
|
delayMultiplier: 2,
|
|
215
241
|
logFirstAttempt: false,
|
|
216
242
|
logFailures: true,
|
|
217
243
|
// logAll: true,
|
|
244
|
+
logger: this.cfg.logger,
|
|
218
245
|
},
|
|
219
246
|
)
|
|
220
247
|
|
|
@@ -222,7 +249,10 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
|
222
249
|
await pMap(_chunk(entities, MAX_ITEMS), async batch => await save(batch))
|
|
223
250
|
} catch (err) {
|
|
224
251
|
// console.log(`datastore.save ${kind}`, { obj, entity })
|
|
225
|
-
|
|
252
|
+
this.cfg.logger.error(
|
|
253
|
+
`error in DatastoreLib.saveBatch for ${table} (${rows.length} rows)`,
|
|
254
|
+
err,
|
|
255
|
+
)
|
|
226
256
|
// don't throw, because datastore SDK makes it in separate thread, so exception will be unhandled otherwise
|
|
227
257
|
return await Promise.reject(err)
|
|
228
258
|
}
|
package/src/datastore.model.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { DatastoreOptions, Key, Transaction } from '@google-cloud/datastore'
|
|
2
2
|
import { CommonDBOptions, CommonDBSaveOptions } from '@naturalcycles/db-lib'
|
|
3
|
-
import { AnyObjectWithId, ObjectWithId } from '@naturalcycles/js-lib'
|
|
3
|
+
import { AnyObjectWithId, CommonLogger, ObjectWithId } from '@naturalcycles/js-lib'
|
|
4
4
|
|
|
5
5
|
export interface DatastorePayload<T = any> {
|
|
6
6
|
key: Key
|
|
@@ -36,6 +36,11 @@ export interface DatastoreDBCfg extends DatastoreOptions {
|
|
|
36
36
|
* e.g you can globally enable `experimentalCursorStream` here, set the batchSize, etc.
|
|
37
37
|
*/
|
|
38
38
|
streamOptions?: DatastoreDBStreamOptions
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Default to `console`
|
|
42
|
+
*/
|
|
43
|
+
logger?: CommonLogger
|
|
39
44
|
}
|
|
40
45
|
|
|
41
46
|
export interface DatastoreCredentials {
|