@naturalcycles/datastore-lib 4.14.0 → 4.15.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.
@@ -4,7 +4,6 @@ import { _round } from '@naturalcycles/js-lib';
4
4
  import { _chunk } from '@naturalcycles/js-lib/array/array.util.js';
5
5
  import { _assert } from '@naturalcycles/js-lib/error/assert.js';
6
6
  import { _errorDataAppend, TimeoutError } from '@naturalcycles/js-lib/error/error.util.js';
7
- import { commonLoggerMinLevel } from '@naturalcycles/js-lib/log';
8
7
  import { _omit } from '@naturalcycles/js-lib/object/object.util.js';
9
8
  import { pMap } from '@naturalcycles/js-lib/promise/pMap.js';
10
9
  import { pRetry, pRetryFn } from '@naturalcycles/js-lib/promise/pRetry.js';
@@ -219,13 +218,14 @@ export class DatastoreDB extends BaseCommonDB {
219
218
  void this.ds().then(async (ds) => {
220
219
  const q = dbQueryToDatastoreQuery(dbQuery, ds.createQuery(dbQuery.table), await this.getPropertyFilter());
221
220
  const opt = {
221
+ logger: this.cfg.logger,
222
222
  ...this.cfg.streamOptions,
223
223
  ..._opt,
224
224
  };
225
225
  (opt.experimentalCursorStream
226
- ? new DatastoreStreamReadable(q, opt, commonLoggerMinLevel(this.cfg.logger, opt.debug ? 'log' : 'warn'))
226
+ ? new DatastoreStreamReadable(q, opt)
227
227
  : ds.runQueryStream(q, this.getRunQueryOptions(opt)))
228
- .on('error', err => transform.emit('error', err))
228
+ .on('error', err => transform.destroy(err))
229
229
  .pipe(transform);
230
230
  });
231
231
  return transform;
@@ -1,6 +1,6 @@
1
1
  import type { DatastoreOptions, Key } from '@google-cloud/datastore';
2
2
  import type { CommonDBOptions, CommonDBReadOptions, CommonDBSaveOptions } from '@naturalcycles/db-lib';
3
- import type { CommonLogger } from '@naturalcycles/js-lib/log';
3
+ import type { CommonLogger, CommonLogLevel } from '@naturalcycles/js-lib/log';
4
4
  import type { NumberOfSeconds, ObjectWithId, PositiveInteger } from '@naturalcycles/js-lib/types';
5
5
  export interface DatastorePayload<T = any> {
6
6
  key: Key;
@@ -30,6 +30,10 @@ export interface DatastoreDBCfg extends DatastoreOptions {
30
30
  * Default to `console`
31
31
  */
32
32
  logger?: CommonLogger;
33
+ /**
34
+ * Defaults to `log`.
35
+ */
36
+ logLevel?: CommonLogLevel;
33
37
  /**
34
38
  * Experimental option, currently only applies to `getByIds`.
35
39
  * Applies pTimeout to Datastore operation, re-creates Datastore on any error.
@@ -70,12 +74,12 @@ export interface DatastoreDBStreamOptions extends DatastoreDBReadOptions {
70
74
  * between the queries.
71
75
  */
72
76
  highWaterMark?: PositiveInteger;
77
+ logger?: CommonLogger;
73
78
  /**
74
- * Set to `true` to log additional debug info, when using experimentalCursorStream.
75
- *
76
- * @default false
79
+ * Defaults to `log`.
80
+ * Set to `debug` to allow for extra debugging, e.g in experimentalCursorStream.
77
81
  */
78
- debug?: boolean;
82
+ logLevel?: CommonLogLevel;
79
83
  /**
80
84
  * Default is undefined.
81
85
  * If set - sets a "safety timer", which will force call _read after the specified number of seconds.
@@ -1,11 +1,9 @@
1
1
  import { Readable } from 'node:stream';
2
2
  import type { Query } from '@google-cloud/datastore';
3
- import type { CommonLogger } from '@naturalcycles/js-lib/log';
4
3
  import type { ReadableTyped } from '@naturalcycles/nodejs-lib/stream';
5
4
  import type { DatastoreDBStreamOptions } from './datastore.model.js';
6
5
  export declare class DatastoreStreamReadable<T = any> extends Readable implements ReadableTyped<T> {
7
6
  private q;
8
- private logger;
9
7
  private readonly table;
10
8
  private readonly originalLimit;
11
9
  private rowsRetrieved;
@@ -26,8 +24,9 @@ export declare class DatastoreStreamReadable<T = any> extends Readable implement
26
24
  private lastReadTimestamp;
27
25
  private readonly maxWaitInterval;
28
26
  private readonly opt;
27
+ private readonly logger;
29
28
  private readonly dsOpt;
30
- constructor(q: Query, opt: DatastoreDBStreamOptions, logger: CommonLogger);
29
+ constructor(q: Query, opt: DatastoreDBStreamOptions);
31
30
  _read(): void;
32
31
  private runNextQuery;
33
32
  private runQuery;
@@ -1,10 +1,10 @@
1
1
  import { Readable } from 'node:stream';
2
2
  import { localTime } from '@naturalcycles/js-lib/datetime/localTime.js';
3
3
  import { _ms } from '@naturalcycles/js-lib/datetime/time.util.js';
4
+ import { createCommonLoggerAtLevel } from '@naturalcycles/js-lib/log';
4
5
  import { pRetry } from '@naturalcycles/js-lib/promise/pRetry.js';
5
6
  export class DatastoreStreamReadable extends Readable {
6
7
  q;
7
- logger;
8
8
  table;
9
9
  originalLimit;
10
10
  rowsRetrieved = 0;
@@ -25,15 +25,15 @@ export class DatastoreStreamReadable extends Readable {
25
25
  lastReadTimestamp = 0;
26
26
  maxWaitInterval;
27
27
  opt;
28
+ logger;
28
29
  dsOpt;
29
- constructor(q, opt, logger) {
30
+ constructor(q, opt) {
30
31
  // 1_000 was optimal in benchmarks
31
32
  const { batchSize = 1000 } = opt;
32
33
  const { highWaterMark = batchSize * 3 } = opt;
33
34
  // Defaulting highWaterMark to 3x batchSize
34
35
  super({ objectMode: true, highWaterMark });
35
36
  this.q = q;
36
- this.logger = logger;
37
37
  this.opt = {
38
38
  ...opt,
39
39
  batchSize,
@@ -44,24 +44,26 @@ export class DatastoreStreamReadable extends Readable {
44
44
  // Datastore expects UnixTimestamp in milliseconds
45
45
  this.dsOpt.readTime = opt.readAt * 1000;
46
46
  }
47
+ const logger = createCommonLoggerAtLevel(opt.logger, opt.logLevel);
48
+ this.logger = logger;
47
49
  this.originalLimit = q.limitVal;
48
50
  this.table = q.kinds[0];
49
- logger.warn(`!! using experimentalCursorStream`, {
51
+ logger.log(`!! using experimentalCursorStream`, {
50
52
  table: this.table,
51
53
  batchSize,
52
54
  highWaterMark,
53
55
  });
54
56
  const { maxWait } = this.opt;
55
57
  if (maxWait) {
56
- logger.warn(`!! ${this.table} maxWait ${maxWait}`);
58
+ logger.log(`!! ${this.table} maxWait ${maxWait}`);
57
59
  this.maxWaitInterval = setInterval(() => {
58
60
  const millisSinceLastRead = Date.now() - this.lastReadTimestamp;
59
61
  if (millisSinceLastRead < maxWait * 1000) {
60
- logger.warn(`!! ${this.table} millisSinceLastRead(${millisSinceLastRead}) < maxWait*1000`);
62
+ logger.log(`!! ${this.table} millisSinceLastRead(${millisSinceLastRead}) < maxWait*1000`);
61
63
  return;
62
64
  }
63
65
  const { queryIsRunning, rowsRetrieved } = this;
64
- logger.warn(`maxWait of ${maxWait} seconds reached, force-triggering _read`, {
66
+ logger.log(`maxWait of ${maxWait} seconds reached, force-triggering _read`, {
65
67
  running: queryIsRunning,
66
68
  rowsRetrieved,
67
69
  });
@@ -72,7 +74,7 @@ export class DatastoreStreamReadable extends Readable {
72
74
  }
73
75
  }
74
76
  _read() {
75
- this.lastReadTimestamp = Date.now();
77
+ this.lastReadTimestamp = localTime.nowUnixMillis();
76
78
  // console.log(`_read called ${++this.count}, wasRunning: ${this.running}`) // debugging
77
79
  this.countReads++;
78
80
  if (this.done) {
@@ -84,12 +86,12 @@ export class DatastoreStreamReadable extends Readable {
84
86
  this.paused = false;
85
87
  }
86
88
  if (this.queryIsRunning) {
87
- this.logger.log(`_read #${this.countReads}, queryIsRunning: true, doing nothing`);
89
+ this.logger.debug(`_read #${this.countReads}, queryIsRunning: true, doing nothing`);
88
90
  return;
89
91
  }
90
92
  void this.runNextQuery().catch(err => {
91
- console.log('error in runNextQuery', err);
92
- this.emit('error', err);
93
+ this.logger.error('error in runNextQuery', err);
94
+ this.destroy(err);
93
95
  });
94
96
  }
95
97
  async runNextQuery() {
@@ -139,13 +141,13 @@ export class DatastoreStreamReadable extends Readable {
139
141
  }
140
142
  if (shouldContinue) {
141
143
  // Keep the stream flowing
142
- logger.log(`${table} continuing the stream`);
144
+ logger.debug(`${table} continuing the stream`);
143
145
  void this.runNextQuery();
144
146
  }
145
147
  else {
146
148
  // Not starting the next query
147
149
  if (this.paused) {
148
- logger.log(`${table} stream is already paused`);
150
+ logger.debug(`${table} stream is already paused`);
149
151
  }
150
152
  else {
151
153
  logger.log(`${table} pausing the stream`);
@@ -160,6 +162,7 @@ export class DatastoreStreamReadable extends Readable {
160
162
  return await q.run(this.dsOpt);
161
163
  }, {
162
164
  name: `DatastoreStreamReadable.query(${table})`,
165
+ predicate: err => RETRY_ON.some(s => err?.message?.toLowerCase()?.includes(s)),
163
166
  maxAttempts: 5,
164
167
  delay: 5000,
165
168
  delayMultiplier: 2,
@@ -168,13 +171,25 @@ export class DatastoreStreamReadable extends Readable {
168
171
  });
169
172
  }
170
173
  catch (err) {
171
- console.log(`DatastoreStreamReadable error!\n`, {
174
+ logger.error(`DatastoreStreamReadable error!\n`, {
172
175
  table,
173
176
  rowsRetrieved: this.rowsRetrieved,
174
177
  }, err);
175
- this.emit('error', err);
176
178
  clearInterval(this.maxWaitInterval);
179
+ this.destroy(err);
177
180
  return;
178
181
  }
179
182
  }
180
183
  }
184
+ // Examples of errors:
185
+ // UNKNOWN: Stream removed
186
+ const RETRY_ON = [
187
+ 'GOAWAY',
188
+ 'UNAVAILABLE',
189
+ 'UNKNOWN',
190
+ 'DEADLINE_EXCEEDED',
191
+ 'ABORTED',
192
+ 'much contention',
193
+ 'try again',
194
+ 'timeout',
195
+ ].map(s => s.toLowerCase());
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@naturalcycles/datastore-lib",
3
3
  "type": "module",
4
- "version": "4.14.0",
4
+ "version": "4.15.1",
5
5
  "description": "Opinionated library to work with Google Datastore, implements CommonDB",
6
6
  "dependencies": {
7
7
  "@google-cloud/datastore": "^10",
@@ -29,7 +29,6 @@ import type {
29
29
  JsonSchemaString,
30
30
  } from '@naturalcycles/js-lib/json-schema'
31
31
  import type { CommonLogger } from '@naturalcycles/js-lib/log'
32
- import { commonLoggerMinLevel } from '@naturalcycles/js-lib/log'
33
32
  import { _omit } from '@naturalcycles/js-lib/object/object.util.js'
34
33
  import type { PRetryOptions } from '@naturalcycles/js-lib/promise'
35
34
  import { pMap } from '@naturalcycles/js-lib/promise/pMap.js'
@@ -344,19 +343,16 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
344
343
  )
345
344
 
346
345
  const opt = {
346
+ logger: this.cfg.logger,
347
347
  ...this.cfg.streamOptions,
348
348
  ..._opt,
349
349
  }
350
350
 
351
351
  ;(opt.experimentalCursorStream
352
- ? new DatastoreStreamReadable<ROW>(
353
- q,
354
- opt,
355
- commonLoggerMinLevel(this.cfg.logger, opt.debug ? 'log' : 'warn'),
356
- )
352
+ ? new DatastoreStreamReadable<ROW>(q, opt)
357
353
  : (ds.runQueryStream(q, this.getRunQueryOptions(opt)) as ReadableTyped<ROW>)
358
354
  )
359
- .on('error', err => transform.emit('error', err))
355
+ .on('error', err => transform.destroy(err))
360
356
  .pipe(transform)
361
357
  })
362
358
 
@@ -4,7 +4,7 @@ import type {
4
4
  CommonDBReadOptions,
5
5
  CommonDBSaveOptions,
6
6
  } from '@naturalcycles/db-lib'
7
- import type { CommonLogger } from '@naturalcycles/js-lib/log'
7
+ import type { CommonLogger, CommonLogLevel } from '@naturalcycles/js-lib/log'
8
8
  import type { NumberOfSeconds, ObjectWithId, PositiveInteger } from '@naturalcycles/js-lib/types'
9
9
 
10
10
  export interface DatastorePayload<T = any> {
@@ -41,6 +41,11 @@ export interface DatastoreDBCfg extends DatastoreOptions {
41
41
  */
42
42
  logger?: CommonLogger
43
43
 
44
+ /**
45
+ * Defaults to `log`.
46
+ */
47
+ logLevel?: CommonLogLevel
48
+
44
49
  /**
45
50
  * Experimental option, currently only applies to `getByIds`.
46
51
  * Applies pTimeout to Datastore operation, re-creates Datastore on any error.
@@ -86,12 +91,13 @@ export interface DatastoreDBStreamOptions extends DatastoreDBReadOptions {
86
91
  */
87
92
  highWaterMark?: PositiveInteger
88
93
 
94
+ logger?: CommonLogger
95
+
89
96
  /**
90
- * Set to `true` to log additional debug info, when using experimentalCursorStream.
91
- *
92
- * @default false
97
+ * Defaults to `log`.
98
+ * Set to `debug` to allow for extra debugging, e.g in experimentalCursorStream.
93
99
  */
94
- debug?: boolean
100
+ logLevel?: CommonLogLevel
95
101
 
96
102
  /**
97
103
  * Default is undefined.
@@ -7,7 +7,7 @@ import type {
7
7
  } from '@google-cloud/datastore/build/src/query.js'
8
8
  import { localTime } from '@naturalcycles/js-lib/datetime/localTime.js'
9
9
  import { _ms } from '@naturalcycles/js-lib/datetime/time.util.js'
10
- import type { CommonLogger } from '@naturalcycles/js-lib/log'
10
+ import { type CommonLogger, createCommonLoggerAtLevel } from '@naturalcycles/js-lib/log'
11
11
  import { pRetry } from '@naturalcycles/js-lib/promise/pRetry.js'
12
12
  import type { UnixTimestampMillis } from '@naturalcycles/js-lib/types'
13
13
  import type { ReadableTyped } from '@naturalcycles/nodejs-lib/stream'
@@ -35,12 +35,12 @@ export class DatastoreStreamReadable<T = any> extends Readable implements Readab
35
35
  private readonly maxWaitInterval: NodeJS.Timeout | undefined
36
36
 
37
37
  private readonly opt: DatastoreDBStreamOptions & { batchSize: number; highWaterMark: number }
38
+ private readonly logger: CommonLogger
38
39
  private readonly dsOpt: RunQueryOptions
39
40
 
40
41
  constructor(
41
42
  private q: Query,
42
43
  opt: DatastoreDBStreamOptions,
43
- private logger: CommonLogger,
44
44
  ) {
45
45
  // 1_000 was optimal in benchmarks
46
46
  const { batchSize = 1000 } = opt
@@ -59,10 +59,12 @@ export class DatastoreStreamReadable<T = any> extends Readable implements Readab
59
59
  this.dsOpt.readTime = opt.readAt * 1000
60
60
  }
61
61
 
62
+ const logger = createCommonLoggerAtLevel(opt.logger, opt.logLevel)
63
+ this.logger = logger
62
64
  this.originalLimit = q.limitVal
63
65
  this.table = q.kinds[0]!
64
66
 
65
- logger.warn(`!! using experimentalCursorStream`, {
67
+ logger.log(`!! using experimentalCursorStream`, {
66
68
  table: this.table,
67
69
  batchSize,
68
70
  highWaterMark,
@@ -70,21 +72,21 @@ export class DatastoreStreamReadable<T = any> extends Readable implements Readab
70
72
 
71
73
  const { maxWait } = this.opt
72
74
  if (maxWait) {
73
- logger.warn(`!! ${this.table} maxWait ${maxWait}`)
75
+ logger.log(`!! ${this.table} maxWait ${maxWait}`)
74
76
 
75
77
  this.maxWaitInterval = setInterval(
76
78
  () => {
77
79
  const millisSinceLastRead = Date.now() - this.lastReadTimestamp
78
80
 
79
81
  if (millisSinceLastRead < maxWait * 1000) {
80
- logger.warn(
82
+ logger.log(
81
83
  `!! ${this.table} millisSinceLastRead(${millisSinceLastRead}) < maxWait*1000`,
82
84
  )
83
85
  return
84
86
  }
85
87
 
86
88
  const { queryIsRunning, rowsRetrieved } = this
87
- logger.warn(`maxWait of ${maxWait} seconds reached, force-triggering _read`, {
89
+ logger.log(`maxWait of ${maxWait} seconds reached, force-triggering _read`, {
88
90
  running: queryIsRunning,
89
91
  rowsRetrieved,
90
92
  })
@@ -99,7 +101,7 @@ export class DatastoreStreamReadable<T = any> extends Readable implements Readab
99
101
  }
100
102
 
101
103
  override _read(): void {
102
- this.lastReadTimestamp = Date.now() as UnixTimestampMillis
104
+ this.lastReadTimestamp = localTime.nowUnixMillis()
103
105
 
104
106
  // console.log(`_read called ${++this.count}, wasRunning: ${this.running}`) // debugging
105
107
  this.countReads++
@@ -117,13 +119,13 @@ export class DatastoreStreamReadable<T = any> extends Readable implements Readab
117
119
  }
118
120
 
119
121
  if (this.queryIsRunning) {
120
- this.logger.log(`_read #${this.countReads}, queryIsRunning: true, doing nothing`)
122
+ this.logger.debug(`_read #${this.countReads}, queryIsRunning: true, doing nothing`)
121
123
  return
122
124
  }
123
125
 
124
126
  void this.runNextQuery().catch(err => {
125
- console.log('error in runNextQuery', err)
126
- this.emit('error', err)
127
+ this.logger.error('error in runNextQuery', err)
128
+ this.destroy(err)
127
129
  })
128
130
  }
129
131
 
@@ -192,12 +194,12 @@ export class DatastoreStreamReadable<T = any> extends Readable implements Readab
192
194
 
193
195
  if (shouldContinue) {
194
196
  // Keep the stream flowing
195
- logger.log(`${table} continuing the stream`)
197
+ logger.debug(`${table} continuing the stream`)
196
198
  void this.runNextQuery()
197
199
  } else {
198
200
  // Not starting the next query
199
201
  if (this.paused) {
200
- logger.log(`${table} stream is already paused`)
202
+ logger.debug(`${table} stream is already paused`)
201
203
  } else {
202
204
  logger.log(`${table} pausing the stream`)
203
205
  this.paused = true
@@ -215,6 +217,7 @@ export class DatastoreStreamReadable<T = any> extends Readable implements Readab
215
217
  },
216
218
  {
217
219
  name: `DatastoreStreamReadable.query(${table})`,
220
+ predicate: err => RETRY_ON.some(s => err?.message?.toLowerCase()?.includes(s)),
218
221
  maxAttempts: 5,
219
222
  delay: 5000,
220
223
  delayMultiplier: 2,
@@ -223,7 +226,7 @@ export class DatastoreStreamReadable<T = any> extends Readable implements Readab
223
226
  },
224
227
  )
225
228
  } catch (err) {
226
- console.log(
229
+ logger.error(
227
230
  `DatastoreStreamReadable error!\n`,
228
231
  {
229
232
  table,
@@ -231,9 +234,22 @@ export class DatastoreStreamReadable<T = any> extends Readable implements Readab
231
234
  },
232
235
  err,
233
236
  )
234
- this.emit('error', err)
235
237
  clearInterval(this.maxWaitInterval)
238
+ this.destroy(err as Error)
236
239
  return
237
240
  }
238
241
  }
239
242
  }
243
+
244
+ // Examples of errors:
245
+ // UNKNOWN: Stream removed
246
+ const RETRY_ON = [
247
+ 'GOAWAY',
248
+ 'UNAVAILABLE',
249
+ 'UNKNOWN',
250
+ 'DEADLINE_EXCEEDED',
251
+ 'ABORTED',
252
+ 'much contention',
253
+ 'try again',
254
+ 'timeout',
255
+ ].map(s => s.toLowerCase())