@naturalcycles/datastore-lib 3.28.3 → 3.30.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.
@@ -15,9 +15,18 @@ export declare class DatastoreStreamReadable<T = any> extends Readable implement
15
15
  private lastQueryDone?;
16
16
  private totalWait;
17
17
  private table;
18
+ /**
19
+ * Used to support maxWait
20
+ */
21
+ private lastReadTimestamp;
22
+ private maxWaitInterval;
18
23
  private opt;
19
24
  constructor(q: Query, opt: DatastoreDBStreamOptions, logger: CommonLogger);
20
25
  private runNextQuery;
26
+ /**
27
+ * Counts how many times _read was called.
28
+ * For debugging.
29
+ */
21
30
  count: number;
22
31
  _read(): void;
23
32
  }
@@ -12,7 +12,15 @@ class DatastoreStreamReadable extends node_stream_1.Readable {
12
12
  this.running = false;
13
13
  this.done = false;
14
14
  this.totalWait = 0;
15
- this.count = 0; // use for debugging
15
+ /**
16
+ * Used to support maxWait
17
+ */
18
+ this.lastReadTimestamp = 0;
19
+ /**
20
+ * Counts how many times _read was called.
21
+ * For debugging.
22
+ */
23
+ this.count = 0;
16
24
  this.opt = {
17
25
  rssLimitMB: 1000,
18
26
  batchSize: 1000,
@@ -21,6 +29,25 @@ class DatastoreStreamReadable extends node_stream_1.Readable {
21
29
  this.originalLimit = q.limitVal;
22
30
  this.table = q.kinds[0];
23
31
  logger.log(`!! using experimentalCursorStream !! ${this.table}, batchSize: ${opt.batchSize}`);
32
+ const { maxWait } = this.opt;
33
+ if (maxWait) {
34
+ this.logger.warn(`!! ${this.table} maxWait ${maxWait}`);
35
+ this.maxWaitInterval = setInterval(() => {
36
+ const millisSinceLastRead = Date.now() - this.lastReadTimestamp;
37
+ if (millisSinceLastRead < maxWait * 1000) {
38
+ this.logger.warn(`!! ${this.table} millisSinceLastRead(${millisSinceLastRead}) < maxWait*1000`);
39
+ return;
40
+ }
41
+ const { running, rowsRetrieved } = this;
42
+ this.logger.warn(`maxWait of ${maxWait} seconds reached, force-triggering _read`, {
43
+ running,
44
+ rowsRetrieved,
45
+ });
46
+ // force-trigger _read
47
+ // regardless of `running` status
48
+ this._read();
49
+ }, (maxWait * 1000) / 2);
50
+ }
24
51
  }
25
52
  async runNextQuery() {
26
53
  if (this.done)
@@ -53,6 +80,7 @@ class DatastoreStreamReadable extends node_stream_1.Readable {
53
80
  delay: 5000,
54
81
  delayMultiplier: 2,
55
82
  logger: this.logger,
83
+ timeout: 120000, // 2 minutes
56
84
  });
57
85
  }
58
86
  catch (err) {
@@ -61,6 +89,7 @@ class DatastoreStreamReadable extends node_stream_1.Readable {
61
89
  rowsRetrieved: this.rowsRetrieved,
62
90
  }, err);
63
91
  this.emit('error', err);
92
+ clearInterval(this.maxWaitInterval);
64
93
  return;
65
94
  }
66
95
  this.rowsRetrieved += rows.length;
@@ -75,6 +104,7 @@ class DatastoreStreamReadable extends node_stream_1.Readable {
75
104
  this.logger.log(`!!!! DONE! ${this.rowsRetrieved} rowsRetrieved, totalWait: ${(0, js_lib_1._ms)(this.totalWait)}`);
76
105
  this.push(null);
77
106
  this.done = true;
107
+ clearInterval(this.maxWaitInterval);
78
108
  }
79
109
  else if (this.opt.singleBatchBuffer) {
80
110
  // here we don't start next query until we're asked (via next _read call)
@@ -91,11 +121,9 @@ class DatastoreStreamReadable extends node_stream_1.Readable {
91
121
  }
92
122
  }
93
123
  _read() {
124
+ this.lastReadTimestamp = Date.now();
94
125
  // console.log(`_read called ${++this.count}, wasRunning: ${this.running}`) // debugging
95
126
  this.count++;
96
- if (this.running) {
97
- this.logger.log(`_read ${this.count}, wasRunning: true`);
98
- }
99
127
  if (this.done) {
100
128
  this.logger.warn(`!!! _read was called, but done==true`);
101
129
  return;
@@ -103,6 +131,9 @@ class DatastoreStreamReadable extends node_stream_1.Readable {
103
131
  if (!this.running) {
104
132
  void this.runNextQuery();
105
133
  }
134
+ else {
135
+ this.logger.log(`_read ${this.count}, wasRunning: true`);
136
+ }
106
137
  }
107
138
  }
108
139
  exports.DatastoreStreamReadable = DatastoreStreamReadable;
@@ -6,8 +6,8 @@ const datastore_1 = require("@google-cloud/datastore");
6
6
  const db_lib_1 = require("@naturalcycles/db-lib");
7
7
  const js_lib_1 = require("@naturalcycles/js-lib");
8
8
  const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
9
- const datastore_model_1 = require("./datastore.model");
10
9
  const DatastoreStreamReadable_1 = require("./DatastoreStreamReadable");
10
+ const datastore_model_1 = require("./datastore.model");
11
11
  const query_util_1 = require("./query.util");
12
12
  // Datastore (also Firestore and other Google APIs) supports max 500 of items when saving/deleting, etc.
13
13
  const MAX_ITEMS = 500;
@@ -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 { CommonLogger, ObjectWithId } from '@naturalcycles/js-lib';
3
+ import { CommonLogger, NumberOfSeconds, ObjectWithId } from '@naturalcycles/js-lib';
4
4
  export interface DatastorePayload<T = any> {
5
5
  key: Key;
6
6
  data: T;
@@ -90,6 +90,14 @@ export interface DatastoreDBStreamOptions extends DatastoreDBOptions {
90
90
  * @default false
91
91
  */
92
92
  debug?: boolean;
93
+ /**
94
+ * Default is undefined.
95
+ * If set - sets a "safety timer", which will force call _read after the specified number of seconds.
96
+ * This is to prevent possible "dead-lock"/race-condition that would make the stream "hang".
97
+ *
98
+ * @experimental
99
+ */
100
+ maxWait?: NumberOfSeconds;
93
101
  }
94
102
  export interface DatastoreDBOptions extends CommonDBOptions {
95
103
  tx?: Transaction;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/datastore-lib",
3
- "version": "3.28.3",
3
+ "version": "3.30.0",
4
4
  "description": "Opinionated library to work with Google Datastore",
5
5
  "scripts": {
6
6
  "prepare": "husky install"
@@ -1,7 +1,7 @@
1
1
  import { Readable } from 'node:stream'
2
- import type { RunQueryInfo } from '@google-cloud/datastore/build/src/query'
3
2
  import { Query } from '@google-cloud/datastore'
4
- import { _ms, CommonLogger, pRetry } from '@naturalcycles/js-lib'
3
+ import type { RunQueryInfo } from '@google-cloud/datastore/build/src/query'
4
+ import { _ms, CommonLogger, pRetry, UnixTimestampMillisNumber } from '@naturalcycles/js-lib'
5
5
  import type { ReadableTyped } from '@naturalcycles/nodejs-lib'
6
6
  import type { DatastoreDBStreamOptions } from './datastore.model'
7
7
 
@@ -14,6 +14,11 @@ export class DatastoreStreamReadable<T = any> extends Readable implements Readab
14
14
  private lastQueryDone?: number
15
15
  private totalWait = 0
16
16
  private table: string
17
+ /**
18
+ * Used to support maxWait
19
+ */
20
+ private lastReadTimestamp: UnixTimestampMillisNumber = 0
21
+ private maxWaitInterval: NodeJS.Timeout | undefined
17
22
 
18
23
  private opt: DatastoreDBStreamOptions & { batchSize: number }
19
24
 
@@ -34,6 +39,35 @@ export class DatastoreStreamReadable<T = any> extends Readable implements Readab
34
39
  this.table = q.kinds[0]!
35
40
 
36
41
  logger.log(`!! using experimentalCursorStream !! ${this.table}, batchSize: ${opt.batchSize}`)
42
+
43
+ const { maxWait } = this.opt
44
+ if (maxWait) {
45
+ this.logger.warn(`!! ${this.table} maxWait ${maxWait}`)
46
+
47
+ this.maxWaitInterval = setInterval(
48
+ () => {
49
+ const millisSinceLastRead = Date.now() - this.lastReadTimestamp
50
+
51
+ if (millisSinceLastRead < maxWait * 1000) {
52
+ this.logger.warn(
53
+ `!! ${this.table} millisSinceLastRead(${millisSinceLastRead}) < maxWait*1000`,
54
+ )
55
+ return
56
+ }
57
+
58
+ const { running, rowsRetrieved } = this
59
+ this.logger.warn(`maxWait of ${maxWait} seconds reached, force-triggering _read`, {
60
+ running,
61
+ rowsRetrieved,
62
+ })
63
+
64
+ // force-trigger _read
65
+ // regardless of `running` status
66
+ this._read()
67
+ },
68
+ (maxWait * 1000) / 2,
69
+ )
70
+ }
37
71
  }
38
72
 
39
73
  private async runNextQuery(): Promise<void> {
@@ -75,6 +109,7 @@ export class DatastoreStreamReadable<T = any> extends Readable implements Readab
75
109
  delay: 5000,
76
110
  delayMultiplier: 2,
77
111
  logger: this.logger,
112
+ timeout: 120_000, // 2 minutes
78
113
  },
79
114
  )
80
115
  } catch (err) {
@@ -87,6 +122,7 @@ export class DatastoreStreamReadable<T = any> extends Readable implements Readab
87
122
  err,
88
123
  )
89
124
  this.emit('error', err)
125
+ clearInterval(this.maxWaitInterval)
90
126
  return
91
127
  }
92
128
 
@@ -114,6 +150,7 @@ export class DatastoreStreamReadable<T = any> extends Readable implements Readab
114
150
  )
115
151
  this.push(null)
116
152
  this.done = true
153
+ clearInterval(this.maxWaitInterval)
117
154
  } else if (this.opt.singleBatchBuffer) {
118
155
  // here we don't start next query until we're asked (via next _read call)
119
156
  // do, let's do nothing
@@ -128,14 +165,17 @@ export class DatastoreStreamReadable<T = any> extends Readable implements Readab
128
165
  }
129
166
  }
130
167
 
131
- count = 0 // use for debugging
168
+ /**
169
+ * Counts how many times _read was called.
170
+ * For debugging.
171
+ */
172
+ count = 0
132
173
 
133
174
  override _read(): void {
175
+ this.lastReadTimestamp = Date.now()
176
+
134
177
  // console.log(`_read called ${++this.count}, wasRunning: ${this.running}`) // debugging
135
178
  this.count++
136
- if (this.running) {
137
- this.logger.log(`_read ${this.count}, wasRunning: true`)
138
- }
139
179
 
140
180
  if (this.done) {
141
181
  this.logger.warn(`!!! _read was called, but done==true`)
@@ -144,6 +184,8 @@ export class DatastoreStreamReadable<T = any> extends Readable implements Readab
144
184
 
145
185
  if (!this.running) {
146
186
  void this.runNextQuery()
187
+ } else {
188
+ this.logger.log(`_read ${this.count}, wasRunning: true`)
147
189
  }
148
190
  }
149
191
  }
@@ -30,8 +30,8 @@ import {
30
30
  pRetry,
31
31
  PRetryOptions,
32
32
  } from '@naturalcycles/js-lib'
33
- import { ReadableTyped } from '@naturalcycles/nodejs-lib'
34
- import { boldWhite } from '@naturalcycles/nodejs-lib'
33
+ import { ReadableTyped, boldWhite } from '@naturalcycles/nodejs-lib'
34
+ import { DatastoreStreamReadable } from './DatastoreStreamReadable'
35
35
  import {
36
36
  DatastoreDBCfg,
37
37
  DatastoreDBOptions,
@@ -42,7 +42,6 @@ import {
42
42
  DatastoreStats,
43
43
  DatastoreType,
44
44
  } from './datastore.model'
45
- import { DatastoreStreamReadable } from './DatastoreStreamReadable'
46
45
  import { dbQueryToDatastoreQuery } from './query.util'
47
46
 
48
47
  // Datastore (also Firestore and other Google APIs) supports max 500 of items when saving/deleting, etc.
@@ -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 { CommonLogger, ObjectWithId } from '@naturalcycles/js-lib'
3
+ import { CommonLogger, NumberOfSeconds, ObjectWithId } from '@naturalcycles/js-lib'
4
4
 
5
5
  export interface DatastorePayload<T = any> {
6
6
  key: Key
@@ -103,6 +103,15 @@ export interface DatastoreDBStreamOptions extends DatastoreDBOptions {
103
103
  * @default false
104
104
  */
105
105
  debug?: boolean
106
+
107
+ /**
108
+ * Default is undefined.
109
+ * If set - sets a "safety timer", which will force call _read after the specified number of seconds.
110
+ * This is to prevent possible "dead-lock"/race-condition that would make the stream "hang".
111
+ *
112
+ * @experimental
113
+ */
114
+ maxWait?: NumberOfSeconds
106
115
  }
107
116
 
108
117
  export interface DatastoreDBOptions extends CommonDBOptions {