@naturalcycles/datastore-lib 3.18.1 → 3.19.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.
@@ -35,8 +35,12 @@ class DatastoreDB extends db_lib_1.BaseCommonDB {
35
35
  const datastoreLib = require('@google-cloud/datastore');
36
36
  const DS = datastoreLib.Datastore;
37
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)}`);
38
+ if (this.cfg.projectId) {
39
+ this.cfg.logger.log(`DatastoreDB connected to ${(0, colors_1.boldWhite)(this.cfg.projectId)}`);
40
+ }
41
+ else if (process.env['GOOGLE_APPLICATION_CREDENTIALS']) {
42
+ this.cfg.logger.log(`DatastoreDB connected via GOOGLE_APPLICATION_CREDENTIALS`);
43
+ }
40
44
  if (this.cfg.useLegacyGRPC) {
41
45
  this.cfg.grpc = require('grpc');
42
46
  }
@@ -56,25 +60,35 @@ class DatastoreDB extends db_lib_1.BaseCommonDB {
56
60
  return [];
57
61
  const keys = ids.map(id => this.key(table, id));
58
62
  let rows;
59
- try {
60
- if (this.cfg.timeout) {
63
+ if (this.cfg.timeout) {
64
+ // First try
65
+ try {
61
66
  const r = await (0, js_lib_1.pTimeout)(this.ds().get(keys), {
62
67
  timeout: this.cfg.timeout,
63
68
  name: `datastore.getByIds(${table})`,
64
69
  });
65
70
  rows = r[0];
66
71
  }
67
- else {
68
- rows = (await this.ds().get(keys))[0];
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];
69
88
  }
70
89
  }
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;
90
+ else {
91
+ rows = (await this.ds().get(keys))[0];
78
92
  }
79
93
  return (rows
80
94
  .map(r => this.mapId(r))
@@ -133,12 +147,12 @@ class DatastoreDB extends db_lib_1.BaseCommonDB {
133
147
  */
134
148
  async saveBatch(table, rows, opt = {}) {
135
149
  const entities = rows.map(obj => this.toDatastoreEntity(table, obj, opt.excludeFromIndexes));
136
- const save = (0, js_lib_1.pRetry)(async (batch) => {
150
+ const save = (0, js_lib_1.pRetryFn)(async (batch) => {
137
151
  await (opt.tx || this.ds()).save(batch);
138
152
  }, {
139
153
  // Here we retry the GOAWAY errors that are somewhat common for Datastore
140
154
  // Currently only retrying them here in .saveBatch(), cause probably they're only thrown when saving
141
- predicate: err => RETRY_ON.some(s => err?.message.includes(s)),
155
+ predicate: err => RETRY_ON.some(s => err?.message?.includes(s)),
142
156
  name: `DatastoreLib.saveBatch(${table})`,
143
157
  maxAttempts: 5,
144
158
  delay: 5000,
@@ -149,13 +163,19 @@ class DatastoreDB extends db_lib_1.BaseCommonDB {
149
163
  logger: this.cfg.logger,
150
164
  });
151
165
  try {
152
- await (0, js_lib_1.pMap)((0, js_lib_1._chunk)(entities, MAX_ITEMS), async (batch) => await save(batch));
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
+ }
153
174
  }
154
175
  catch (err) {
155
176
  // console.log(`datastore.save ${kind}`, { obj, entity })
156
177
  this.cfg.logger.error(`error in DatastoreLib.saveBatch for ${table} (${rows.length} rows)`, err);
157
- // don't throw, because datastore SDK makes it in separate thread, so exception will be unhandled otherwise
158
- return await Promise.reject(err);
178
+ throw err;
159
179
  }
160
180
  }
161
181
  async deleteByQuery(q, opt) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/datastore-lib",
3
- "version": "3.18.1",
3
+ "version": "3.19.1",
4
4
  "description": "Opinionated library to work with Google Datastore",
5
5
  "scripts": {
6
6
  "prepare": "husky install"
@@ -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'
@@ -84,9 +84,11 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
84
84
  const DS = datastoreLib.Datastore as typeof Datastore
85
85
  this.cfg.projectId ||= this.cfg.credentials?.project_id || process.env['GOOGLE_CLOUD_PROJECT']
86
86
 
87
- _assert(this.cfg.projectId, '"projectId" is not set for DatastoreDB')
88
-
89
- this.cfg.logger.log(`DatastoreDB connected to ${boldWhite(this.cfg.projectId)}`)
87
+ if (this.cfg.projectId) {
88
+ this.cfg.logger.log(`DatastoreDB connected to ${boldWhite(this.cfg.projectId)}`)
89
+ } else if (process.env['GOOGLE_APPLICATION_CREDENTIALS']) {
90
+ this.cfg.logger.log(`DatastoreDB connected via GOOGLE_APPLICATION_CREDENTIALS`)
91
+ }
90
92
 
91
93
  if (this.cfg.useLegacyGRPC) {
92
94
  this.cfg.grpc = require('grpc')
@@ -116,25 +118,35 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
116
118
  const keys = ids.map(id => this.key(table, id))
117
119
  let rows: any[]
118
120
 
119
- try {
120
- if (this.cfg.timeout) {
121
+ if (this.cfg.timeout) {
122
+ // First try
123
+ try {
121
124
  const r = await pTimeout(this.ds().get(keys), {
122
125
  timeout: this.cfg.timeout,
123
126
  name: `datastore.getByIds(${table})`,
124
127
  })
125
128
  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')
129
+ } catch {
130
+ this.cfg.logger.log('datastore recreated on error')
131
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)
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
136
 
137
- throw err
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]
138
150
  }
139
151
 
140
152
  return (
@@ -235,14 +247,14 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
235
247
  this.toDatastoreEntity(table, obj, opt.excludeFromIndexes as string[]),
236
248
  )
237
249
 
238
- const save = pRetry(
250
+ const save = pRetryFn(
239
251
  async (batch: DatastorePayload<ROW>[]) => {
240
252
  await (opt.tx || this.ds()).save(batch)
241
253
  },
242
254
  {
243
255
  // Here we retry the GOAWAY errors that are somewhat common for Datastore
244
256
  // Currently only retrying them here in .saveBatch(), cause probably they're only thrown when saving
245
- predicate: err => RETRY_ON.some(s => (err as Error)?.message.includes(s)),
257
+ predicate: err => RETRY_ON.some(s => err?.message?.includes(s)),
246
258
  name: `DatastoreLib.saveBatch(${table})`,
247
259
  maxAttempts: 5,
248
260
  delay: 5000,
@@ -255,15 +267,21 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
255
267
  )
256
268
 
257
269
  try {
258
- await pMap(_chunk(entities, MAX_ITEMS), async batch => await save(batch))
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
+ }
259
277
  } catch (err) {
260
278
  // console.log(`datastore.save ${kind}`, { obj, entity })
261
279
  this.cfg.logger.error(
262
280
  `error in DatastoreLib.saveBatch for ${table} (${rows.length} rows)`,
263
281
  err,
264
282
  )
265
- // don't throw, because datastore SDK makes it in separate thread, so exception will be unhandled otherwise
266
- return await Promise.reject(err)
283
+
284
+ throw err
267
285
  }
268
286
  }
269
287