@naturalcycles/firestore-lib 2.12.0 → 2.13.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.
@@ -3,7 +3,7 @@ import type { CommonDB, CommonDBOptions, CommonDBReadOptions, CommonDBSaveOption
3
3
  import { BaseCommonDB } from '@naturalcycles/db-lib';
4
4
  import type { CommonLogger, CommonLogLevel } from '@naturalcycles/js-lib/log';
5
5
  import type { ObjectWithId, PositiveInteger, StringMap } from '@naturalcycles/js-lib/types';
6
- import type { ReadableTyped } from '@naturalcycles/nodejs-lib/stream';
6
+ import { Pipeline } from '@naturalcycles/nodejs-lib/stream';
7
7
  export declare class FirestoreDB extends BaseCommonDB implements CommonDB {
8
8
  constructor(cfg: FirestoreDBCfg);
9
9
  cfg: FirestoreDBCfg & {
@@ -15,7 +15,7 @@ export declare class FirestoreDB extends BaseCommonDB implements CommonDB {
15
15
  runQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: FirestoreDBOptions): Promise<RunQueryResult<ROW>>;
16
16
  runFirestoreQuery<ROW extends ObjectWithId>(q: Query): Promise<ROW[]>;
17
17
  runQueryCount<ROW extends ObjectWithId>(q: DBQuery<ROW>, _opt?: FirestoreDBOptions): Promise<number>;
18
- streamQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, opt_?: FirestoreDBStreamOptions): ReadableTyped<ROW>;
18
+ streamQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, opt_?: FirestoreDBStreamOptions): Pipeline<ROW>;
19
19
  saveBatch<ROW extends ObjectWithId>(table: string, rows: ROW[], opt?: FirestoreDBSaveOptions<ROW>): Promise<void>;
20
20
  multiSave<ROW extends ObjectWithId>(map: StringMap<ROW[]>, opt?: FirestoreDBSaveOptions<ROW>): Promise<void>;
21
21
  patchById<ROW extends ObjectWithId>(table: string, id: string, patch: Partial<ROW>, opt?: FirestoreDBOptions): Promise<void>;
@@ -6,6 +6,7 @@ import { _assert } from '@naturalcycles/js-lib/error/assert.js';
6
6
  import { _filterUndefinedValues, _omit } from '@naturalcycles/js-lib/object/object.util.js';
7
7
  import { pMap } from '@naturalcycles/js-lib/promise/pMap.js';
8
8
  import { _stringMapEntries } from '@naturalcycles/js-lib/types';
9
+ import { Pipeline } from '@naturalcycles/nodejs-lib/stream';
9
10
  import { escapeDocId, unescapeDocId } from './firestore.util.js';
10
11
  import { FirestoreShardedReadable } from './firestoreShardedReadable.js';
11
12
  import { FirestoreStreamReadable } from './firestoreStreamReadable.js';
@@ -101,17 +102,17 @@ export class FirestoreDB extends BaseCommonDB {
101
102
  ...opt_,
102
103
  };
103
104
  if (opt.experimentalCursorStream) {
104
- return new FirestoreStreamReadable(firestoreQuery, q, opt);
105
+ return Pipeline.from(new FirestoreStreamReadable(firestoreQuery, q, opt));
105
106
  }
106
107
  if (opt.experimentalShardedStream) {
107
- return new FirestoreShardedReadable(firestoreQuery, q, opt);
108
+ return Pipeline.from(new FirestoreShardedReadable(firestoreQuery, q, opt));
108
109
  }
109
- return firestoreQuery.stream().map(doc => {
110
+ return Pipeline.from(firestoreQuery.stream().map(doc => {
110
111
  return {
111
112
  id: unescapeDocId(doc.id),
112
113
  ...doc.data(),
113
114
  };
114
- });
115
+ }));
115
116
  }
116
117
  // SAVE
117
118
  async saveBatch(table, rows, opt = {}) {
@@ -65,7 +65,7 @@ export class FirestoreShardedReadable extends Readable {
65
65
  }
66
66
  void this.runNextQuery(shard).catch(err => {
67
67
  this.logger.error('error in runNextQuery', err);
68
- this.emit('error', err);
68
+ this.destroy(err);
69
69
  });
70
70
  }
71
71
  async runNextQuery(shard) {
@@ -155,7 +155,7 @@ export class FirestoreShardedReadable extends Readable {
155
155
  table,
156
156
  rowsRetrieved: this.rowsRetrieved,
157
157
  }, err);
158
- this.emit('error', err);
158
+ this.destroy(err);
159
159
  return;
160
160
  }
161
161
  }
@@ -1,5 +1,5 @@
1
1
  import { Readable } from 'node:stream';
2
- import { FieldPath } from '@google-cloud/firestore';
2
+ import { FieldPath, } from '@google-cloud/firestore';
3
3
  import { localTime } from '@naturalcycles/js-lib/datetime/localTime.js';
4
4
  import { _ms } from '@naturalcycles/js-lib/datetime/time.util.js';
5
5
  import { createCommonLoggerAtLevel } from '@naturalcycles/js-lib/log';
@@ -62,7 +62,7 @@ export class FirestoreStreamReadable extends Readable {
62
62
  }
63
63
  void this.runNextQuery().catch(err => {
64
64
  this.logger.error('error in runNextQuery', err);
65
- this.emit('error', err);
65
+ this.destroy(err);
66
66
  });
67
67
  }
68
68
  async runNextQuery() {
@@ -91,9 +91,9 @@ export class FirestoreStreamReadable extends Readable {
91
91
  return;
92
92
  }
93
93
  const rows = [];
94
- let lastDocId;
94
+ let lastDoc;
95
95
  for (const doc of qs.docs) {
96
- lastDocId = doc.id;
96
+ lastDoc = doc;
97
97
  rows.push({
98
98
  id: unescapeDocId(doc.id),
99
99
  ...doc.data(),
@@ -101,7 +101,7 @@ export class FirestoreStreamReadable extends Readable {
101
101
  }
102
102
  this.rowsRetrieved += rows.length;
103
103
  logger.debug(`${table} got ${rows.length} rows in ${_ms(queryTook)}, ${this.rowsRetrieved} rowsRetrieved`);
104
- this.endCursor = lastDocId;
104
+ this.endCursor = lastDoc;
105
105
  this.queryIsRunning = false; // ready to take more _reads
106
106
  let shouldContinue = false;
107
107
  for (const row of rows) {
@@ -137,6 +137,7 @@ export class FirestoreStreamReadable extends Readable {
137
137
  return await q.get();
138
138
  }, {
139
139
  name: `FirestoreStreamReadable.query(${table})`,
140
+ predicate: err => RETRY_ON.some(s => err?.message?.toLowerCase()?.includes(s)),
140
141
  maxAttempts: 5,
141
142
  delay: 5000,
142
143
  delayMultiplier: 2,
@@ -145,12 +146,25 @@ export class FirestoreStreamReadable extends Readable {
145
146
  });
146
147
  }
147
148
  catch (err) {
149
+ console.log(q._queryOptions);
148
150
  logger.error(`FirestoreStreamReadable error!\n`, {
149
151
  table,
150
152
  rowsRetrieved: this.rowsRetrieved,
151
153
  }, err);
152
- this.emit('error', err);
154
+ this.destroy(err);
153
155
  return;
154
156
  }
155
157
  }
156
158
  }
159
+ // Examples of errors:
160
+ // UNKNOWN: Stream removed
161
+ const RETRY_ON = [
162
+ 'GOAWAY',
163
+ 'UNAVAILABLE',
164
+ 'UNKNOWN',
165
+ 'DEADLINE_EXCEEDED',
166
+ 'ABORTED',
167
+ 'much contention',
168
+ 'try again',
169
+ 'timeout',
170
+ ].map(s => s.toLowerCase());
package/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "@types/node": "^24",
13
13
  "dotenv": "^17",
14
14
  "firebase-admin": "^13",
15
- "@naturalcycles/dev-lib": "18.4.2"
15
+ "@naturalcycles/dev-lib": "19.37.0"
16
16
  },
17
17
  "exports": {
18
18
  ".": "./dist/index.js"
@@ -38,7 +38,7 @@
38
38
  "engines": {
39
39
  "node": ">=22.12.0"
40
40
  },
41
- "version": "2.12.0",
41
+ "version": "2.13.0",
42
42
  "description": "Firestore implementation of CommonDB interface",
43
43
  "author": "Natural Cycles Team",
44
44
  "license": "MIT",
@@ -30,7 +30,7 @@ import { _filterUndefinedValues, _omit } from '@naturalcycles/js-lib/object/obje
30
30
  import { pMap } from '@naturalcycles/js-lib/promise/pMap.js'
31
31
  import type { ObjectWithId, PositiveInteger, StringMap } from '@naturalcycles/js-lib/types'
32
32
  import { _stringMapEntries } from '@naturalcycles/js-lib/types'
33
- import type { ReadableTyped } from '@naturalcycles/nodejs-lib/stream'
33
+ import { Pipeline, type ReadableTyped } from '@naturalcycles/nodejs-lib/stream'
34
34
  import { escapeDocId, unescapeDocId } from './firestore.util.js'
35
35
  import { FirestoreShardedReadable } from './firestoreShardedReadable.js'
36
36
  import { FirestoreStreamReadable } from './firestoreStreamReadable.js'
@@ -152,7 +152,7 @@ export class FirestoreDB extends BaseCommonDB implements CommonDB {
152
152
  override streamQuery<ROW extends ObjectWithId>(
153
153
  q: DBQuery<ROW>,
154
154
  opt_?: FirestoreDBStreamOptions,
155
- ): ReadableTyped<ROW> {
155
+ ): Pipeline<ROW> {
156
156
  const firestoreQuery = dbQueryToFirestoreQuery(q, this.cfg.firestore.collection(q.table))
157
157
 
158
158
  const opt: FirestoreDBStreamOptions = {
@@ -163,19 +163,21 @@ export class FirestoreDB extends BaseCommonDB implements CommonDB {
163
163
  }
164
164
 
165
165
  if (opt.experimentalCursorStream) {
166
- return new FirestoreStreamReadable(firestoreQuery, q, opt)
166
+ return Pipeline.from(new FirestoreStreamReadable(firestoreQuery, q, opt))
167
167
  }
168
168
 
169
169
  if (opt.experimentalShardedStream) {
170
- return new FirestoreShardedReadable(firestoreQuery, q, opt)
170
+ return Pipeline.from(new FirestoreShardedReadable(firestoreQuery, q, opt))
171
171
  }
172
172
 
173
- return (firestoreQuery.stream() as ReadableTyped<QueryDocumentSnapshot<any>>).map(doc => {
174
- return {
175
- id: unescapeDocId(doc.id),
176
- ...doc.data(),
177
- } as ROW
178
- })
173
+ return Pipeline.from(
174
+ (firestoreQuery.stream() as ReadableTyped<QueryDocumentSnapshot<any>>).map(doc => {
175
+ return {
176
+ id: unescapeDocId(doc.id),
177
+ ...doc.data(),
178
+ } as ROW
179
+ }),
180
+ )
179
181
  }
180
182
 
181
183
  // SAVE
@@ -91,7 +91,7 @@ export class FirestoreShardedReadable<T extends ObjectWithId = any>
91
91
  }
92
92
  void this.runNextQuery(shard).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
 
@@ -213,7 +213,7 @@ export class FirestoreShardedReadable<T extends ObjectWithId = any>
213
213
  },
214
214
  err,
215
215
  )
216
- this.emit('error', err)
216
+ this.destroy(err as Error)
217
217
  return
218
218
  }
219
219
  }
@@ -1,5 +1,10 @@
1
1
  import { Readable } from 'node:stream'
2
- import { FieldPath, type Query, type QuerySnapshot } from '@google-cloud/firestore'
2
+ import {
3
+ FieldPath,
4
+ type Query,
5
+ type QueryDocumentSnapshot,
6
+ type QuerySnapshot,
7
+ } from '@google-cloud/firestore'
3
8
  import type { DBQuery } from '@naturalcycles/db-lib'
4
9
  import { localTime } from '@naturalcycles/js-lib/datetime/localTime.js'
5
10
  import { _ms } from '@naturalcycles/js-lib/datetime/time.util.js'
@@ -17,7 +22,7 @@ export class FirestoreStreamReadable<T extends ObjectWithId = any>
17
22
  private readonly table: string
18
23
  private readonly originalLimit: number
19
24
  private rowsRetrieved = 0
20
- private endCursor?: string
25
+ private endCursor?: QueryDocumentSnapshot
21
26
  private queryIsRunning = false
22
27
  private paused = false
23
28
  private done = false
@@ -85,7 +90,7 @@ export class FirestoreStreamReadable<T extends ObjectWithId = any>
85
90
 
86
91
  void this.runNextQuery().catch(err => {
87
92
  this.logger.error('error in runNextQuery', err)
88
- this.emit('error', err)
93
+ this.destroy(err)
89
94
  })
90
95
  }
91
96
 
@@ -121,10 +126,10 @@ export class FirestoreStreamReadable<T extends ObjectWithId = any>
121
126
  }
122
127
 
123
128
  const rows: T[] = []
124
- let lastDocId: string | undefined
129
+ let lastDoc: QueryDocumentSnapshot | undefined
125
130
 
126
131
  for (const doc of qs.docs) {
127
- lastDocId = doc.id
132
+ lastDoc = doc
128
133
  rows.push({
129
134
  id: unescapeDocId(doc.id),
130
135
  ...doc.data(),
@@ -136,7 +141,7 @@ export class FirestoreStreamReadable<T extends ObjectWithId = any>
136
141
  `${table} got ${rows.length} rows in ${_ms(queryTook)}, ${this.rowsRetrieved} rowsRetrieved`,
137
142
  )
138
143
 
139
- this.endCursor = lastDocId
144
+ this.endCursor = lastDoc
140
145
  this.queryIsRunning = false // ready to take more _reads
141
146
  let shouldContinue = false
142
147
 
@@ -177,6 +182,7 @@ export class FirestoreStreamReadable<T extends ObjectWithId = any>
177
182
  },
178
183
  {
179
184
  name: `FirestoreStreamReadable.query(${table})`,
185
+ predicate: err => RETRY_ON.some(s => err?.message?.toLowerCase()?.includes(s)),
180
186
  maxAttempts: 5,
181
187
  delay: 5000,
182
188
  delayMultiplier: 2,
@@ -185,6 +191,8 @@ export class FirestoreStreamReadable<T extends ObjectWithId = any>
185
191
  },
186
192
  )
187
193
  } catch (err) {
194
+ console.log((q as any)._queryOptions)
195
+
188
196
  logger.error(
189
197
  `FirestoreStreamReadable error!\n`,
190
198
  {
@@ -193,8 +201,21 @@ export class FirestoreStreamReadable<T extends ObjectWithId = any>
193
201
  },
194
202
  err,
195
203
  )
196
- this.emit('error', err)
204
+ this.destroy(err as Error)
197
205
  return
198
206
  }
199
207
  }
200
208
  }
209
+
210
+ // Examples of errors:
211
+ // UNKNOWN: Stream removed
212
+ const RETRY_ON = [
213
+ 'GOAWAY',
214
+ 'UNAVAILABLE',
215
+ 'UNKNOWN',
216
+ 'DEADLINE_EXCEEDED',
217
+ 'ABORTED',
218
+ 'much contention',
219
+ 'try again',
220
+ 'timeout',
221
+ ].map(s => s.toLowerCase())