@naturalcycles/firestore-lib 2.4.0 → 2.5.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.
- package/dist/firestore.db.d.ts +1 -1
- package/dist/firestore.db.js +19 -17
- package/dist/query.util.js +7 -8
- package/package.json +1 -1
- package/src/firestore.db.ts +40 -32
- package/src/query.util.ts +8 -8
package/dist/firestore.db.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ export declare class FirestoreDB extends BaseCommonDB implements CommonDB {
|
|
|
19
19
|
support: CommonDBSupport;
|
|
20
20
|
getByIds<ROW extends ObjectWithId>(table: string, ids: string[], opt?: FirestoreDBOptions): Promise<ROW[]>;
|
|
21
21
|
runQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: FirestoreDBOptions): Promise<RunQueryResult<ROW>>;
|
|
22
|
-
runFirestoreQuery<ROW extends ObjectWithId>(q: Query
|
|
22
|
+
runFirestoreQuery<ROW extends ObjectWithId>(q: Query): Promise<ROW[]>;
|
|
23
23
|
runQueryCount<ROW extends ObjectWithId>(q: DBQuery<ROW>, _opt?: FirestoreDBOptions): Promise<number>;
|
|
24
24
|
streamQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, _opt?: CommonDBStreamOptions): ReadableTyped<ROW>;
|
|
25
25
|
saveBatch<ROW extends ObjectWithId>(table: string, rows: ROW[], opt?: FirestoreDBSaveOptions<ROW>): Promise<void>;
|
package/dist/firestore.db.js
CHANGED
|
@@ -57,14 +57,14 @@ export class FirestoreDB extends BaseCommonDB {
|
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
59
|
const firestoreQuery = dbQueryToFirestoreQuery(q, this.cfg.firestore.collection(q.table));
|
|
60
|
-
let rows = await this.runFirestoreQuery(firestoreQuery
|
|
60
|
+
let rows = await this.runFirestoreQuery(firestoreQuery);
|
|
61
61
|
// Special case when projection query didn't specify 'id'
|
|
62
62
|
if (q._selectedFieldNames && !q._selectedFieldNames.includes('id')) {
|
|
63
63
|
rows = rows.map(r => _omit(r, ['id']));
|
|
64
64
|
}
|
|
65
65
|
return { rows };
|
|
66
66
|
}
|
|
67
|
-
async runFirestoreQuery(q
|
|
67
|
+
async runFirestoreQuery(q) {
|
|
68
68
|
return this.querySnapshotToArray(await q.get());
|
|
69
69
|
}
|
|
70
70
|
async runQueryCount(q, _opt) {
|
|
@@ -90,19 +90,20 @@ export class FirestoreDB extends BaseCommonDB {
|
|
|
90
90
|
const { tx } = opt.tx;
|
|
91
91
|
for (const row of rows) {
|
|
92
92
|
_assert(row.id, `firestore-db doesn't support id auto-generation, but empty id was provided in saveBatch`);
|
|
93
|
-
|
|
93
|
+
const { id, ...rowWithoutId } = row;
|
|
94
|
+
tx[method](col.doc(escapeDocId(id)), _filterUndefinedValues(rowWithoutId));
|
|
94
95
|
}
|
|
95
96
|
return;
|
|
96
97
|
}
|
|
97
|
-
|
|
98
|
-
await pMap(_chunk(rows, 500), async (chunk) => {
|
|
98
|
+
await pMap(_chunk(rows, MAX_ITEMS), async (chunk) => {
|
|
99
99
|
const batch = firestore.batch();
|
|
100
100
|
for (const row of chunk) {
|
|
101
101
|
_assert(row.id, `firestore-db doesn't support id auto-generation, but empty id was provided in saveBatch`);
|
|
102
|
-
|
|
102
|
+
const { id, ...rowWithoutId } = row;
|
|
103
|
+
batch[method](col.doc(escapeDocId(id)), _filterUndefinedValues(rowWithoutId));
|
|
103
104
|
}
|
|
104
105
|
await batch.commit();
|
|
105
|
-
}, { concurrency:
|
|
106
|
+
}, { concurrency: FIRESTORE_RECOMMENDED_CONCURRENCY });
|
|
106
107
|
}
|
|
107
108
|
// DELETE
|
|
108
109
|
async deleteByQuery(q, opt) {
|
|
@@ -128,24 +129,22 @@ export class FirestoreDB extends BaseCommonDB {
|
|
|
128
129
|
}
|
|
129
130
|
return ids.length;
|
|
130
131
|
}
|
|
131
|
-
await pMap(_chunk(ids,
|
|
132
|
+
await pMap(_chunk(ids, MAX_ITEMS), async (chunk) => {
|
|
132
133
|
const batch = firestore.batch();
|
|
133
134
|
for (const id of chunk) {
|
|
134
135
|
batch.delete(col.doc(escapeDocId(id)));
|
|
135
136
|
}
|
|
136
137
|
await batch.commit();
|
|
138
|
+
}, {
|
|
139
|
+
concurrency: FIRESTORE_RECOMMENDED_CONCURRENCY,
|
|
137
140
|
});
|
|
138
141
|
return ids.length;
|
|
139
142
|
}
|
|
140
143
|
querySnapshotToArray(qs) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
...doc.data(),
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
return rows;
|
|
144
|
+
return qs.docs.map(doc => ({
|
|
145
|
+
id: unescapeDocId(doc.id),
|
|
146
|
+
...doc.data(),
|
|
147
|
+
}));
|
|
149
148
|
}
|
|
150
149
|
async runInTransaction(fn, opt = {}) {
|
|
151
150
|
const { readOnly } = opt;
|
|
@@ -174,7 +173,6 @@ export class FirestoreDB extends BaseCommonDB {
|
|
|
174
173
|
const batch = firestore.batch();
|
|
175
174
|
for (const [id, increment] of _stringMapEntries(incrementMap)) {
|
|
176
175
|
batch.set(col.doc(escapeDocId(id)), {
|
|
177
|
-
// todo: lazy-load FieldValue
|
|
178
176
|
[prop]: FieldValue.increment(increment),
|
|
179
177
|
}, { merge: true });
|
|
180
178
|
}
|
|
@@ -214,3 +212,7 @@ export class FirestoreDBTransaction {
|
|
|
214
212
|
return await this.db.deleteByIds(table, ids, { ...opt, tx: this });
|
|
215
213
|
}
|
|
216
214
|
}
|
|
215
|
+
// Datastore (also Firestore and other Google APIs) supports max 500 of items when saving/deleting, etc.
|
|
216
|
+
const MAX_ITEMS = 500;
|
|
217
|
+
// It's an empyrical value, but anything less than infinity is better than infinity
|
|
218
|
+
const FIRESTORE_RECOMMENDED_CONCURRENCY = 8;
|
package/dist/query.util.js
CHANGED
|
@@ -5,16 +5,15 @@ const OP_MAP = {
|
|
|
5
5
|
// in: 'array-contains',
|
|
6
6
|
};
|
|
7
7
|
export function dbQueryToFirestoreQuery(dbQuery, emptyQuery) {
|
|
8
|
+
let q = emptyQuery;
|
|
8
9
|
// filter
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}, emptyQuery);
|
|
10
|
+
for (const f of dbQuery._filters) {
|
|
11
|
+
q = q.where(f.name, OP_MAP[f.op] || f.op, f.val);
|
|
12
|
+
}
|
|
13
13
|
// order
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}, q);
|
|
14
|
+
for (const ord of dbQuery._orders) {
|
|
15
|
+
q = q.orderBy(ord.name, ord.descending ? 'desc' : 'asc');
|
|
16
|
+
}
|
|
18
17
|
// limit
|
|
19
18
|
q = q.limit(dbQuery._limitValue);
|
|
20
19
|
// selectedFields
|
package/package.json
CHANGED
package/src/firestore.db.ts
CHANGED
|
@@ -85,8 +85,8 @@ export class FirestoreDB extends BaseCommonDB implements CommonDB {
|
|
|
85
85
|
if (data === undefined) return
|
|
86
86
|
return {
|
|
87
87
|
id: unescapeDocId(doc.id),
|
|
88
|
-
...
|
|
89
|
-
}
|
|
88
|
+
...data,
|
|
89
|
+
} as ROW
|
|
90
90
|
})
|
|
91
91
|
.filter(_isTruthy)
|
|
92
92
|
}
|
|
@@ -106,7 +106,7 @@ export class FirestoreDB extends BaseCommonDB implements CommonDB {
|
|
|
106
106
|
|
|
107
107
|
const firestoreQuery = dbQueryToFirestoreQuery(q, this.cfg.firestore.collection(q.table))
|
|
108
108
|
|
|
109
|
-
let rows = await this.runFirestoreQuery<ROW>(firestoreQuery
|
|
109
|
+
let rows = await this.runFirestoreQuery<ROW>(firestoreQuery)
|
|
110
110
|
|
|
111
111
|
// Special case when projection query didn't specify 'id'
|
|
112
112
|
if (q._selectedFieldNames && !q._selectedFieldNames.includes('id')) {
|
|
@@ -116,10 +116,7 @@ export class FirestoreDB extends BaseCommonDB implements CommonDB {
|
|
|
116
116
|
return { rows }
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
async runFirestoreQuery<ROW extends ObjectWithId>(
|
|
120
|
-
q: Query,
|
|
121
|
-
_opt?: FirestoreDBOptions,
|
|
122
|
-
): Promise<ROW[]> {
|
|
119
|
+
async runFirestoreQuery<ROW extends ObjectWithId>(q: Query): Promise<ROW[]> {
|
|
123
120
|
return this.querySnapshotToArray(await q.get())
|
|
124
121
|
}
|
|
125
122
|
|
|
@@ -165,14 +162,17 @@ export class FirestoreDB extends BaseCommonDB implements CommonDB {
|
|
|
165
162
|
`firestore-db doesn't support id auto-generation, but empty id was provided in saveBatch`,
|
|
166
163
|
)
|
|
167
164
|
|
|
168
|
-
|
|
165
|
+
const { id, ...rowWithoutId } = row
|
|
166
|
+
tx[method as 'set' | 'create'](
|
|
167
|
+
col.doc(escapeDocId(id)),
|
|
168
|
+
_filterUndefinedValues(rowWithoutId),
|
|
169
|
+
)
|
|
169
170
|
}
|
|
170
171
|
return
|
|
171
172
|
}
|
|
172
173
|
|
|
173
|
-
// Firestore allows max 500 items in one batch
|
|
174
174
|
await pMap(
|
|
175
|
-
_chunk(rows,
|
|
175
|
+
_chunk(rows, MAX_ITEMS),
|
|
176
176
|
async chunk => {
|
|
177
177
|
const batch = firestore.batch()
|
|
178
178
|
|
|
@@ -181,15 +181,16 @@ export class FirestoreDB extends BaseCommonDB implements CommonDB {
|
|
|
181
181
|
row.id,
|
|
182
182
|
`firestore-db doesn't support id auto-generation, but empty id was provided in saveBatch`,
|
|
183
183
|
)
|
|
184
|
+
const { id, ...rowWithoutId } = row
|
|
184
185
|
batch[method as 'set' | 'create'](
|
|
185
|
-
col.doc(escapeDocId(
|
|
186
|
-
_filterUndefinedValues(
|
|
186
|
+
col.doc(escapeDocId(id)),
|
|
187
|
+
_filterUndefinedValues(rowWithoutId),
|
|
187
188
|
)
|
|
188
189
|
}
|
|
189
190
|
|
|
190
191
|
await batch.commit()
|
|
191
192
|
},
|
|
192
|
-
{ concurrency:
|
|
193
|
+
{ concurrency: FIRESTORE_RECOMMENDED_CONCURRENCY },
|
|
193
194
|
)
|
|
194
195
|
}
|
|
195
196
|
|
|
@@ -208,7 +209,7 @@ export class FirestoreDB extends BaseCommonDB implements CommonDB {
|
|
|
208
209
|
q.select([]),
|
|
209
210
|
this.cfg.firestore.collection(q.table),
|
|
210
211
|
)
|
|
211
|
-
ids = (await this.runFirestoreQuery<
|
|
212
|
+
ids = (await this.runFirestoreQuery<ObjectWithId>(firestoreQuery)).map(obj => obj.id)
|
|
212
213
|
}
|
|
213
214
|
|
|
214
215
|
await this.deleteByIds(q.table, ids, opt)
|
|
@@ -233,30 +234,33 @@ export class FirestoreDB extends BaseCommonDB implements CommonDB {
|
|
|
233
234
|
return ids.length
|
|
234
235
|
}
|
|
235
236
|
|
|
236
|
-
await pMap(
|
|
237
|
-
|
|
237
|
+
await pMap(
|
|
238
|
+
_chunk(ids, MAX_ITEMS),
|
|
239
|
+
async chunk => {
|
|
240
|
+
const batch = firestore.batch()
|
|
238
241
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
+
for (const id of chunk) {
|
|
243
|
+
batch.delete(col.doc(escapeDocId(id)))
|
|
244
|
+
}
|
|
242
245
|
|
|
243
|
-
|
|
244
|
-
|
|
246
|
+
await batch.commit()
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
concurrency: FIRESTORE_RECOMMENDED_CONCURRENCY,
|
|
250
|
+
},
|
|
251
|
+
)
|
|
245
252
|
|
|
246
253
|
return ids.length
|
|
247
254
|
}
|
|
248
255
|
|
|
249
256
|
private querySnapshotToArray<T = any>(qs: QuerySnapshot): T[] {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
})
|
|
258
|
-
|
|
259
|
-
return rows
|
|
257
|
+
return qs.docs.map(
|
|
258
|
+
doc =>
|
|
259
|
+
({
|
|
260
|
+
id: unescapeDocId(doc.id),
|
|
261
|
+
...doc.data(),
|
|
262
|
+
}) as T,
|
|
263
|
+
)
|
|
260
264
|
}
|
|
261
265
|
|
|
262
266
|
override async runInTransaction(
|
|
@@ -301,7 +305,6 @@ export class FirestoreDB extends BaseCommonDB implements CommonDB {
|
|
|
301
305
|
batch.set(
|
|
302
306
|
col.doc(escapeDocId(id)),
|
|
303
307
|
{
|
|
304
|
-
// todo: lazy-load FieldValue
|
|
305
308
|
[prop]: FieldValue.increment(increment),
|
|
306
309
|
},
|
|
307
310
|
{ merge: true },
|
|
@@ -358,3 +361,8 @@ export class FirestoreDBTransaction implements DBTransaction {
|
|
|
358
361
|
return await this.db.deleteByIds(table, ids, { ...opt, tx: this })
|
|
359
362
|
}
|
|
360
363
|
}
|
|
364
|
+
|
|
365
|
+
// Datastore (also Firestore and other Google APIs) supports max 500 of items when saving/deleting, etc.
|
|
366
|
+
const MAX_ITEMS = 500
|
|
367
|
+
// It's an empyrical value, but anything less than infinity is better than infinity
|
|
368
|
+
const FIRESTORE_RECOMMENDED_CONCURRENCY = 8
|
package/src/query.util.ts
CHANGED
|
@@ -13,17 +13,17 @@ export function dbQueryToFirestoreQuery<ROW extends ObjectWithId>(
|
|
|
13
13
|
dbQuery: DBQuery<ROW>,
|
|
14
14
|
emptyQuery: Query,
|
|
15
15
|
): Query {
|
|
16
|
+
let q = emptyQuery
|
|
17
|
+
|
|
16
18
|
// filter
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}, emptyQuery)
|
|
19
|
+
for (const f of dbQuery._filters) {
|
|
20
|
+
q = q.where(f.name as string, OP_MAP[f.op] || (f.op as WhereFilterOp), f.val)
|
|
21
|
+
}
|
|
21
22
|
|
|
22
23
|
// order
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}, q)
|
|
24
|
+
for (const ord of dbQuery._orders) {
|
|
25
|
+
q = q.orderBy(ord.name as string, ord.descending ? 'desc' : 'asc')
|
|
26
|
+
}
|
|
27
27
|
|
|
28
28
|
// limit
|
|
29
29
|
q = q.limit(dbQuery._limitValue)
|