@naturalcycles/datastore-lib 4.15.0 → 4.16.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.
@@ -4,7 +4,7 @@ import { BaseCommonDB } from '@naturalcycles/db-lib';
4
4
  import type { JsonSchemaObject, JsonSchemaRootObject } from '@naturalcycles/js-lib/json-schema';
5
5
  import type { CommonLogger } from '@naturalcycles/js-lib/log';
6
6
  import { type ObjectWithId, type StringMap } from '@naturalcycles/js-lib/types';
7
- import type { ReadableTyped } from '@naturalcycles/nodejs-lib/stream';
7
+ import { Pipeline } from '@naturalcycles/nodejs-lib/stream';
8
8
  import type { DatastoreDBCfg, DatastoreDBOptions, DatastoreDBReadOptions, DatastoreDBSaveOptions, DatastoreDBStreamOptions, DatastorePropertyStats, DatastoreStats } from './datastore.model.js';
9
9
  /**
10
10
  * Datastore API:
@@ -31,7 +31,7 @@ export declare class DatastoreDB extends BaseCommonDB implements CommonDB {
31
31
  runQuery<ROW extends ObjectWithId>(dbQuery: DBQuery<ROW>, opt?: DatastoreDBReadOptions): Promise<RunQueryResult<ROW>>;
32
32
  runQueryCount<ROW extends ObjectWithId>(dbQuery: DBQuery<ROW>, opt?: DatastoreDBReadOptions): Promise<number>;
33
33
  private runDatastoreQuery;
34
- streamQuery<ROW extends ObjectWithId>(dbQuery: DBQuery<ROW>, _opt?: DatastoreDBStreamOptions): ReadableTyped<ROW>;
34
+ streamQuery<ROW extends ObjectWithId>(dbQuery: DBQuery<ROW>, _opt?: DatastoreDBStreamOptions): Pipeline<ROW>;
35
35
  /**
36
36
  * Returns saved entities with generated id/updated/created (non-mutating!)
37
37
  */
@@ -1,4 +1,3 @@
1
- import { Transform } from 'node:stream';
2
1
  import { BaseCommonDB, commonDBFullSupport } from '@naturalcycles/db-lib';
3
2
  import { _round } from '@naturalcycles/js-lib';
4
3
  import { _chunk } from '@naturalcycles/js-lib/array/array.util.js';
@@ -10,6 +9,7 @@ import { pRetry, pRetryFn } from '@naturalcycles/js-lib/promise/pRetry.js';
10
9
  import { pTimeout } from '@naturalcycles/js-lib/promise/pTimeout.js';
11
10
  import { _stringMapEntries, _stringMapValues, } from '@naturalcycles/js-lib/types';
12
11
  import { boldWhite } from '@naturalcycles/nodejs-lib/colors';
12
+ import { Pipeline } from '@naturalcycles/nodejs-lib/stream';
13
13
  import { DatastoreType } from './datastore.model.js';
14
14
  import { DatastoreStreamReadable } from './datastoreStreamReadable.js';
15
15
  import { dbQueryToDatastoreQuery } from './query.util.js';
@@ -209,26 +209,18 @@ export class DatastoreDB extends BaseCommonDB {
209
209
  };
210
210
  }
211
211
  streamQuery(dbQuery, _opt) {
212
- const transform = new Transform({
213
- objectMode: true,
214
- transform: (chunk, _, cb) => {
215
- cb(null, this.mapId(chunk));
216
- },
217
- });
218
- void this.ds().then(async (ds) => {
212
+ return Pipeline.fromAsyncReadable(async () => {
213
+ const ds = await this.ds();
219
214
  const q = dbQueryToDatastoreQuery(dbQuery, ds.createQuery(dbQuery.table), await this.getPropertyFilter());
220
215
  const opt = {
221
216
  logger: this.cfg.logger,
222
217
  ...this.cfg.streamOptions,
223
218
  ..._opt,
224
219
  };
225
- (opt.experimentalCursorStream
220
+ return opt.experimentalCursorStream
226
221
  ? new DatastoreStreamReadable(q, opt)
227
- : ds.runQueryStream(q, this.getRunQueryOptions(opt)))
228
- .on('error', err => transform.emit('error', err))
229
- .pipe(transform);
230
- });
231
- return transform;
222
+ : ds.runQueryStream(q, this.getRunQueryOptions(opt));
223
+ }).mapSync(r => this.mapId(r));
232
224
  }
233
225
  // https://github.com/GoogleCloudPlatform/nodejs-getting-started/blob/master/2-structured-data/books/model-datastore.js
234
226
  /**
@@ -1,5 +1,5 @@
1
1
  import type { CommonKeyValueDB, IncrementTuple, KeyValueDBTuple } from '@naturalcycles/db-lib/kv';
2
- import type { ReadableTyped } from '@naturalcycles/nodejs-lib/stream';
2
+ import type { Pipeline } from '@naturalcycles/nodejs-lib/stream';
3
3
  import { DatastoreDB } from './datastore.db.js';
4
4
  import type { DatastoreDBCfg } from './datastore.model.js';
5
5
  export interface DatastoreKeyValueDBCfg extends DatastoreDBCfg {
@@ -14,9 +14,9 @@ export declare class DatastoreKeyValueDB implements CommonKeyValueDB {
14
14
  getByIds(table: string, ids: string[]): Promise<KeyValueDBTuple[]>;
15
15
  deleteByIds(table: string, ids: string[]): Promise<void>;
16
16
  saveBatch(table: string, entries: KeyValueDBTuple[]): Promise<void>;
17
- streamIds(table: string, limit?: number): ReadableTyped<string>;
18
- streamValues(table: string, limit?: number): ReadableTyped<Buffer>;
19
- streamEntries(table: string, limit?: number): ReadableTyped<KeyValueDBTuple>;
17
+ streamIds(table: string, limit?: number): Pipeline<string>;
18
+ streamValues(table: string, limit?: number): Pipeline<Buffer>;
19
+ streamEntries(table: string, limit?: number): Pipeline<KeyValueDBTuple>;
20
20
  count(table: string): Promise<number>;
21
21
  incrementBatch(_table: string, _entries: IncrementTuple[]): Promise<IncrementTuple[]>;
22
22
  }
@@ -33,10 +33,7 @@ export class DatastoreKeyValueDB {
33
33
  const q = DBQuery.create(table)
34
34
  .select(['id'])
35
35
  .limit(limit || 0);
36
- return (this.db
37
- .streamQuery(q)
38
- // .on('error', err => stream.emit('error', err))
39
- .map(r => r.id));
36
+ return this.db.streamQuery(q).mapSync(r => r.id);
40
37
  }
41
38
  streamValues(table, limit) {
42
39
  // `select v` doesn't work for some reason
@@ -44,14 +41,11 @@ export class DatastoreKeyValueDB {
44
41
  return (this.db
45
42
  .streamQuery(q)
46
43
  // .on('error', err => stream.emit('error', err))
47
- .map(r => r.v));
44
+ .mapSync(r => r.v));
48
45
  }
49
46
  streamEntries(table, limit) {
50
47
  const q = DBQuery.create(table).limit(limit || 0);
51
- return (this.db
52
- .streamQuery(q)
53
- // .on('error', err => stream.emit('error', err))
54
- .map(r => [r.id, r.v]));
48
+ return this.db.streamQuery(q).mapSync(r => [r.id, r.v]);
55
49
  }
56
50
  async count(table) {
57
51
  const q = DBQuery.create(table);
@@ -91,7 +91,7 @@ export class DatastoreStreamReadable extends Readable {
91
91
  }
92
92
  void this.runNextQuery().catch(err => {
93
93
  this.logger.error('error in runNextQuery', err);
94
- this.emit('error', err);
94
+ this.destroy(err);
95
95
  });
96
96
  }
97
97
  async runNextQuery() {
@@ -162,6 +162,7 @@ export class DatastoreStreamReadable extends Readable {
162
162
  return await q.run(this.dsOpt);
163
163
  }, {
164
164
  name: `DatastoreStreamReadable.query(${table})`,
165
+ predicate: err => RETRY_ON.some(s => err?.message?.toLowerCase()?.includes(s)),
165
166
  maxAttempts: 5,
166
167
  delay: 5000,
167
168
  delayMultiplier: 2,
@@ -174,9 +175,21 @@ export class DatastoreStreamReadable extends Readable {
174
175
  table,
175
176
  rowsRetrieved: this.rowsRetrieved,
176
177
  }, err);
177
- this.emit('error', err);
178
178
  clearInterval(this.maxWaitInterval);
179
+ this.destroy(err);
179
180
  return;
180
181
  }
181
182
  }
182
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.15.0",
4
+ "version": "4.16.0",
5
5
  "description": "Opinionated library to work with Google Datastore, implements CommonDB",
6
6
  "dependencies": {
7
7
  "@google-cloud/datastore": "^10",
@@ -11,7 +11,7 @@
11
11
  },
12
12
  "devDependencies": {
13
13
  "@types/node": "^24",
14
- "@naturalcycles/dev-lib": "18.4.2"
14
+ "@naturalcycles/dev-lib": "19.37.0"
15
15
  },
16
16
  "exports": {
17
17
  ".": "./dist/index.js"
@@ -1,4 +1,3 @@
1
- import { Transform } from 'node:stream'
2
1
  import type { Datastore, Key, PropertyFilter, Query, Transaction } from '@google-cloud/datastore'
3
2
  import type { RunQueryOptions } from '@google-cloud/datastore/build/src/query.js'
4
3
  import type {
@@ -41,7 +40,7 @@ import {
41
40
  type StringMap,
42
41
  } from '@naturalcycles/js-lib/types'
43
42
  import { boldWhite } from '@naturalcycles/nodejs-lib/colors'
44
- import type { ReadableTyped } from '@naturalcycles/nodejs-lib/stream'
43
+ import { Pipeline } from '@naturalcycles/nodejs-lib/stream'
45
44
  import type {
46
45
  DatastoreDBCfg,
47
46
  DatastoreDBOptions,
@@ -327,15 +326,10 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
327
326
  override streamQuery<ROW extends ObjectWithId>(
328
327
  dbQuery: DBQuery<ROW>,
329
328
  _opt?: DatastoreDBStreamOptions,
330
- ): ReadableTyped<ROW> {
331
- const transform = new Transform({
332
- objectMode: true,
333
- transform: (chunk, _, cb) => {
334
- cb(null, this.mapId(chunk))
335
- },
336
- })
329
+ ): Pipeline<ROW> {
330
+ return Pipeline.fromAsyncReadable<ROW>(async () => {
331
+ const ds = await this.ds()
337
332
 
338
- void this.ds().then(async ds => {
339
333
  const q = dbQueryToDatastoreQuery(
340
334
  dbQuery,
341
335
  ds.createQuery(dbQuery.table),
@@ -348,15 +342,10 @@ export class DatastoreDB extends BaseCommonDB implements CommonDB {
348
342
  ..._opt,
349
343
  }
350
344
 
351
- ;(opt.experimentalCursorStream
345
+ return opt.experimentalCursorStream
352
346
  ? new DatastoreStreamReadable<ROW>(q, opt)
353
- : (ds.runQueryStream(q, this.getRunQueryOptions(opt)) as ReadableTyped<ROW>)
354
- )
355
- .on('error', err => transform.emit('error', err))
356
- .pipe(transform)
357
- })
358
-
359
- return transform
347
+ : ds.runQueryStream(q, this.getRunQueryOptions(opt))
348
+ }).mapSync(r => this.mapId<ROW>(r))
360
349
  }
361
350
 
362
351
  // https://github.com/GoogleCloudPlatform/nodejs-getting-started/blob/master/2-structured-data/books/model-datastore.js
@@ -3,7 +3,7 @@ import type { CommonKeyValueDB, IncrementTuple, KeyValueDBTuple } from '@natural
3
3
  import { commonKeyValueDBFullSupport } from '@naturalcycles/db-lib/kv'
4
4
  import { AppError } from '@naturalcycles/js-lib/error/error.util.js'
5
5
  import type { ObjectWithId } from '@naturalcycles/js-lib/types'
6
- import type { ReadableTyped } from '@naturalcycles/nodejs-lib/stream'
6
+ import type { Pipeline } from '@naturalcycles/nodejs-lib/stream'
7
7
  import { DatastoreDB } from './datastore.db.js'
8
8
  import type { DatastoreDBCfg } from './datastore.model.js'
9
9
 
@@ -52,20 +52,15 @@ export class DatastoreKeyValueDB implements CommonKeyValueDB {
52
52
  )
53
53
  }
54
54
 
55
- streamIds(table: string, limit?: number): ReadableTyped<string> {
55
+ streamIds(table: string, limit?: number): Pipeline<string> {
56
56
  const q = DBQuery.create<ObjectWithId>(table)
57
57
  .select(['id'])
58
58
  .limit(limit || 0)
59
59
 
60
- return (
61
- this.db
62
- .streamQuery(q)
63
- // .on('error', err => stream.emit('error', err))
64
- .map(r => r.id)
65
- )
60
+ return this.db.streamQuery(q).mapSync(r => r.id)
66
61
  }
67
62
 
68
- streamValues(table: string, limit?: number): ReadableTyped<Buffer> {
63
+ streamValues(table: string, limit?: number): Pipeline<Buffer> {
69
64
  // `select v` doesn't work for some reason
70
65
  const q = DBQuery.create<KVObject>(table).limit(limit || 0)
71
66
 
@@ -73,19 +68,14 @@ export class DatastoreKeyValueDB implements CommonKeyValueDB {
73
68
  this.db
74
69
  .streamQuery(q)
75
70
  // .on('error', err => stream.emit('error', err))
76
- .map(r => r.v)
71
+ .mapSync(r => r.v)
77
72
  )
78
73
  }
79
74
 
80
- streamEntries(table: string, limit?: number): ReadableTyped<KeyValueDBTuple> {
75
+ streamEntries(table: string, limit?: number): Pipeline<KeyValueDBTuple> {
81
76
  const q = DBQuery.create<KVObject>(table).limit(limit || 0)
82
77
 
83
- return (
84
- this.db
85
- .streamQuery(q)
86
- // .on('error', err => stream.emit('error', err))
87
- .map(r => [r.id, r.v])
88
- )
78
+ return this.db.streamQuery(q).mapSync(r => [r.id, r.v])
89
79
  }
90
80
 
91
81
  async count(table: string): Promise<number> {
@@ -125,7 +125,7 @@ export class DatastoreStreamReadable<T = any> extends Readable implements Readab
125
125
 
126
126
  void this.runNextQuery().catch(err => {
127
127
  this.logger.error('error in runNextQuery', err)
128
- this.emit('error', err)
128
+ this.destroy(err)
129
129
  })
130
130
  }
131
131
 
@@ -217,6 +217,7 @@ export class DatastoreStreamReadable<T = any> extends Readable implements Readab
217
217
  },
218
218
  {
219
219
  name: `DatastoreStreamReadable.query(${table})`,
220
+ predicate: err => RETRY_ON.some(s => err?.message?.toLowerCase()?.includes(s)),
220
221
  maxAttempts: 5,
221
222
  delay: 5000,
222
223
  delayMultiplier: 2,
@@ -233,9 +234,22 @@ export class DatastoreStreamReadable<T = any> extends Readable implements Readab
233
234
  },
234
235
  err,
235
236
  )
236
- this.emit('error', err)
237
237
  clearInterval(this.maxWaitInterval)
238
+ this.destroy(err as Error)
238
239
  return
239
240
  }
240
241
  }
241
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())