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.
Files changed (35) hide show
  1. package/dist/app/NexaApp.d.ts +3 -0
  2. package/dist/app/NexaApp.js +38 -11
  3. package/dist/firestore/CollectionReference.d.ts +2 -2
  4. package/dist/firestore/CollectionReference.js +41 -2
  5. package/dist/firestore/DocumentReference.d.ts +1 -1
  6. package/dist/firestore/DocumentReference.js +41 -11
  7. package/dist/firestore/FieldPath.d.ts +2 -1
  8. package/dist/firestore/FieldPath.js +31 -4
  9. package/dist/firestore/FieldValue.d.ts +8 -4
  10. package/dist/firestore/FieldValue.js +59 -7
  11. package/dist/firestore/Firestore.js +16 -4
  12. package/dist/firestore/GeoPoint.d.ts +1 -0
  13. package/dist/firestore/GeoPoint.js +13 -6
  14. package/dist/firestore/Query.d.ts +5 -0
  15. package/dist/firestore/Query.js +190 -56
  16. package/dist/firestore/QueryUtils.d.ts +19 -2
  17. package/dist/firestore/QueryUtils.js +296 -58
  18. package/dist/firestore/Snapshot.js +62 -17
  19. package/dist/firestore/SnapshotManager.js +27 -12
  20. package/dist/firestore/Timestamp.d.ts +1 -0
  21. package/dist/firestore/Timestamp.js +28 -2
  22. package/dist/firestore/batch.js +89 -36
  23. package/dist/firestore/pathValidation.d.ts +17 -0
  24. package/dist/firestore/pathValidation.js +61 -0
  25. package/dist/firestore/queryConstraints.d.ts +4 -4
  26. package/dist/firestore/queryConstraints.js +69 -8
  27. package/dist/firestore/transaction.js +72 -18
  28. package/dist/firestore/writes.d.ts +1 -1
  29. package/dist/firestore/writes.js +32 -14
  30. package/dist/transport/WebSocketClient.d.ts +341 -9
  31. package/dist/transport/WebSocketClient.js +1895 -131
  32. package/dist/transport/WebSocketClient.test.d.ts +1 -0
  33. package/dist/transport/WebSocketClient.test.js +454 -0
  34. package/dist/types/index.d.ts +1 -0
  35. package/package.json +1 -1
@@ -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
- // Basic client-side validation
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 === 'limit' && (!Number.isSafeInteger(c.limitValue) || c.limitValue <= 0)) {
20
- throw Object.assign(new Error('Limit must be a positive safe integer.'), { code: 'invalid-argument' });
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
- if (['in', 'not-in', 'array-contains-any'].includes(c.opStr || '') && (!Array.isArray(c.value) || c.value.length === 0)) {
23
- throw Object.assign(new Error(`Invalid value for ${c.opStr}: must be a non-empty array.`), { code: 'invalid-argument' });
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
- results.push({
42
- id: docId,
43
- ref: docRef,
44
- metadata: { fromCache, hasPendingWrites: db.hasPendingWrites(docRef.path) },
45
- exists: () => true,
46
- data: () => cachedData,
47
- get: (fieldPath) => (0, helpers_1.getNestedValue)(cachedData, fieldPath, docId)
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.push({ type: 'orderBy', fieldPath: '__name__', directionStr: 'asc' });
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 = (0, helpers_1.getNestedValue)(docItem.data(), c.fieldPath, docItem.id);
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 should not match inequalities by default
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 Error(`Unsupported operator: ${c.opStr}`);
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
- if (ob.fieldPath !== '__name__') {
97
- const val = (0, helpers_1.getNestedValue)(docItem.data(), ob.fieldPath, docItem.id);
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 field = ob.fieldPath === '__name__' ? null : ob.fieldPath;
107
- const valA = field ? (0, helpers_1.getNestedValue)(a.data(), field, a.id) : a.id;
108
- const valB = field ? (0, helpers_1.getNestedValue)(b.data(), field, b.id) : b.id;
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
- if (ob.fieldPath === '__name__')
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, helpers_1.getNestedValue)(c.docSnapshot.data ? c.docSnapshot.data() : c.docSnapshot, ob.fieldPath, c.docSnapshot.id);
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 field = orderBys[i].fieldPath === '__name__' ? null : orderBys[i].fieldPath;
131
- const dVal = field ? (0, helpers_1.getNestedValue)(d.data(), field, d.id) : d.id;
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 = orderBys[i].directionStr === 'desc' ? -1 : 1;
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[collectionPath] || !cached.empty)) {
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
- const docs = res.data?.documents;
174
- if (!Array.isArray(docs))
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: collectionPath,
315
+ queryKey,
179
316
  collectionPath,
180
- serverDocs: docs,
317
+ serverDocs: validDocs,
181
318
  isComplete: complete,
182
319
  constraints: serializedConstraints
183
320
  });
184
- db._queryCacheMetadata[collectionPath] = true;
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 failed:', err);
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`, { collectionPath, constraints: serializedConstraints });
195
- const docs = res.data?.documents;
196
- if (!Array.isArray(docs)) {
197
- throw Object.assign(new Error('Invalid server response: documents must be an array'), { code: 'internal' });
198
- }
199
- const seenIds = new Set();
200
- const validDocs = [];
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: collectionPath,
344
+ queryKey,
211
345
  collectionPath,
212
346
  serverDocs: validDocs,
213
347
  isComplete: complete,
214
348
  constraints: serializedConstraints
215
349
  });
216
- db._queryCacheMetadata[collectionPath] = true;
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 Object.assign(new Error('Invalid aggregate server response'), { code: 'internal' });
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 reviveNexaData(data: any): any;
3
- export declare function cloneNexaData(data: any): any;
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;