@naturalcycles/firestore-lib 1.7.1 → 2.0.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,7 +1,8 @@
1
- import { Firestore, Query, Transaction } from '@google-cloud/firestore';
2
- import { BaseCommonDB, CommonDB, CommonDBOptions, CommonDBSaveOptions, CommonDBStreamOptions, CommonDBSupport, CommonDBTransactionOptions, DBQuery, DBTransaction, DBTransactionFn, RunQueryResult } from '@naturalcycles/db-lib';
3
- import { ObjectWithId } from '@naturalcycles/js-lib';
4
- import { ReadableTyped } from '@naturalcycles/nodejs-lib';
1
+ import type { Firestore, Query, Transaction } from '@google-cloud/firestore';
2
+ import type { CommonDB, CommonDBOptions, CommonDBSaveOptions, CommonDBStreamOptions, CommonDBSupport, CommonDBTransactionOptions, DBQuery, DBTransaction, DBTransactionFn, RunQueryResult } from '@naturalcycles/db-lib';
3
+ import { BaseCommonDB } from '@naturalcycles/db-lib';
4
+ import type { ObjectWithId, StringMap } from '@naturalcycles/js-lib';
5
+ import type { ReadableTyped } from '@naturalcycles/nodejs-lib';
5
6
  export interface FirestoreDBCfg {
6
7
  firestore: Firestore;
7
8
  }
@@ -26,6 +27,10 @@ export declare class FirestoreDB extends BaseCommonDB implements CommonDB {
26
27
  deleteByIds(table: string, ids: string[], opt?: FirestoreDBOptions): Promise<number>;
27
28
  private querySnapshotToArray;
28
29
  runInTransaction(fn: DBTransactionFn, opt?: CommonDBTransactionOptions): Promise<void>;
30
+ /**
31
+ * Caveat: it always returns an empty object, not the actual incrementMap.
32
+ */
33
+ incrementBatch(table: string, prop: string, incrementMap: StringMap<number>, _opt?: CommonDBOptions): Promise<StringMap<number>>;
29
34
  ping(): Promise<void>;
30
35
  getTables(): Promise<string[]>;
31
36
  }
@@ -1,48 +1,46 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.FirestoreDBTransaction = exports.FirestoreDB = exports.RollbackError = void 0;
4
- const db_lib_1 = require("@naturalcycles/db-lib");
5
- const js_lib_1 = require("@naturalcycles/js-lib");
6
- const firestore_util_1 = require("./firestore.util");
7
- const query_util_1 = require("./query.util");
1
+ import { FieldValue } from '@google-cloud/firestore';
2
+ import { BaseCommonDB, commonDBFullSupport } from '@naturalcycles/db-lib';
3
+ import { _assert, _chunk, _filterUndefinedValues, _isTruthy, _omit, _stringMapEntries, pMap, } from '@naturalcycles/js-lib';
4
+ import { escapeDocId, unescapeDocId } from './firestore.util.js';
5
+ import { dbQueryToFirestoreQuery } from './query.util.js';
8
6
  const methodMap = {
9
7
  insert: 'create',
10
8
  update: 'update',
11
9
  upsert: 'set',
12
10
  };
13
- class RollbackError extends Error {
11
+ export class RollbackError extends Error {
14
12
  constructor() {
15
13
  super('rollback');
16
14
  }
17
15
  }
18
- exports.RollbackError = RollbackError;
19
- class FirestoreDB extends db_lib_1.BaseCommonDB {
16
+ export class FirestoreDB extends BaseCommonDB {
17
+ cfg;
20
18
  constructor(cfg) {
21
19
  super();
22
20
  this.cfg = cfg;
23
- this.support = {
24
- ...db_lib_1.commonDBFullSupport,
25
- updateByQuery: false,
26
- tableSchemas: false,
27
- };
28
21
  }
22
+ support = {
23
+ ...commonDBFullSupport,
24
+ patchByQuery: false, // todo: can be implemented
25
+ tableSchemas: false,
26
+ };
29
27
  // GET
30
28
  async getByIds(table, ids, opt = {}) {
31
29
  if (!ids.length)
32
30
  return [];
33
31
  const { firestore } = this.cfg;
34
32
  const col = firestore.collection(table);
35
- return (await (opt.tx?.tx || firestore).getAll(...ids.map(id => col.doc((0, firestore_util_1.escapeDocId)(id)))))
33
+ return (await (opt.tx?.tx || firestore).getAll(...ids.map(id => col.doc(escapeDocId(id)))))
36
34
  .map(doc => {
37
35
  const data = doc.data();
38
36
  if (data === undefined)
39
37
  return;
40
38
  return {
41
- id: (0, firestore_util_1.unescapeDocId)(doc.id),
39
+ id: unescapeDocId(doc.id),
42
40
  ...data,
43
41
  };
44
42
  })
45
- .filter(js_lib_1._isTruthy);
43
+ .filter(_isTruthy);
46
44
  }
47
45
  // QUERY
48
46
  async runQuery(q, opt) {
@@ -53,11 +51,11 @@ class FirestoreDB extends db_lib_1.BaseCommonDB {
53
51
  rows: await this.getByIds(q.table, ids, opt),
54
52
  };
55
53
  }
56
- const firestoreQuery = (0, query_util_1.dbQueryToFirestoreQuery)(q, this.cfg.firestore.collection(q.table));
54
+ const firestoreQuery = dbQueryToFirestoreQuery(q, this.cfg.firestore.collection(q.table));
57
55
  let rows = await this.runFirestoreQuery(firestoreQuery, opt);
58
56
  // Special case when projection query didn't specify 'id'
59
57
  if (q._selectedFieldNames && !q._selectedFieldNames.includes('id')) {
60
- rows = rows.map(r => (0, js_lib_1._omit)(r, ['id']));
58
+ rows = rows.map(r => _omit(r, ['id']));
61
59
  }
62
60
  return { rows };
63
61
  }
@@ -65,15 +63,15 @@ class FirestoreDB extends db_lib_1.BaseCommonDB {
65
63
  return this.querySnapshotToArray(await q.get());
66
64
  }
67
65
  async runQueryCount(q, _opt) {
68
- const firestoreQuery = (0, query_util_1.dbQueryToFirestoreQuery)(q, this.cfg.firestore.collection(q.table));
66
+ const firestoreQuery = dbQueryToFirestoreQuery(q, this.cfg.firestore.collection(q.table));
69
67
  const r = await firestoreQuery.count().get();
70
68
  return r.data().count;
71
69
  }
72
70
  streamQuery(q, _opt) {
73
- const firestoreQuery = (0, query_util_1.dbQueryToFirestoreQuery)(q, this.cfg.firestore.collection(q.table));
71
+ const firestoreQuery = dbQueryToFirestoreQuery(q, this.cfg.firestore.collection(q.table));
74
72
  return firestoreQuery.stream().map(doc => {
75
73
  return {
76
- id: (0, firestore_util_1.unescapeDocId)(doc.id),
74
+ id: unescapeDocId(doc.id),
77
75
  ...doc.data(),
78
76
  };
79
77
  });
@@ -86,17 +84,17 @@ class FirestoreDB extends db_lib_1.BaseCommonDB {
86
84
  if (opt.tx) {
87
85
  const { tx } = opt.tx;
88
86
  rows.forEach(row => {
89
- (0, js_lib_1._assert)(row.id, `firestore-db doesn't support id auto-generation, but empty id was provided in saveBatch`);
90
- tx[method](col.doc((0, firestore_util_1.escapeDocId)(row.id)), (0, js_lib_1._filterUndefinedValues)(row));
87
+ _assert(row.id, `firestore-db doesn't support id auto-generation, but empty id was provided in saveBatch`);
88
+ tx[method](col.doc(escapeDocId(row.id)), _filterUndefinedValues(row));
91
89
  });
92
90
  return;
93
91
  }
94
92
  // Firestore allows max 500 items in one batch
95
- await (0, js_lib_1.pMap)((0, js_lib_1._chunk)(rows, 500), async (chunk) => {
93
+ await pMap(_chunk(rows, 500), async (chunk) => {
96
94
  const batch = firestore.batch();
97
95
  chunk.forEach(row => {
98
- (0, js_lib_1._assert)(row.id, `firestore-db doesn't support id auto-generation, but empty id was provided in saveBatch`);
99
- batch[method](col.doc((0, firestore_util_1.escapeDocId)(row.id)), (0, js_lib_1._filterUndefinedValues)(row));
96
+ _assert(row.id, `firestore-db doesn't support id auto-generation, but empty id was provided in saveBatch`);
97
+ batch[method](col.doc(escapeDocId(row.id)), _filterUndefinedValues(row));
100
98
  });
101
99
  await batch.commit();
102
100
  }, { concurrency: 1 });
@@ -109,7 +107,7 @@ class FirestoreDB extends db_lib_1.BaseCommonDB {
109
107
  ids = Array.isArray(idFilter.val) ? idFilter.val : [idFilter.val];
110
108
  }
111
109
  else {
112
- const firestoreQuery = (0, query_util_1.dbQueryToFirestoreQuery)(q.select([]), this.cfg.firestore.collection(q.table));
110
+ const firestoreQuery = dbQueryToFirestoreQuery(q.select([]), this.cfg.firestore.collection(q.table));
113
111
  ids = (await this.runFirestoreQuery(firestoreQuery)).map(obj => obj.id);
114
112
  }
115
113
  await this.deleteByIds(q.table, ids, opt);
@@ -121,14 +119,14 @@ class FirestoreDB extends db_lib_1.BaseCommonDB {
121
119
  if (opt.tx) {
122
120
  const { tx } = opt.tx;
123
121
  ids.forEach(id => {
124
- tx.delete(col.doc((0, firestore_util_1.escapeDocId)(id)));
122
+ tx.delete(col.doc(escapeDocId(id)));
125
123
  });
126
124
  return ids.length;
127
125
  }
128
- await (0, js_lib_1.pMap)((0, js_lib_1._chunk)(ids, 500), async (chunk) => {
126
+ await pMap(_chunk(ids, 500), async (chunk) => {
129
127
  const batch = firestore.batch();
130
128
  chunk.forEach(id => {
131
- batch.delete(col.doc((0, firestore_util_1.escapeDocId)(id)));
129
+ batch.delete(col.doc(escapeDocId(id)));
132
130
  });
133
131
  await batch.commit();
134
132
  });
@@ -138,7 +136,7 @@ class FirestoreDB extends db_lib_1.BaseCommonDB {
138
136
  const rows = [];
139
137
  qs.forEach(doc => {
140
138
  rows.push({
141
- id: (0, firestore_util_1.unescapeDocId)(doc.id),
139
+ id: unescapeDocId(doc.id),
142
140
  ...doc.data(),
143
141
  });
144
142
  });
@@ -162,6 +160,22 @@ class FirestoreDB extends db_lib_1.BaseCommonDB {
162
160
  throw err;
163
161
  }
164
162
  }
163
+ /**
164
+ * Caveat: it always returns an empty object, not the actual incrementMap.
165
+ */
166
+ async incrementBatch(table, prop, incrementMap, _opt) {
167
+ const { firestore } = this.cfg;
168
+ const col = firestore.collection(table);
169
+ const batch = firestore.batch();
170
+ for (const [id, increment] of _stringMapEntries(incrementMap)) {
171
+ batch.set(col.doc(escapeDocId(id)), {
172
+ // todo: lazy-load FieldValue
173
+ [prop]: FieldValue.increment(increment),
174
+ }, { merge: true });
175
+ }
176
+ await batch.commit();
177
+ return {};
178
+ }
165
179
  async ping() {
166
180
  // no-op now
167
181
  }
@@ -169,11 +183,12 @@ class FirestoreDB extends db_lib_1.BaseCommonDB {
169
183
  return [];
170
184
  }
171
185
  }
172
- exports.FirestoreDB = FirestoreDB;
173
186
  /**
174
187
  * https://firebase.google.com/docs/firestore/manage-data/transactions
175
188
  */
176
- class FirestoreDBTransaction {
189
+ export class FirestoreDBTransaction {
190
+ db;
191
+ tx;
177
192
  constructor(db, tx) {
178
193
  this.db = db;
179
194
  this.tx = tx;
@@ -191,4 +206,3 @@ class FirestoreDBTransaction {
191
206
  return await this.db.deleteByIds(table, ids, { ...opt, tx: this });
192
207
  }
193
208
  }
194
- exports.FirestoreDBTransaction = FirestoreDBTransaction;
@@ -1,15 +1,10 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.unescapeDocId = exports.escapeDocId = void 0;
4
1
  const SLASH = '_SLASH_';
5
- function escapeDocId(docId) {
2
+ export function escapeDocId(docId) {
6
3
  if (typeof docId === 'number')
7
4
  return String(docId);
8
5
  return docId.replaceAll('/', SLASH);
9
6
  }
10
- exports.escapeDocId = escapeDocId;
11
- function unescapeDocId(docId) {
7
+ export function unescapeDocId(docId) {
12
8
  // if (typeof docId === 'number') return docId
13
9
  return docId.replaceAll(SLASH, '/');
14
10
  }
15
- exports.unescapeDocId = unescapeDocId;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  import { Firestore } from '@google-cloud/firestore';
2
- export * from './firestore.db';
3
- export * from './query.util';
2
+ export * from './firestore.db.js';
3
+ export * from './query.util.js';
4
4
  export { Firestore };
package/dist/index.js CHANGED
@@ -1,8 +1,4 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Firestore = void 0;
4
- const tslib_1 = require("tslib");
5
- const firestore_1 = require("@google-cloud/firestore");
6
- Object.defineProperty(exports, "Firestore", { enumerable: true, get: function () { return firestore_1.Firestore; } });
7
- tslib_1.__exportStar(require("./firestore.db"), exports);
8
- tslib_1.__exportStar(require("./query.util"), exports);
1
+ import { Firestore } from '@google-cloud/firestore';
2
+ export * from './firestore.db.js';
3
+ export * from './query.util.js';
4
+ export { Firestore };
@@ -1,4 +1,4 @@
1
- import { Query } from '@google-cloud/firestore';
2
- import { DBQuery } from '@naturalcycles/db-lib';
3
- import { ObjectWithId } from '@naturalcycles/js-lib';
1
+ import type { Query } from '@google-cloud/firestore';
2
+ import type { DBQuery } from '@naturalcycles/db-lib';
3
+ import type { ObjectWithId } from '@naturalcycles/js-lib';
4
4
  export declare function dbQueryToFirestoreQuery<ROW extends ObjectWithId>(dbQuery: DBQuery<ROW>, emptyQuery: Query): Query;
@@ -1,13 +1,10 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.dbQueryToFirestoreQuery = void 0;
4
1
  // Map DBQueryFilterOp to WhereFilterOp
5
2
  // Currently it's fully aligned!
6
3
  const OP_MAP = {
7
4
  // '=': '==',
8
5
  // in: 'array-contains',
9
6
  };
10
- function dbQueryToFirestoreQuery(dbQuery, emptyQuery) {
7
+ export function dbQueryToFirestoreQuery(dbQuery, emptyQuery) {
11
8
  // filter
12
9
  // eslint-disable-next-line unicorn/no-array-reduce
13
10
  let q = dbQuery._filters.reduce((q, f) => {
@@ -27,4 +24,3 @@ function dbQueryToFirestoreQuery(dbQuery, emptyQuery) {
27
24
  }
28
25
  return q;
29
26
  }
30
- exports.dbQueryToFirestoreQuery = dbQueryToFirestoreQuery;
package/package.json CHANGED
@@ -1,21 +1,28 @@
1
1
  {
2
2
  "name": "@naturalcycles/firestore-lib",
3
+ "type": "module",
3
4
  "scripts": {
4
- "prepare": "husky"
5
+ "prepare": "husky",
6
+ "build": "dev-lib build",
7
+ "test": "dev-lib test",
8
+ "lint": "dev-lib lint",
9
+ "bt": "dev-lib bt",
10
+ "lbt": "dev-lib lbt"
5
11
  },
6
12
  "dependencies": {
7
13
  "@google-cloud/firestore": "^7.0.0",
8
- "@naturalcycles/db-lib": "^9.0.0",
14
+ "@naturalcycles/db-lib": "^10.0.2",
9
15
  "@naturalcycles/js-lib": "^14.6.0",
10
16
  "@naturalcycles/nodejs-lib": "^13.1.0"
11
17
  },
12
18
  "devDependencies": {
13
- "@naturalcycles/dev-lib": "^13.0.1",
14
- "@naturalcycles/test-lib": "^1.0.3",
15
- "@types/node": "^20.1.2",
19
+ "@naturalcycles/dev-lib": "^17.3.0",
20
+ "@types/node": "^22.7.5",
21
+ "@vitest/coverage-v8": "^3.1.1",
16
22
  "dotenv": "^16.0.0",
17
- "firebase-admin": "^12.0.0",
18
- "jest": "^29.1.2"
23
+ "firebase-admin": "^13.2.0",
24
+ "tsx": "^4.19.3",
25
+ "vitest": "^3.1.1"
19
26
  },
20
27
  "files": [
21
28
  "dist",
@@ -35,9 +42,9 @@
35
42
  "url": "https://github.com/NaturalCycles/firestore-lib"
36
43
  },
37
44
  "engines": {
38
- "node": ">=18.12.0"
45
+ "node": ">=22.12.0"
39
46
  },
40
- "version": "1.7.1",
47
+ "version": "2.0.0",
41
48
  "description": "Firestore implementation of CommonDB interface",
42
49
  "author": "Natural Cycles Team",
43
50
  "license": "MIT"
@@ -1,14 +1,13 @@
1
- import {
1
+ import type {
2
2
  Firestore,
3
3
  Query,
4
4
  QueryDocumentSnapshot,
5
5
  QuerySnapshot,
6
6
  Transaction,
7
7
  } from '@google-cloud/firestore'
8
- import {
9
- BaseCommonDB,
8
+ import { FieldValue } from '@google-cloud/firestore'
9
+ import type {
10
10
  CommonDB,
11
- commonDBFullSupport,
12
11
  CommonDBOptions,
13
12
  CommonDBSaveMethod,
14
13
  CommonDBSaveOptions,
@@ -20,18 +19,20 @@ import {
20
19
  DBTransactionFn,
21
20
  RunQueryResult,
22
21
  } from '@naturalcycles/db-lib'
22
+ import { BaseCommonDB, commonDBFullSupport } from '@naturalcycles/db-lib'
23
+ import type { ObjectWithId, StringMap } from '@naturalcycles/js-lib'
23
24
  import {
24
- pMap,
25
+ _assert,
25
26
  _chunk,
26
- _omit,
27
27
  _filterUndefinedValues,
28
- ObjectWithId,
29
- _assert,
30
28
  _isTruthy,
29
+ _omit,
30
+ _stringMapEntries,
31
+ pMap,
31
32
  } from '@naturalcycles/js-lib'
32
- import { ReadableTyped } from '@naturalcycles/nodejs-lib'
33
- import { escapeDocId, unescapeDocId } from './firestore.util'
34
- import { dbQueryToFirestoreQuery } from './query.util'
33
+ import type { ReadableTyped } from '@naturalcycles/nodejs-lib'
34
+ import { escapeDocId, unescapeDocId } from './firestore.util.js'
35
+ import { dbQueryToFirestoreQuery } from './query.util.js'
35
36
 
36
37
  export interface FirestoreDBCfg {
37
38
  firestore: Firestore
@@ -62,7 +63,7 @@ export class FirestoreDB extends BaseCommonDB implements CommonDB {
62
63
 
63
64
  override support: CommonDBSupport = {
64
65
  ...commonDBFullSupport,
65
- updateByQuery: false,
66
+ patchByQuery: false, // todo: can be implemented
66
67
  tableSchemas: false,
67
68
  }
68
69
 
@@ -286,6 +287,34 @@ export class FirestoreDB extends BaseCommonDB implements CommonDB {
286
287
  }
287
288
  }
288
289
 
290
+ /**
291
+ * Caveat: it always returns an empty object, not the actual incrementMap.
292
+ */
293
+ override async incrementBatch(
294
+ table: string,
295
+ prop: string,
296
+ incrementMap: StringMap<number>,
297
+ _opt?: CommonDBOptions,
298
+ ): Promise<StringMap<number>> {
299
+ const { firestore } = this.cfg
300
+ const col = firestore.collection(table)
301
+ const batch = firestore.batch()
302
+
303
+ for (const [id, increment] of _stringMapEntries(incrementMap)) {
304
+ batch.set(
305
+ col.doc(escapeDocId(id)),
306
+ {
307
+ // todo: lazy-load FieldValue
308
+ [prop]: FieldValue.increment(increment),
309
+ },
310
+ { merge: true },
311
+ )
312
+ }
313
+
314
+ await batch.commit()
315
+ return {}
316
+ }
317
+
289
318
  override async ping(): Promise<void> {
290
319
  // no-op now
291
320
  }
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  import { Firestore } from '@google-cloud/firestore'
2
- export * from './firestore.db'
3
- export * from './query.util'
2
+ export * from './firestore.db.js'
3
+ export * from './query.util.js'
4
4
  export { Firestore }
package/src/query.util.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { Query, WhereFilterOp } from '@google-cloud/firestore'
2
- import { DBQuery, DBQueryFilterOperator } from '@naturalcycles/db-lib'
3
- import { ObjectWithId } from '@naturalcycles/js-lib'
1
+ import type { Query, WhereFilterOp } from '@google-cloud/firestore'
2
+ import type { DBQuery, DBQueryFilterOperator } from '@naturalcycles/db-lib'
3
+ import type { ObjectWithId } from '@naturalcycles/js-lib'
4
4
 
5
5
  // Map DBQueryFilterOp to WhereFilterOp
6
6
  // Currently it's fully aligned!