@naturalcycles/datastore-lib 3.18.3 → 3.19.2
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/datastore.db.d.ts +4 -4
- package/dist/datastore.db.js +32 -16
- package/package.json +1 -1
- package/src/datastore.db.ts +39 -23
- package/src/datastoreKeyValueDB.ts +1 -1
package/dist/datastore.db.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export declare class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
|
20
20
|
protected KEY: symbol;
|
|
21
21
|
ds(): Datastore;
|
|
22
22
|
ping(): Promise<void>;
|
|
23
|
-
getByIds<ROW extends ObjectWithId>(table: string, ids:
|
|
23
|
+
getByIds<ROW extends ObjectWithId>(table: string, ids: ROW['id'][], _opt?: DatastoreDBOptions): Promise<ROW[]>;
|
|
24
24
|
getQueryKind(q: Query): string;
|
|
25
25
|
runQuery<ROW extends ObjectWithId>(dbQuery: DBQuery<ROW>, _opt?: DatastoreDBOptions): Promise<RunQueryResult<ROW>>;
|
|
26
26
|
runQueryCount<ROW extends ObjectWithId>(dbQuery: DBQuery<ROW>, _opt?: DatastoreDBOptions): Promise<number>;
|
|
@@ -36,7 +36,7 @@ export declare class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
|
36
36
|
* Limitation: Datastore's delete returns void, so we always return all ids here as "deleted"
|
|
37
37
|
* regardless if they were actually deleted or not.
|
|
38
38
|
*/
|
|
39
|
-
deleteByIds(table: string, ids:
|
|
39
|
+
deleteByIds<ROW extends ObjectWithId>(table: string, ids: ROW['id'][], opt?: DatastoreDBOptions): Promise<number>;
|
|
40
40
|
/**
|
|
41
41
|
* https://cloud.google.com/datastore/docs/concepts/transactions#datastore-datastore-transactional-update-nodejs
|
|
42
42
|
*/
|
|
@@ -50,9 +50,9 @@ export declare class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
|
50
50
|
getTableProperties(table: string): Promise<DatastorePropertyStats[]>;
|
|
51
51
|
mapId<T = any>(o: any, preserveKey?: boolean): T;
|
|
52
52
|
toDatastoreEntity<T = any>(kind: string, o: T & {
|
|
53
|
-
id?: string;
|
|
53
|
+
id?: string | number;
|
|
54
54
|
}, excludeFromIndexes?: string[]): DatastorePayload<T>;
|
|
55
|
-
key(kind: string, id: string): Key;
|
|
55
|
+
key(kind: string, id: string | number): Key;
|
|
56
56
|
getDsKey(o: any): Key | undefined;
|
|
57
57
|
getKey(key: Key): string | undefined;
|
|
58
58
|
getTables(): Promise<string[]>;
|
package/dist/datastore.db.js
CHANGED
|
@@ -60,25 +60,35 @@ class DatastoreDB extends db_lib_1.BaseCommonDB {
|
|
|
60
60
|
return [];
|
|
61
61
|
const keys = ids.map(id => this.key(table, id));
|
|
62
62
|
let rows;
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
if (this.cfg.timeout) {
|
|
64
|
+
// First try
|
|
65
|
+
try {
|
|
65
66
|
const r = await (0, js_lib_1.pTimeout)(this.ds().get(keys), {
|
|
66
67
|
timeout: this.cfg.timeout,
|
|
67
68
|
name: `datastore.getByIds(${table})`,
|
|
68
69
|
});
|
|
69
70
|
rows = r[0];
|
|
70
71
|
}
|
|
71
|
-
|
|
72
|
-
|
|
72
|
+
catch {
|
|
73
|
+
this.cfg.logger.log('datastore recreated on error');
|
|
74
|
+
// This is to debug "GCP Datastore Timeout issue"
|
|
75
|
+
const datastoreLib = require('@google-cloud/datastore');
|
|
76
|
+
const DS = datastoreLib.Datastore;
|
|
77
|
+
this.cachedDatastore = new DS(this.cfg);
|
|
78
|
+
// Second try (will throw)
|
|
79
|
+
const r = await (0, js_lib_1.pTimeout)(this.ds().get(keys), {
|
|
80
|
+
timeout: this.cfg.timeout,
|
|
81
|
+
name: `datastore.getByIds(${table}) second try`,
|
|
82
|
+
errorData: {
|
|
83
|
+
// This error will be grouped ACROSS all endpoints and usages
|
|
84
|
+
fingerprint: ['DATASTORE_TIMEOUT'],
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
rows = r[0];
|
|
73
88
|
}
|
|
74
89
|
}
|
|
75
|
-
|
|
76
|
-
this.
|
|
77
|
-
// This is to debug "GCP Datastore Timeout issue"
|
|
78
|
-
const datastoreLib = require('@google-cloud/datastore');
|
|
79
|
-
const DS = datastoreLib.Datastore;
|
|
80
|
-
this.cachedDatastore = new DS(this.cfg);
|
|
81
|
-
throw err;
|
|
90
|
+
else {
|
|
91
|
+
rows = (await this.ds().get(keys))[0];
|
|
82
92
|
}
|
|
83
93
|
return (rows
|
|
84
94
|
.map(r => this.mapId(r))
|
|
@@ -137,12 +147,12 @@ class DatastoreDB extends db_lib_1.BaseCommonDB {
|
|
|
137
147
|
*/
|
|
138
148
|
async saveBatch(table, rows, opt = {}) {
|
|
139
149
|
const entities = rows.map(obj => this.toDatastoreEntity(table, obj, opt.excludeFromIndexes));
|
|
140
|
-
const save = (0, js_lib_1.
|
|
150
|
+
const save = (0, js_lib_1.pRetryFn)(async (batch) => {
|
|
141
151
|
await (opt.tx || this.ds()).save(batch);
|
|
142
152
|
}, {
|
|
143
153
|
// Here we retry the GOAWAY errors that are somewhat common for Datastore
|
|
144
154
|
// Currently only retrying them here in .saveBatch(), cause probably they're only thrown when saving
|
|
145
|
-
predicate: err => RETRY_ON.some(s => err?.message
|
|
155
|
+
predicate: err => RETRY_ON.some(s => err?.message?.includes(s)),
|
|
146
156
|
name: `DatastoreLib.saveBatch(${table})`,
|
|
147
157
|
maxAttempts: 5,
|
|
148
158
|
delay: 5000,
|
|
@@ -153,13 +163,19 @@ class DatastoreDB extends db_lib_1.BaseCommonDB {
|
|
|
153
163
|
logger: this.cfg.logger,
|
|
154
164
|
});
|
|
155
165
|
try {
|
|
156
|
-
|
|
166
|
+
const chunks = (0, js_lib_1._chunk)(entities, MAX_ITEMS);
|
|
167
|
+
if (chunks.length === 1) {
|
|
168
|
+
// Not using pMap in hope to preserve stack trace
|
|
169
|
+
await save(chunks[0]);
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
await (0, js_lib_1.pMap)(chunks, async (batch) => await save(batch));
|
|
173
|
+
}
|
|
157
174
|
}
|
|
158
175
|
catch (err) {
|
|
159
176
|
// console.log(`datastore.save ${kind}`, { obj, entity })
|
|
160
177
|
this.cfg.logger.error(`error in DatastoreLib.saveBatch for ${table} (${rows.length} rows)`, err);
|
|
161
|
-
|
|
162
|
-
return await Promise.reject(err);
|
|
178
|
+
throw err;
|
|
163
179
|
}
|
|
164
180
|
}
|
|
165
181
|
async deleteByQuery(q, opt) {
|
package/package.json
CHANGED
package/src/datastore.db.ts
CHANGED
|
@@ -17,7 +17,6 @@ import {
|
|
|
17
17
|
JsonSchemaObject,
|
|
18
18
|
JsonSchemaString,
|
|
19
19
|
pMap,
|
|
20
|
-
pRetry,
|
|
21
20
|
_assert,
|
|
22
21
|
_chunk,
|
|
23
22
|
_omit,
|
|
@@ -25,6 +24,7 @@ import {
|
|
|
25
24
|
CommonLogger,
|
|
26
25
|
commonLoggerMinLevel,
|
|
27
26
|
pTimeout,
|
|
27
|
+
pRetryFn,
|
|
28
28
|
} from '@naturalcycles/js-lib'
|
|
29
29
|
import { ReadableTyped } from '@naturalcycles/nodejs-lib'
|
|
30
30
|
import { boldWhite } from '@naturalcycles/nodejs-lib/dist/colors'
|
|
@@ -111,32 +111,42 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
|
111
111
|
|
|
112
112
|
override async getByIds<ROW extends ObjectWithId>(
|
|
113
113
|
table: string,
|
|
114
|
-
ids:
|
|
114
|
+
ids: ROW['id'][],
|
|
115
115
|
_opt?: DatastoreDBOptions,
|
|
116
116
|
): Promise<ROW[]> {
|
|
117
117
|
if (!ids.length) return []
|
|
118
118
|
const keys = ids.map(id => this.key(table, id))
|
|
119
119
|
let rows: any[]
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
if (this.cfg.timeout) {
|
|
122
|
+
// First try
|
|
123
|
+
try {
|
|
123
124
|
const r = await pTimeout(this.ds().get(keys), {
|
|
124
125
|
timeout: this.cfg.timeout,
|
|
125
126
|
name: `datastore.getByIds(${table})`,
|
|
126
127
|
})
|
|
127
128
|
rows = r[0]
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
} catch (err) {
|
|
132
|
-
this.cfg.logger.log('datastore recreated on error')
|
|
129
|
+
} catch {
|
|
130
|
+
this.cfg.logger.log('datastore recreated on error')
|
|
133
131
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
132
|
+
// This is to debug "GCP Datastore Timeout issue"
|
|
133
|
+
const datastoreLib = require('@google-cloud/datastore')
|
|
134
|
+
const DS = datastoreLib.Datastore as typeof Datastore
|
|
135
|
+
this.cachedDatastore = new DS(this.cfg)
|
|
138
136
|
|
|
139
|
-
|
|
137
|
+
// Second try (will throw)
|
|
138
|
+
const r = await pTimeout(this.ds().get(keys), {
|
|
139
|
+
timeout: this.cfg.timeout,
|
|
140
|
+
name: `datastore.getByIds(${table}) second try`,
|
|
141
|
+
errorData: {
|
|
142
|
+
// This error will be grouped ACROSS all endpoints and usages
|
|
143
|
+
fingerprint: ['DATASTORE_TIMEOUT'],
|
|
144
|
+
},
|
|
145
|
+
})
|
|
146
|
+
rows = r[0]
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
rows = (await this.ds().get(keys))[0]
|
|
140
150
|
}
|
|
141
151
|
|
|
142
152
|
return (
|
|
@@ -237,14 +247,14 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
|
237
247
|
this.toDatastoreEntity(table, obj, opt.excludeFromIndexes as string[]),
|
|
238
248
|
)
|
|
239
249
|
|
|
240
|
-
const save =
|
|
250
|
+
const save = pRetryFn(
|
|
241
251
|
async (batch: DatastorePayload<ROW>[]) => {
|
|
242
252
|
await (opt.tx || this.ds()).save(batch)
|
|
243
253
|
},
|
|
244
254
|
{
|
|
245
255
|
// Here we retry the GOAWAY errors that are somewhat common for Datastore
|
|
246
256
|
// Currently only retrying them here in .saveBatch(), cause probably they're only thrown when saving
|
|
247
|
-
predicate: err => RETRY_ON.some(s =>
|
|
257
|
+
predicate: err => RETRY_ON.some(s => err?.message?.includes(s)),
|
|
248
258
|
name: `DatastoreLib.saveBatch(${table})`,
|
|
249
259
|
maxAttempts: 5,
|
|
250
260
|
delay: 5000,
|
|
@@ -257,15 +267,21 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
|
257
267
|
)
|
|
258
268
|
|
|
259
269
|
try {
|
|
260
|
-
|
|
270
|
+
const chunks = _chunk(entities, MAX_ITEMS)
|
|
271
|
+
if (chunks.length === 1) {
|
|
272
|
+
// Not using pMap in hope to preserve stack trace
|
|
273
|
+
await save(chunks[0]!)
|
|
274
|
+
} else {
|
|
275
|
+
await pMap(chunks, async batch => await save(batch))
|
|
276
|
+
}
|
|
261
277
|
} catch (err) {
|
|
262
278
|
// console.log(`datastore.save ${kind}`, { obj, entity })
|
|
263
279
|
this.cfg.logger.error(
|
|
264
280
|
`error in DatastoreLib.saveBatch for ${table} (${rows.length} rows)`,
|
|
265
281
|
err,
|
|
266
282
|
)
|
|
267
|
-
|
|
268
|
-
|
|
283
|
+
|
|
284
|
+
throw err
|
|
269
285
|
}
|
|
270
286
|
}
|
|
271
287
|
|
|
@@ -286,9 +302,9 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
|
286
302
|
* Limitation: Datastore's delete returns void, so we always return all ids here as "deleted"
|
|
287
303
|
* regardless if they were actually deleted or not.
|
|
288
304
|
*/
|
|
289
|
-
override async deleteByIds(
|
|
305
|
+
override async deleteByIds<ROW extends ObjectWithId>(
|
|
290
306
|
table: string,
|
|
291
|
-
ids:
|
|
307
|
+
ids: ROW['id'][],
|
|
292
308
|
opt: DatastoreDBOptions = {},
|
|
293
309
|
): Promise<number> {
|
|
294
310
|
const keys = ids.map(id => this.key(table, id))
|
|
@@ -370,7 +386,7 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
|
370
386
|
// if key field exists on entity, it will be used as key (prevent to duplication of numeric keyed entities)
|
|
371
387
|
toDatastoreEntity<T = any>(
|
|
372
388
|
kind: string,
|
|
373
|
-
o: T & { id?: string },
|
|
389
|
+
o: T & { id?: string | number },
|
|
374
390
|
excludeFromIndexes: string[] = [],
|
|
375
391
|
): DatastorePayload<T> {
|
|
376
392
|
const key = this.getDsKey(o) || this.key(kind, o.id!)
|
|
@@ -385,7 +401,7 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
|
|
|
385
401
|
}
|
|
386
402
|
}
|
|
387
403
|
|
|
388
|
-
key(kind: string, id: string): Key {
|
|
404
|
+
key(kind: string, id: string | number): Key {
|
|
389
405
|
_assert(id, `Cannot save "${kind}" entity without "id"`)
|
|
390
406
|
return this.ds().key([kind, String(id)])
|
|
391
407
|
}
|
|
@@ -50,7 +50,7 @@ export class DatastoreKeyValueDB implements CommonKeyValueDB {
|
|
|
50
50
|
.limit(limit || 0)
|
|
51
51
|
|
|
52
52
|
return this.db.streamQuery<KVObject>(q).pipe(
|
|
53
|
-
transformMapSimple<ObjectWithId
|
|
53
|
+
transformMapSimple<ObjectWithId<string>, string>(objectWithId => objectWithId.id, {
|
|
54
54
|
errorMode: ErrorMode.SUPPRESS, // cause .pipe() cannot propagate errors
|
|
55
55
|
}),
|
|
56
56
|
)
|