nexabase-console 2.1.2 → 2.1.3
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/app/NexaApp.d.ts +3 -0
- package/dist/app/NexaApp.js +38 -11
- package/dist/firestore/CollectionReference.d.ts +2 -2
- package/dist/firestore/CollectionReference.js +41 -2
- package/dist/firestore/DocumentReference.d.ts +1 -1
- package/dist/firestore/DocumentReference.js +41 -11
- package/dist/firestore/FieldPath.d.ts +2 -1
- package/dist/firestore/FieldPath.js +31 -4
- package/dist/firestore/FieldValue.d.ts +8 -4
- package/dist/firestore/FieldValue.js +59 -7
- package/dist/firestore/Firestore.js +16 -4
- package/dist/firestore/GeoPoint.d.ts +1 -0
- package/dist/firestore/GeoPoint.js +13 -6
- package/dist/firestore/Query.d.ts +5 -0
- package/dist/firestore/Query.js +190 -56
- package/dist/firestore/QueryUtils.d.ts +19 -2
- package/dist/firestore/QueryUtils.js +296 -58
- package/dist/firestore/Snapshot.js +62 -17
- package/dist/firestore/SnapshotManager.js +27 -12
- package/dist/firestore/Timestamp.d.ts +1 -0
- package/dist/firestore/Timestamp.js +28 -2
- package/dist/firestore/batch.js +89 -36
- package/dist/firestore/pathValidation.d.ts +17 -0
- package/dist/firestore/pathValidation.js +61 -0
- package/dist/firestore/queryConstraints.d.ts +4 -4
- package/dist/firestore/queryConstraints.js +69 -8
- package/dist/firestore/transaction.js +72 -18
- package/dist/firestore/writes.d.ts +1 -1
- package/dist/firestore/writes.js +32 -14
- package/dist/transport/WebSocketClient.d.ts +341 -9
- package/dist/transport/WebSocketClient.js +1895 -131
- package/dist/transport/WebSocketClient.test.d.ts +1 -0
- package/dist/transport/WebSocketClient.test.js +454 -0
- package/dist/types/index.d.ts +1 -0
- package/package.json +1 -1
package/dist/firestore/Query.js
CHANGED
|
@@ -1,12 +1,73 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getCountFromServer = exports.getAggregateFromServer = exports.average = exports.sum = exports.count = exports.getDocs = exports.getCachedDocs = exports.query = void 0;
|
|
4
|
+
exports.getCanonicalQueryKey = getCanonicalQueryKey;
|
|
5
|
+
exports.validateAndDeduplicateServerDocs = validateAndDeduplicateServerDocs;
|
|
6
|
+
const FieldPath_1 = require("./FieldPath");
|
|
4
7
|
const SnapshotManager_1 = require("./SnapshotManager");
|
|
5
8
|
const DocumentReference_1 = require("./DocumentReference");
|
|
6
9
|
const NexaError_1 = require("../errors/NexaError");
|
|
7
|
-
const helpers_1 = require("../utils/helpers");
|
|
8
10
|
const QueryUtils_1 = require("./QueryUtils");
|
|
11
|
+
function getCanonicalQueryKey(collectionPath, constraints) {
|
|
12
|
+
const parts = [`path:${collectionPath}`];
|
|
13
|
+
const wheres = constraints
|
|
14
|
+
.filter((c) => c.type === 'where')
|
|
15
|
+
.map((c) => {
|
|
16
|
+
const fp = typeof c.fieldPath === 'string' ? c.fieldPath : c.fieldPath.getPathString();
|
|
17
|
+
return `${fp}:${c.opStr}:${JSON.stringify(c.value)}`;
|
|
18
|
+
})
|
|
19
|
+
.sort();
|
|
20
|
+
if (wheres.length > 0)
|
|
21
|
+
parts.push(`where:[${wheres.join(',')}]`);
|
|
22
|
+
const orderBys = constraints
|
|
23
|
+
.filter((c) => c.type === 'orderBy')
|
|
24
|
+
.map((c) => {
|
|
25
|
+
const fp = typeof c.fieldPath === 'string' ? c.fieldPath : c.fieldPath.getPathString();
|
|
26
|
+
return `${fp}:${c.directionStr || 'asc'}`;
|
|
27
|
+
});
|
|
28
|
+
if (orderBys.length > 0)
|
|
29
|
+
parts.push(`order:[${orderBys.join(',')}]`);
|
|
30
|
+
const limitConstraint = constraints.find((c) => c.type === 'limit');
|
|
31
|
+
if (limitConstraint)
|
|
32
|
+
parts.push(`limit:${limitConstraint.limitValue}`);
|
|
33
|
+
const cursorConstraints = constraints.filter((c) => ['startAt', 'startAfter', 'endAt', 'endBefore'].includes(c.type));
|
|
34
|
+
if (cursorConstraints.length > 0) {
|
|
35
|
+
const cursors = cursorConstraints.map((c) => {
|
|
36
|
+
if (c.docSnapshot)
|
|
37
|
+
return `${c.type}:doc:${c.docSnapshot.id}`;
|
|
38
|
+
return `${c.type}:val:${JSON.stringify(c.cursorValues)}`;
|
|
39
|
+
});
|
|
40
|
+
parts.push(`cursor:[${cursors.join(',')}]`);
|
|
41
|
+
}
|
|
42
|
+
return parts.join('|');
|
|
43
|
+
}
|
|
44
|
+
function validateAndDeduplicateServerDocs(docs) {
|
|
45
|
+
if (!Array.isArray(docs)) {
|
|
46
|
+
throw new NexaError_1.NexaError('internal', 'Invalid server response: documents must be an array.');
|
|
47
|
+
}
|
|
48
|
+
const seenIds = new Set();
|
|
49
|
+
const validDocs = [];
|
|
50
|
+
for (const d of docs) {
|
|
51
|
+
if (!d || typeof d !== 'object')
|
|
52
|
+
continue;
|
|
53
|
+
if (typeof d.id !== 'string' || d.id.trim().length === 0 || d.id.includes('/'))
|
|
54
|
+
continue;
|
|
55
|
+
if (!d.fields || typeof d.fields !== 'object' || Array.isArray(d.fields))
|
|
56
|
+
continue;
|
|
57
|
+
if (!seenIds.has(d.id)) {
|
|
58
|
+
seenIds.add(d.id);
|
|
59
|
+
validDocs.push({
|
|
60
|
+
id: d.id,
|
|
61
|
+
fields: d.fields
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return validDocs;
|
|
66
|
+
}
|
|
9
67
|
const query = (queryObject, ...queryConstraints) => {
|
|
68
|
+
if (!queryObject || typeof queryObject !== 'object' || !queryObject.path) {
|
|
69
|
+
throw new NexaError_1.NexaError('invalid-argument', 'First argument to query() must be a valid Query or CollectionReference.');
|
|
70
|
+
}
|
|
10
71
|
const q = {
|
|
11
72
|
type: 'query',
|
|
12
73
|
path: queryObject.path,
|
|
@@ -14,13 +75,57 @@ const query = (queryObject, ...queryConstraints) => {
|
|
|
14
75
|
constraints: queryObject.type === 'query' ? [...(queryObject.constraints || [])] : []
|
|
15
76
|
};
|
|
16
77
|
q.constraints = [...(q.constraints || []), ...queryConstraints];
|
|
17
|
-
//
|
|
78
|
+
// Validate constraint combinations
|
|
79
|
+
let inequalityField = null;
|
|
80
|
+
let hasNotEquals = false;
|
|
81
|
+
let hasNotIn = false;
|
|
82
|
+
let inOrNotInOrArrayContainsAnyCount = 0;
|
|
83
|
+
let arrayContainsCount = 0;
|
|
18
84
|
for (const c of q.constraints) {
|
|
19
|
-
if (c.type === '
|
|
20
|
-
|
|
85
|
+
if (c.type === 'where') {
|
|
86
|
+
const fieldStr = typeof c.fieldPath === 'string' ? c.fieldPath : c.fieldPath.getPathString();
|
|
87
|
+
if (['<', '<=', '>', '>=', '!=', 'not-in'].includes(c.opStr || '')) {
|
|
88
|
+
if (inequalityField && inequalityField !== fieldStr) {
|
|
89
|
+
throw new NexaError_1.NexaError('invalid-argument', 'All where filters with an inequality (<, <=, >, >=, !=, not-in) must be on the same field.');
|
|
90
|
+
}
|
|
91
|
+
inequalityField = fieldStr;
|
|
92
|
+
}
|
|
93
|
+
if (c.opStr === '!=') {
|
|
94
|
+
if (hasNotIn) {
|
|
95
|
+
throw new NexaError_1.NexaError('invalid-argument', 'Cannot combine != with not-in in a single query.');
|
|
96
|
+
}
|
|
97
|
+
hasNotEquals = true;
|
|
98
|
+
}
|
|
99
|
+
if (c.opStr === 'not-in') {
|
|
100
|
+
if (hasNotEquals) {
|
|
101
|
+
throw new NexaError_1.NexaError('invalid-argument', 'Cannot combine != with not-in in a single query.');
|
|
102
|
+
}
|
|
103
|
+
hasNotIn = true;
|
|
104
|
+
}
|
|
105
|
+
if (['in', 'not-in', 'array-contains-any'].includes(c.opStr || '')) {
|
|
106
|
+
inOrNotInOrArrayContainsAnyCount++;
|
|
107
|
+
if (inOrNotInOrArrayContainsAnyCount > 1) {
|
|
108
|
+
throw new NexaError_1.NexaError('invalid-argument', 'A query cannot have more than one in, not-in, or array-contains-any filter.');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (c.opStr === 'array-contains') {
|
|
112
|
+
arrayContainsCount++;
|
|
113
|
+
if (arrayContainsCount > 1) {
|
|
114
|
+
throw new NexaError_1.NexaError('invalid-argument', 'A query cannot have more than one array-contains filter.');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
21
117
|
}
|
|
22
|
-
|
|
23
|
-
|
|
118
|
+
}
|
|
119
|
+
// If inequality filter exists, verify first explicit orderBy matches the inequality field
|
|
120
|
+
if (inequalityField) {
|
|
121
|
+
const firstOrderBy = q.constraints.find((c) => c.type === 'orderBy');
|
|
122
|
+
if (firstOrderBy) {
|
|
123
|
+
const orderFieldStr = typeof firstOrderBy.fieldPath === 'string'
|
|
124
|
+
? firstOrderBy.fieldPath
|
|
125
|
+
: firstOrderBy.fieldPath.getPathString();
|
|
126
|
+
if (orderFieldStr !== inequalityField && orderFieldStr !== '__name__') {
|
|
127
|
+
throw new NexaError_1.NexaError('invalid-argument', `The first orderBy field (${orderFieldStr}) must match the inequality filter field (${inequalityField}).`);
|
|
128
|
+
}
|
|
24
129
|
}
|
|
25
130
|
}
|
|
26
131
|
return q;
|
|
@@ -38,31 +143,49 @@ const getCachedDocs = (queryOrCollection, fromCache = true) => {
|
|
|
38
143
|
const docId = parts[parts.length - 1];
|
|
39
144
|
const docRef = (0, DocumentReference_1.doc)(db, `${collectionPath}/${docId}`);
|
|
40
145
|
const cachedData = (0, QueryUtils_1.cloneNexaData)(db._firestoreCache[p]);
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
146
|
+
if (cachedData !== null && cachedData !== undefined) {
|
|
147
|
+
results.push({
|
|
148
|
+
id: docId,
|
|
149
|
+
ref: docRef,
|
|
150
|
+
metadata: { fromCache, hasPendingWrites: db.hasPendingWrites(docRef.path) },
|
|
151
|
+
exists: () => true,
|
|
152
|
+
data: () => (0, QueryUtils_1.cloneNexaData)(cachedData),
|
|
153
|
+
get: (fieldPath) => {
|
|
154
|
+
if (fieldPath === '__name__' || (fieldPath instanceof FieldPath_1.FieldPath && fieldPath.segments[0] === '__name__')) {
|
|
155
|
+
return docId;
|
|
156
|
+
}
|
|
157
|
+
return (0, QueryUtils_1.getNestedValue)(cachedData, fieldPath);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
}
|
|
49
161
|
}
|
|
50
162
|
}
|
|
51
163
|
let filtered = results;
|
|
52
164
|
let limitCount = undefined;
|
|
53
165
|
const orderBys = [];
|
|
54
166
|
for (const c of constraints) {
|
|
55
|
-
if (c.type === 'orderBy')
|
|
167
|
+
if (c.type === 'orderBy') {
|
|
56
168
|
orderBys.push(c);
|
|
169
|
+
}
|
|
57
170
|
}
|
|
58
|
-
// Add implicit tie-breaker by document ID
|
|
59
|
-
orderBys.
|
|
171
|
+
// Add implicit tie-breaker by document ID if __name__ is not explicitly ordered
|
|
172
|
+
const hasNameOrder = orderBys.some((ob) => {
|
|
173
|
+
const fp = typeof ob.fieldPath === 'string' ? ob.fieldPath : ob.fieldPath?.getPathString();
|
|
174
|
+
return fp === '__name__';
|
|
175
|
+
});
|
|
176
|
+
if (!hasNameOrder) {
|
|
177
|
+
const lastDirection = orderBys.length > 0 ? orderBys[orderBys.length - 1].directionStr || 'asc' : 'asc';
|
|
178
|
+
orderBys.push({ type: 'orderBy', fieldPath: '__name__', directionStr: lastDirection });
|
|
179
|
+
}
|
|
180
|
+
// Where filtering
|
|
60
181
|
for (const c of constraints) {
|
|
61
182
|
if (c.type === 'where' && c.fieldPath && c.opStr) {
|
|
62
183
|
filtered = filtered.filter((docItem) => {
|
|
63
|
-
const val =
|
|
184
|
+
const val = c.fieldPath === '__name__' || (c.fieldPath instanceof FieldPath_1.FieldPath && c.fieldPath.segments[0] === '__name__')
|
|
185
|
+
? docItem.id
|
|
186
|
+
: (0, QueryUtils_1.getNestedValue)(docItem.data(), c.fieldPath);
|
|
64
187
|
if (val === undefined) {
|
|
65
|
-
// Missing fields
|
|
188
|
+
// Missing fields do not match inequalities
|
|
66
189
|
if (['!=', 'not-in', '<', '<=', '>', '>='].includes(c.opStr))
|
|
67
190
|
return false;
|
|
68
191
|
}
|
|
@@ -86,26 +209,28 @@ const getCachedDocs = (queryOrCollection, fromCache = true) => {
|
|
|
86
209
|
return Array.isArray(val) && val.some((v) => (0, QueryUtils_1.compareNexaValues)(v, c.value) === 0);
|
|
87
210
|
if (c.opStr === 'array-contains-any')
|
|
88
211
|
return Array.isArray(val) && Array.isArray(c.value) && c.value.some((cVal) => val.some((v) => (0, QueryUtils_1.compareNexaValues)(v, cVal) === 0));
|
|
89
|
-
throw new
|
|
212
|
+
throw new NexaError_1.NexaError('invalid-argument', `Unsupported query operator: ${c.opStr}`);
|
|
90
213
|
});
|
|
91
214
|
}
|
|
92
215
|
}
|
|
93
216
|
// Documents without orderBy field must be excluded
|
|
94
217
|
filtered = filtered.filter((docItem) => {
|
|
95
218
|
for (const ob of orderBys) {
|
|
96
|
-
|
|
97
|
-
|
|
219
|
+
const fieldStr = typeof ob.fieldPath === 'string' ? ob.fieldPath : ob.fieldPath?.getPathString();
|
|
220
|
+
if (fieldStr !== '__name__') {
|
|
221
|
+
const val = (0, QueryUtils_1.getNestedValue)(docItem.data(), ob.fieldPath);
|
|
98
222
|
if (val === undefined)
|
|
99
223
|
return false;
|
|
100
224
|
}
|
|
101
225
|
}
|
|
102
226
|
return true;
|
|
103
227
|
});
|
|
228
|
+
// Sorting
|
|
104
229
|
filtered = [...filtered].sort((a, b) => {
|
|
105
230
|
for (const ob of orderBys) {
|
|
106
|
-
const
|
|
107
|
-
const valA =
|
|
108
|
-
const valB =
|
|
231
|
+
const fieldStr = typeof ob.fieldPath === 'string' ? ob.fieldPath : ob.fieldPath?.getPathString();
|
|
232
|
+
const valA = fieldStr === '__name__' ? a.id : (0, QueryUtils_1.getNestedValue)(a.data(), ob.fieldPath);
|
|
233
|
+
const valB = fieldStr === '__name__' ? b.id : (0, QueryUtils_1.getNestedValue)(b.data(), ob.fieldPath);
|
|
109
234
|
const comp = (0, QueryUtils_1.compareNexaValues)(valA, valB);
|
|
110
235
|
if (comp !== 0) {
|
|
111
236
|
return ob.directionStr === 'desc' ? -comp : comp;
|
|
@@ -113,26 +238,29 @@ const getCachedDocs = (queryOrCollection, fromCache = true) => {
|
|
|
113
238
|
}
|
|
114
239
|
return 0;
|
|
115
240
|
});
|
|
241
|
+
// Cursors and limits
|
|
116
242
|
for (const c of constraints) {
|
|
117
243
|
if (['startAt', 'startAfter', 'endAt', 'endBefore'].includes(c.type)) {
|
|
118
244
|
const isSnapshot = c.docSnapshot && typeof c.docSnapshot === 'object' && 'id' in c.docSnapshot;
|
|
119
245
|
const cursorVals = isSnapshot
|
|
120
|
-
? orderBys.map(ob => {
|
|
121
|
-
|
|
246
|
+
? orderBys.map((ob) => {
|
|
247
|
+
const fieldStr = typeof ob.fieldPath === 'string' ? ob.fieldPath : ob.fieldPath?.getPathString();
|
|
248
|
+
if (fieldStr === '__name__')
|
|
122
249
|
return c.docSnapshot.id;
|
|
123
250
|
return typeof c.docSnapshot.get === 'function'
|
|
124
251
|
? c.docSnapshot.get(ob.fieldPath)
|
|
125
|
-
: (0,
|
|
252
|
+
: (0, QueryUtils_1.getNestedValue)(c.docSnapshot.data ? c.docSnapshot.data() : c.docSnapshot, ob.fieldPath);
|
|
126
253
|
})
|
|
127
254
|
: (Array.isArray(c.cursorValues) ? c.cursorValues : [c.cursorValues]);
|
|
128
|
-
filtered = filtered.filter(d => {
|
|
255
|
+
filtered = filtered.filter((d) => {
|
|
129
256
|
for (let i = 0; i < Math.min(orderBys.length, cursorVals.length); i++) {
|
|
130
|
-
const
|
|
131
|
-
const
|
|
257
|
+
const ob = orderBys[i];
|
|
258
|
+
const fieldStr = typeof ob.fieldPath === 'string' ? ob.fieldPath : ob.fieldPath?.getPathString();
|
|
259
|
+
const dVal = fieldStr === '__name__' ? d.id : (0, QueryUtils_1.getNestedValue)(d.data(), ob.fieldPath);
|
|
132
260
|
const cVal = cursorVals[i];
|
|
133
261
|
const comp = (0, QueryUtils_1.compareNexaValues)(dVal, cVal);
|
|
134
262
|
if (comp !== 0) {
|
|
135
|
-
const dir =
|
|
263
|
+
const dir = ob.directionStr === 'desc' ? -1 : 1;
|
|
136
264
|
const adjustedComp = comp * dir;
|
|
137
265
|
if (c.type === 'startAt' || c.type === 'startAfter')
|
|
138
266
|
return adjustedComp > 0;
|
|
@@ -153,7 +281,7 @@ const getCachedDocs = (queryOrCollection, fromCache = true) => {
|
|
|
153
281
|
if (limitCount !== undefined) {
|
|
154
282
|
filtered = filtered.slice(0, limitCount);
|
|
155
283
|
}
|
|
156
|
-
return SnapshotManager_1.SnapshotManager.createQuerySnapshot(filtered, fromCache, filtered.some(d => d.metadata.hasPendingWrites) || db.hasPendingWrites(collectionPath));
|
|
284
|
+
return SnapshotManager_1.SnapshotManager.createQuerySnapshot(filtered, fromCache, filtered.some((d) => d.metadata.hasPendingWrites) || db.hasPendingWrites(collectionPath));
|
|
157
285
|
};
|
|
158
286
|
exports.getCachedDocs = getCachedDocs;
|
|
159
287
|
const getDocs = async (queryOrCollection) => {
|
|
@@ -163,57 +291,63 @@ const getDocs = async (queryOrCollection) => {
|
|
|
163
291
|
const collectionPath = queryOrCollection.path;
|
|
164
292
|
const constraints = 'constraints' in queryOrCollection ? queryOrCollection.constraints || [] : [];
|
|
165
293
|
const serializedConstraints = constraints.map(QueryUtils_1.serializeConstraint);
|
|
294
|
+
const queryKey = getCanonicalQueryKey(collectionPath, constraints);
|
|
295
|
+
// Track request timestamp to prevent stale responses from overwriting newer cache
|
|
296
|
+
const requestTimestamp = Date.now();
|
|
297
|
+
db._activeQueryTimestamps.set(queryKey, requestTimestamp);
|
|
166
298
|
if (db.cacheStrategy === 'cache-first') {
|
|
167
299
|
const cached = (0, exports.getCachedDocs)(queryOrCollection, true);
|
|
168
|
-
if (cached && (db._queryCacheMetadata[
|
|
300
|
+
if (cached && (db._queryCacheMetadata[queryKey] || !cached.empty)) {
|
|
169
301
|
// background refresh
|
|
170
302
|
db.client
|
|
171
303
|
.post(`/api/firestore/${db.projectId}/query`, { collectionPath, constraints: serializedConstraints })
|
|
172
304
|
.then(async (res) => {
|
|
173
|
-
|
|
174
|
-
if (
|
|
305
|
+
// Check if a newer request has already superseded this one
|
|
306
|
+
if (db._activeQueryTimestamps.get(queryKey) !== requestTimestamp) {
|
|
175
307
|
return;
|
|
308
|
+
}
|
|
309
|
+
const rawDocs = res.data?.documents;
|
|
310
|
+
if (!Array.isArray(rawDocs))
|
|
311
|
+
return;
|
|
312
|
+
const validDocs = validateAndDeduplicateServerDocs(rawDocs);
|
|
176
313
|
const complete = res.data?.complete === true;
|
|
177
314
|
await db._commitAtomicQueryResults({
|
|
178
|
-
queryKey
|
|
315
|
+
queryKey,
|
|
179
316
|
collectionPath,
|
|
180
|
-
serverDocs:
|
|
317
|
+
serverDocs: validDocs,
|
|
181
318
|
isComplete: complete,
|
|
182
319
|
constraints: serializedConstraints
|
|
183
320
|
});
|
|
184
|
-
db._queryCacheMetadata[
|
|
321
|
+
db._queryCacheMetadata[queryKey] = true;
|
|
185
322
|
db._notifySnapshotCallbacks(collectionPath, 'bulk_update', null);
|
|
186
323
|
})
|
|
187
324
|
.catch((err) => {
|
|
188
|
-
console.error('[NexaBase] Background query refresh
|
|
325
|
+
console.error('[NexaBase] Background query refresh error:', err);
|
|
189
326
|
});
|
|
190
327
|
return cached;
|
|
191
328
|
}
|
|
192
329
|
}
|
|
193
330
|
try {
|
|
194
|
-
const res = await db.client.post(`/api/firestore/${db.projectId}/query`, {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
for (const d of docs) {
|
|
202
|
-
if (d && typeof d.id === 'string' && d.fields && typeof d.fields === 'object' && !seenIds.has(d.id)) {
|
|
203
|
-
seenIds.add(d.id);
|
|
204
|
-
validDocs.push(d);
|
|
205
|
-
}
|
|
331
|
+
const res = await db.client.post(`/api/firestore/${db.projectId}/query`, {
|
|
332
|
+
collectionPath,
|
|
333
|
+
constraints: serializedConstraints
|
|
334
|
+
});
|
|
335
|
+
if (db._activeQueryTimestamps.get(queryKey) !== requestTimestamp) {
|
|
336
|
+
// Stale response superseded by a newer query
|
|
337
|
+
return (0, exports.getCachedDocs)(queryOrCollection, false);
|
|
206
338
|
}
|
|
339
|
+
const rawDocs = res.data?.documents;
|
|
340
|
+
const validDocs = validateAndDeduplicateServerDocs(rawDocs);
|
|
207
341
|
const complete = res.data?.complete === true;
|
|
208
342
|
// Apply results while preserving pending mutations safely
|
|
209
343
|
await db._commitAtomicQueryResults({
|
|
210
|
-
queryKey
|
|
344
|
+
queryKey,
|
|
211
345
|
collectionPath,
|
|
212
346
|
serverDocs: validDocs,
|
|
213
347
|
isComplete: complete,
|
|
214
348
|
constraints: serializedConstraints
|
|
215
349
|
});
|
|
216
|
-
db._queryCacheMetadata[
|
|
350
|
+
db._queryCacheMetadata[queryKey] = true;
|
|
217
351
|
// After persistence, read the newly reconciled local state
|
|
218
352
|
return (0, exports.getCachedDocs)(queryOrCollection, false);
|
|
219
353
|
}
|
|
@@ -251,7 +385,7 @@ const getAggregateFromServer = async (queryOrCollection, aggregateSpec) => {
|
|
|
251
385
|
const constraints = 'constraints' in queryOrCollection ? queryOrCollection.constraints || [] : [];
|
|
252
386
|
const serializedSpec = {};
|
|
253
387
|
for (const [key, spec] of Object.entries(aggregateSpec)) {
|
|
254
|
-
if (key === '__proto__' || key === 'constructor')
|
|
388
|
+
if (key === '__proto__' || key === 'constructor' || key === 'prototype')
|
|
255
389
|
continue;
|
|
256
390
|
let fp = spec.fieldPath;
|
|
257
391
|
if (fp && typeof fp === 'object' && 'getPathString' in fp && typeof fp.getPathString === 'function') {
|
|
@@ -269,7 +403,7 @@ const getAggregateFromServer = async (queryOrCollection, aggregateSpec) => {
|
|
|
269
403
|
aggregateSpec: serializedSpec
|
|
270
404
|
});
|
|
271
405
|
if (!res.data || typeof res.data.result !== 'object' || res.data.result === null) {
|
|
272
|
-
throw
|
|
406
|
+
throw new NexaError_1.NexaError('internal', 'Invalid aggregate server response');
|
|
273
407
|
}
|
|
274
408
|
return {
|
|
275
409
|
type: 'AggregateQuerySnapshot',
|
|
@@ -1,5 +1,22 @@
|
|
|
1
|
+
import { FieldPath } from './FieldPath';
|
|
1
2
|
export declare function isDirectChildDocument(collectionPath: string, documentPath: string): boolean;
|
|
2
|
-
export declare function
|
|
3
|
-
|
|
3
|
+
export declare function getNestedValue(obj: any, fieldPath: string | FieldPath): any;
|
|
4
|
+
/**
|
|
5
|
+
* Type-aware codec that preserves supported Firestore primitives/classes
|
|
6
|
+
* while rejecting cycles, unsupported prototypes, and prototype pollution.
|
|
7
|
+
*/
|
|
8
|
+
export declare function cloneNexaData(data: any, seen?: WeakSet<object>): any;
|
|
9
|
+
/**
|
|
10
|
+
* Revives serialized JSON data back into rich Nexa/Firestore types.
|
|
11
|
+
* Never infers Timestamp from an unbranded ordinary object.
|
|
12
|
+
*/
|
|
13
|
+
export declare function reviveNexaData(data: any, seen?: WeakSet<object>): any;
|
|
14
|
+
/**
|
|
15
|
+
* Deterministic Firestore-compatible value comparison.
|
|
16
|
+
*/
|
|
4
17
|
export declare function compareNexaValues(a: any, b: any): number;
|
|
18
|
+
/**
|
|
19
|
+
* Deterministic deep equality check.
|
|
20
|
+
*/
|
|
21
|
+
export declare function deepEqual(a: any, b: any, seen?: WeakSet<object>): boolean;
|
|
5
22
|
export declare function serializeConstraint(c: any): any;
|