@naturalcycles/firestore-lib 1.6.0 → 1.6.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.
@@ -1,5 +1,5 @@
1
1
  import { Firestore, Query, Transaction } from '@google-cloud/firestore';
2
- import { BaseCommonDB, CommonDB, CommonDBOptions, CommonDBSaveOptions, CommonDBStreamOptions, CommonDBSupport, DBQuery, DBTransaction, RunQueryResult } from '@naturalcycles/db-lib';
2
+ import { BaseCommonDB, CommonDB, CommonDBOptions, CommonDBSaveOptions, CommonDBStreamOptions, CommonDBSupport, DBQuery, DBTransaction, DBTransactionFn, RunQueryResult } from '@naturalcycles/db-lib';
3
3
  import { ObjectWithId, AnyObjectWithId } from '@naturalcycles/js-lib';
4
4
  import { ReadableTyped } from '@naturalcycles/nodejs-lib';
5
5
  export interface FirestoreDBCfg {
@@ -9,11 +9,14 @@ export interface FirestoreDBOptions extends CommonDBOptions {
9
9
  }
10
10
  export interface FirestoreDBSaveOptions<ROW extends Partial<ObjectWithId> = AnyObjectWithId> extends CommonDBSaveOptions<ROW> {
11
11
  }
12
+ export declare class RollbackError extends Error {
13
+ constructor();
14
+ }
12
15
  export declare class FirestoreDB extends BaseCommonDB implements CommonDB {
13
16
  cfg: FirestoreDBCfg;
14
17
  constructor(cfg: FirestoreDBCfg);
15
18
  support: CommonDBSupport;
16
- getByIds<ROW extends ObjectWithId>(table: string, ids: string[], _opt?: FirestoreDBOptions): Promise<ROW[]>;
19
+ getByIds<ROW extends ObjectWithId>(table: string, ids: string[], opt?: FirestoreDBOptions): Promise<ROW[]>;
17
20
  runQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: FirestoreDBOptions): Promise<RunQueryResult<ROW>>;
18
21
  runFirestoreQuery<ROW extends ObjectWithId>(q: Query, _opt?: FirestoreDBOptions): Promise<ROW[]>;
19
22
  runQueryCount<ROW extends ObjectWithId>(q: DBQuery<ROW>, _opt?: FirestoreDBOptions): Promise<number>;
@@ -22,7 +25,7 @@ export declare class FirestoreDB extends BaseCommonDB implements CommonDB {
22
25
  deleteByQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: FirestoreDBOptions): Promise<number>;
23
26
  deleteByIds(table: string, ids: string[], opt?: FirestoreDBOptions): Promise<number>;
24
27
  private querySnapshotToArray;
25
- createTransaction(): Promise<FirestoreDBTransaction>;
28
+ runInTransaction(fn: DBTransactionFn): Promise<void>;
26
29
  ping(): Promise<void>;
27
30
  getTables(): Promise<string[]>;
28
31
  }
@@ -32,18 +35,9 @@ export declare class FirestoreDB extends BaseCommonDB implements CommonDB {
32
35
  export declare class FirestoreDBTransaction implements DBTransaction {
33
36
  db: FirestoreDB;
34
37
  tx: Transaction;
35
- private txPendingDefer;
36
- private txCompletedDefer;
37
- /**
38
- * This defer is held during Transaction and
39
- * is released when it's ready to be committed or rolled back.
40
- */
41
- /**
42
- * This is resolved after Transaction is committed or rolled back.
43
- * On error - it rejects with that error.
44
- */
45
- private constructor();
46
- static create(db: FirestoreDB): Promise<FirestoreDBTransaction>;
47
- commit(): Promise<void>;
38
+ constructor(db: FirestoreDB, tx: Transaction);
48
39
  rollback(): Promise<void>;
40
+ getByIds<ROW extends ObjectWithId>(table: string, ids: string[], opt?: CommonDBOptions): Promise<ROW[]>;
41
+ saveBatch<ROW extends Partial<ObjectWithId>>(table: string, rows: ROW[], opt?: CommonDBSaveOptions<ROW>): Promise<void>;
42
+ deleteByIds(table: string, ids: string[], opt?: CommonDBOptions): Promise<number>;
49
43
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.FirestoreDBTransaction = exports.FirestoreDB = void 0;
3
+ exports.FirestoreDBTransaction = exports.FirestoreDB = exports.RollbackError = void 0;
4
4
  const db_lib_1 = require("@naturalcycles/db-lib");
5
5
  const js_lib_1 = require("@naturalcycles/js-lib");
6
6
  const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
@@ -11,6 +11,12 @@ const methodMap = {
11
11
  update: 'update',
12
12
  upsert: 'set',
13
13
  };
14
+ class RollbackError extends Error {
15
+ constructor() {
16
+ super('rollback');
17
+ }
18
+ }
19
+ exports.RollbackError = RollbackError;
14
20
  class FirestoreDB extends db_lib_1.BaseCommonDB {
15
21
  constructor(cfg) {
16
22
  super();
@@ -22,17 +28,12 @@ class FirestoreDB extends db_lib_1.BaseCommonDB {
22
28
  };
23
29
  }
24
30
  // GET
25
- async getByIds(table, ids, _opt) {
26
- // Oj, doesn't look like a very optimal implementation!
27
- // TODO: check if we can query by keys or smth
28
- // return (await Promise.all(ids.map(id => this.getById<ROW>(table, id, opt)))).filter(
29
- // Boolean,
30
- // ) as ROW[]
31
+ async getByIds(table, ids, opt = {}) {
31
32
  if (!ids.length)
32
33
  return [];
33
34
  const { firestore } = this.cfg;
34
35
  const col = firestore.collection(table);
35
- return (await firestore.getAll(...ids.map(id => col.doc((0, firestore_util_1.escapeDocId)(id)))))
36
+ return (await (opt.tx?.tx || firestore).getAll(...ids.map(id => col.doc((0, firestore_util_1.escapeDocId)(id)))))
36
37
  .map(doc => {
37
38
  const data = doc.data();
38
39
  if (data === undefined)
@@ -148,30 +149,20 @@ class FirestoreDB extends db_lib_1.BaseCommonDB {
148
149
  });
149
150
  return rows;
150
151
  }
151
- // override async commitTransaction(tx: DBTransaction, _opt?: CommonDBSaveOptions): Promise<void> {
152
- // const { firestore } = this.cfg
153
- //
154
- // await firestore.runTransaction(async tr => {
155
- // for (const op of tx.ops) {
156
- // if (op.type === 'saveBatch') {
157
- // op.rows.forEach(row => {
158
- // tr.set(
159
- // firestore.collection(op.table).doc(escapeDocId(row.id)),
160
- // _filterUndefinedValues(row),
161
- // )
162
- // })
163
- // } else if (op.type === 'deleteByIds') {
164
- // op.ids.forEach(id => {
165
- // tr.delete(firestore.collection(op.table).doc(escapeDocId(id)))
166
- // })
167
- // } else {
168
- // throw new Error(`DBOperation not supported: ${(op as any).type}`)
169
- // }
170
- // }
171
- // })
172
- // }
173
- async createTransaction() {
174
- return await FirestoreDBTransaction.create(this);
152
+ async runInTransaction(fn) {
153
+ try {
154
+ await this.cfg.firestore.runTransaction(async (firestoreTx) => {
155
+ const tx = new FirestoreDBTransaction(this, firestoreTx);
156
+ await fn(tx);
157
+ });
158
+ }
159
+ catch (err) {
160
+ if (err instanceof RollbackError) {
161
+ // RollbackError should be handled gracefully (not re-throw)
162
+ return;
163
+ }
164
+ throw err;
165
+ }
175
166
  }
176
167
  async ping() {
177
168
  // no-op now
@@ -185,49 +176,21 @@ exports.FirestoreDB = FirestoreDB;
185
176
  * https://firebase.google.com/docs/firestore/manage-data/transactions
186
177
  */
187
178
  class FirestoreDBTransaction {
188
- /**
189
- * This defer is held during Transaction and
190
- * is released when it's ready to be committed or rolled back.
191
- */
192
- // private txPendingDefer = pDefer()
193
- /**
194
- * This is resolved after Transaction is committed or rolled back.
195
- * On error - it rejects with that error.
196
- */
197
- // private txCompletedDefer = pDefer()
198
- constructor(db, tx, txPendingDefer, txCompletedDefer) {
179
+ constructor(db, tx) {
199
180
  this.db = db;
200
181
  this.tx = tx;
201
- this.txPendingDefer = txPendingDefer;
202
- this.txCompletedDefer = txCompletedDefer;
203
- }
204
- static async create(db) {
205
- const txCreated = (0, js_lib_1.pDefer)();
206
- const txPendingDefer = (0, js_lib_1.pDefer)();
207
- const txCompletedDefer = (0, js_lib_1.pDefer)();
208
- db.cfg.firestore
209
- .runTransaction(async (tx) => {
210
- txCreated.resolve(tx);
211
- // Now we pause and let consumers to use the Transaction,
212
- // until commit/rollback is called
213
- await txPendingDefer;
214
- })
215
- .then(() => {
216
- txCompletedDefer.resolve();
217
- })
218
- .catch(err => {
219
- txCompletedDefer.reject(err);
220
- });
221
- const tx = await txCreated;
222
- return new FirestoreDBTransaction(db, tx, txPendingDefer, txCompletedDefer);
223
- }
224
- async commit() {
225
- this.txPendingDefer.resolve();
226
- await this.txCompletedDefer;
227
182
  }
228
183
  async rollback() {
229
- this.txPendingDefer.reject(new Error('rollback'));
230
- await this.txCompletedDefer;
184
+ throw new RollbackError();
185
+ }
186
+ async getByIds(table, ids, opt) {
187
+ return await this.db.getByIds(table, ids, { ...opt, tx: this });
188
+ }
189
+ async saveBatch(table, rows, opt) {
190
+ await this.db.saveBatch(table, rows, { ...opt, tx: this });
191
+ }
192
+ async deleteByIds(table, ids, opt) {
193
+ return await this.db.deleteByIds(table, ids, { ...opt, tx: this });
231
194
  }
232
195
  }
233
196
  exports.FirestoreDBTransaction = FirestoreDBTransaction;
package/package.json CHANGED
@@ -37,7 +37,7 @@
37
37
  "engines": {
38
38
  "node": ">=18.12.0"
39
39
  },
40
- "version": "1.6.0",
40
+ "version": "1.6.1",
41
41
  "description": "Firestore implementation of CommonDB interface",
42
42
  "author": "Natural Cycles Team",
43
43
  "license": "MIT"
@@ -16,6 +16,7 @@ import {
16
16
  CommonDBSupport,
17
17
  DBQuery,
18
18
  DBTransaction,
19
+ DBTransactionFn,
19
20
  RunQueryResult,
20
21
  } from '@naturalcycles/db-lib'
21
22
  import {
@@ -28,8 +29,6 @@ import {
28
29
  AnyObjectWithId,
29
30
  _assert,
30
31
  _isTruthy,
31
- pDefer,
32
- DeferredPromise,
33
32
  } from '@naturalcycles/js-lib'
34
33
  import { ReadableTyped, transformMapSimple } from '@naturalcycles/nodejs-lib'
35
34
  import { escapeDocId, unescapeDocId } from './firestore.util'
@@ -51,6 +50,12 @@ const methodMap: Record<CommonDBSaveMethod, SaveOp> = {
51
50
  upsert: 'set',
52
51
  }
53
52
 
53
+ export class RollbackError extends Error {
54
+ constructor() {
55
+ super('rollback')
56
+ }
57
+ }
58
+
54
59
  export class FirestoreDB extends BaseCommonDB implements CommonDB {
55
60
  constructor(public cfg: FirestoreDBCfg) {
56
61
  super()
@@ -66,19 +71,18 @@ export class FirestoreDB extends BaseCommonDB implements CommonDB {
66
71
  override async getByIds<ROW extends ObjectWithId>(
67
72
  table: string,
68
73
  ids: string[],
69
- _opt?: FirestoreDBOptions,
74
+ opt: FirestoreDBOptions = {},
70
75
  ): Promise<ROW[]> {
71
- // Oj, doesn't look like a very optimal implementation!
72
- // TODO: check if we can query by keys or smth
73
- // return (await Promise.all(ids.map(id => this.getById<ROW>(table, id, opt)))).filter(
74
- // Boolean,
75
- // ) as ROW[]
76
76
  if (!ids.length) return []
77
77
 
78
78
  const { firestore } = this.cfg
79
79
  const col = firestore.collection(table)
80
80
 
81
- return (await firestore.getAll(...ids.map(id => col.doc(escapeDocId(id)))))
81
+ return (
82
+ await ((opt.tx as FirestoreDBTransaction)?.tx || firestore).getAll(
83
+ ...ids.map(id => col.doc(escapeDocId(id))),
84
+ )
85
+ )
82
86
  .map(doc => {
83
87
  const data = doc.data()
84
88
  if (data === undefined) return
@@ -268,31 +272,19 @@ export class FirestoreDB extends BaseCommonDB implements CommonDB {
268
272
  return rows
269
273
  }
270
274
 
271
- // override async commitTransaction(tx: DBTransaction, _opt?: CommonDBSaveOptions): Promise<void> {
272
- // const { firestore } = this.cfg
273
- //
274
- // await firestore.runTransaction(async tr => {
275
- // for (const op of tx.ops) {
276
- // if (op.type === 'saveBatch') {
277
- // op.rows.forEach(row => {
278
- // tr.set(
279
- // firestore.collection(op.table).doc(escapeDocId(row.id)),
280
- // _filterUndefinedValues(row),
281
- // )
282
- // })
283
- // } else if (op.type === 'deleteByIds') {
284
- // op.ids.forEach(id => {
285
- // tr.delete(firestore.collection(op.table).doc(escapeDocId(id)))
286
- // })
287
- // } else {
288
- // throw new Error(`DBOperation not supported: ${(op as any).type}`)
289
- // }
290
- // }
291
- // })
292
- // }
293
-
294
- override async createTransaction(): Promise<FirestoreDBTransaction> {
295
- return await FirestoreDBTransaction.create(this)
275
+ override async runInTransaction(fn: DBTransactionFn): Promise<void> {
276
+ try {
277
+ await this.cfg.firestore.runTransaction(async firestoreTx => {
278
+ const tx = new FirestoreDBTransaction(this, firestoreTx)
279
+ await fn(tx)
280
+ })
281
+ } catch (err) {
282
+ if (err instanceof RollbackError) {
283
+ // RollbackError should be handled gracefully (not re-throw)
284
+ return
285
+ }
286
+ throw err
287
+ }
296
288
  }
297
289
 
298
290
  override async ping(): Promise<void> {
@@ -308,55 +300,32 @@ export class FirestoreDB extends BaseCommonDB implements CommonDB {
308
300
  * https://firebase.google.com/docs/firestore/manage-data/transactions
309
301
  */
310
302
  export class FirestoreDBTransaction implements DBTransaction {
311
- /**
312
- * This defer is held during Transaction and
313
- * is released when it's ready to be committed or rolled back.
314
- */
315
- // private txPendingDefer = pDefer()
316
-
317
- /**
318
- * This is resolved after Transaction is committed or rolled back.
319
- * On error - it rejects with that error.
320
- */
321
- // private txCompletedDefer = pDefer()
322
-
323
- private constructor(
303
+ constructor(
324
304
  public db: FirestoreDB,
325
305
  public tx: Transaction,
326
- private txPendingDefer: DeferredPromise,
327
- private txCompletedDefer: DeferredPromise,
328
306
  ) {}
329
307
 
330
- static async create(db: FirestoreDB): Promise<FirestoreDBTransaction> {
331
- const txCreated = pDefer<Transaction>()
332
- const txPendingDefer = pDefer()
333
- const txCompletedDefer = pDefer()
334
-
335
- db.cfg.firestore
336
- .runTransaction(async tx => {
337
- txCreated.resolve(tx)
338
-
339
- // Now we pause and let consumers to use the Transaction,
340
- // until commit/rollback is called
341
- await txPendingDefer
342
- })
343
- .then(() => {
344
- txCompletedDefer.resolve()
345
- })
346
- .catch(err => {
347
- txCompletedDefer.reject(err)
348
- })
308
+ async rollback(): Promise<void> {
309
+ throw new RollbackError()
310
+ }
349
311
 
350
- const tx = await txCreated
351
- return new FirestoreDBTransaction(db, tx, txPendingDefer, txCompletedDefer)
312
+ async getByIds<ROW extends ObjectWithId>(
313
+ table: string,
314
+ ids: string[],
315
+ opt?: CommonDBOptions,
316
+ ): Promise<ROW[]> {
317
+ return await this.db.getByIds(table, ids, { ...opt, tx: this })
352
318
  }
353
319
 
354
- async commit(): Promise<void> {
355
- this.txPendingDefer.resolve()
356
- await this.txCompletedDefer
320
+ async saveBatch<ROW extends Partial<ObjectWithId>>(
321
+ table: string,
322
+ rows: ROW[],
323
+ opt?: CommonDBSaveOptions<ROW>,
324
+ ): Promise<void> {
325
+ await this.db.saveBatch(table, rows, { ...opt, tx: this })
357
326
  }
358
- async rollback(): Promise<void> {
359
- this.txPendingDefer.reject(new Error('rollback'))
360
- await this.txCompletedDefer
327
+
328
+ async deleteByIds(table: string, ids: string[], opt?: CommonDBOptions): Promise<number> {
329
+ return await this.db.deleteByIds(table, ids, { ...opt, tx: this })
361
330
  }
362
331
  }