@naturalcycles/firestore-lib 1.6.0 → 1.7.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.
@@ -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, CommonDBTransactionOptions, 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, opt?: CommonDBTransactionOptions): 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,23 @@ 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, opt = {}) {
153
+ const { readOnly } = opt;
154
+ try {
155
+ await this.cfg.firestore.runTransaction(async (firestoreTx) => {
156
+ const tx = new FirestoreDBTransaction(this, firestoreTx);
157
+ await fn(tx);
158
+ }, {
159
+ readOnly,
160
+ });
161
+ }
162
+ catch (err) {
163
+ if (err instanceof RollbackError) {
164
+ // RollbackError should be handled gracefully (not re-throw)
165
+ return;
166
+ }
167
+ throw err;
168
+ }
175
169
  }
176
170
  async ping() {
177
171
  // no-op now
@@ -185,49 +179,21 @@ exports.FirestoreDB = FirestoreDB;
185
179
  * https://firebase.google.com/docs/firestore/manage-data/transactions
186
180
  */
187
181
  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) {
182
+ constructor(db, tx) {
199
183
  this.db = db;
200
184
  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
185
  }
228
186
  async rollback() {
229
- this.txPendingDefer.reject(new Error('rollback'));
230
- await this.txCompletedDefer;
187
+ throw new RollbackError();
188
+ }
189
+ async getByIds(table, ids, opt) {
190
+ return await this.db.getByIds(table, ids, { ...opt, tx: this });
191
+ }
192
+ async saveBatch(table, rows, opt) {
193
+ await this.db.saveBatch(table, rows, { ...opt, tx: this });
194
+ }
195
+ async deleteByIds(table, ids, opt) {
196
+ return await this.db.deleteByIds(table, ids, { ...opt, tx: this });
231
197
  }
232
198
  }
233
199
  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.7.0",
41
41
  "description": "Firestore implementation of CommonDB interface",
42
42
  "author": "Natural Cycles Team",
43
43
  "license": "MIT"
@@ -14,8 +14,10 @@ import {
14
14
  CommonDBSaveOptions,
15
15
  CommonDBStreamOptions,
16
16
  CommonDBSupport,
17
+ CommonDBTransactionOptions,
17
18
  DBQuery,
18
19
  DBTransaction,
20
+ DBTransactionFn,
19
21
  RunQueryResult,
20
22
  } from '@naturalcycles/db-lib'
21
23
  import {
@@ -28,8 +30,6 @@ import {
28
30
  AnyObjectWithId,
29
31
  _assert,
30
32
  _isTruthy,
31
- pDefer,
32
- DeferredPromise,
33
33
  } from '@naturalcycles/js-lib'
34
34
  import { ReadableTyped, transformMapSimple } from '@naturalcycles/nodejs-lib'
35
35
  import { escapeDocId, unescapeDocId } from './firestore.util'
@@ -51,6 +51,12 @@ const methodMap: Record<CommonDBSaveMethod, SaveOp> = {
51
51
  upsert: 'set',
52
52
  }
53
53
 
54
+ export class RollbackError extends Error {
55
+ constructor() {
56
+ super('rollback')
57
+ }
58
+ }
59
+
54
60
  export class FirestoreDB extends BaseCommonDB implements CommonDB {
55
61
  constructor(public cfg: FirestoreDBCfg) {
56
62
  super()
@@ -66,19 +72,18 @@ export class FirestoreDB extends BaseCommonDB implements CommonDB {
66
72
  override async getByIds<ROW extends ObjectWithId>(
67
73
  table: string,
68
74
  ids: string[],
69
- _opt?: FirestoreDBOptions,
75
+ opt: FirestoreDBOptions = {},
70
76
  ): 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
77
  if (!ids.length) return []
77
78
 
78
79
  const { firestore } = this.cfg
79
80
  const col = firestore.collection(table)
80
81
 
81
- return (await firestore.getAll(...ids.map(id => col.doc(escapeDocId(id)))))
82
+ return (
83
+ await ((opt.tx as FirestoreDBTransaction)?.tx || firestore).getAll(
84
+ ...ids.map(id => col.doc(escapeDocId(id))),
85
+ )
86
+ )
82
87
  .map(doc => {
83
88
  const data = doc.data()
84
89
  if (data === undefined) return
@@ -268,31 +273,29 @@ export class FirestoreDB extends BaseCommonDB implements CommonDB {
268
273
  return rows
269
274
  }
270
275
 
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)
276
+ override async runInTransaction(
277
+ fn: DBTransactionFn,
278
+ opt: CommonDBTransactionOptions = {},
279
+ ): Promise<void> {
280
+ const { readOnly } = opt
281
+
282
+ try {
283
+ await this.cfg.firestore.runTransaction(
284
+ async firestoreTx => {
285
+ const tx = new FirestoreDBTransaction(this, firestoreTx)
286
+ await fn(tx)
287
+ },
288
+ {
289
+ readOnly,
290
+ },
291
+ )
292
+ } catch (err) {
293
+ if (err instanceof RollbackError) {
294
+ // RollbackError should be handled gracefully (not re-throw)
295
+ return
296
+ }
297
+ throw err
298
+ }
296
299
  }
297
300
 
298
301
  override async ping(): Promise<void> {
@@ -308,55 +311,32 @@ export class FirestoreDB extends BaseCommonDB implements CommonDB {
308
311
  * https://firebase.google.com/docs/firestore/manage-data/transactions
309
312
  */
310
313
  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(
314
+ constructor(
324
315
  public db: FirestoreDB,
325
316
  public tx: Transaction,
326
- private txPendingDefer: DeferredPromise,
327
- private txCompletedDefer: DeferredPromise,
328
317
  ) {}
329
318
 
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
- })
319
+ async rollback(): Promise<void> {
320
+ throw new RollbackError()
321
+ }
349
322
 
350
- const tx = await txCreated
351
- return new FirestoreDBTransaction(db, tx, txPendingDefer, txCompletedDefer)
323
+ async getByIds<ROW extends ObjectWithId>(
324
+ table: string,
325
+ ids: string[],
326
+ opt?: CommonDBOptions,
327
+ ): Promise<ROW[]> {
328
+ return await this.db.getByIds(table, ids, { ...opt, tx: this })
352
329
  }
353
330
 
354
- async commit(): Promise<void> {
355
- this.txPendingDefer.resolve()
356
- await this.txCompletedDefer
331
+ async saveBatch<ROW extends Partial<ObjectWithId>>(
332
+ table: string,
333
+ rows: ROW[],
334
+ opt?: CommonDBSaveOptions<ROW>,
335
+ ): Promise<void> {
336
+ await this.db.saveBatch(table, rows, { ...opt, tx: this })
357
337
  }
358
- async rollback(): Promise<void> {
359
- this.txPendingDefer.reject(new Error('rollback'))
360
- await this.txCompletedDefer
338
+
339
+ async deleteByIds(table: string, ids: string[], opt?: CommonDBOptions): Promise<number> {
340
+ return await this.db.deleteByIds(table, ids, { ...opt, tx: this })
361
341
  }
362
342
  }