@knymbus/firestoredb 1.0.10 → 1.0.12
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/cjs/FirestoreDB.d.ts +22 -9
- package/dist/cjs/FirestoreDB.d.ts.map +1 -1
- package/dist/cjs/FirestoreDB.js +77 -66
- package/dist/cjs/FirestoreQuery.d.ts +41 -4
- package/dist/cjs/FirestoreQuery.d.ts.map +1 -1
- package/dist/cjs/FirestoreQuery.js +62 -16
- package/dist/cjs/index.d.ts +1 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +1 -0
- package/dist/cjs/types.d.ts +44 -0
- package/dist/cjs/types.d.ts.map +1 -1
- package/dist/cjs/utils/FirestoreCursor.d.ts +41 -0
- package/dist/cjs/utils/FirestoreCursor.d.ts.map +1 -0
- package/dist/cjs/utils/FirestoreCursor.js +95 -0
- package/dist/cjs/utils/createFirestoreDB.d.ts +10 -0
- package/dist/cjs/utils/createFirestoreDB.d.ts.map +1 -0
- package/dist/cjs/utils/createFirestoreDB.js +19 -0
- package/dist/cjs/utils/index.d.ts +1 -0
- package/dist/cjs/utils/index.d.ts.map +1 -1
- package/dist/cjs/utils/index.js +1 -0
- package/dist/esm/FirestoreDB.d.ts +22 -9
- package/dist/esm/FirestoreDB.d.ts.map +1 -1
- package/dist/esm/FirestoreDB.js +77 -66
- package/dist/esm/FirestoreQuery.d.ts +41 -4
- package/dist/esm/FirestoreQuery.d.ts.map +1 -1
- package/dist/esm/FirestoreQuery.js +62 -16
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/types.d.ts +44 -0
- package/dist/esm/types.d.ts.map +1 -1
- package/dist/esm/utils/FirestoreCursor.d.ts +41 -0
- package/dist/esm/utils/FirestoreCursor.d.ts.map +1 -0
- package/dist/esm/utils/FirestoreCursor.js +95 -0
- package/dist/esm/utils/createFirestoreDB.d.ts +10 -0
- package/dist/esm/utils/createFirestoreDB.d.ts.map +1 -0
- package/dist/esm/utils/createFirestoreDB.js +19 -0
- package/dist/esm/utils/index.d.ts +1 -0
- package/dist/esm/utils/index.d.ts.map +1 -1
- package/dist/esm/utils/index.js +1 -0
- package/package.json +1 -1
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import { Firestore } from
|
|
1
|
+
import type { Firestore, FirestoreError, Unsubscribe } from 'firebase/firestore';
|
|
2
2
|
import { FirestoreQuery } from './FirestoreQuery';
|
|
3
|
-
import { DocumentInput, QueryFilter, UpdateDocument, WithSystemFields } from './types';
|
|
3
|
+
import { DocumentInput, FirestoreSDK, QueryFilter, UpdateDocument, WithSystemFields } from './types';
|
|
4
4
|
export interface DBOptions {
|
|
5
5
|
softDelete?: boolean;
|
|
6
6
|
}
|
|
7
7
|
export declare class FirestoreDB<T> {
|
|
8
8
|
private _db;
|
|
9
9
|
private _collectionName;
|
|
10
|
+
private _sdk;
|
|
11
|
+
private _options;
|
|
10
12
|
private _collectionRef;
|
|
11
13
|
private _isSoftDeleteEnabled;
|
|
12
14
|
/**
|
|
@@ -14,7 +16,7 @@ export declare class FirestoreDB<T> {
|
|
|
14
16
|
* @param db: Firebase
|
|
15
17
|
* @param collection string
|
|
16
18
|
*/
|
|
17
|
-
constructor(
|
|
19
|
+
constructor(_db: Firestore, _collectionName: string, _sdk: FirestoreSDK, _options?: DBOptions);
|
|
18
20
|
/**
|
|
19
21
|
* FIRESTOREDB: findOne({ age: '13' }); or findOne('id_123');
|
|
20
22
|
*/
|
|
@@ -83,13 +85,18 @@ export declare class FirestoreDB<T> {
|
|
|
83
85
|
restoredCount: number;
|
|
84
86
|
}>;
|
|
85
87
|
/**
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
* @title
|
|
89
|
+
* FIRESTOREDB: collection.watch(filter)
|
|
90
|
+
*
|
|
91
|
+
* @detail
|
|
92
|
+
* A real-time listener that bypasses the manual cache and
|
|
93
|
+
* pushes updates as they happen in the database.
|
|
94
|
+
*/
|
|
95
|
+
watch<T>(filter: Record<string, any> | undefined, callback: (data: WithSystemFields<T>[]) => void, onError?: (error: FirestoreError) => void): Unsubscribe;
|
|
91
96
|
/**
|
|
92
97
|
* Efficiently checks if a document exists without downloading it. (only metadata).
|
|
98
|
+
*
|
|
99
|
+
* @optimized
|
|
93
100
|
* Optimized to only check for the presence of a document
|
|
94
101
|
*/
|
|
95
102
|
exists: (filter: Record<string, any>) => Promise<boolean>;
|
|
@@ -106,9 +113,15 @@ export declare class FirestoreDB<T> {
|
|
|
106
113
|
private _buildConstraints;
|
|
107
114
|
private _flattenFilter;
|
|
108
115
|
/**
|
|
109
|
-
*
|
|
116
|
+
* FirestoreDB class:
|
|
110
117
|
* Purges all cached queries for THIS collection to ensure data freshness.
|
|
111
118
|
*/
|
|
112
119
|
private _invalidateCache;
|
|
120
|
+
/**
|
|
121
|
+
* This will try to get the document Reference of a given id, however if id missing return a auto generated Document Referrence
|
|
122
|
+
* @param docId optional
|
|
123
|
+
* @returns Firebase DocumentReferrence
|
|
124
|
+
*/
|
|
125
|
+
private _getDocRef;
|
|
113
126
|
}
|
|
114
127
|
//# sourceMappingURL=FirestoreDB.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FirestoreDB.d.ts","sourceRoot":"","sources":["../../src/FirestoreDB.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"FirestoreDB.d.ts","sourceRoot":"","sources":["../../src/FirestoreDB.ts"],"names":[],"mappings":"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"}
|
package/dist/cjs/FirestoreDB.js
CHANGED
|
@@ -10,7 +10,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.FirestoreDB = void 0;
|
|
13
|
-
const firestore_1 = require("firebase/firestore");
|
|
14
13
|
const FirestoreQuery_1 = require("./FirestoreQuery");
|
|
15
14
|
const utils_1 = require("./utils");
|
|
16
15
|
class FirestoreDB {
|
|
@@ -19,8 +18,12 @@ class FirestoreDB {
|
|
|
19
18
|
* @param db: Firebase
|
|
20
19
|
* @param collection string
|
|
21
20
|
*/
|
|
22
|
-
constructor(
|
|
21
|
+
constructor(_db, _collectionName, _sdk, _options = {}) {
|
|
23
22
|
var _a;
|
|
23
|
+
this._db = _db;
|
|
24
|
+
this._collectionName = _collectionName;
|
|
25
|
+
this._sdk = _sdk;
|
|
26
|
+
this._options = _options;
|
|
24
27
|
/**
|
|
25
28
|
* FIRESTOREDB: findOne({ age: '13' }); or findOne('id_123');
|
|
26
29
|
*/
|
|
@@ -46,29 +49,29 @@ class FirestoreDB {
|
|
|
46
49
|
* @returns number
|
|
47
50
|
*/
|
|
48
51
|
this.countDocuments = (...args_1) => __awaiter(this, [...args_1], void 0, function* (filter = {}) {
|
|
49
|
-
let q =
|
|
52
|
+
let q = this._sdk.query(this._collectionRef, ...this._buildConstraints(filter));
|
|
50
53
|
// Update the query with the incoming filter
|
|
51
|
-
const snapshot = yield
|
|
54
|
+
const snapshot = yield this._sdk.getCountFromServer(q);
|
|
52
55
|
return snapshot.data().count;
|
|
53
56
|
});
|
|
54
57
|
this.findOneAndUpdate = (filter, updateObject) => __awaiter(this, void 0, void 0, function* () {
|
|
55
58
|
let docRef;
|
|
56
59
|
if (typeof filter === 'string') {
|
|
57
|
-
docRef =
|
|
60
|
+
docRef = this._getDocRef(filter);
|
|
58
61
|
}
|
|
59
62
|
else {
|
|
60
63
|
// if object we need to find the id first using the findOne fn
|
|
61
64
|
const existing = yield this.findOne(filter);
|
|
62
65
|
if (!existing)
|
|
63
66
|
throw new Error("Document not found for update");
|
|
64
|
-
docRef =
|
|
67
|
+
docRef = this._getDocRef(existing._id);
|
|
65
68
|
}
|
|
66
|
-
return yield
|
|
69
|
+
return yield this._sdk.runTransaction(this._db, (transaction) => __awaiter(this, void 0, void 0, function* () {
|
|
67
70
|
const docSnap = yield transaction.get(docRef);
|
|
68
71
|
if (!docSnap.exists())
|
|
69
72
|
throw new Error("Document Vanished during transaction");
|
|
70
|
-
transaction.update(docRef, Object.assign(Object.assign({}, updateObject), { updatedAt:
|
|
71
|
-
return Object.assign(Object.assign(Object.assign({ _id: docRef.id }, (docSnap.data() || {})), updateObject), { updatedAt:
|
|
73
|
+
transaction.update(docRef, Object.assign(Object.assign({}, updateObject), { updatedAt: this._sdk.serverTimestamp() }));
|
|
74
|
+
return Object.assign(Object.assign(Object.assign({ _id: docRef.id }, (docSnap.data() || {})), updateObject), { updatedAt: this._sdk.serverTimestamp() });
|
|
72
75
|
}));
|
|
73
76
|
});
|
|
74
77
|
/**
|
|
@@ -76,7 +79,7 @@ class FirestoreDB {
|
|
|
76
79
|
* Basic implementation. For production, you'd expand the 'filter' to handle where clauses.
|
|
77
80
|
*/
|
|
78
81
|
this.find = (filter = {}) => {
|
|
79
|
-
return new FirestoreQuery_1.FirestoreQuery(this._db, this._collectionName, this._collectionRef, filter, this._buildConstraints.bind(this), //Pass the private helper
|
|
82
|
+
return new FirestoreQuery_1.FirestoreQuery(this._db, this._collectionName, this._collectionRef, filter, this._sdk, this._buildConstraints.bind(this), //Pass the private helper
|
|
80
83
|
this.countDocuments.bind(this), // pass the count helper
|
|
81
84
|
this._isSoftDeleteEnabled);
|
|
82
85
|
};
|
|
@@ -85,16 +88,14 @@ class FirestoreDB {
|
|
|
85
88
|
* Optimized: Uses set with merge or create
|
|
86
89
|
*/
|
|
87
90
|
this.insertOne = (entity) => __awaiter(this, void 0, void 0, function* () {
|
|
88
|
-
const docRef = entity._id
|
|
89
|
-
? (0, firestore_1.doc)(this._db, this._collectionRef.id, entity._id) //Custom ID
|
|
90
|
-
: (0, firestore_1.doc)(this._collectionRef); //Auto ID
|
|
91
|
+
const docRef = this._getDocRef(entity._id);
|
|
91
92
|
// Using 'set' with { merge: false } acts like an insert/overwrite
|
|
92
|
-
yield
|
|
93
|
+
yield this._sdk.setDoc(docRef, Object.assign(Object.assign({}, entity), { _id: docRef.id, isDeleted: false, createdAt: this._sdk.serverTimestamp(), updatedAt: this._sdk.serverTimestamp() }));
|
|
93
94
|
// 2. TRIGGER INVALIDATION: Any cached 'find' results for this
|
|
94
95
|
// collection are now potentially stale.
|
|
95
96
|
this._invalidateCache();
|
|
96
97
|
// 3. REFRESH: Fetch the document back to get the real server-generated timestamps
|
|
97
|
-
const freshSnap = yield
|
|
98
|
+
const freshSnap = yield this._sdk.getDoc(docRef);
|
|
98
99
|
const freshData = Object.assign({ _id: freshSnap.id }, freshSnap.data());
|
|
99
100
|
return (0, utils_1.hydrateDates)(freshData);
|
|
100
101
|
});
|
|
@@ -108,14 +109,12 @@ class FirestoreDB {
|
|
|
108
109
|
const batchPromises = [];
|
|
109
110
|
// 1. Outer Loop: Creates a new batch for every 500 items
|
|
110
111
|
for (let i = 0; i < entities.length; i += CHUNK_SIZE) {
|
|
111
|
-
const batch =
|
|
112
|
+
const batch = this._sdk.writeBatch(this._db);
|
|
112
113
|
const chunk = entities.slice(i, i + CHUNK_SIZE);
|
|
113
114
|
// Inner loop: Set the item to be created
|
|
114
115
|
chunk.forEach(entity => {
|
|
115
|
-
const docRef = entity._id
|
|
116
|
-
|
|
117
|
-
: (0, firestore_1.doc)(this._collectionRef); //Auto ID
|
|
118
|
-
const finalDoc = Object.assign(Object.assign({}, entity), { _id: docRef.id, isDeleted: false, createdAt: (0, firestore_1.serverTimestamp)(), updatedAt: (0, firestore_1.serverTimestamp)() // Added
|
|
116
|
+
const docRef = this._getDocRef(entity._id);
|
|
117
|
+
const finalDoc = Object.assign(Object.assign({}, entity), { _id: docRef.id, isDeleted: false, createdAt: this._sdk.serverTimestamp(), updatedAt: this._sdk.serverTimestamp() // Added
|
|
119
118
|
});
|
|
120
119
|
// Add operation to the current batch
|
|
121
120
|
batch.set(docRef, finalDoc);
|
|
@@ -146,7 +145,7 @@ class FirestoreDB {
|
|
|
146
145
|
let docRef;
|
|
147
146
|
// 1. If it's a string, we have the direct path (Fastest)
|
|
148
147
|
if (typeof filter === 'string') {
|
|
149
|
-
docRef =
|
|
148
|
+
docRef = this._getDocRef(filter);
|
|
150
149
|
}
|
|
151
150
|
else {
|
|
152
151
|
// 2. If it's an object, we must find the first matching ID (Slower)
|
|
@@ -155,23 +154,23 @@ class FirestoreDB {
|
|
|
155
154
|
if (!match) {
|
|
156
155
|
if (options === null || options === void 0 ? void 0 : options.upsert) {
|
|
157
156
|
// If upsert is true, we create a new doc with a random ID or from filter
|
|
158
|
-
const newDocRef =
|
|
159
|
-
const initialData = Object.assign(Object.assign(Object.assign({}, this._flattenFilter), updateObject), { updatedAt:
|
|
160
|
-
yield
|
|
157
|
+
const newDocRef = this._sdk.doc(this._collectionRef);
|
|
158
|
+
const initialData = Object.assign(Object.assign(Object.assign({}, this._flattenFilter), updateObject), { updatedAt: this._sdk.serverTimestamp(), createdAt: this._sdk.serverTimestamp() });
|
|
159
|
+
yield this._sdk.setDoc(newDocRef, initialData);
|
|
161
160
|
return Object.assign({ _id: newDocRef.id }, initialData);
|
|
162
161
|
}
|
|
163
162
|
return null;
|
|
164
163
|
}
|
|
165
|
-
docRef =
|
|
164
|
+
docRef = this._getDocRef(match._id);
|
|
166
165
|
}
|
|
167
166
|
// 3. Perform the update
|
|
168
167
|
// Using { merge: true } acts as an 'upsert' for the specific document reference
|
|
169
|
-
yield
|
|
168
|
+
yield this._sdk.setDoc(docRef, Object.assign(Object.assign({}, updateObject), { updatedAt: this._sdk.serverTimestamp() }), { merge: true });
|
|
170
169
|
// 2. TRIGGER INVALIDATION: Any cached 'find' results for this
|
|
171
170
|
// collection are now potentially stale.
|
|
172
171
|
this._invalidateCache();
|
|
173
172
|
// We need to retrive the saved object and return that
|
|
174
|
-
const freshSnap = yield
|
|
173
|
+
const freshSnap = yield this._sdk.getDoc(docRef);
|
|
175
174
|
const freshData = Object.assign({ _id: freshSnap.id }, freshSnap.data() || {});
|
|
176
175
|
return (0, utils_1.hydrateDates)(freshData);
|
|
177
176
|
});
|
|
@@ -185,13 +184,13 @@ class FirestoreDB {
|
|
|
185
184
|
const results = [];
|
|
186
185
|
// Math-based Chunking Loop (Jumps by 500)
|
|
187
186
|
for (let i = 0; i < updates.length; i += CHUNK_SIZE) {
|
|
188
|
-
const batch =
|
|
187
|
+
const batch = this._sdk.writeBatch(this._db);
|
|
189
188
|
const chunk = updates.slice(i, i + CHUNK_SIZE);
|
|
190
189
|
chunk.forEach(item => {
|
|
191
190
|
const { docId, entity } = item;
|
|
192
|
-
const docRef =
|
|
191
|
+
const docRef = this._getDocRef(docId);
|
|
193
192
|
// Inject updatedAt into the update payload
|
|
194
|
-
const updateData = Object.assign(Object.assign({}, entity), { updatedAt:
|
|
193
|
+
const updateData = Object.assign(Object.assign({}, entity), { updatedAt: this._sdk.serverTimestamp() });
|
|
195
194
|
if (options.upsert) {
|
|
196
195
|
// UPSERT: Create if missing, merge if exists
|
|
197
196
|
// We ensure _id is included in the document for consistency
|
|
@@ -199,7 +198,7 @@ class FirestoreDB {
|
|
|
199
198
|
// Firestore's 'set' with merge doesn't know if it's new, so we
|
|
200
199
|
// usually set createdAt only on first creation via a different method
|
|
201
200
|
if (!this.exists({ _id: docRef.id }))
|
|
202
|
-
updateData['createdAt'] =
|
|
201
|
+
updateData['createdAt'] = this._sdk.serverTimestamp();
|
|
203
202
|
batch.set(docRef, updateData, { merge: true });
|
|
204
203
|
// Set the Id of the object so the consumer knows which id was affected
|
|
205
204
|
results.push({ _id: docRef.id });
|
|
@@ -229,23 +228,23 @@ class FirestoreDB {
|
|
|
229
228
|
* FIRESTOREDB: deleteOne('id_123')
|
|
230
229
|
*/
|
|
231
230
|
this.deleteOne = (docId) => __awaiter(this, void 0, void 0, function* () {
|
|
232
|
-
const docRef =
|
|
231
|
+
const docRef = this._getDocRef(docId);
|
|
233
232
|
// Check if it exists first if you want an accurate deletedCount
|
|
234
|
-
const snap = yield
|
|
233
|
+
const snap = yield this._sdk.getDoc(docRef);
|
|
235
234
|
if (!snap.exists()) {
|
|
236
235
|
return { acknowledged: true, deletedCount: 0 };
|
|
237
236
|
}
|
|
238
237
|
if (this._isSoftDeleteEnabled) {
|
|
239
238
|
// SOFT DELETE: Update with flag and timestamp
|
|
240
|
-
yield
|
|
239
|
+
yield this._sdk.updateDoc(docRef, {
|
|
241
240
|
isDeleted: true,
|
|
242
|
-
deletedAt:
|
|
243
|
-
updatedAt:
|
|
241
|
+
deletedAt: this._sdk.serverTimestamp(),
|
|
242
|
+
updatedAt: this._sdk.serverTimestamp()
|
|
244
243
|
});
|
|
245
244
|
}
|
|
246
245
|
else {
|
|
247
246
|
// HARD DELETE: Remove from disk
|
|
248
|
-
yield
|
|
247
|
+
yield this._sdk.deleteDoc(docRef);
|
|
249
248
|
}
|
|
250
249
|
// 2. TRIGGER INVALIDATION: Any cached 'find' results for this
|
|
251
250
|
// collection are now potentially stale.
|
|
@@ -261,15 +260,15 @@ class FirestoreDB {
|
|
|
261
260
|
const batchPromises = [];
|
|
262
261
|
let totalDeleted = 0;
|
|
263
262
|
for (let i = 0; i < docIds.length; i += CHUNK_SIZE) {
|
|
264
|
-
const batch =
|
|
263
|
+
const batch = this._sdk.writeBatch(this._db);
|
|
265
264
|
const chunk = docIds.slice(i, i + CHUNK_SIZE);
|
|
266
265
|
chunk.forEach(id => {
|
|
267
|
-
let docRef =
|
|
266
|
+
let docRef = this._getDocRef(id);
|
|
268
267
|
if (this._isSoftDeleteEnabled) {
|
|
269
268
|
batch.update(docRef, {
|
|
270
269
|
isDeleted: true,
|
|
271
|
-
deletedAt:
|
|
272
|
-
updatedAt:
|
|
270
|
+
deletedAt: this._sdk.serverTimestamp(),
|
|
271
|
+
updatedAt: this._sdk.serverTimestamp()
|
|
273
272
|
});
|
|
274
273
|
}
|
|
275
274
|
else {
|
|
@@ -287,11 +286,13 @@ class FirestoreDB {
|
|
|
287
286
|
});
|
|
288
287
|
/**
|
|
289
288
|
* Efficiently checks if a document exists without downloading it. (only metadata).
|
|
289
|
+
*
|
|
290
|
+
* @optimized
|
|
290
291
|
* Optimized to only check for the presence of a document
|
|
291
292
|
*/
|
|
292
293
|
this.exists = (filter) => __awaiter(this, void 0, void 0, function* () {
|
|
293
|
-
const q =
|
|
294
|
-
const snapshot = yield
|
|
294
|
+
const q = this._sdk.query(this._collectionRef, ...this._buildConstraints(filter), this._sdk.limit(1));
|
|
295
|
+
const snapshot = yield this._sdk.getDocs(q);
|
|
295
296
|
return !snapshot.empty;
|
|
296
297
|
});
|
|
297
298
|
/**
|
|
@@ -299,19 +300,19 @@ class FirestoreDB {
|
|
|
299
300
|
*/
|
|
300
301
|
this.aggregate = (...args_1) => __awaiter(this, [...args_1], void 0, function* (filter = {}, aggregations) {
|
|
301
302
|
const constraints = this._buildConstraints(filter);
|
|
302
|
-
const q =
|
|
303
|
+
const q = this._sdk.query(this._collectionRef, ...constraints);
|
|
303
304
|
// 1. Map the request to Firestore's aggregation functions
|
|
304
305
|
const spec = {};
|
|
305
306
|
Object.entries(aggregations).forEach(([alias, op]) => {
|
|
306
307
|
if (op.$sum)
|
|
307
|
-
spec[alias] =
|
|
308
|
+
spec[alias] = this._sdk.sum(op.$sum);
|
|
308
309
|
if (op.$avg)
|
|
309
|
-
spec[alias] =
|
|
310
|
+
spec[alias] = this._sdk.average(op.$avg);
|
|
310
311
|
if (op.$count)
|
|
311
|
-
spec[alias] =
|
|
312
|
+
spec[alias] = this._sdk.count();
|
|
312
313
|
});
|
|
313
314
|
// 2. Execute on server (Calculates across millions of docs instantly)
|
|
314
|
-
const snapshot = yield
|
|
315
|
+
const snapshot = yield this._sdk.getAggregateFromServer(q, spec);
|
|
315
316
|
return snapshot.data();
|
|
316
317
|
});
|
|
317
318
|
this._flattenFilter = (obj, prefix = '') => {
|
|
@@ -324,7 +325,7 @@ class FirestoreDB {
|
|
|
324
325
|
if (firstKey.startsWith("$")) {
|
|
325
326
|
// get the symbol if not found then default to equality
|
|
326
327
|
const op = utils_1.$OperatorMap[firstKey] || '==';
|
|
327
|
-
constraints.push(
|
|
328
|
+
constraints.push(this._sdk.where(path, op, value[firstKey]));
|
|
328
329
|
}
|
|
329
330
|
else {
|
|
330
331
|
// It's a nested object, keep flattening
|
|
@@ -333,24 +334,31 @@ class FirestoreDB {
|
|
|
333
334
|
}
|
|
334
335
|
else {
|
|
335
336
|
// It's a primitive value, use standard equality
|
|
336
|
-
constraints.push(
|
|
337
|
+
constraints.push(this._sdk.where(path, '==', value));
|
|
337
338
|
}
|
|
338
339
|
});
|
|
339
340
|
return constraints;
|
|
340
341
|
};
|
|
341
|
-
|
|
342
|
-
|
|
342
|
+
/**
|
|
343
|
+
* This will try to get the document Reference of a given id, however if id missing return a auto generated Document Referrence
|
|
344
|
+
* @param docId optional
|
|
345
|
+
* @returns Firebase DocumentReferrence
|
|
346
|
+
*/
|
|
347
|
+
this._getDocRef = (docId) => {
|
|
348
|
+
return docId
|
|
349
|
+
? this._sdk.doc(this._db, this._collectionRef.id, docId) //Custom ID
|
|
350
|
+
: this._sdk.doc(this._collectionRef); //Auto ID
|
|
351
|
+
};
|
|
343
352
|
// Ensure Store is initialized or passed in
|
|
344
|
-
console.log("Is valid Firestore instance?", this._db instanceof firestore_1.Firestore);
|
|
345
353
|
try {
|
|
346
354
|
if (!this._db) {
|
|
347
355
|
throw new Error(`Firestore db not present ${this._db} @collection: ${this._collectionName}`);
|
|
348
356
|
}
|
|
349
|
-
this._collectionRef =
|
|
350
|
-
this._isSoftDeleteEnabled = (_a =
|
|
357
|
+
this._collectionRef = this._sdk.collection(this._db, this._collectionName);
|
|
358
|
+
this._isSoftDeleteEnabled = (_a = this._options.softDelete) !== null && _a !== void 0 ? _a : false;
|
|
351
359
|
}
|
|
352
360
|
catch (error) {
|
|
353
|
-
throw new Error(`error on init @collection: ${this._collectionName}` + error.message);
|
|
361
|
+
throw new Error(`error on init @collection: ${this._collectionName} - ` + error.message);
|
|
354
362
|
}
|
|
355
363
|
}
|
|
356
364
|
/**
|
|
@@ -359,11 +367,11 @@ class FirestoreDB {
|
|
|
359
367
|
*/
|
|
360
368
|
restore(id) {
|
|
361
369
|
return __awaiter(this, void 0, void 0, function* () {
|
|
362
|
-
const docRef =
|
|
363
|
-
yield
|
|
370
|
+
const docRef = this._getDocRef(id);
|
|
371
|
+
yield this._sdk.updateDoc(docRef, {
|
|
364
372
|
isDeleted: false,
|
|
365
373
|
deletedAt: null, // Clear the timestamp
|
|
366
|
-
updatedAt:
|
|
374
|
+
updatedAt: this._sdk.serverTimestamp()
|
|
367
375
|
});
|
|
368
376
|
// 2. TRIGGER INVALIDATION: Any cached 'find' results for this
|
|
369
377
|
// collection are now potentially stale.
|
|
@@ -372,15 +380,18 @@ class FirestoreDB {
|
|
|
372
380
|
});
|
|
373
381
|
}
|
|
374
382
|
/**
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
383
|
+
* @title
|
|
384
|
+
* FIRESTOREDB: collection.watch(filter)
|
|
385
|
+
*
|
|
386
|
+
* @detail
|
|
387
|
+
* A real-time listener that bypasses the manual cache and
|
|
388
|
+
* pushes updates as they happen in the database.
|
|
389
|
+
*/
|
|
379
390
|
watch(filter = {}, callback, onError) {
|
|
380
391
|
const constraints = this._buildConstraints(filter);
|
|
381
|
-
const q =
|
|
392
|
+
const q = this._sdk.query(this._collectionRef, ...constraints);
|
|
382
393
|
// Returns the unsubscribe function
|
|
383
|
-
return
|
|
394
|
+
return this._sdk.onSnapshot(q, (snapshot) => {
|
|
384
395
|
const docs = snapshot.docs.map(d => {
|
|
385
396
|
const raw = Object.assign({ _id: d.id }, d.data());
|
|
386
397
|
return (0, utils_1.hydrateDates)(raw);
|
|
@@ -398,7 +409,7 @@ class FirestoreDB {
|
|
|
398
409
|
return [...this._flattenFilter(filter)];
|
|
399
410
|
}
|
|
400
411
|
/**
|
|
401
|
-
*
|
|
412
|
+
* FirestoreDB class:
|
|
402
413
|
* Purges all cached queries for THIS collection to ensure data freshness.
|
|
403
414
|
*/
|
|
404
415
|
_invalidateCache() {
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { QueryConstraint } from "firebase/firestore";
|
|
2
|
-
import { WithSystemFields } from "./types";
|
|
1
|
+
import type { QueryConstraint } from "firebase/firestore";
|
|
2
|
+
import { FirestoreSDK, WithSystemFields } from "./types";
|
|
3
3
|
import { LRUCache } from './utils';
|
|
4
|
+
import { FirestoreCursor } from "./utils/FirestoreCursor";
|
|
4
5
|
export type SortDescriptor = "asc" | "desc";
|
|
5
6
|
export interface PaginationMetadata {
|
|
6
7
|
totalRecords: number;
|
|
7
8
|
totalPages: number;
|
|
9
|
+
currentPage: number;
|
|
8
10
|
hasNext: boolean;
|
|
9
11
|
hasPrevious: boolean;
|
|
10
12
|
}
|
|
@@ -13,19 +15,25 @@ export declare class FirestoreQuery<T> {
|
|
|
13
15
|
private collectionName;
|
|
14
16
|
private collectionRef;
|
|
15
17
|
private filter;
|
|
18
|
+
private _sdk;
|
|
16
19
|
private buildConstraints;
|
|
17
20
|
private countDocs;
|
|
18
21
|
static _globalCache: LRUCache;
|
|
19
22
|
private _useCache;
|
|
20
23
|
private _ttl;
|
|
24
|
+
private _pageNumber;
|
|
21
25
|
private _deleteMode;
|
|
22
26
|
private _limit;
|
|
23
27
|
private _sort;
|
|
24
28
|
private _cursorId?;
|
|
25
29
|
private _cursorType;
|
|
26
|
-
constructor(db: any, collectionName: string, collectionRef: any, filter: Record<string, any>, buildConstraints: (f: any) => QueryConstraint[], countDocs: (f: any) => Promise<number>, isSoftDeleteEnabled: boolean);
|
|
30
|
+
constructor(db: any, collectionName: string, collectionRef: any, filter: Record<string, any>, _sdk: FirestoreSDK, buildConstraints: (f: any) => QueryConstraint[], countDocs: (f: any) => Promise<number>, isSoftDeleteEnabled: boolean);
|
|
27
31
|
/** Enables result caching for this query */
|
|
28
32
|
cache(ttlMs?: number): this;
|
|
33
|
+
/**
|
|
34
|
+
* Optional: Set the current page number for metadata reporting.
|
|
35
|
+
*/
|
|
36
|
+
page(n: number): this;
|
|
29
37
|
sort(sortObj: Record<keyof T, SortDescriptor>): this;
|
|
30
38
|
limit(n: number): this;
|
|
31
39
|
/** Move to the next page */
|
|
@@ -40,7 +48,24 @@ export declare class FirestoreQuery<T> {
|
|
|
40
48
|
onlyDeleted(): this;
|
|
41
49
|
/** Simple execution (Returns Array) */
|
|
42
50
|
execute(): Promise<WithSystemFields<T>[]>;
|
|
43
|
-
/**
|
|
51
|
+
/**
|
|
52
|
+
* Paginated execution (Returns Data + Metadata)
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* // React State example
|
|
56
|
+
const [currentPage, setCurrentPage] = useState(1);
|
|
57
|
+
const [lastId, setLastId] = useState(null);
|
|
58
|
+
|
|
59
|
+
const { data, metadata } = await Products.find({ category: 'electronics' })
|
|
60
|
+
.sort({ price: 'asc' })
|
|
61
|
+
.limit(10)
|
|
62
|
+
.page(currentPage) // Sets the metadata
|
|
63
|
+
.after(lastId) // Sets the cursor
|
|
64
|
+
.cache() // Speeds up the "Back" button
|
|
65
|
+
.paginate();
|
|
66
|
+
|
|
67
|
+
console.log(`Viewing page ${metadata.currentPage} of ${metadata.totalPages}`);
|
|
68
|
+
*/
|
|
44
69
|
paginate(): Promise<{
|
|
45
70
|
data: WithSystemFields<T>[];
|
|
46
71
|
metadata: PaginationMetadata;
|
|
@@ -48,6 +73,18 @@ export declare class FirestoreQuery<T> {
|
|
|
48
73
|
stream(): ReadableStream<T & {
|
|
49
74
|
_id: string;
|
|
50
75
|
}>;
|
|
76
|
+
/**
|
|
77
|
+
* @example
|
|
78
|
+
* const cursor = await Users.find({ role: 'admin' }).sort({ name: 1 }).cursor();
|
|
79
|
+
*
|
|
80
|
+
*
|
|
81
|
+
* while (await cursor.hasNext()) {
|
|
82
|
+
* const doc = await cursor.next();
|
|
83
|
+
* console.log("Processing admin:", doc.name);
|
|
84
|
+
* }
|
|
85
|
+
* @returns
|
|
86
|
+
*/
|
|
87
|
+
cursor(): Promise<FirestoreCursor<T>>;
|
|
51
88
|
private _prepareConstraints;
|
|
52
89
|
private _generateCacheKey;
|
|
53
90
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FirestoreQuery.d.ts","sourceRoot":"","sources":["../../src/FirestoreQuery.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"FirestoreQuery.d.ts","sourceRoot":"","sources":["../../src/FirestoreQuery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAGzD,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAClC,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAE1D,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,MAAM,CAAA;AAC3C,MAAM,WAAW,kBAAkB;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;CACxB;AAED,qBAAa,cAAc,CAAC,CAAC;IAgBrB,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,aAAa;IACrB,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,IAAI;IACZ,OAAO,CAAC,gBAAgB;IACxB,OAAO,CAAC,SAAS;IAnBrB,OAAc,YAAY,WAAkB;IAC5C,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,IAAI,CAAiB;IAC7B,OAAO,CAAC,WAAW,CAAa;IAGhC,OAAO,CAAC,WAAW,CAAuC;IAC1D,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,KAAK,CAAsC;IACnD,OAAO,CAAC,SAAS,CAAC,CAAS;IAC3B,OAAO,CAAC,WAAW,CAAmC;gBAG1C,EAAE,EAAE,GAAG,EACP,cAAc,EAAE,MAAM,EACtB,aAAa,EAAE,GAAG,EAClB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,IAAI,EAAE,YAAY,EAClB,gBAAgB,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,eAAe,EAAE,EAC/C,SAAS,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC,MAAM,CAAC,EAC9C,mBAAmB,EAAE,OAAO;IAKhC,4CAA4C;IAC5C,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM;IAMpB;;OAEG;IACH,IAAI,CAAC,CAAC,EAAE,MAAM;IAKP,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC;IAK7C,KAAK,CAAC,CAAC,EAAE,MAAM;IAKtB,4BAA4B;IACrB,KAAK,CAAC,EAAE,EAAE,MAAM;IAMvB,gCAAgC;IACzB,MAAM,CAAC,EAAE,EAAE,MAAM;IAMxB;;;OAGG;IACH,WAAW;IAKX,WAAW;IAEX,uCAAuC;IAC1B,OAAO,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC;IAUtD;;;;;;;;;;;;;;;;;OAiBG;IACU,QAAQ,IAAI,OAAO,CAAC;QAAE,IAAI,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC;QAAC,QAAQ,EAAE,kBAAkB,CAAA;KAAE,CAAC;IAuCxF,MAAM,IAAI,cAAc,CAAC,CAAC,GAAG;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAkBpD;;;;;;;;;;OAUG;IACU,MAAM,IAAI,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;YAapC,mBAAmB;IAsCjC,OAAO,CAAC,iBAAiB;IAezB;;GAED;IACC,OAAO,CAAC,eAAe;CAQ1B"}
|