@naturalcycles/datastore-lib 3.17.0 → 3.18.1

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.
@@ -55,9 +55,29 @@ 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
- const [entities] = await this.ds().get(keys);
59
- return (entities
60
- .map(e => this.mapId(e))
58
+ let rows;
59
+ try {
60
+ if (this.cfg.timeout) {
61
+ const r = await (0, js_lib_1.pTimeout)(this.ds().get(keys), {
62
+ timeout: this.cfg.timeout,
63
+ name: `datastore.getByIds(${table})`,
64
+ });
65
+ rows = r[0];
66
+ }
67
+ else {
68
+ rows = (await this.ds().get(keys))[0];
69
+ }
70
+ }
71
+ catch (err) {
72
+ this.cfg.logger.log('datastore recreated on error');
73
+ // This is to debug "GCP Datastore Timeout issue"
74
+ const datastoreLib = require('@google-cloud/datastore');
75
+ const DS = datastoreLib.Datastore;
76
+ this.cachedDatastore = new DS(this.cfg);
77
+ throw err;
78
+ }
79
+ return (rows
80
+ .map(r => this.mapId(r))
61
81
  // Seems like datastore .get() method doesn't return items properly sorted by input ids, so we gonna sort them here
62
82
  // same ids are not expected here
63
83
  .sort((a, b) => (a.id > b.id ? 1 : -1)));
@@ -119,19 +139,21 @@ class DatastoreDB extends db_lib_1.BaseCommonDB {
119
139
  // Here we retry the GOAWAY errors that are somewhat common for Datastore
120
140
  // Currently only retrying them here in .saveBatch(), cause probably they're only thrown when saving
121
141
  predicate: err => RETRY_ON.some(s => err?.message.includes(s)),
142
+ name: `DatastoreLib.saveBatch(${table})`,
122
143
  maxAttempts: 5,
123
144
  delay: 5000,
124
145
  delayMultiplier: 2,
125
146
  logFirstAttempt: false,
126
147
  logFailures: true,
127
148
  // logAll: true,
149
+ logger: this.cfg.logger,
128
150
  });
129
151
  try {
130
152
  await (0, js_lib_1.pMap)((0, js_lib_1._chunk)(entities, MAX_ITEMS), async (batch) => await save(batch));
131
153
  }
132
154
  catch (err) {
133
155
  // console.log(`datastore.save ${kind}`, { obj, entity })
134
- this.cfg.logger.error(`error in datastore.save for ${table}`, err);
156
+ this.cfg.logger.error(`error in DatastoreLib.saveBatch for ${table} (${rows.length} rows)`, err);
135
157
  // don't throw, because datastore SDK makes it in separate thread, so exception will be unhandled otherwise
136
158
  return await Promise.reject(err);
137
159
  }
@@ -34,6 +34,13 @@ export interface DatastoreDBCfg extends DatastoreOptions {
34
34
  * Default to `console`
35
35
  */
36
36
  logger?: CommonLogger;
37
+ /**
38
+ * Experimental option, currently only applies to `getByIds`.
39
+ * Applies pTimeout to Datastore operation, re-creates Datastore on any error.
40
+ *
41
+ * @experimental
42
+ */
43
+ timeout?: number;
37
44
  }
38
45
  export interface DatastoreCredentials {
39
46
  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.17.0",
3
+ "version": "3.18.1",
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": "^16.0.0",
20
+ "@types/node": "^17.0.8",
21
21
  "jest": "^27.0.4"
22
22
  },
23
23
  "files": [
@@ -24,6 +24,7 @@ import {
24
24
  JsonSchemaRootObject,
25
25
  CommonLogger,
26
26
  commonLoggerMinLevel,
27
+ pTimeout,
27
28
  } from '@naturalcycles/js-lib'
28
29
  import { ReadableTyped } from '@naturalcycles/nodejs-lib'
29
30
  import { boldWhite } from '@naturalcycles/nodejs-lib/dist/colors'
@@ -113,11 +114,32 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
113
114
  ): Promise<ROW[]> {
114
115
  if (!ids.length) return []
115
116
  const keys = ids.map(id => this.key(table, id))
116
- const [entities] = await this.ds().get(keys)
117
+ let rows: any[]
118
+
119
+ try {
120
+ if (this.cfg.timeout) {
121
+ const r = await pTimeout(this.ds().get(keys), {
122
+ timeout: this.cfg.timeout,
123
+ name: `datastore.getByIds(${table})`,
124
+ })
125
+ rows = r[0]
126
+ } else {
127
+ rows = (await this.ds().get(keys))[0]
128
+ }
129
+ } catch (err) {
130
+ this.cfg.logger.log('datastore recreated on error')
131
+
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)
136
+
137
+ throw err
138
+ }
117
139
 
118
140
  return (
119
- (entities as any[])
120
- .map(e => this.mapId<ROW>(e))
141
+ rows
142
+ .map(r => this.mapId<ROW>(r))
121
143
  // Seems like datastore .get() method doesn't return items properly sorted by input ids, so we gonna sort them here
122
144
  // same ids are not expected here
123
145
  .sort((a, b) => (a.id > b.id ? 1 : -1))
@@ -221,12 +243,14 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
221
243
  // Here we retry the GOAWAY errors that are somewhat common for Datastore
222
244
  // Currently only retrying them here in .saveBatch(), cause probably they're only thrown when saving
223
245
  predicate: err => RETRY_ON.some(s => (err as Error)?.message.includes(s)),
246
+ name: `DatastoreLib.saveBatch(${table})`,
224
247
  maxAttempts: 5,
225
248
  delay: 5000,
226
249
  delayMultiplier: 2,
227
250
  logFirstAttempt: false,
228
251
  logFailures: true,
229
252
  // logAll: true,
253
+ logger: this.cfg.logger,
230
254
  },
231
255
  )
232
256
 
@@ -234,7 +258,10 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
234
258
  await pMap(_chunk(entities, MAX_ITEMS), async batch => await save(batch))
235
259
  } catch (err) {
236
260
  // console.log(`datastore.save ${kind}`, { obj, entity })
237
- this.cfg.logger.error(`error in datastore.save for ${table}`, err)
261
+ this.cfg.logger.error(
262
+ `error in DatastoreLib.saveBatch for ${table} (${rows.length} rows)`,
263
+ err,
264
+ )
238
265
  // don't throw, because datastore SDK makes it in separate thread, so exception will be unhandled otherwise
239
266
  return await Promise.reject(err)
240
267
  }
@@ -41,6 +41,14 @@ export interface DatastoreDBCfg extends DatastoreOptions {
41
41
  * Default to `console`
42
42
  */
43
43
  logger?: CommonLogger
44
+
45
+ /**
46
+ * Experimental option, currently only applies to `getByIds`.
47
+ * Applies pTimeout to Datastore operation, re-creates Datastore on any error.
48
+ *
49
+ * @experimental
50
+ */
51
+ timeout?: number
44
52
  }
45
53
 
46
54
  export interface DatastoreCredentials {
@@ -76,4 +76,9 @@ export class DatastoreKeyValueDB implements CommonKeyValueDB {
76
76
  }),
77
77
  )
78
78
  }
79
+
80
+ async count(_table: string): Promise<number> {
81
+ this.db.cfg.logger.warn(`DatastoreKeyValueDB.count is not supported`)
82
+ return 0
83
+ }
79
84
  }