nexabase-console 2.0.7 → 2.0.9
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 +41 -2
- package/dist/app/NexaApp.js +229 -5
- package/dist/firestore/Query.d.ts +1 -1
- package/dist/firestore/Query.js +176 -125
- package/dist/firestore/QueryUtils.d.ts +5 -0
- package/dist/firestore/QueryUtils.js +111 -0
- package/dist/firestore/writes.d.ts +17 -4
- package/dist/firestore/writes.js +176 -122
- package/dist/persistence/LocalStore.d.ts +4 -0
- package/dist/persistence/LocalStore.js +16 -3
- package/dist/persistence/MutationQueue.d.ts +3 -2
- package/dist/persistence/MutationQueue.js +32 -7
- package/dist/types/index.d.ts +8 -1
- package/package.json +1 -1
package/dist/firestore/Query.js
CHANGED
|
@@ -5,6 +5,7 @@ const SnapshotManager_1 = require("./SnapshotManager");
|
|
|
5
5
|
const DocumentReference_1 = require("./DocumentReference");
|
|
6
6
|
const NexaError_1 = require("../errors/NexaError");
|
|
7
7
|
const helpers_1 = require("../utils/helpers");
|
|
8
|
+
const QueryUtils_1 = require("./QueryUtils");
|
|
8
9
|
const query = (queryObject, ...queryConstraints) => {
|
|
9
10
|
const q = {
|
|
10
11
|
type: 'query',
|
|
@@ -13,85 +14,137 @@ const query = (queryObject, ...queryConstraints) => {
|
|
|
13
14
|
constraints: queryObject.type === 'query' ? [...(queryObject.constraints || [])] : []
|
|
14
15
|
};
|
|
15
16
|
q.constraints = [...(q.constraints || []), ...queryConstraints];
|
|
17
|
+
// Basic client-side validation
|
|
18
|
+
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' });
|
|
21
|
+
}
|
|
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' });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
16
26
|
return q;
|
|
17
27
|
};
|
|
18
28
|
exports.query = query;
|
|
19
|
-
const getCachedDocs = (queryOrCollection) => {
|
|
29
|
+
const getCachedDocs = (queryOrCollection, fromCache = true) => {
|
|
20
30
|
const db = queryOrCollection.db;
|
|
21
31
|
const collectionPath = queryOrCollection.path;
|
|
22
32
|
const constraints = 'constraints' in queryOrCollection ? queryOrCollection.constraints || [] : [];
|
|
23
33
|
const results = [];
|
|
24
|
-
|
|
25
|
-
|
|
34
|
+
// Local evaluation over cache
|
|
35
|
+
for (const p of Object.keys(db._firestoreCache)) {
|
|
36
|
+
if ((0, QueryUtils_1.isDirectChildDocument)(collectionPath, p)) {
|
|
26
37
|
const parts = p.split('/');
|
|
27
38
|
const docId = parts[parts.length - 1];
|
|
28
39
|
const docRef = (0, DocumentReference_1.doc)(db, `${collectionPath}/${docId}`);
|
|
29
|
-
const cachedData = db._firestoreCache[p];
|
|
40
|
+
const cachedData = (0, QueryUtils_1.cloneNexaData)(db._firestoreCache[p]);
|
|
30
41
|
results.push({
|
|
31
42
|
id: docId,
|
|
32
43
|
ref: docRef,
|
|
33
|
-
metadata: { fromCache
|
|
44
|
+
metadata: { fromCache, hasPendingWrites: db.hasPendingWrites(docRef.path) },
|
|
34
45
|
exists: () => true,
|
|
35
46
|
data: () => cachedData,
|
|
36
47
|
get: (fieldPath) => (0, helpers_1.getNestedValue)(cachedData, fieldPath, docId)
|
|
37
48
|
});
|
|
38
49
|
}
|
|
39
|
-
}
|
|
50
|
+
}
|
|
40
51
|
let filtered = results;
|
|
41
52
|
let limitCount = undefined;
|
|
53
|
+
const orderBys = [];
|
|
54
|
+
for (const c of constraints) {
|
|
55
|
+
if (c.type === 'orderBy')
|
|
56
|
+
orderBys.push(c);
|
|
57
|
+
}
|
|
58
|
+
// Add implicit tie-breaker by document ID
|
|
59
|
+
orderBys.push({ type: 'orderBy', fieldPath: '__name__', directionStr: 'asc' });
|
|
42
60
|
for (const c of constraints) {
|
|
43
61
|
if (c.type === 'where' && c.fieldPath && c.opStr) {
|
|
44
62
|
filtered = filtered.filter((docItem) => {
|
|
45
63
|
const val = (0, helpers_1.getNestedValue)(docItem.data(), c.fieldPath, docItem.id);
|
|
64
|
+
if (val === undefined) {
|
|
65
|
+
// Missing fields should not match inequalities by default
|
|
66
|
+
if (['!=', 'not-in', '<', '<=', '>', '>='].includes(c.opStr))
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
46
69
|
if (c.opStr === '==')
|
|
47
|
-
return val
|
|
70
|
+
return (0, QueryUtils_1.compareNexaValues)(val, c.value) === 0;
|
|
48
71
|
if (c.opStr === '>')
|
|
49
|
-
return val
|
|
72
|
+
return (0, QueryUtils_1.compareNexaValues)(val, c.value) > 0;
|
|
50
73
|
if (c.opStr === '<')
|
|
51
|
-
return val
|
|
74
|
+
return (0, QueryUtils_1.compareNexaValues)(val, c.value) < 0;
|
|
52
75
|
if (c.opStr === '>=')
|
|
53
|
-
return val
|
|
76
|
+
return (0, QueryUtils_1.compareNexaValues)(val, c.value) >= 0;
|
|
54
77
|
if (c.opStr === '<=')
|
|
55
|
-
return val
|
|
78
|
+
return (0, QueryUtils_1.compareNexaValues)(val, c.value) <= 0;
|
|
56
79
|
if (c.opStr === '!=')
|
|
57
|
-
return val
|
|
80
|
+
return (0, QueryUtils_1.compareNexaValues)(val, c.value) !== 0;
|
|
58
81
|
if (c.opStr === 'in')
|
|
59
|
-
return Array.isArray(c.value) && c.value.
|
|
82
|
+
return Array.isArray(c.value) && c.value.some((v) => (0, QueryUtils_1.compareNexaValues)(v, val) === 0);
|
|
60
83
|
if (c.opStr === 'not-in')
|
|
61
|
-
return Array.isArray(c.value) && !c.value.
|
|
84
|
+
return Array.isArray(c.value) && !c.value.some((v) => (0, QueryUtils_1.compareNexaValues)(v, val) === 0);
|
|
62
85
|
if (c.opStr === 'array-contains')
|
|
63
|
-
return Array.isArray(val) && val.
|
|
86
|
+
return Array.isArray(val) && val.some((v) => (0, QueryUtils_1.compareNexaValues)(v, c.value) === 0);
|
|
64
87
|
if (c.opStr === 'array-contains-any')
|
|
65
|
-
return Array.isArray(val) && Array.isArray(c.value) && c.value.some((
|
|
66
|
-
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
else if (c.type === 'orderBy' && c.fieldPath) {
|
|
70
|
-
filtered = [...filtered].sort((a, b) => {
|
|
71
|
-
const valA = (0, helpers_1.getNestedValue)(a.data(), c.fieldPath, a.id);
|
|
72
|
-
const valB = (0, helpers_1.getNestedValue)(b.data(), c.fieldPath, b.id);
|
|
73
|
-
if (valA < valB)
|
|
74
|
-
return c.directionStr === 'desc' ? 1 : -1;
|
|
75
|
-
if (valA > valB)
|
|
76
|
-
return c.directionStr === 'desc' ? -1 : 1;
|
|
77
|
-
return 0;
|
|
88
|
+
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}`);
|
|
78
90
|
});
|
|
79
91
|
}
|
|
80
92
|
}
|
|
93
|
+
// Documents without orderBy field must be excluded
|
|
94
|
+
filtered = filtered.filter((docItem) => {
|
|
95
|
+
for (const ob of orderBys) {
|
|
96
|
+
if (ob.fieldPath !== '__name__') {
|
|
97
|
+
const val = (0, helpers_1.getNestedValue)(docItem.data(), ob.fieldPath, docItem.id);
|
|
98
|
+
if (val === undefined)
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return true;
|
|
103
|
+
});
|
|
104
|
+
filtered = [...filtered].sort((a, b) => {
|
|
105
|
+
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;
|
|
109
|
+
const comp = (0, QueryUtils_1.compareNexaValues)(valA, valB);
|
|
110
|
+
if (comp !== 0) {
|
|
111
|
+
return ob.directionStr === 'desc' ? -comp : comp;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return 0;
|
|
115
|
+
});
|
|
81
116
|
for (const c of constraints) {
|
|
82
117
|
if (['startAt', 'startAfter', 'endAt', 'endBefore'].includes(c.type)) {
|
|
83
118
|
const isSnapshot = c.docSnapshot && typeof c.docSnapshot === 'object' && 'id' in c.docSnapshot;
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
119
|
+
const cursorVals = isSnapshot
|
|
120
|
+
? orderBys.map(ob => {
|
|
121
|
+
if (ob.fieldPath === '__name__')
|
|
122
|
+
return c.docSnapshot.id;
|
|
123
|
+
return typeof c.docSnapshot.get === 'function'
|
|
124
|
+
? c.docSnapshot.get(ob.fieldPath)
|
|
125
|
+
: (0, helpers_1.getNestedValue)(c.docSnapshot.data ? c.docSnapshot.data() : c.docSnapshot, ob.fieldPath, c.docSnapshot.id);
|
|
126
|
+
})
|
|
127
|
+
: (Array.isArray(c.cursorValues) ? c.cursorValues : [c.cursorValues]);
|
|
128
|
+
filtered = filtered.filter(d => {
|
|
129
|
+
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;
|
|
132
|
+
const cVal = cursorVals[i];
|
|
133
|
+
const comp = (0, QueryUtils_1.compareNexaValues)(dVal, cVal);
|
|
134
|
+
if (comp !== 0) {
|
|
135
|
+
const dir = orderBys[i].directionStr === 'desc' ? -1 : 1;
|
|
136
|
+
const adjustedComp = comp * dir;
|
|
137
|
+
if (c.type === 'startAt' || c.type === 'startAfter')
|
|
138
|
+
return adjustedComp > 0;
|
|
139
|
+
if (c.type === 'endAt' || c.type === 'endBefore')
|
|
140
|
+
return adjustedComp < 0;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// If all evaluated fields are equal
|
|
144
|
+
if (c.type === 'startAt' || c.type === 'endAt')
|
|
145
|
+
return true;
|
|
146
|
+
return false;
|
|
147
|
+
});
|
|
95
148
|
}
|
|
96
149
|
else if (c.type === 'limit' && typeof c.limitValue === 'number') {
|
|
97
150
|
limitCount = c.limitValue;
|
|
@@ -100,7 +153,7 @@ const getCachedDocs = (queryOrCollection) => {
|
|
|
100
153
|
if (limitCount !== undefined) {
|
|
101
154
|
filtered = filtered.slice(0, limitCount);
|
|
102
155
|
}
|
|
103
|
-
return SnapshotManager_1.SnapshotManager.createQuerySnapshot(filtered,
|
|
156
|
+
return SnapshotManager_1.SnapshotManager.createQuerySnapshot(filtered, fromCache, filtered.some(d => d.metadata.hasPendingWrites) || db.hasPendingWrites(collectionPath));
|
|
104
157
|
};
|
|
105
158
|
exports.getCachedDocs = getCachedDocs;
|
|
106
159
|
const getDocs = async (queryOrCollection) => {
|
|
@@ -109,68 +162,72 @@ const getDocs = async (queryOrCollection) => {
|
|
|
109
162
|
await db._initPromise;
|
|
110
163
|
const collectionPath = queryOrCollection.path;
|
|
111
164
|
const constraints = 'constraints' in queryOrCollection ? queryOrCollection.constraints || [] : [];
|
|
165
|
+
const serializedConstraints = constraints.map(QueryUtils_1.serializeConstraint);
|
|
112
166
|
if (db.cacheStrategy === 'cache-first') {
|
|
113
|
-
const cached = (0, exports.getCachedDocs)(queryOrCollection);
|
|
114
|
-
if (cached && !cached.empty) {
|
|
167
|
+
const cached = (0, exports.getCachedDocs)(queryOrCollection, true);
|
|
168
|
+
if (cached && (db._queryCacheMetadata[collectionPath] || !cached.empty)) {
|
|
169
|
+
// background refresh
|
|
115
170
|
db.client
|
|
116
|
-
.post(`/api/firestore/${db.projectId}/query`, { collectionPath, constraints })
|
|
171
|
+
.post(`/api/firestore/${db.projectId}/query`, { collectionPath, constraints: serializedConstraints })
|
|
117
172
|
.then(async (res) => {
|
|
118
|
-
const docs = res.data
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
173
|
+
const docs = res.data?.documents;
|
|
174
|
+
if (!Array.isArray(docs))
|
|
175
|
+
return;
|
|
176
|
+
const complete = res.data?.complete === true;
|
|
177
|
+
await db._commitAtomicQueryResults({
|
|
178
|
+
queryKey: collectionPath,
|
|
179
|
+
collectionPath,
|
|
180
|
+
serverDocs: docs,
|
|
181
|
+
isComplete: complete,
|
|
182
|
+
constraints: serializedConstraints
|
|
183
|
+
});
|
|
184
|
+
db._queryCacheMetadata[collectionPath] = true;
|
|
133
185
|
db._notifySnapshotCallbacks(collectionPath, 'bulk_update', null);
|
|
134
186
|
})
|
|
135
|
-
.catch(() => {
|
|
187
|
+
.catch((err) => {
|
|
188
|
+
console.error('[NexaBase] Background query refresh failed:', err);
|
|
189
|
+
});
|
|
136
190
|
return cached;
|
|
137
191
|
}
|
|
138
192
|
}
|
|
139
193
|
try {
|
|
140
|
-
const res = await db.client.post(`/api/firestore/${db.projectId}/query`, { collectionPath, constraints });
|
|
141
|
-
const docs = res.data
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
await db._setCache(`${collectionPath}/${d.id}`, d.fields);
|
|
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' });
|
|
145
198
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
}
|
|
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);
|
|
154
205
|
}
|
|
155
206
|
}
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
get: (fieldPath) => (0, helpers_1.getNestedValue)(d.fields, fieldPath, d.id)
|
|
165
|
-
};
|
|
207
|
+
const complete = res.data?.complete === true;
|
|
208
|
+
// Apply results while preserving pending mutations safely
|
|
209
|
+
await db._commitAtomicQueryResults({
|
|
210
|
+
queryKey: collectionPath,
|
|
211
|
+
collectionPath,
|
|
212
|
+
serverDocs: validDocs,
|
|
213
|
+
isComplete: complete,
|
|
214
|
+
constraints: serializedConstraints
|
|
166
215
|
});
|
|
167
|
-
|
|
216
|
+
db._queryCacheMetadata[collectionPath] = true;
|
|
217
|
+
// After persistence, read the newly reconciled local state
|
|
218
|
+
return (0, exports.getCachedDocs)(queryOrCollection, false);
|
|
168
219
|
}
|
|
169
220
|
catch (err) {
|
|
170
|
-
|
|
171
|
-
|
|
221
|
+
const status = err?.response?.status;
|
|
222
|
+
const code = err?.code || 'internal';
|
|
223
|
+
const isRetryable = status
|
|
224
|
+
? [408, 425, 429, 500, 502, 503, 504].includes(status)
|
|
225
|
+
: ['network-error', 'timeout', 'unavailable', 'aborted', 'resource-exhausted'].includes(code);
|
|
226
|
+
if (isRetryable) {
|
|
227
|
+
// Return cache if it's just a network disconnect/retryable error
|
|
228
|
+
return (0, exports.getCachedDocs)(queryOrCollection, true);
|
|
172
229
|
}
|
|
173
|
-
|
|
230
|
+
throw (0, NexaError_1.toNexaError)(err);
|
|
174
231
|
}
|
|
175
232
|
};
|
|
176
233
|
exports.getDocs = getDocs;
|
|
@@ -187,51 +244,45 @@ const average = (field) => {
|
|
|
187
244
|
};
|
|
188
245
|
exports.average = average;
|
|
189
246
|
const getAggregateFromServer = async (queryOrCollection, aggregateSpec) => {
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
247
|
+
const db = queryOrCollection.db;
|
|
248
|
+
if (db._initPromise)
|
|
249
|
+
await db._initPromise;
|
|
250
|
+
const collectionPath = queryOrCollection.path;
|
|
251
|
+
const constraints = 'constraints' in queryOrCollection ? queryOrCollection.constraints || [] : [];
|
|
252
|
+
const serializedSpec = {};
|
|
193
253
|
for (const [key, spec] of Object.entries(aggregateSpec)) {
|
|
194
|
-
if (
|
|
195
|
-
|
|
254
|
+
if (key === '__proto__' || key === 'constructor')
|
|
255
|
+
continue;
|
|
256
|
+
let fp = spec.fieldPath;
|
|
257
|
+
if (fp && typeof fp === 'object' && 'getPathString' in fp && typeof fp.getPathString === 'function') {
|
|
258
|
+
fp = fp.getPathString();
|
|
196
259
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
let sumVal = 0;
|
|
211
|
-
let countVal = 0;
|
|
212
|
-
for (const d of docs) {
|
|
213
|
-
const val = (0, helpers_1.getNestedValue)(d.data(), field, d.id);
|
|
214
|
-
if (typeof val === 'number' && !isNaN(val)) {
|
|
215
|
-
sumVal += val;
|
|
216
|
-
countVal++;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
resultData[key] = countVal > 0 ? sumVal / countVal : null;
|
|
260
|
+
serializedSpec[key] = {
|
|
261
|
+
type: spec.type,
|
|
262
|
+
fieldPath: typeof fp === 'string' ? fp : undefined
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
try {
|
|
266
|
+
const res = await db.client.post(`/api/firestore/${db.projectId}/aggregate`, {
|
|
267
|
+
collectionPath,
|
|
268
|
+
constraints: constraints.map(QueryUtils_1.serializeConstraint),
|
|
269
|
+
aggregateSpec: serializedSpec
|
|
270
|
+
});
|
|
271
|
+
if (!res.data || typeof res.data.result !== 'object' || res.data.result === null) {
|
|
272
|
+
throw Object.assign(new Error('Invalid aggregate server response'), { code: 'internal' });
|
|
220
273
|
}
|
|
274
|
+
return {
|
|
275
|
+
type: 'AggregateQuerySnapshot',
|
|
276
|
+
query: queryOrCollection,
|
|
277
|
+
data: () => (0, QueryUtils_1.cloneNexaData)(res.data.result)
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
catch (err) {
|
|
281
|
+
throw (0, NexaError_1.toNexaError)(err);
|
|
221
282
|
}
|
|
222
|
-
return {
|
|
223
|
-
type: 'AggregateQuerySnapshot',
|
|
224
|
-
query: queryOrCollection,
|
|
225
|
-
data: () => resultData
|
|
226
|
-
};
|
|
227
283
|
};
|
|
228
284
|
exports.getAggregateFromServer = getAggregateFromServer;
|
|
229
285
|
const getCountFromServer = async (queryOrCollection) => {
|
|
230
|
-
|
|
231
|
-
return {
|
|
232
|
-
type: 'AggregateQuerySnapshot',
|
|
233
|
-
query: queryOrCollection,
|
|
234
|
-
data: () => ({ count: querySnapshot.docs.length })
|
|
235
|
-
};
|
|
286
|
+
return (0, exports.getAggregateFromServer)(queryOrCollection, { count: (0, exports.count)() });
|
|
236
287
|
};
|
|
237
288
|
exports.getCountFromServer = getCountFromServer;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
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;
|
|
4
|
+
export declare function compareNexaValues(a: any, b: any): number;
|
|
5
|
+
export declare function serializeConstraint(c: any): any;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isDirectChildDocument = isDirectChildDocument;
|
|
4
|
+
exports.reviveNexaData = reviveNexaData;
|
|
5
|
+
exports.cloneNexaData = cloneNexaData;
|
|
6
|
+
exports.compareNexaValues = compareNexaValues;
|
|
7
|
+
exports.serializeConstraint = serializeConstraint;
|
|
8
|
+
const Timestamp_1 = require("./Timestamp");
|
|
9
|
+
function isDirectChildDocument(collectionPath, documentPath) {
|
|
10
|
+
if (!documentPath.startsWith(`${collectionPath}/`))
|
|
11
|
+
return false;
|
|
12
|
+
return documentPath.split('/').length === collectionPath.split('/').length + 1;
|
|
13
|
+
}
|
|
14
|
+
function reviveNexaData(data) {
|
|
15
|
+
if (data === null || data === undefined)
|
|
16
|
+
return data;
|
|
17
|
+
if (data instanceof Timestamp_1.Timestamp)
|
|
18
|
+
return new Timestamp_1.Timestamp(data.seconds, data.nanoseconds);
|
|
19
|
+
if (Array.isArray(data))
|
|
20
|
+
return data.map(reviveNexaData);
|
|
21
|
+
if (typeof data === 'object') {
|
|
22
|
+
if (typeof data.isEqual === 'function' && typeof data.toMillis === 'function') {
|
|
23
|
+
return data;
|
|
24
|
+
}
|
|
25
|
+
// Revive timestamp
|
|
26
|
+
if (typeof data.seconds === 'number' && typeof data.nanoseconds === 'number' && Object.keys(data).length <= 2) {
|
|
27
|
+
return new Timestamp_1.Timestamp(data.seconds, data.nanoseconds);
|
|
28
|
+
}
|
|
29
|
+
const cloned = {};
|
|
30
|
+
for (const k in data) {
|
|
31
|
+
if (Object.prototype.hasOwnProperty.call(data, k)) {
|
|
32
|
+
cloned[k] = reviveNexaData(data[k]);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return cloned;
|
|
36
|
+
}
|
|
37
|
+
return data;
|
|
38
|
+
}
|
|
39
|
+
function cloneNexaData(data) {
|
|
40
|
+
if (data === null || data === undefined)
|
|
41
|
+
return data;
|
|
42
|
+
if (data instanceof Timestamp_1.Timestamp)
|
|
43
|
+
return new Timestamp_1.Timestamp(data.seconds, data.nanoseconds);
|
|
44
|
+
if (Array.isArray(data))
|
|
45
|
+
return data.map(cloneNexaData);
|
|
46
|
+
if (typeof data === 'object') {
|
|
47
|
+
if (typeof data.isEqual === 'function' && typeof data.toMillis === 'function') {
|
|
48
|
+
return data; // Treat as immutable if it's our own object like Timestamp/GeoPoint
|
|
49
|
+
}
|
|
50
|
+
const cloned = {};
|
|
51
|
+
for (const k in data) {
|
|
52
|
+
if (Object.prototype.hasOwnProperty.call(data, k)) {
|
|
53
|
+
cloned[k] = cloneNexaData(data[k]);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return cloned;
|
|
57
|
+
}
|
|
58
|
+
return data;
|
|
59
|
+
}
|
|
60
|
+
function compareNexaValues(a, b) {
|
|
61
|
+
if (a === b)
|
|
62
|
+
return 0;
|
|
63
|
+
if (a === null && b !== null)
|
|
64
|
+
return -1;
|
|
65
|
+
if (a !== null && b === null)
|
|
66
|
+
return 1;
|
|
67
|
+
if (a === undefined && b !== undefined)
|
|
68
|
+
return -1;
|
|
69
|
+
if (a !== undefined && b === undefined)
|
|
70
|
+
return 1;
|
|
71
|
+
if (typeof a === 'number' && typeof b === 'number') {
|
|
72
|
+
if (Number.isNaN(a) && Number.isNaN(b))
|
|
73
|
+
return 0;
|
|
74
|
+
if (Number.isNaN(a))
|
|
75
|
+
return -1;
|
|
76
|
+
if (Number.isNaN(b))
|
|
77
|
+
return 1;
|
|
78
|
+
return a < b ? -1 : 1;
|
|
79
|
+
}
|
|
80
|
+
if (a?.toMillis && b?.toMillis) {
|
|
81
|
+
const ma = a.toMillis();
|
|
82
|
+
const mb = b.toMillis();
|
|
83
|
+
return ma < mb ? -1 : (ma > mb ? 1 : 0);
|
|
84
|
+
}
|
|
85
|
+
if (typeof a === 'string' && typeof b === 'string')
|
|
86
|
+
return a < b ? -1 : 1;
|
|
87
|
+
if (typeof a === 'boolean' && typeof b === 'boolean')
|
|
88
|
+
return a === b ? 0 : (a ? 1 : -1);
|
|
89
|
+
return 0; // Fallback
|
|
90
|
+
}
|
|
91
|
+
function serializeConstraint(c) {
|
|
92
|
+
const res = { type: c.type };
|
|
93
|
+
if (c.fieldPath) {
|
|
94
|
+
res.fieldPath = typeof c.fieldPath === 'string' ? c.fieldPath : c.fieldPath.getPathString();
|
|
95
|
+
}
|
|
96
|
+
if (c.opStr)
|
|
97
|
+
res.opStr = c.opStr;
|
|
98
|
+
if (c.value !== undefined)
|
|
99
|
+
res.value = cloneNexaData(c.value);
|
|
100
|
+
if (c.directionStr)
|
|
101
|
+
res.directionStr = c.directionStr;
|
|
102
|
+
if (c.limitValue !== undefined)
|
|
103
|
+
res.limitValue = c.limitValue;
|
|
104
|
+
if (c.docSnapshot) {
|
|
105
|
+
res.docSnapshotId = c.docSnapshot.id;
|
|
106
|
+
}
|
|
107
|
+
if (c.cursorValues) {
|
|
108
|
+
res.cursorValues = cloneNexaData(c.cursorValues);
|
|
109
|
+
}
|
|
110
|
+
return res;
|
|
111
|
+
}
|
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
import { DocumentReference } from '../types/index';
|
|
2
2
|
export { FieldValue } from './FieldValue';
|
|
3
|
+
export type WriteResult<T = unknown> = {
|
|
4
|
+
status: 'committed';
|
|
5
|
+
committed: true;
|
|
6
|
+
writtenLocally: true;
|
|
7
|
+
mutationId: string;
|
|
8
|
+
data?: T;
|
|
9
|
+
} | {
|
|
10
|
+
status: 'pending';
|
|
11
|
+
committed: false;
|
|
12
|
+
writtenLocally: true;
|
|
13
|
+
mutationId: string;
|
|
14
|
+
};
|
|
3
15
|
export declare const setDoc: (docRef: DocumentReference, data: any, options?: {
|
|
4
16
|
merge?: boolean;
|
|
5
17
|
idempotencyKey?: string;
|
|
6
|
-
}) => Promise<
|
|
18
|
+
}) => Promise<WriteResult>;
|
|
7
19
|
export declare const patchDoc: (docRef: DocumentReference, data: any, options?: {
|
|
8
20
|
idempotencyKey?: string;
|
|
9
|
-
|
|
21
|
+
preconditionExists?: boolean;
|
|
22
|
+
}) => Promise<WriteResult>;
|
|
10
23
|
export declare const updateDoc: (docRef: DocumentReference, data: any, options?: {
|
|
11
24
|
idempotencyKey?: string;
|
|
12
|
-
}) => Promise<
|
|
25
|
+
}) => Promise<WriteResult>;
|
|
13
26
|
export declare const deleteDoc: (docRef: DocumentReference, options?: {
|
|
14
27
|
idempotencyKey?: string;
|
|
15
|
-
}) => Promise<
|
|
28
|
+
}) => Promise<WriteResult>;
|