@naturalcycles/datastore-lib 3.28.3 → 3.29.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,12 +12,35 @@ 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,
19
27
  ...opt,
20
28
  };
29
+ if (this.opt.maxWait) {
30
+ this.maxWaitInterval = setInterval(() => {
31
+ if (Date.now() - this.lastReadTimestamp < this.opt.maxWait * 1000) {
32
+ return;
33
+ }
34
+ const { running, rowsRetrieved } = this;
35
+ this.logger.warn(`maxWait of ${this.opt.maxWait} seconds reached, force-triggering _read`, {
36
+ running,
37
+ rowsRetrieved,
38
+ });
39
+ // force-trigger _read
40
+ // regardless of `running` status
41
+ this._read();
42
+ }, (this.opt.maxWait * 1000) / 2);
43
+ }
21
44
  this.originalLimit = q.limitVal;
22
45
  this.table = q.kinds[0];
23
46
  logger.log(`!! using experimentalCursorStream !! ${this.table}, batchSize: ${opt.batchSize}`);
@@ -53,6 +76,7 @@ class DatastoreStreamReadable extends node_stream_1.Readable {
53
76
  delay: 5000,
54
77
  delayMultiplier: 2,
55
78
  logger: this.logger,
79
+ timeout: 120000, // 2 minutes
56
80
  });
57
81
  }
58
82
  catch (err) {
@@ -75,6 +99,7 @@ class DatastoreStreamReadable extends node_stream_1.Readable {
75
99
  this.logger.log(`!!!! DONE! ${this.rowsRetrieved} rowsRetrieved, totalWait: ${(0, js_lib_1._ms)(this.totalWait)}`);
76
100
  this.push(null);
77
101
  this.done = true;
102
+ clearInterval(this.maxWaitInterval);
78
103
  }
79
104
  else if (this.opt.singleBatchBuffer) {
80
105
  // here we don't start next query until we're asked (via next _read call)
@@ -91,11 +116,9 @@ class DatastoreStreamReadable extends node_stream_1.Readable {
91
116
  }
92
117
  }
93
118
  _read() {
119
+ this.lastReadTimestamp = Date.now();
94
120
  // console.log(`_read called ${++this.count}, wasRunning: ${this.running}`) // debugging
95
121
  this.count++;
96
- if (this.running) {
97
- this.logger.log(`_read ${this.count}, wasRunning: true`);
98
- }
99
122
  if (this.done) {
100
123
  this.logger.warn(`!!! _read was called, but done==true`);
101
124
  return;
@@ -103,6 +126,9 @@ class DatastoreStreamReadable extends node_stream_1.Readable {
103
126
  if (!this.running) {
104
127
  void this.runNextQuery();
105
128
  }
129
+ else {
130
+ this.logger.log(`_read ${this.count}, wasRunning: true`);
131
+ }
106
132
  }
107
133
  }
108
134
  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.29.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
 
@@ -30,6 +35,30 @@ export class DatastoreStreamReadable<T = any> extends Readable implements Readab
30
35
  ...opt,
31
36
  }
32
37
 
38
+ if (this.opt.maxWait) {
39
+ this.maxWaitInterval = setInterval(
40
+ () => {
41
+ if (Date.now() - this.lastReadTimestamp < this.opt.maxWait! * 1000) {
42
+ return
43
+ }
44
+
45
+ const { running, rowsRetrieved } = this
46
+ this.logger.warn(
47
+ `maxWait of ${this.opt.maxWait} seconds reached, force-triggering _read`,
48
+ {
49
+ running,
50
+ rowsRetrieved,
51
+ },
52
+ )
53
+
54
+ // force-trigger _read
55
+ // regardless of `running` status
56
+ this._read()
57
+ },
58
+ (this.opt.maxWait * 1000) / 2,
59
+ )
60
+ }
61
+
33
62
  this.originalLimit = q.limitVal
34
63
  this.table = q.kinds[0]!
35
64
 
@@ -75,6 +104,7 @@ export class DatastoreStreamReadable<T = any> extends Readable implements Readab
75
104
  delay: 5000,
76
105
  delayMultiplier: 2,
77
106
  logger: this.logger,
107
+ timeout: 120_000, // 2 minutes
78
108
  },
79
109
  )
80
110
  } catch (err) {
@@ -114,6 +144,7 @@ export class DatastoreStreamReadable<T = any> extends Readable implements Readab
114
144
  )
115
145
  this.push(null)
116
146
  this.done = true
147
+ clearInterval(this.maxWaitInterval)
117
148
  } else if (this.opt.singleBatchBuffer) {
118
149
  // here we don't start next query until we're asked (via next _read call)
119
150
  // do, let's do nothing
@@ -128,14 +159,17 @@ export class DatastoreStreamReadable<T = any> extends Readable implements Readab
128
159
  }
129
160
  }
130
161
 
131
- count = 0 // use for debugging
162
+ /**
163
+ * Counts how many times _read was called.
164
+ * For debugging.
165
+ */
166
+ count = 0
132
167
 
133
168
  override _read(): void {
169
+ this.lastReadTimestamp = Date.now()
170
+
134
171
  // console.log(`_read called ${++this.count}, wasRunning: ${this.running}`) // debugging
135
172
  this.count++
136
- if (this.running) {
137
- this.logger.log(`_read ${this.count}, wasRunning: true`)
138
- }
139
173
 
140
174
  if (this.done) {
141
175
  this.logger.warn(`!!! _read was called, but done==true`)
@@ -144,6 +178,8 @@ export class DatastoreStreamReadable<T = any> extends Readable implements Readab
144
178
 
145
179
  if (!this.running) {
146
180
  void this.runNextQuery()
181
+ } else {
182
+ this.logger.log(`_read ${this.count}, wasRunning: true`)
147
183
  }
148
184
  }
149
185
  }
@@ -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 {