@knymbus/firestoredb 1.0.2
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/FirestoreDB.d.ts +114 -0
- package/dist/FirestoreDB.d.ts.map +1 -0
- package/dist/FirestoreDB.js +407 -0
- package/dist/FirestoreQuery.d.ts +58 -0
- package/dist/FirestoreQuery.d.ts.map +1 -0
- package/dist/FirestoreQuery.js +200 -0
- package/dist/ParallelPipe.d.ts +22 -0
- package/dist/ParallelPipe.d.ts.map +1 -0
- package/dist/ParallelPipe.js +63 -0
- package/dist/cjs/FirestoreDB.d.ts +114 -0
- package/dist/cjs/FirestoreDB.d.ts.map +1 -0
- package/dist/cjs/FirestoreDB.js +407 -0
- package/dist/cjs/FirestoreQuery.d.ts +58 -0
- package/dist/cjs/FirestoreQuery.d.ts.map +1 -0
- package/dist/cjs/FirestoreQuery.js +200 -0
- package/dist/cjs/ParallelPipe.d.ts +22 -0
- package/dist/cjs/ParallelPipe.d.ts.map +1 -0
- package/dist/cjs/ParallelPipe.js +63 -0
- package/dist/cjs/index.d.ts +4 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +19 -0
- package/dist/cjs/types.d.ts +22 -0
- package/dist/cjs/types.d.ts.map +1 -0
- package/dist/cjs/types.js +2 -0
- package/dist/cjs/utils/$Operators.d.ts +4 -0
- package/dist/cjs/utils/$Operators.d.ts.map +1 -0
- package/dist/cjs/utils/$Operators.js +12 -0
- package/dist/cjs/utils/Hasher.d.ts +11 -0
- package/dist/cjs/utils/Hasher.d.ts.map +1 -0
- package/dist/cjs/utils/Hasher.js +28 -0
- package/dist/cjs/utils/LRUCache.d.ts +13 -0
- package/dist/cjs/utils/LRUCache.d.ts.map +1 -0
- package/dist/cjs/utils/LRUCache.js +39 -0
- package/dist/cjs/utils/hydrateDates.d.ts +6 -0
- package/dist/cjs/utils/hydrateDates.d.ts.map +1 -0
- package/dist/cjs/utils/hydrateDates.js +29 -0
- package/dist/cjs/utils/index.d.ts +5 -0
- package/dist/cjs/utils/index.d.ts.map +1 -0
- package/dist/cjs/utils/index.js +20 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/types.d.ts +22 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/utils/$Operators.d.ts +4 -0
- package/dist/utils/$Operators.d.ts.map +1 -0
- package/dist/utils/$Operators.js +12 -0
- package/dist/utils/Hasher.d.ts +11 -0
- package/dist/utils/Hasher.d.ts.map +1 -0
- package/dist/utils/Hasher.js +28 -0
- package/dist/utils/LRUCache.d.ts +13 -0
- package/dist/utils/LRUCache.d.ts.map +1 -0
- package/dist/utils/LRUCache.js +39 -0
- package/dist/utils/hydrateDates.d.ts +6 -0
- package/dist/utils/hydrateDates.d.ts.map +1 -0
- package/dist/utils/hydrateDates.js +29 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +20 -0
- package/package.json +45 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { Firestore } from "firebase/firestore";
|
|
2
|
+
import { FirestoreQuery } from './FirestoreQuery';
|
|
3
|
+
import { DocumentInput, QueryFilter, UpdateDocument, WithSystemFields } from './types';
|
|
4
|
+
export interface DBOptions {
|
|
5
|
+
softDelete?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare class FirestoreDB<T> {
|
|
8
|
+
private _db;
|
|
9
|
+
private _collectionName;
|
|
10
|
+
private _collectionRef;
|
|
11
|
+
private _isSoftDeleteEnabled;
|
|
12
|
+
/**
|
|
13
|
+
* Initialize with Firebase DB and targeted collection Name
|
|
14
|
+
* @param db: Firebase
|
|
15
|
+
* @param collection string
|
|
16
|
+
*/
|
|
17
|
+
constructor(db: Firestore, collectionName: string, options?: DBOptions);
|
|
18
|
+
/**
|
|
19
|
+
* FIRESTOREDB: findOne({ age: '13' }); or findOne('id_123');
|
|
20
|
+
*/
|
|
21
|
+
findOne: (filter: string | Record<string, any>) => Promise<WithSystemFields<T> | null>;
|
|
22
|
+
/**
|
|
23
|
+
* This will count the number of documents found based on the given filter
|
|
24
|
+
* @param filter Object with the filter key/value pair
|
|
25
|
+
* @returns number
|
|
26
|
+
*/
|
|
27
|
+
countDocuments: (filter?: QueryFilter<T>) => Promise<number>;
|
|
28
|
+
findOneAndUpdate: (filter: string | QueryFilter<T>, updateObject: Partial<T>) => Promise<WithSystemFields<T>>;
|
|
29
|
+
/**
|
|
30
|
+
* NEW: find(query)
|
|
31
|
+
* Basic implementation. For production, you'd expand the 'filter' to handle where clauses.
|
|
32
|
+
*/
|
|
33
|
+
find: (filter?: QueryFilter<T>) => FirestoreQuery<T>;
|
|
34
|
+
/**
|
|
35
|
+
* FIRESTOREDB: insertOne(doc)
|
|
36
|
+
* Optimized: Uses set with merge or create
|
|
37
|
+
*/
|
|
38
|
+
insertOne: (entity: Partial<T & {
|
|
39
|
+
_id?: string;
|
|
40
|
+
}>) => Promise<WithSystemFields<T>>;
|
|
41
|
+
/**
|
|
42
|
+
* FIRESTOREDB: insertMany(docs) - Uses Firestore Batched Writes (Limit 500 per batch)
|
|
43
|
+
*
|
|
44
|
+
*/
|
|
45
|
+
insertMany: (entities: DocumentInput<T>[]) => Promise<string[]>;
|
|
46
|
+
/**
|
|
47
|
+
* FIRESTOREDB: updateOne(filter, update)
|
|
48
|
+
* Use "Upsert" logic when true will create a new document default to false
|
|
49
|
+
*/
|
|
50
|
+
updateOne: (filter: string | Record<string, any>, updateObject: Partial<T>, options?: {
|
|
51
|
+
upsert?: boolean;
|
|
52
|
+
}) => Promise<Record<string, any> | null>;
|
|
53
|
+
/**
|
|
54
|
+
* FIRESTOREDB: updateMany([ { docId: '1', entity: { status: 'A' } }, ... ])
|
|
55
|
+
* Optimized: Chunks updates into batches of 500 to handle large datasets.
|
|
56
|
+
*/
|
|
57
|
+
updateMany: <T_1>(updates: UpdateDocument<T_1>[], options?: {
|
|
58
|
+
upsert?: boolean;
|
|
59
|
+
}) => Promise<{
|
|
60
|
+
_id: string;
|
|
61
|
+
}[]>;
|
|
62
|
+
/**
|
|
63
|
+
* FIRESTOREDB: deleteOne('id_123')
|
|
64
|
+
*/
|
|
65
|
+
deleteOne: (docId: string) => Promise<{
|
|
66
|
+
acknowledged: boolean;
|
|
67
|
+
deletedCount: number;
|
|
68
|
+
}>;
|
|
69
|
+
/**
|
|
70
|
+
* FIRESTOREDB: deleteMany()
|
|
71
|
+
* Uses Batched Writes for speed.
|
|
72
|
+
*/
|
|
73
|
+
deleteMany: (docIds: string[]) => Promise<{
|
|
74
|
+
acknowledged: boolean;
|
|
75
|
+
deletedCount: number;
|
|
76
|
+
}>;
|
|
77
|
+
/**
|
|
78
|
+
* FIRESTOREDB: db.collection.restore(id)
|
|
79
|
+
* Reverses a soft delete by flipping the flag and removing deletedAt.
|
|
80
|
+
*/
|
|
81
|
+
restore(id: string): Promise<{
|
|
82
|
+
acknowledged: boolean;
|
|
83
|
+
restoredCount: number;
|
|
84
|
+
}>;
|
|
85
|
+
/**
|
|
86
|
+
* MONGODB: db.collection.watch(filter)
|
|
87
|
+
* A real-time listener that bypasses the manual cache and
|
|
88
|
+
* pushes updates as they happen in the database.
|
|
89
|
+
*/
|
|
90
|
+
watch<T>(filter: Record<string, any> | undefined, callback: (data: WithSystemFields<T>[]) => void, onError?: (error: any) => void): import("@firebase/firestore").Unsubscribe;
|
|
91
|
+
/**
|
|
92
|
+
* Efficiently checks if a document exists without downloading it. (only metadata).
|
|
93
|
+
* Optimized to only check for the presence of a document
|
|
94
|
+
*/
|
|
95
|
+
exists: (filter: Record<string, any>) => Promise<boolean>;
|
|
96
|
+
/**
|
|
97
|
+
* FIRESTOREDB STYLE: aggregate({ status: 'sold' }, { total: { $sum: 'price' }, avg: { $avg: 'price' } })
|
|
98
|
+
*/
|
|
99
|
+
aggregate: (filter: Record<string, any> | undefined, aggregations: {
|
|
100
|
+
[key: string]: {
|
|
101
|
+
$sum?: string;
|
|
102
|
+
$avg?: string;
|
|
103
|
+
$count?: boolean;
|
|
104
|
+
};
|
|
105
|
+
}) => Promise<import("@firebase/firestore").AggregateSpecData<any>>;
|
|
106
|
+
private _buildConstraints;
|
|
107
|
+
private _flattenFilter;
|
|
108
|
+
/**
|
|
109
|
+
* In FirestoreDB class:
|
|
110
|
+
* Purges all cached queries for THIS collection to ensure data freshness.
|
|
111
|
+
*/
|
|
112
|
+
private _invalidateCache;
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=FirestoreDB.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FirestoreDB.d.ts","sourceRoot":"","sources":["../src/FirestoreDB.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.FirestoreDB = void 0;
|
|
13
|
+
const firestore_1 = require("firebase/firestore");
|
|
14
|
+
const FirestoreQuery_1 = require("./FirestoreQuery");
|
|
15
|
+
const utils_1 = require("./utils");
|
|
16
|
+
class FirestoreDB {
|
|
17
|
+
/**
|
|
18
|
+
* Initialize with Firebase DB and targeted collection Name
|
|
19
|
+
* @param db: Firebase
|
|
20
|
+
* @param collection string
|
|
21
|
+
*/
|
|
22
|
+
constructor(db, collectionName, options = {}) {
|
|
23
|
+
var _a;
|
|
24
|
+
/**
|
|
25
|
+
* FIRESTOREDB: findOne({ age: '13' }); or findOne('id_123');
|
|
26
|
+
*/
|
|
27
|
+
this.findOne = (filter) => __awaiter(this, void 0, void 0, function* () {
|
|
28
|
+
// 1. Handle case where user passes a direct string ID
|
|
29
|
+
if (typeof filter === 'string') {
|
|
30
|
+
// We use the find() builder even for IDs to ensure the 'where("isDeleted", "==", false)'
|
|
31
|
+
// constraint is automatically added by the _buildConstraints helper.
|
|
32
|
+
const results = yield this.find({ _id: filter })
|
|
33
|
+
.limit(1)
|
|
34
|
+
.execute();
|
|
35
|
+
return results.length > 0 ? results[0] : null;
|
|
36
|
+
}
|
|
37
|
+
// 2. If it's an object filter, use the standard find logic
|
|
38
|
+
const results = yield this.find(filter)
|
|
39
|
+
.limit(1)
|
|
40
|
+
.execute();
|
|
41
|
+
return results.length > 0 ? results[0] : null;
|
|
42
|
+
});
|
|
43
|
+
/**
|
|
44
|
+
* This will count the number of documents found based on the given filter
|
|
45
|
+
* @param filter Object with the filter key/value pair
|
|
46
|
+
* @returns number
|
|
47
|
+
*/
|
|
48
|
+
this.countDocuments = (...args_1) => __awaiter(this, [...args_1], void 0, function* (filter = {}) {
|
|
49
|
+
let q = (0, firestore_1.query)(this._collectionRef, ...this._buildConstraints(filter));
|
|
50
|
+
// Update the query with the incoming filter
|
|
51
|
+
const snapshot = yield (0, firestore_1.getCountFromServer)(q);
|
|
52
|
+
return snapshot.data().count;
|
|
53
|
+
});
|
|
54
|
+
this.findOneAndUpdate = (filter, updateObject) => __awaiter(this, void 0, void 0, function* () {
|
|
55
|
+
let docRef;
|
|
56
|
+
if (typeof filter === 'string') {
|
|
57
|
+
docRef = (0, firestore_1.doc)(this._db, this._collectionRef.id, filter);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
// if object we need to find the id first using the findOne fn
|
|
61
|
+
const existing = yield this.findOne(filter);
|
|
62
|
+
if (!existing)
|
|
63
|
+
throw new Error("Document not found for update");
|
|
64
|
+
docRef = (0, firestore_1.doc)(this._db, this._collectionRef.id, existing._id);
|
|
65
|
+
}
|
|
66
|
+
return yield (0, firestore_1.runTransaction)(this._db, (transaction) => __awaiter(this, void 0, void 0, function* () {
|
|
67
|
+
const docSnap = yield transaction.get(docRef);
|
|
68
|
+
if (!docSnap.exists())
|
|
69
|
+
throw new Error("Document Vanished during transaction");
|
|
70
|
+
transaction.update(docRef, Object.assign(Object.assign({}, updateObject), { updatedAt: (0, firestore_1.serverTimestamp)() }));
|
|
71
|
+
return Object.assign(Object.assign(Object.assign({ _id: docRef.id }, (docSnap.data() || {})), updateObject), { updatedAt: (0, firestore_1.serverTimestamp)() });
|
|
72
|
+
}));
|
|
73
|
+
});
|
|
74
|
+
/**
|
|
75
|
+
* NEW: find(query)
|
|
76
|
+
* Basic implementation. For production, you'd expand the 'filter' to handle where clauses.
|
|
77
|
+
*/
|
|
78
|
+
this.find = (filter = {}) => {
|
|
79
|
+
return new FirestoreQuery_1.FirestoreQuery(this._db, this._collectionName, this._collectionRef, filter, this._buildConstraints.bind(this), //Pass the private helper
|
|
80
|
+
this.countDocuments.bind(this), // pass the count helper
|
|
81
|
+
this._isSoftDeleteEnabled);
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* FIRESTOREDB: insertOne(doc)
|
|
85
|
+
* Optimized: Uses set with merge or create
|
|
86
|
+
*/
|
|
87
|
+
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
|
+
// Using 'set' with { merge: false } acts like an insert/overwrite
|
|
92
|
+
yield (0, firestore_1.setDoc)(docRef, Object.assign(Object.assign({}, entity), { _id: docRef.id, isDeleted: false, createdAt: (0, firestore_1.serverTimestamp)(), updatedAt: (0, firestore_1.serverTimestamp)() }));
|
|
93
|
+
// 2. TRIGGER INVALIDATION: Any cached 'find' results for this
|
|
94
|
+
// collection are now potentially stale.
|
|
95
|
+
this._invalidateCache();
|
|
96
|
+
// 3. REFRESH: Fetch the document back to get the real server-generated timestamps
|
|
97
|
+
const freshSnap = yield (0, firestore_1.getDoc)(docRef);
|
|
98
|
+
const freshData = Object.assign({ _id: freshSnap.id }, freshSnap.data());
|
|
99
|
+
return (0, utils_1.hydrateDates)(freshData);
|
|
100
|
+
});
|
|
101
|
+
/**
|
|
102
|
+
* FIRESTOREDB: insertMany(docs) - Uses Firestore Batched Writes (Limit 500 per batch)
|
|
103
|
+
*
|
|
104
|
+
*/
|
|
105
|
+
this.insertMany = (entities) => __awaiter(this, void 0, void 0, function* () {
|
|
106
|
+
const CHUNK_SIZE = 500;
|
|
107
|
+
const results = [];
|
|
108
|
+
const batchPromises = [];
|
|
109
|
+
// 1. Outer Loop: Creates a new batch for every 500 items
|
|
110
|
+
for (let i = 0; i < entities.length; i += CHUNK_SIZE) {
|
|
111
|
+
const batch = (0, firestore_1.writeBatch)(this._db);
|
|
112
|
+
const chunk = entities.slice(i, i + CHUNK_SIZE);
|
|
113
|
+
// Inner loop: Set the item to be created
|
|
114
|
+
chunk.forEach(entity => {
|
|
115
|
+
const docRef = entity._id
|
|
116
|
+
? (0, firestore_1.doc)(this._db, this._collectionRef.id, entity._id) //Custom ID
|
|
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
|
|
119
|
+
});
|
|
120
|
+
// Add operation to the current batch
|
|
121
|
+
batch.set(docRef, finalDoc);
|
|
122
|
+
// Track the processed doc to return to the user
|
|
123
|
+
results.push(finalDoc._id);
|
|
124
|
+
});
|
|
125
|
+
// Queue the batch commit
|
|
126
|
+
batchPromises.push(batch.commit());
|
|
127
|
+
}
|
|
128
|
+
// 2. TRIGGER INVALIDATION: Any cached 'find' results for this
|
|
129
|
+
// collection are now potentially stale.
|
|
130
|
+
this._invalidateCache();
|
|
131
|
+
// 4. Execute all batches in parallel for maximum speed
|
|
132
|
+
try {
|
|
133
|
+
yield Promise.all(batchPromises);
|
|
134
|
+
return results;
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
console.error("FirestoreDB::insertMany: Error committing batches", error);
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
/**
|
|
142
|
+
* FIRESTOREDB: updateOne(filter, update)
|
|
143
|
+
* Use "Upsert" logic when true will create a new document default to false
|
|
144
|
+
*/
|
|
145
|
+
this.updateOne = (filter_1, updateObject_1, ...args_1) => __awaiter(this, [filter_1, updateObject_1, ...args_1], void 0, function* (filter, updateObject, options = {}) {
|
|
146
|
+
let docRef;
|
|
147
|
+
// 1. If it's a string, we have the direct path (Fastest)
|
|
148
|
+
if (typeof filter === 'string') {
|
|
149
|
+
docRef = (0, firestore_1.doc)(this._db, this._collectionRef.id, filter);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
// 2. If it's an object, we must find the first matching ID (Slower)
|
|
153
|
+
const match = yield this.findOne(filter);
|
|
154
|
+
// If we do not find any object we need to check if we can create a new object
|
|
155
|
+
if (!match) {
|
|
156
|
+
if (options === null || options === void 0 ? void 0 : options.upsert) {
|
|
157
|
+
// If upsert is true, we create a new doc with a random ID or from filter
|
|
158
|
+
const newDocRef = (0, firestore_1.doc)(this._collectionRef);
|
|
159
|
+
const initialData = Object.assign(Object.assign(Object.assign({}, this._flattenFilter), updateObject), { updatedAt: (0, firestore_1.serverTimestamp)(), createdAt: (0, firestore_1.serverTimestamp)() });
|
|
160
|
+
yield (0, firestore_1.setDoc)(newDocRef, initialData);
|
|
161
|
+
return Object.assign({ _id: newDocRef.id }, initialData);
|
|
162
|
+
}
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
docRef = (0, firestore_1.doc)(this._db, this._collectionRef.id, match._id);
|
|
166
|
+
}
|
|
167
|
+
// 3. Perform the update
|
|
168
|
+
// Using { merge: true } acts as an 'upsert' for the specific document reference
|
|
169
|
+
yield (0, firestore_1.setDoc)(docRef, Object.assign(Object.assign({}, updateObject), { updatedAt: (0, firestore_1.serverTimestamp)() }), { merge: true });
|
|
170
|
+
// 2. TRIGGER INVALIDATION: Any cached 'find' results for this
|
|
171
|
+
// collection are now potentially stale.
|
|
172
|
+
this._invalidateCache();
|
|
173
|
+
// We need to retrive the saved object and return that
|
|
174
|
+
const freshSnap = yield (0, firestore_1.getDoc)(docRef);
|
|
175
|
+
const freshData = Object.assign({ _id: freshSnap.id }, freshSnap.data() || {});
|
|
176
|
+
return (0, utils_1.hydrateDates)(freshData);
|
|
177
|
+
});
|
|
178
|
+
/**
|
|
179
|
+
* FIRESTOREDB: updateMany([ { docId: '1', entity: { status: 'A' } }, ... ])
|
|
180
|
+
* Optimized: Chunks updates into batches of 500 to handle large datasets.
|
|
181
|
+
*/
|
|
182
|
+
this.updateMany = (updates_1, ...args_1) => __awaiter(this, [updates_1, ...args_1], void 0, function* (updates, options = { upsert: false }) {
|
|
183
|
+
const CHUNK_SIZE = 500;
|
|
184
|
+
const batchPromises = [];
|
|
185
|
+
const results = [];
|
|
186
|
+
// Math-based Chunking Loop (Jumps by 500)
|
|
187
|
+
for (let i = 0; i < updates.length; i += CHUNK_SIZE) {
|
|
188
|
+
const batch = (0, firestore_1.writeBatch)(this._db);
|
|
189
|
+
const chunk = updates.slice(i, i + CHUNK_SIZE);
|
|
190
|
+
chunk.forEach(item => {
|
|
191
|
+
const { docId, entity } = item;
|
|
192
|
+
const docRef = (0, firestore_1.doc)(this._db, this._collectionRef.id, docId);
|
|
193
|
+
// Inject updatedAt into the update payload
|
|
194
|
+
const updateData = Object.assign(Object.assign({}, entity), { updatedAt: (0, firestore_1.serverTimestamp)() });
|
|
195
|
+
if (options.upsert) {
|
|
196
|
+
// UPSERT: Create if missing, merge if exists
|
|
197
|
+
// We ensure _id is included in the document for consistency
|
|
198
|
+
// For upserts, we also need to ensure createdAt is set if the doc is new
|
|
199
|
+
// Firestore's 'set' with merge doesn't know if it's new, so we
|
|
200
|
+
// usually set createdAt only on first creation via a different method
|
|
201
|
+
if (!this.exists({ _id: docRef.id }))
|
|
202
|
+
updateData['createdAt'] = (0, firestore_1.serverTimestamp)();
|
|
203
|
+
batch.set(docRef, updateData, { merge: true });
|
|
204
|
+
// Set the Id of the object so the consumer knows which id was affected
|
|
205
|
+
results.push({ _id: docRef.id });
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
// STRICT UPDATE: Fails if document doesn't exist (Standard Mongo)
|
|
209
|
+
batch.update(docRef, updateData);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
// Queue the batch commit
|
|
213
|
+
batchPromises.push(batch.commit());
|
|
214
|
+
}
|
|
215
|
+
// 2. TRIGGER INVALIDATION: Any cached 'find' results for this
|
|
216
|
+
// collection are now potentially stale.
|
|
217
|
+
this._invalidateCache();
|
|
218
|
+
// Run all batches in parallel
|
|
219
|
+
try {
|
|
220
|
+
yield Promise.all(batchPromises);
|
|
221
|
+
return results;
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
console.error("FirestoreDB::updateMany: Error during batched update", error);
|
|
225
|
+
throw error;
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
/**
|
|
229
|
+
* FIRESTOREDB: deleteOne('id_123')
|
|
230
|
+
*/
|
|
231
|
+
this.deleteOne = (docId) => __awaiter(this, void 0, void 0, function* () {
|
|
232
|
+
const docRef = (0, firestore_1.doc)(this._db, this._collectionRef.id, docId);
|
|
233
|
+
// Check if it exists first if you want an accurate deletedCount
|
|
234
|
+
const snap = yield (0, firestore_1.getDoc)(docRef);
|
|
235
|
+
if (!snap.exists()) {
|
|
236
|
+
return { acknowledged: true, deletedCount: 0 };
|
|
237
|
+
}
|
|
238
|
+
if (this._isSoftDeleteEnabled) {
|
|
239
|
+
// SOFT DELETE: Update with flag and timestamp
|
|
240
|
+
yield (0, firestore_1.updateDoc)(docRef, {
|
|
241
|
+
isDeleted: true,
|
|
242
|
+
deletedAt: (0, firestore_1.serverTimestamp)(),
|
|
243
|
+
updatedAt: (0, firestore_1.serverTimestamp)()
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
// HARD DELETE: Remove from disk
|
|
248
|
+
yield (0, firestore_1.deleteDoc)(docRef);
|
|
249
|
+
}
|
|
250
|
+
// 2. TRIGGER INVALIDATION: Any cached 'find' results for this
|
|
251
|
+
// collection are now potentially stale.
|
|
252
|
+
this._invalidateCache();
|
|
253
|
+
return { acknowledged: true, deletedCount: 1 };
|
|
254
|
+
});
|
|
255
|
+
/**
|
|
256
|
+
* FIRESTOREDB: deleteMany()
|
|
257
|
+
* Uses Batched Writes for speed.
|
|
258
|
+
*/
|
|
259
|
+
this.deleteMany = (docIds) => __awaiter(this, void 0, void 0, function* () {
|
|
260
|
+
const CHUNK_SIZE = 500;
|
|
261
|
+
const batchPromises = [];
|
|
262
|
+
let totalDeleted = 0;
|
|
263
|
+
for (let i = 0; i < docIds.length; i += CHUNK_SIZE) {
|
|
264
|
+
const batch = (0, firestore_1.writeBatch)(this._db);
|
|
265
|
+
const chunk = docIds.slice(i, i + CHUNK_SIZE);
|
|
266
|
+
chunk.forEach(id => {
|
|
267
|
+
let docRef = (0, firestore_1.doc)(this._db, this._collectionRef.id, id);
|
|
268
|
+
if (this._isSoftDeleteEnabled) {
|
|
269
|
+
batch.update(docRef, {
|
|
270
|
+
isDeleted: true,
|
|
271
|
+
deletedAt: (0, firestore_1.serverTimestamp)(),
|
|
272
|
+
updatedAt: (0, firestore_1.serverTimestamp)()
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
batch.delete(docRef);
|
|
277
|
+
}
|
|
278
|
+
totalDeleted++;
|
|
279
|
+
});
|
|
280
|
+
batchPromises.push(batch.commit());
|
|
281
|
+
}
|
|
282
|
+
yield Promise.all(batchPromises);
|
|
283
|
+
// 2. TRIGGER INVALIDATION: Any cached 'find' results for this
|
|
284
|
+
// collection are now potentially stale.
|
|
285
|
+
this._invalidateCache();
|
|
286
|
+
return { acknowledged: true, deletedCount: totalDeleted };
|
|
287
|
+
});
|
|
288
|
+
/**
|
|
289
|
+
* Efficiently checks if a document exists without downloading it. (only metadata).
|
|
290
|
+
* Optimized to only check for the presence of a document
|
|
291
|
+
*/
|
|
292
|
+
this.exists = (filter) => __awaiter(this, void 0, void 0, function* () {
|
|
293
|
+
const q = (0, firestore_1.query)(this._collectionRef, ...this._buildConstraints(filter), (0, firestore_1.limit)(1));
|
|
294
|
+
const snapshot = yield (0, firestore_1.getDocs)(q);
|
|
295
|
+
return !snapshot.empty;
|
|
296
|
+
});
|
|
297
|
+
/**
|
|
298
|
+
* FIRESTOREDB STYLE: aggregate({ status: 'sold' }, { total: { $sum: 'price' }, avg: { $avg: 'price' } })
|
|
299
|
+
*/
|
|
300
|
+
this.aggregate = (...args_1) => __awaiter(this, [...args_1], void 0, function* (filter = {}, aggregations) {
|
|
301
|
+
const constraints = this._buildConstraints(filter);
|
|
302
|
+
const q = (0, firestore_1.query)(this._collectionRef, ...constraints);
|
|
303
|
+
// 1. Map the request to Firestore's aggregation functions
|
|
304
|
+
const spec = {};
|
|
305
|
+
Object.entries(aggregations).forEach(([alias, op]) => {
|
|
306
|
+
if (op.$sum)
|
|
307
|
+
spec[alias] = (0, firestore_1.sum)(op.$sum);
|
|
308
|
+
if (op.$avg)
|
|
309
|
+
spec[alias] = (0, firestore_1.average)(op.$avg);
|
|
310
|
+
if (op.$count)
|
|
311
|
+
spec[alias] = (0, firestore_1.count)();
|
|
312
|
+
});
|
|
313
|
+
// 2. Execute on server (Calculates across millions of docs instantly)
|
|
314
|
+
const snapshot = yield (0, firestore_1.getAggregateFromServer)(q, spec);
|
|
315
|
+
return snapshot.data();
|
|
316
|
+
});
|
|
317
|
+
this._flattenFilter = (obj, prefix = '') => {
|
|
318
|
+
let constraints = [];
|
|
319
|
+
Object.entries(obj).forEach(([key, value]) => {
|
|
320
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
321
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
322
|
+
// Check if this object is a KnymbusDB operator (e.g. { $gt: 20 })
|
|
323
|
+
const firstKey = Object.keys(value)[0];
|
|
324
|
+
if (firstKey.startsWith("$")) {
|
|
325
|
+
// get the symbol if not found then default to equality
|
|
326
|
+
const op = utils_1.$OperatorMap[firstKey] || '==';
|
|
327
|
+
constraints.push((0, firestore_1.where)(path, op, value[firstKey]));
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
// It's a nested object, keep flattening
|
|
331
|
+
constraints = [...constraints, ...this._flattenFilter(value, path)];
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
// It's a primitive value, use standard equality
|
|
336
|
+
constraints.push((0, firestore_1.where)(path, '==', value));
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
return constraints;
|
|
340
|
+
};
|
|
341
|
+
this._db = db;
|
|
342
|
+
this._collectionName = collectionName;
|
|
343
|
+
// Ensure Store is initialized or passed in
|
|
344
|
+
this._collectionRef = (0, firestore_1.collection)(this._db, this._collectionName);
|
|
345
|
+
this._isSoftDeleteEnabled = (_a = options.softDelete) !== null && _a !== void 0 ? _a : false;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* FIRESTOREDB: db.collection.restore(id)
|
|
349
|
+
* Reverses a soft delete by flipping the flag and removing deletedAt.
|
|
350
|
+
*/
|
|
351
|
+
restore(id) {
|
|
352
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
353
|
+
const docRef = (0, firestore_1.doc)(this._db, this._collectionName, id);
|
|
354
|
+
yield (0, firestore_1.updateDoc)(docRef, {
|
|
355
|
+
isDeleted: false,
|
|
356
|
+
deletedAt: null, // Clear the timestamp
|
|
357
|
+
updatedAt: (0, firestore_1.serverTimestamp)()
|
|
358
|
+
});
|
|
359
|
+
// 2. TRIGGER INVALIDATION: Any cached 'find' results for this
|
|
360
|
+
// collection are now potentially stale.
|
|
361
|
+
this._invalidateCache();
|
|
362
|
+
return { acknowledged: true, restoredCount: 1 };
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* MONGODB: db.collection.watch(filter)
|
|
367
|
+
* A real-time listener that bypasses the manual cache and
|
|
368
|
+
* pushes updates as they happen in the database.
|
|
369
|
+
*/
|
|
370
|
+
watch(filter = {}, callback, onError) {
|
|
371
|
+
const constraints = this._buildConstraints(filter);
|
|
372
|
+
const q = (0, firestore_1.query)(this._collectionRef, ...constraints);
|
|
373
|
+
// Returns the unsubscribe function
|
|
374
|
+
return (0, firestore_1.onSnapshot)(q, (snapshot) => {
|
|
375
|
+
const docs = snapshot.docs.map(d => {
|
|
376
|
+
const raw = Object.assign({ _id: d.id }, d.data());
|
|
377
|
+
return (0, utils_1.hydrateDates)(raw);
|
|
378
|
+
});
|
|
379
|
+
callback(docs);
|
|
380
|
+
}, (err) => {
|
|
381
|
+
if (onError)
|
|
382
|
+
onError(err);
|
|
383
|
+
else
|
|
384
|
+
console.error("FirestoreDB::watch:Error: ", err);
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
// PRIVATE METHODS
|
|
388
|
+
_buildConstraints(filter) {
|
|
389
|
+
return [...this._flattenFilter(filter)];
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* In FirestoreDB class:
|
|
393
|
+
* Purges all cached queries for THIS collection to ensure data freshness.
|
|
394
|
+
*/
|
|
395
|
+
_invalidateCache() {
|
|
396
|
+
const prefix = `${this._collectionName}_`;
|
|
397
|
+
// Convert keys to an array before iterating to avoid
|
|
398
|
+
// "Concurrent Modification" issues while deleting
|
|
399
|
+
const keys = Array.from(FirestoreQuery_1.FirestoreQuery._globalCache.keys());
|
|
400
|
+
keys.forEach(key => {
|
|
401
|
+
if (key.startsWith(prefix)) {
|
|
402
|
+
FirestoreQuery_1.FirestoreQuery._globalCache.delete(key);
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
exports.FirestoreDB = FirestoreDB;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { QueryConstraint } from "firebase/firestore";
|
|
2
|
+
import { WithSystemFields } from "./types";
|
|
3
|
+
import { LRUCache } from './utils';
|
|
4
|
+
export type SortDescriptor = "asc" | "desc";
|
|
5
|
+
export interface PaginationMetadata {
|
|
6
|
+
totalRecords: number;
|
|
7
|
+
totalPages: number;
|
|
8
|
+
hasNext: boolean;
|
|
9
|
+
hasPrevious: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare class FirestoreQuery<T> {
|
|
12
|
+
private db;
|
|
13
|
+
private collectionName;
|
|
14
|
+
private collectionRef;
|
|
15
|
+
private filter;
|
|
16
|
+
private buildConstraints;
|
|
17
|
+
private countDocs;
|
|
18
|
+
static _globalCache: LRUCache;
|
|
19
|
+
private _useCache;
|
|
20
|
+
private _ttl;
|
|
21
|
+
private _deleteMode;
|
|
22
|
+
private _limit;
|
|
23
|
+
private _sort;
|
|
24
|
+
private _cursorId?;
|
|
25
|
+
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);
|
|
27
|
+
/** Enables result caching for this query */
|
|
28
|
+
cache(ttlMs?: number): this;
|
|
29
|
+
sort(sortObj: Record<keyof T, SortDescriptor>): this;
|
|
30
|
+
limit(n: number): this;
|
|
31
|
+
/** Move to the next page */
|
|
32
|
+
after(id: string): this;
|
|
33
|
+
/** Move to the previous page */
|
|
34
|
+
before(id: string): this;
|
|
35
|
+
/**
|
|
36
|
+
* FirestoreDB: db.collection.find({}).withDeleted().execute()
|
|
37
|
+
* Allows viewing soft-deleted documents for this specific query.
|
|
38
|
+
*/
|
|
39
|
+
withDeleted(): this;
|
|
40
|
+
onlyDeleted(): this;
|
|
41
|
+
/** Simple execution (Returns Array) */
|
|
42
|
+
execute(): Promise<WithSystemFields<T>[]>;
|
|
43
|
+
/** Paginated execution (Returns Data + Metadata) */
|
|
44
|
+
paginate(): Promise<{
|
|
45
|
+
data: WithSystemFields<T>[];
|
|
46
|
+
metadata: PaginationMetadata;
|
|
47
|
+
}>;
|
|
48
|
+
stream(): ReadableStream<T & {
|
|
49
|
+
_id: string;
|
|
50
|
+
}>;
|
|
51
|
+
private _prepareConstraints;
|
|
52
|
+
private _generateCacheKey;
|
|
53
|
+
/**
|
|
54
|
+
* Helper to ensure {a:1, b:2} and {b:2, a:1} result in the same JSON string
|
|
55
|
+
*/
|
|
56
|
+
private _sortObjectKeys;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=FirestoreQuery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FirestoreQuery.d.ts","sourceRoot":"","sources":["../src/FirestoreQuery.ts"],"names":[],"mappings":"AAAA,OAAO,EAC8E,eAAe,EAEnG,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAG3C,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAElC,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,MAAM,CAAA;AAC3C,MAAM,WAAW,kBAAkB;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;CACxB;AAED,qBAAa,cAAc,CAAC,CAAC;IAerB,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,aAAa;IACrB,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,gBAAgB;IACxB,OAAO,CAAC,SAAS;IAjBrB,OAAc,YAAY,WAAkB;IAC5C,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,IAAI,CAAiB;IAG7B,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,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;IAMb,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,oDAAoD;IACvC,QAAQ,IAAI,OAAO,CAAC;QAAE,IAAI,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC;QAAC,QAAQ,EAAE,kBAAkB,CAAA;KAAE,CAAC;IAsCxF,MAAM,IAAI,cAAc,CAAC,CAAC,GAAG;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;YAoBtC,mBAAmB;IAsCjC,OAAO,CAAC,iBAAiB;IAczB;;GAED;IACC,OAAO,CAAC,eAAe;CAQ1B"}
|