@ponceca/firestore-sdk 0.1.0
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/LICENSE +21 -0
- package/README.md +692 -0
- package/dist/app.d.mts +51 -0
- package/dist/app.d.ts +51 -0
- package/dist/app.js +16 -0
- package/dist/app.js.map +1 -0
- package/dist/app.mjs +16 -0
- package/dist/app.mjs.map +1 -0
- package/dist/auth/index.d.mts +43 -0
- package/dist/auth/index.d.ts +43 -0
- package/dist/auth/index.js +18 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/index.mjs +18 -0
- package/dist/auth/index.mjs.map +1 -0
- package/dist/chunk-2RQUHE2K.js +719 -0
- package/dist/chunk-2RQUHE2K.js.map +1 -0
- package/dist/chunk-4CV4JOE5.js +27 -0
- package/dist/chunk-4CV4JOE5.js.map +1 -0
- package/dist/chunk-57XXMSJA.js +65 -0
- package/dist/chunk-57XXMSJA.js.map +1 -0
- package/dist/chunk-6J3LNKUQ.js +213 -0
- package/dist/chunk-6J3LNKUQ.js.map +1 -0
- package/dist/chunk-BXV7KTHB.js +645 -0
- package/dist/chunk-BXV7KTHB.js.map +1 -0
- package/dist/chunk-C3PCJJX4.mjs +645 -0
- package/dist/chunk-C3PCJJX4.mjs.map +1 -0
- package/dist/chunk-C6SKWUQV.mjs +213 -0
- package/dist/chunk-C6SKWUQV.mjs.map +1 -0
- package/dist/chunk-DXPQJR5D.mjs +2469 -0
- package/dist/chunk-DXPQJR5D.mjs.map +1 -0
- package/dist/chunk-MRVKMKSO.mjs +65 -0
- package/dist/chunk-MRVKMKSO.mjs.map +1 -0
- package/dist/chunk-NFEGQTCC.mjs +27 -0
- package/dist/chunk-NFEGQTCC.mjs.map +1 -0
- package/dist/chunk-RSBBZLDE.js +128 -0
- package/dist/chunk-RSBBZLDE.js.map +1 -0
- package/dist/chunk-RZWTSZSJ.js +2469 -0
- package/dist/chunk-RZWTSZSJ.js.map +1 -0
- package/dist/chunk-SZKHE2TQ.mjs +719 -0
- package/dist/chunk-SZKHE2TQ.mjs.map +1 -0
- package/dist/chunk-ZJ4A4Y2T.mjs +128 -0
- package/dist/chunk-ZJ4A4Y2T.mjs.map +1 -0
- package/dist/firestore/index.d.mts +1476 -0
- package/dist/firestore/index.d.ts +1476 -0
- package/dist/firestore/index.js +156 -0
- package/dist/firestore/index.js.map +1 -0
- package/dist/firestore/index.mjs +156 -0
- package/dist/firestore/index.mjs.map +1 -0
- package/dist/http-A2S5CWEV.js +10 -0
- package/dist/http-A2S5CWEV.js.map +1 -0
- package/dist/http-SZFONH6Z.mjs +10 -0
- package/dist/http-SZFONH6Z.mjs.map +1 -0
- package/dist/index.d.mts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +171 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +171 -0
- package/dist/index.mjs.map +1 -0
- package/dist/indexeddb-mutation-queue-5EB7C2D5.js +192 -0
- package/dist/indexeddb-mutation-queue-5EB7C2D5.js.map +1 -0
- package/dist/indexeddb-mutation-queue-M2MAH4E4.mjs +192 -0
- package/dist/indexeddb-mutation-queue-M2MAH4E4.mjs.map +1 -0
- package/dist/indexeddb-store-D23ZY3PR.mjs +162 -0
- package/dist/indexeddb-store-D23ZY3PR.mjs.map +1 -0
- package/dist/indexeddb-store-DNWBZUQE.js +162 -0
- package/dist/indexeddb-store-DNWBZUQE.js.map +1 -0
- package/dist/snapshot-MCQVLVHL.js +22 -0
- package/dist/snapshot-MCQVLVHL.js.map +1 -0
- package/dist/snapshot-ZWZFIFZD.mjs +22 -0
- package/dist/snapshot-ZWZFIFZD.mjs.map +1 -0
- package/dist/types-meoR-Ecp.d.mts +269 -0
- package/dist/types-meoR-Ecp.d.ts +269 -0
- package/package.json +78 -0
|
@@ -0,0 +1,2469 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getPersistenceManager
|
|
3
|
+
} from "./chunk-SZKHE2TQ.mjs";
|
|
4
|
+
import {
|
|
5
|
+
getHttpClient
|
|
6
|
+
} from "./chunk-C6SKWUQV.mjs";
|
|
7
|
+
import {
|
|
8
|
+
DocumentSnapshotImpl,
|
|
9
|
+
QuerySnapshotImpl,
|
|
10
|
+
createDocumentSnapshot,
|
|
11
|
+
createQuerySnapshot,
|
|
12
|
+
toFirestoreFields,
|
|
13
|
+
toFirestoreValue
|
|
14
|
+
} from "./chunk-C3PCJJX4.mjs";
|
|
15
|
+
|
|
16
|
+
// src/firestore/reference.ts
|
|
17
|
+
var DocumentReferenceImpl = class {
|
|
18
|
+
constructor(firestore, path, parent) {
|
|
19
|
+
this.type = "document";
|
|
20
|
+
this.firestore = firestore;
|
|
21
|
+
this.path = path;
|
|
22
|
+
this.id = path.split("/").pop() || "";
|
|
23
|
+
this.parent = parent;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
var CollectionReferenceImpl = class {
|
|
27
|
+
constructor(firestore, path, parent = null) {
|
|
28
|
+
this.type = "collection";
|
|
29
|
+
this.firestore = firestore;
|
|
30
|
+
this.path = path;
|
|
31
|
+
this.id = path.split("/").pop() || "";
|
|
32
|
+
this.parent = parent;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
function normalizePath(path) {
|
|
36
|
+
return path.replace(/\/+/g, "/").replace(/^\//, "").replace(/\/$/, "");
|
|
37
|
+
}
|
|
38
|
+
function validatePath(path, expectedType) {
|
|
39
|
+
const segments = path.split("/").filter((s) => s.length > 0);
|
|
40
|
+
if (segments.length === 0) {
|
|
41
|
+
throw new Error(`Invalid ${expectedType} path: path cannot be empty`);
|
|
42
|
+
}
|
|
43
|
+
for (const segment of segments) {
|
|
44
|
+
if (segment.startsWith(".")) {
|
|
45
|
+
throw new Error(`Invalid path segment: "${segment}" - segments cannot start with a dot`);
|
|
46
|
+
}
|
|
47
|
+
if (segment.includes("..")) {
|
|
48
|
+
throw new Error(`Invalid path segment: "${segment}" - segments cannot contain double dots`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const isEven = segments.length % 2 === 0;
|
|
52
|
+
if (expectedType === "document" && !isEven) {
|
|
53
|
+
throw new Error(`Invalid document path: "${path}" - document paths must have an even number of segments`);
|
|
54
|
+
}
|
|
55
|
+
if (expectedType === "collection" && isEven) {
|
|
56
|
+
throw new Error(`Invalid collection path: "${path}" - collection paths must have an odd number of segments`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function doc(firestoreOrRef, ...pathSegments) {
|
|
60
|
+
let firestore;
|
|
61
|
+
let fullPath;
|
|
62
|
+
if ("type" in firestoreOrRef && firestoreOrRef.type === "collection") {
|
|
63
|
+
const collRef = firestoreOrRef;
|
|
64
|
+
firestore = collRef.firestore;
|
|
65
|
+
if (pathSegments.length === 0) {
|
|
66
|
+
fullPath = `${collRef.path}/${generateId()}`;
|
|
67
|
+
} else {
|
|
68
|
+
fullPath = `${collRef.path}/${pathSegments.join("/")}`;
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
firestore = firestoreOrRef;
|
|
72
|
+
fullPath = pathSegments.join("/");
|
|
73
|
+
}
|
|
74
|
+
fullPath = normalizePath(fullPath);
|
|
75
|
+
validatePath(fullPath, "document");
|
|
76
|
+
const segments = fullPath.split("/");
|
|
77
|
+
const parentPath = segments.slice(0, -1).join("/");
|
|
78
|
+
const parentRef = new CollectionReferenceImpl(firestore, parentPath);
|
|
79
|
+
return new DocumentReferenceImpl(firestore, fullPath, parentRef);
|
|
80
|
+
}
|
|
81
|
+
function collection(firestoreOrRef, ...pathSegments) {
|
|
82
|
+
let firestore;
|
|
83
|
+
let fullPath;
|
|
84
|
+
let parentDoc = null;
|
|
85
|
+
if ("type" in firestoreOrRef && firestoreOrRef.type === "document") {
|
|
86
|
+
const docRef = firestoreOrRef;
|
|
87
|
+
firestore = docRef.firestore;
|
|
88
|
+
fullPath = `${docRef.path}/${pathSegments.join("/")}`;
|
|
89
|
+
parentDoc = docRef;
|
|
90
|
+
} else {
|
|
91
|
+
firestore = firestoreOrRef;
|
|
92
|
+
fullPath = pathSegments.join("/");
|
|
93
|
+
}
|
|
94
|
+
fullPath = normalizePath(fullPath);
|
|
95
|
+
validatePath(fullPath, "collection");
|
|
96
|
+
if (!parentDoc) {
|
|
97
|
+
const segments = fullPath.split("/").filter(Boolean);
|
|
98
|
+
if (segments.length >= 3) {
|
|
99
|
+
const parentPath = segments.slice(0, -1).join("/");
|
|
100
|
+
const parentCollectionPath = segments.slice(0, -2).join("/");
|
|
101
|
+
const parentCollection = new CollectionReferenceImpl(
|
|
102
|
+
firestore,
|
|
103
|
+
parentCollectionPath
|
|
104
|
+
);
|
|
105
|
+
parentDoc = new DocumentReferenceImpl(
|
|
106
|
+
firestore,
|
|
107
|
+
parentPath,
|
|
108
|
+
parentCollection
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return new CollectionReferenceImpl(firestore, fullPath, parentDoc);
|
|
113
|
+
}
|
|
114
|
+
function generateId() {
|
|
115
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
116
|
+
let result = "";
|
|
117
|
+
for (let i = 0; i < 20; i++) {
|
|
118
|
+
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
119
|
+
}
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// src/firestore/local-write-tracker.ts
|
|
124
|
+
var documentListeners = /* @__PURE__ */ new Map();
|
|
125
|
+
function registerLocalWriteListener(path, listener) {
|
|
126
|
+
let listeners = documentListeners.get(path);
|
|
127
|
+
if (!listeners) {
|
|
128
|
+
listeners = /* @__PURE__ */ new Set();
|
|
129
|
+
documentListeners.set(path, listeners);
|
|
130
|
+
}
|
|
131
|
+
listeners.add(listener);
|
|
132
|
+
return () => {
|
|
133
|
+
const current = documentListeners.get(path);
|
|
134
|
+
if (!current) return;
|
|
135
|
+
current.delete(listener);
|
|
136
|
+
if (current.size === 0) {
|
|
137
|
+
documentListeners.delete(path);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function notifyLocalWrite(path) {
|
|
142
|
+
const listeners = documentListeners.get(path);
|
|
143
|
+
if (!listeners) return;
|
|
144
|
+
listeners.forEach((listener) => listener());
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/firestore/utilities.ts
|
|
148
|
+
function withConverter(reference, converter) {
|
|
149
|
+
const refWithConverter = Object.create(Object.getPrototypeOf(reference));
|
|
150
|
+
Object.assign(refWithConverter, reference);
|
|
151
|
+
refWithConverter.converter = converter;
|
|
152
|
+
return refWithConverter;
|
|
153
|
+
}
|
|
154
|
+
function refEqual(left, right) {
|
|
155
|
+
if (left === right) return true;
|
|
156
|
+
return left.firestore === right.firestore && left.path === right.path && left.type === right.type;
|
|
157
|
+
}
|
|
158
|
+
function queryEqual(left, right) {
|
|
159
|
+
if (left === right) return true;
|
|
160
|
+
if (left.firestore !== right.firestore) return false;
|
|
161
|
+
if (left.path !== right.path) return false;
|
|
162
|
+
if (left.type !== right.type) return false;
|
|
163
|
+
const leftConstraints = left.constraints || [];
|
|
164
|
+
const rightConstraints = right.constraints || [];
|
|
165
|
+
if (leftConstraints.length !== rightConstraints.length) return false;
|
|
166
|
+
return JSON.stringify(leftConstraints) === JSON.stringify(rightConstraints);
|
|
167
|
+
}
|
|
168
|
+
function snapshotEqual(left, right) {
|
|
169
|
+
if (left === right) return true;
|
|
170
|
+
const leftIsDoc = "ref" in left;
|
|
171
|
+
const rightIsDoc = "ref" in right;
|
|
172
|
+
if (leftIsDoc !== rightIsDoc) return false;
|
|
173
|
+
if (leftIsDoc && rightIsDoc) {
|
|
174
|
+
const l = left;
|
|
175
|
+
const r = right;
|
|
176
|
+
if (!refEqual(l.ref, r.ref)) return false;
|
|
177
|
+
if (l.exists() !== r.exists()) return false;
|
|
178
|
+
return JSON.stringify(l.data()) === JSON.stringify(r.data());
|
|
179
|
+
} else {
|
|
180
|
+
const l = left;
|
|
181
|
+
const r = right;
|
|
182
|
+
if (l.size !== r.size) return false;
|
|
183
|
+
for (let i = 0; i < l.docs.length; i++) {
|
|
184
|
+
if (!snapshotEqual(l.docs[i], r.docs[i])) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function onSnapshotsInSync(firestore, onSync) {
|
|
192
|
+
let timeoutId = null;
|
|
193
|
+
let isActive = true;
|
|
194
|
+
const scheduleSync = () => {
|
|
195
|
+
if (!isActive) return;
|
|
196
|
+
if (timeoutId) {
|
|
197
|
+
clearTimeout(timeoutId);
|
|
198
|
+
}
|
|
199
|
+
timeoutId = setTimeout(() => {
|
|
200
|
+
if (isActive) {
|
|
201
|
+
onSync();
|
|
202
|
+
}
|
|
203
|
+
}, 50);
|
|
204
|
+
};
|
|
205
|
+
const firestoreInternal = firestore;
|
|
206
|
+
if (!firestoreInternal._syncCallbacks) {
|
|
207
|
+
firestoreInternal._syncCallbacks = /* @__PURE__ */ new Set();
|
|
208
|
+
}
|
|
209
|
+
firestoreInternal._syncCallbacks.add(scheduleSync);
|
|
210
|
+
scheduleSync();
|
|
211
|
+
return () => {
|
|
212
|
+
isActive = false;
|
|
213
|
+
if (timeoutId) {
|
|
214
|
+
clearTimeout(timeoutId);
|
|
215
|
+
}
|
|
216
|
+
firestoreInternal._syncCallbacks?.delete(scheduleSync);
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function asQueryDocumentSnapshot(snapshot) {
|
|
220
|
+
if (!snapshot.exists()) {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
...snapshot,
|
|
225
|
+
exists: () => true,
|
|
226
|
+
data: () => snapshot.data()
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function applyConverterToDocumentSnapshot(snapshot, converter) {
|
|
230
|
+
if (!converter || !snapshot.exists()) {
|
|
231
|
+
return snapshot;
|
|
232
|
+
}
|
|
233
|
+
const querySnap = asQueryDocumentSnapshot(snapshot);
|
|
234
|
+
if (!querySnap) {
|
|
235
|
+
return snapshot;
|
|
236
|
+
}
|
|
237
|
+
const converted = converter.fromFirestore(querySnap);
|
|
238
|
+
return Object.assign(Object.create(Object.getPrototypeOf(snapshot)), snapshot, {
|
|
239
|
+
data: () => converted
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
function applyConverterToQuerySnapshot(snapshot, converter) {
|
|
243
|
+
if (!converter) {
|
|
244
|
+
return snapshot;
|
|
245
|
+
}
|
|
246
|
+
const convertedDocs = snapshot.docs.map(
|
|
247
|
+
(doc2) => applyConverterToDocumentSnapshot(doc2, converter)
|
|
248
|
+
);
|
|
249
|
+
const convertedChanges = snapshot.docChanges().map((change) => ({
|
|
250
|
+
...change,
|
|
251
|
+
doc: applyConverterToDocumentSnapshot(
|
|
252
|
+
change.doc,
|
|
253
|
+
converter
|
|
254
|
+
)
|
|
255
|
+
}));
|
|
256
|
+
return Object.assign(Object.create(Object.getPrototypeOf(snapshot)), snapshot, {
|
|
257
|
+
docs: convertedDocs,
|
|
258
|
+
docChanges: () => convertedChanges
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// src/firestore/crud.ts
|
|
263
|
+
async function getDoc(reference) {
|
|
264
|
+
if (!reference || !reference.path) {
|
|
265
|
+
throw new Error("getDoc requires a valid DocumentReference");
|
|
266
|
+
}
|
|
267
|
+
const pm = getPersistenceManager(reference.firestore);
|
|
268
|
+
if (!pm) {
|
|
269
|
+
return getDocDirect(reference);
|
|
270
|
+
}
|
|
271
|
+
if (pm.isOnline) {
|
|
272
|
+
try {
|
|
273
|
+
const snapshot = await getDocDirect(reference);
|
|
274
|
+
await pm.cacheDocument(reference.path, snapshot.data() ?? null, snapshot.exists(), snapshot._updateTime);
|
|
275
|
+
const hasPending = await pm.hasPendingWrites(reference.path);
|
|
276
|
+
if (hasPending) {
|
|
277
|
+
return createSnapshotWithMetadata(reference, snapshot, { hasPendingWrites: true });
|
|
278
|
+
}
|
|
279
|
+
return snapshot;
|
|
280
|
+
} catch (error) {
|
|
281
|
+
const code = error?.code;
|
|
282
|
+
if (code === "unavailable" || code === "deadline-exceeded") {
|
|
283
|
+
return getDocFromLocalCache(reference, pm);
|
|
284
|
+
}
|
|
285
|
+
throw error;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return getDocFromLocalCache(reference, pm);
|
|
289
|
+
}
|
|
290
|
+
async function getDocDirect(reference) {
|
|
291
|
+
const client = getHttpClient(reference.firestore);
|
|
292
|
+
try {
|
|
293
|
+
const doc2 = await client.get(
|
|
294
|
+
`/documents/${reference.path}`
|
|
295
|
+
);
|
|
296
|
+
const snapshot = createDocumentSnapshot(reference, doc2);
|
|
297
|
+
return applyConverterToDocumentSnapshot(snapshot, getConverter(reference));
|
|
298
|
+
} catch (error) {
|
|
299
|
+
if (error.code === "not-found") {
|
|
300
|
+
const snapshot = createDocumentSnapshot(reference, null);
|
|
301
|
+
return applyConverterToDocumentSnapshot(snapshot, getConverter(reference));
|
|
302
|
+
}
|
|
303
|
+
throw error;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
async function getDocFromLocalCache(reference, pm) {
|
|
307
|
+
const cached = await pm.getCachedDocument(reference.path);
|
|
308
|
+
if (cached) {
|
|
309
|
+
return new DocumentSnapshotImpl(
|
|
310
|
+
reference,
|
|
311
|
+
cached.exists ? cached.data : void 0,
|
|
312
|
+
cached.exists,
|
|
313
|
+
{ fromCache: true, hasPendingWrites: await pm.hasPendingWrites(reference.path) },
|
|
314
|
+
cached.updateTime
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
return new DocumentSnapshotImpl(
|
|
318
|
+
reference,
|
|
319
|
+
void 0,
|
|
320
|
+
false,
|
|
321
|
+
{ fromCache: true, hasPendingWrites: false }
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
async function getDocFromCache(reference) {
|
|
325
|
+
if (!reference || !reference.path) {
|
|
326
|
+
throw new Error("getDocFromCache requires a valid DocumentReference");
|
|
327
|
+
}
|
|
328
|
+
const pm = getPersistenceManager(reference.firestore);
|
|
329
|
+
if (!pm) {
|
|
330
|
+
throw createFirestoreError(
|
|
331
|
+
"failed-precondition",
|
|
332
|
+
"Failed to get document from cache. Persistence is not enabled."
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
const cached = await pm.getCachedDocument(reference.path);
|
|
336
|
+
if (!cached) {
|
|
337
|
+
throw createFirestoreError(
|
|
338
|
+
"unavailable",
|
|
339
|
+
"Failed to get document from cache. Document is not cached."
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
return await getDocFromLocalCache(reference, pm);
|
|
343
|
+
}
|
|
344
|
+
function createSnapshotWithMetadata(reference, source, metadata) {
|
|
345
|
+
return new DocumentSnapshotImpl(
|
|
346
|
+
reference,
|
|
347
|
+
source.data(),
|
|
348
|
+
source.exists(),
|
|
349
|
+
{
|
|
350
|
+
fromCache: metadata.fromCache ?? source.metadata.fromCache,
|
|
351
|
+
hasPendingWrites: metadata.hasPendingWrites ?? source.metadata.hasPendingWrites
|
|
352
|
+
},
|
|
353
|
+
source._updateTime
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
async function setDoc(reference, data, options) {
|
|
357
|
+
if (!reference || !reference.path) {
|
|
358
|
+
throw new Error("setDoc requires a valid DocumentReference");
|
|
359
|
+
}
|
|
360
|
+
if (!data || typeof data !== "object") {
|
|
361
|
+
throw new Error("setDoc requires a valid data object");
|
|
362
|
+
}
|
|
363
|
+
const dataToWrite = applyConverterToData(reference, data);
|
|
364
|
+
assertValidTypes(dataToWrite);
|
|
365
|
+
const client = getHttpClient(reference.firestore);
|
|
366
|
+
assertDocumentDepth(dataToWrite);
|
|
367
|
+
const fields = toFirestoreFields(dataToWrite);
|
|
368
|
+
assertDocumentSize(fields);
|
|
369
|
+
notifyLocalWrite(reference.path);
|
|
370
|
+
const pm = getPersistenceManager(reference.firestore);
|
|
371
|
+
if (pm) {
|
|
372
|
+
await pm.addLocalWrite("set", reference.path, dataToWrite, options);
|
|
373
|
+
}
|
|
374
|
+
const body = {
|
|
375
|
+
fields
|
|
376
|
+
};
|
|
377
|
+
const sendToServer = async () => {
|
|
378
|
+
if (options?.merge || options?.mergeFields) {
|
|
379
|
+
const fieldPaths = options.mergeFields || Object.keys(dataToWrite);
|
|
380
|
+
body.updateMask = { fieldPaths };
|
|
381
|
+
await client.patch(`/documents/${reference.path}`, body);
|
|
382
|
+
} else {
|
|
383
|
+
const parentPath = reference.path.split("/").slice(0, -1).join("/");
|
|
384
|
+
const documentId2 = reference.id;
|
|
385
|
+
await client.post(`/documents/${parentPath}?documentId=${documentId2}`, { fields });
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
if (pm && !pm.isOnline) {
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
try {
|
|
392
|
+
await sendToServer();
|
|
393
|
+
if (pm) {
|
|
394
|
+
const mutations = await pm.mutations.getForPath(reference.path);
|
|
395
|
+
if (mutations.length > 0) {
|
|
396
|
+
await pm.completeMutation(mutations[mutations.length - 1].id);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
} catch (error) {
|
|
400
|
+
if (pm) {
|
|
401
|
+
const code = error?.code;
|
|
402
|
+
if (code === "unavailable" || code === "deadline-exceeded") {
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
throw error;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
async function getDocFromServer(reference) {
|
|
410
|
+
const snapshot = await getDocDirect(reference);
|
|
411
|
+
const pm = getPersistenceManager(reference.firestore);
|
|
412
|
+
if (pm) {
|
|
413
|
+
await pm.cacheDocument(reference.path, snapshot.data() ?? null, snapshot.exists(), snapshot._updateTime);
|
|
414
|
+
}
|
|
415
|
+
return snapshot;
|
|
416
|
+
}
|
|
417
|
+
async function updateDoc(reference, dataOrField, ...moreFieldsAndValues) {
|
|
418
|
+
if (!reference || !reference.path) {
|
|
419
|
+
throw new Error("updateDoc requires a valid DocumentReference");
|
|
420
|
+
}
|
|
421
|
+
let data;
|
|
422
|
+
if (typeof dataOrField === "string") {
|
|
423
|
+
if (moreFieldsAndValues.length === 0 || moreFieldsAndValues.length % 2 === 0) {
|
|
424
|
+
throw createFirestoreError(
|
|
425
|
+
"invalid-argument",
|
|
426
|
+
"updateDoc with field/value pairs requires an even number of field/value arguments"
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
const obj = { [dataOrField]: moreFieldsAndValues[0] };
|
|
430
|
+
for (let i = 1; i < moreFieldsAndValues.length; i += 2) {
|
|
431
|
+
const field = moreFieldsAndValues[i];
|
|
432
|
+
if (typeof field !== "string") {
|
|
433
|
+
throw createFirestoreError(
|
|
434
|
+
"invalid-argument",
|
|
435
|
+
"Field paths must be strings in updateDoc"
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
obj[field] = moreFieldsAndValues[i + 1];
|
|
439
|
+
}
|
|
440
|
+
data = obj;
|
|
441
|
+
} else {
|
|
442
|
+
data = dataOrField;
|
|
443
|
+
}
|
|
444
|
+
if (!data || typeof data !== "object") {
|
|
445
|
+
throw new Error("updateDoc requires a valid data object");
|
|
446
|
+
}
|
|
447
|
+
const dataToWrite = applyConverterToData(reference, data);
|
|
448
|
+
assertValidTypes(dataToWrite);
|
|
449
|
+
const client = getHttpClient(reference.firestore);
|
|
450
|
+
assertDocumentDepth(dataToWrite);
|
|
451
|
+
const fields = toFirestoreFields(dataToWrite);
|
|
452
|
+
assertDocumentSize(fields);
|
|
453
|
+
notifyLocalWrite(reference.path);
|
|
454
|
+
const pm = getPersistenceManager(reference.firestore);
|
|
455
|
+
if (pm) {
|
|
456
|
+
await pm.addLocalWrite("update", reference.path, dataToWrite);
|
|
457
|
+
}
|
|
458
|
+
const fieldPaths = extractFieldPaths(dataToWrite);
|
|
459
|
+
const sendToServer = async () => {
|
|
460
|
+
await client.patch(`/documents/${reference.path}`, {
|
|
461
|
+
fields,
|
|
462
|
+
updateMask: { fieldPaths }
|
|
463
|
+
});
|
|
464
|
+
};
|
|
465
|
+
if (pm && !pm.isOnline) {
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
try {
|
|
469
|
+
await sendToServer();
|
|
470
|
+
if (pm) {
|
|
471
|
+
const mutations = await pm.mutations.getForPath(reference.path);
|
|
472
|
+
if (mutations.length > 0) {
|
|
473
|
+
await pm.completeMutation(mutations[mutations.length - 1].id);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
} catch (error) {
|
|
477
|
+
if (pm) {
|
|
478
|
+
const code = error?.code;
|
|
479
|
+
if (code === "unavailable" || code === "deadline-exceeded") {
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
throw error;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
function extractFieldPaths(data, prefix = "") {
|
|
487
|
+
const paths = [];
|
|
488
|
+
for (const [key, value] of Object.entries(data)) {
|
|
489
|
+
if (value === void 0) {
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
492
|
+
const fullPath = prefix ? `${prefix}.${key}` : key;
|
|
493
|
+
if (key.includes(".")) {
|
|
494
|
+
paths.push(key);
|
|
495
|
+
} else if (isFieldValueForExtract(value)) {
|
|
496
|
+
paths.push(fullPath);
|
|
497
|
+
} else if (value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date)) {
|
|
498
|
+
paths.push(...extractFieldPaths(value, fullPath));
|
|
499
|
+
} else {
|
|
500
|
+
paths.push(fullPath);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return paths;
|
|
504
|
+
}
|
|
505
|
+
var MAX_DOCUMENT_SIZE_BYTES = 1024 * 1024;
|
|
506
|
+
var MAX_DOCUMENT_DEPTH = 20;
|
|
507
|
+
function createFirestoreError(code, message) {
|
|
508
|
+
const error = new Error(message);
|
|
509
|
+
error.code = code;
|
|
510
|
+
return error;
|
|
511
|
+
}
|
|
512
|
+
function assertDocumentSize(fields) {
|
|
513
|
+
const json = JSON.stringify({ fields });
|
|
514
|
+
const size = typeof TextEncoder !== "undefined" ? new TextEncoder().encode(json).length : json.length;
|
|
515
|
+
if (size > MAX_DOCUMENT_SIZE_BYTES) {
|
|
516
|
+
throw createFirestoreError("invalid-argument", "Document size exceeds 1 MiB");
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
function assertDocumentDepth(fields) {
|
|
520
|
+
if (getDepth(fields, 0) > MAX_DOCUMENT_DEPTH) {
|
|
521
|
+
throw createFirestoreError("invalid-argument", "Document exceeds maximum depth");
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
function assertValidTypes(data, path = "") {
|
|
525
|
+
if (data === void 0) {
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
if (typeof data === "function") {
|
|
529
|
+
throw createFirestoreError(
|
|
530
|
+
"invalid-argument",
|
|
531
|
+
`Function setDoc() called with invalid data. Unsupported field value: a function${path ? ` (found in field ${path})` : ""}`
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
if (typeof data === "symbol") {
|
|
535
|
+
throw createFirestoreError(
|
|
536
|
+
"invalid-argument",
|
|
537
|
+
`Function setDoc() called with invalid data. Unsupported field value: a symbol${path ? ` (found in field ${path})` : ""}`
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
if (data === null) {
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
if (Array.isArray(data)) {
|
|
544
|
+
for (let i = 0; i < data.length; i++) {
|
|
545
|
+
assertValidTypes(data[i], `${path}[${i}]`);
|
|
546
|
+
}
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
if (typeof data === "object") {
|
|
550
|
+
if ("_type" in data && typeof data._type === "string") {
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
for (const [key, value] of Object.entries(data)) {
|
|
554
|
+
const fieldPath = path ? `${path}.${key}` : key;
|
|
555
|
+
assertValidTypes(value, fieldPath);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
function getDepth(value, current) {
|
|
560
|
+
if (value === null || value === void 0) {
|
|
561
|
+
return current;
|
|
562
|
+
}
|
|
563
|
+
if (Array.isArray(value)) {
|
|
564
|
+
let maxDepth = current;
|
|
565
|
+
for (const item of value) {
|
|
566
|
+
const depth = getDepth(item, current);
|
|
567
|
+
if (depth > maxDepth) {
|
|
568
|
+
maxDepth = depth;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
return maxDepth;
|
|
572
|
+
}
|
|
573
|
+
if (typeof value === "object") {
|
|
574
|
+
let maxDepth = current + 1;
|
|
575
|
+
for (const item of Object.values(value)) {
|
|
576
|
+
const depth = getDepth(item, current + 1);
|
|
577
|
+
if (depth > maxDepth) {
|
|
578
|
+
maxDepth = depth;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
return maxDepth;
|
|
582
|
+
}
|
|
583
|
+
return current;
|
|
584
|
+
}
|
|
585
|
+
function isFieldValueForExtract(value) {
|
|
586
|
+
return value !== null && typeof value === "object" && "_type" in value && typeof value._type === "string";
|
|
587
|
+
}
|
|
588
|
+
async function deleteDoc(reference) {
|
|
589
|
+
if (!reference || !reference.path) {
|
|
590
|
+
throw new Error("deleteDoc requires a valid DocumentReference");
|
|
591
|
+
}
|
|
592
|
+
const client = getHttpClient(reference.firestore);
|
|
593
|
+
notifyLocalWrite(reference.path);
|
|
594
|
+
const pm = getPersistenceManager(reference.firestore);
|
|
595
|
+
if (pm) {
|
|
596
|
+
await pm.addLocalWrite("delete", reference.path, null);
|
|
597
|
+
}
|
|
598
|
+
if (pm && !pm.isOnline) {
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
try {
|
|
602
|
+
await client.delete(`/documents/${reference.path}`);
|
|
603
|
+
if (pm) {
|
|
604
|
+
const mutations = await pm.mutations.getForPath(reference.path);
|
|
605
|
+
if (mutations.length > 0) {
|
|
606
|
+
await pm.completeMutation(mutations[mutations.length - 1].id);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
} catch (error) {
|
|
610
|
+
if (pm) {
|
|
611
|
+
const code = error?.code;
|
|
612
|
+
if (code === "unavailable" || code === "deadline-exceeded") {
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
throw error;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
async function addDoc(reference, data) {
|
|
620
|
+
if (!reference || !reference.path) {
|
|
621
|
+
throw new Error("addDoc requires a valid CollectionReference");
|
|
622
|
+
}
|
|
623
|
+
if (!data || typeof data !== "object") {
|
|
624
|
+
throw new Error("addDoc requires a valid data object");
|
|
625
|
+
}
|
|
626
|
+
const newId = generateId();
|
|
627
|
+
const docRef = doc(reference, newId);
|
|
628
|
+
const converter = getConverter(reference);
|
|
629
|
+
if (converter) {
|
|
630
|
+
docRef.converter = converter;
|
|
631
|
+
}
|
|
632
|
+
await setDoc(docRef, data);
|
|
633
|
+
return docRef;
|
|
634
|
+
}
|
|
635
|
+
function getConverter(reference) {
|
|
636
|
+
return reference.converter;
|
|
637
|
+
}
|
|
638
|
+
function applyConverterToData(reference, data) {
|
|
639
|
+
const converter = getConverter(reference);
|
|
640
|
+
return converter ? converter.toFirestore(data) : data;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// src/firestore/query.ts
|
|
644
|
+
var QueryImpl = class {
|
|
645
|
+
constructor(firestore, path, constraints = []) {
|
|
646
|
+
this.firestore = firestore;
|
|
647
|
+
this.path = path;
|
|
648
|
+
this.constraints = constraints;
|
|
649
|
+
this.type = "query";
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
function query(reference, ...constraints) {
|
|
653
|
+
const existingConstraints = "constraints" in reference ? reference.constraints : [];
|
|
654
|
+
const allConstraints = [...existingConstraints, ...constraints];
|
|
655
|
+
validateQueryConflicts(allConstraints);
|
|
656
|
+
const newQuery = new QueryImpl(
|
|
657
|
+
reference.firestore,
|
|
658
|
+
reference.path,
|
|
659
|
+
allConstraints
|
|
660
|
+
);
|
|
661
|
+
const converter = reference.converter;
|
|
662
|
+
if (converter) {
|
|
663
|
+
newQuery.converter = converter;
|
|
664
|
+
}
|
|
665
|
+
return newQuery;
|
|
666
|
+
}
|
|
667
|
+
function validateQueryConflicts(constraints) {
|
|
668
|
+
const whereConstraints = constraints.filter((c) => c.type === "where");
|
|
669
|
+
let hasIn = false;
|
|
670
|
+
let hasNotIn = false;
|
|
671
|
+
let hasNotEqual = false;
|
|
672
|
+
let arrayContainsCount = 0;
|
|
673
|
+
for (const w of whereConstraints) {
|
|
674
|
+
if (w.op === "in") hasIn = true;
|
|
675
|
+
if (w.op === "not-in") hasNotIn = true;
|
|
676
|
+
if (w.op === "!=") hasNotEqual = true;
|
|
677
|
+
if (w.op === "array-contains") arrayContainsCount++;
|
|
678
|
+
}
|
|
679
|
+
if (hasIn && hasNotIn) {
|
|
680
|
+
throw new Error(`Invalid Query. You cannot use 'in' and 'not-in' filters in the same query.`);
|
|
681
|
+
}
|
|
682
|
+
if (hasNotEqual && hasNotIn) {
|
|
683
|
+
throw new Error(`Invalid Query. You cannot use '!=' and 'not-in' filters in the same query.`);
|
|
684
|
+
}
|
|
685
|
+
if (arrayContainsCount > 1) {
|
|
686
|
+
throw new Error(`Invalid Query. You cannot use more than one 'array-contains' filter in a single query.`);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
var VALID_OPS = /* @__PURE__ */ new Set([
|
|
690
|
+
"<",
|
|
691
|
+
"<=",
|
|
692
|
+
"==",
|
|
693
|
+
"!=",
|
|
694
|
+
">=",
|
|
695
|
+
">",
|
|
696
|
+
"array-contains",
|
|
697
|
+
"array-contains-any",
|
|
698
|
+
"in",
|
|
699
|
+
"not-in"
|
|
700
|
+
]);
|
|
701
|
+
var ARRAY_LIMIT_OPS = /* @__PURE__ */ new Set(["in", "not-in", "array-contains-any"]);
|
|
702
|
+
function where(field, op, value) {
|
|
703
|
+
if (!VALID_OPS.has(op)) {
|
|
704
|
+
throw new Error(`Invalid operator '${op}' passed to where(). Valid operators are: <, <=, ==, !=, >=, >, array-contains, array-contains-any, in, not-in.`);
|
|
705
|
+
}
|
|
706
|
+
if (value === void 0) {
|
|
707
|
+
throw new Error(`Function Query.where() requires a valid third argument, but it was undefined.`);
|
|
708
|
+
}
|
|
709
|
+
if (typeof value === "function") {
|
|
710
|
+
throw new Error(`Function Query.where() requires a valid third argument, but it was a function.`);
|
|
711
|
+
}
|
|
712
|
+
if (typeof value === "symbol") {
|
|
713
|
+
throw new Error(`Function Query.where() requires a valid third argument, but it was a symbol.`);
|
|
714
|
+
}
|
|
715
|
+
if (ARRAY_LIMIT_OPS.has(op)) {
|
|
716
|
+
if (!Array.isArray(value)) {
|
|
717
|
+
throw new Error(`Invalid argument: in/not-in/array-contains-any requires an array value.`);
|
|
718
|
+
}
|
|
719
|
+
if (value.length > 30) {
|
|
720
|
+
throw new Error(`Invalid Query. '${op}' filters support a maximum of 30 elements in the value array.`);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
return {
|
|
724
|
+
type: "where",
|
|
725
|
+
field,
|
|
726
|
+
op,
|
|
727
|
+
value
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
function orderBy(field, direction = "asc") {
|
|
731
|
+
return {
|
|
732
|
+
type: "orderBy",
|
|
733
|
+
field,
|
|
734
|
+
direction
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
function limit(n) {
|
|
738
|
+
if (n <= 0) {
|
|
739
|
+
throw new Error(`Function limit() requires a positive number, but it was: ${n}`);
|
|
740
|
+
}
|
|
741
|
+
return {
|
|
742
|
+
type: "limit",
|
|
743
|
+
limit: n
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
function limitToLast(n) {
|
|
747
|
+
if (n <= 0) {
|
|
748
|
+
throw new Error(`Function limitToLast() requires a positive number, but it was: ${n}`);
|
|
749
|
+
}
|
|
750
|
+
return {
|
|
751
|
+
type: "limitToLast",
|
|
752
|
+
limit: n
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
function startAt(...values) {
|
|
756
|
+
return {
|
|
757
|
+
type: "startAt",
|
|
758
|
+
values,
|
|
759
|
+
inclusive: true
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
function startAfter(...values) {
|
|
763
|
+
return {
|
|
764
|
+
type: "startAfter",
|
|
765
|
+
values,
|
|
766
|
+
inclusive: false
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
function endAt(...values) {
|
|
770
|
+
return {
|
|
771
|
+
type: "endAt",
|
|
772
|
+
values,
|
|
773
|
+
inclusive: true
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
function endBefore(...values) {
|
|
777
|
+
return {
|
|
778
|
+
type: "endBefore",
|
|
779
|
+
values,
|
|
780
|
+
inclusive: false
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
async function getDocs(queryRef) {
|
|
784
|
+
const pm = getPersistenceManager(queryRef.firestore);
|
|
785
|
+
if (!pm) {
|
|
786
|
+
return getDocsDirect(queryRef);
|
|
787
|
+
}
|
|
788
|
+
if (pm.isOnline) {
|
|
789
|
+
try {
|
|
790
|
+
const snapshot = await getDocsDirect(queryRef);
|
|
791
|
+
for (const doc2 of snapshot.docs) {
|
|
792
|
+
await pm.cacheDocument(doc2.ref.path, doc2.data() ?? null, doc2.exists(), doc2._updateTime);
|
|
793
|
+
}
|
|
794
|
+
const queryKey = buildQueryCacheKey(queryRef);
|
|
795
|
+
await pm.cacheQuery(queryKey, snapshot.docs.map((d) => d.ref.path));
|
|
796
|
+
return snapshot;
|
|
797
|
+
} catch (error) {
|
|
798
|
+
const code = error?.code;
|
|
799
|
+
if (code === "unavailable" || code === "deadline-exceeded") {
|
|
800
|
+
return getDocsFromLocalCache(queryRef, pm);
|
|
801
|
+
}
|
|
802
|
+
throw error;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
return getDocsFromLocalCache(queryRef, pm);
|
|
806
|
+
}
|
|
807
|
+
async function getDocsDirect(queryRef) {
|
|
808
|
+
const client = getHttpClient(queryRef.firestore);
|
|
809
|
+
const constraints = "constraints" in queryRef ? queryRef.constraints : [];
|
|
810
|
+
const flexibleQueries = Boolean(queryRef.firestore._config?.allowFlexibleQueries);
|
|
811
|
+
validateQueryConstraints(constraints, { allowFlexibleQueries: flexibleQueries });
|
|
812
|
+
const isCollectionGroupQuery = queryRef.type === "collectionGroup";
|
|
813
|
+
const structuredQuery = buildStructuredQuery(queryRef.path, constraints, isCollectionGroupQuery);
|
|
814
|
+
const response = await client.post(
|
|
815
|
+
"/documents:runQuery",
|
|
816
|
+
{ structuredQuery }
|
|
817
|
+
);
|
|
818
|
+
const documents = response.filter((r) => r.document).map((r) => r.document);
|
|
819
|
+
const queryForSnapshot = "constraints" in queryRef ? queryRef : new QueryImpl(queryRef.firestore, queryRef.path, []);
|
|
820
|
+
const snapshot = createQuerySnapshot(queryForSnapshot, documents);
|
|
821
|
+
const converter = queryRef.converter;
|
|
822
|
+
return applyConverterToQuerySnapshot(snapshot, converter);
|
|
823
|
+
}
|
|
824
|
+
async function getDocsFromLocalCache(queryRef, pm) {
|
|
825
|
+
const queryForSnapshot = "constraints" in queryRef ? queryRef : new QueryImpl(queryRef.firestore, queryRef.path, []);
|
|
826
|
+
const queryKey = buildQueryCacheKey(queryRef);
|
|
827
|
+
const cachedPaths = await pm.getCachedQuery(queryKey);
|
|
828
|
+
if (cachedPaths) {
|
|
829
|
+
const docs2 = [];
|
|
830
|
+
for (const path of cachedPaths) {
|
|
831
|
+
const cached = await pm.getCachedDocument(path);
|
|
832
|
+
if (!cached || !cached.exists) continue;
|
|
833
|
+
const segments = path.split("/");
|
|
834
|
+
const ref = {
|
|
835
|
+
type: "document",
|
|
836
|
+
path,
|
|
837
|
+
id: segments[segments.length - 1],
|
|
838
|
+
firestore: queryRef.firestore,
|
|
839
|
+
parent: {
|
|
840
|
+
type: "collection",
|
|
841
|
+
path: segments.slice(0, -1).join("/"),
|
|
842
|
+
id: segments[segments.length - 2] || "",
|
|
843
|
+
firestore: queryRef.firestore,
|
|
844
|
+
parent: null
|
|
845
|
+
}
|
|
846
|
+
};
|
|
847
|
+
docs2.push(new DocumentSnapshotImpl(
|
|
848
|
+
ref,
|
|
849
|
+
cached.data,
|
|
850
|
+
true,
|
|
851
|
+
{ fromCache: true, hasPendingWrites: await pm.hasPendingWrites(path) }
|
|
852
|
+
));
|
|
853
|
+
}
|
|
854
|
+
return new QuerySnapshotImpl(queryForSnapshot, docs2, [], { fromCache: true });
|
|
855
|
+
}
|
|
856
|
+
const collectionDocs = await pm.getCachedCollection(queryRef.path);
|
|
857
|
+
const docs = [];
|
|
858
|
+
for (const cached of collectionDocs) {
|
|
859
|
+
const segments = cached.path.split("/");
|
|
860
|
+
const ref = {
|
|
861
|
+
type: "document",
|
|
862
|
+
path: cached.path,
|
|
863
|
+
id: segments[segments.length - 1],
|
|
864
|
+
firestore: queryRef.firestore,
|
|
865
|
+
parent: {
|
|
866
|
+
type: "collection",
|
|
867
|
+
path: segments.slice(0, -1).join("/"),
|
|
868
|
+
id: segments[segments.length - 2] || "",
|
|
869
|
+
firestore: queryRef.firestore,
|
|
870
|
+
parent: null
|
|
871
|
+
}
|
|
872
|
+
};
|
|
873
|
+
docs.push(new DocumentSnapshotImpl(
|
|
874
|
+
ref,
|
|
875
|
+
cached.data,
|
|
876
|
+
true,
|
|
877
|
+
{ fromCache: true, hasPendingWrites: await pm.hasPendingWrites(cached.path) }
|
|
878
|
+
));
|
|
879
|
+
}
|
|
880
|
+
return new QuerySnapshotImpl(queryForSnapshot, docs, [], { fromCache: true });
|
|
881
|
+
}
|
|
882
|
+
async function getDocsFromCache(queryRef) {
|
|
883
|
+
const pm = getPersistenceManager(queryRef.firestore);
|
|
884
|
+
if (!pm) {
|
|
885
|
+
throw createFirestoreError2(
|
|
886
|
+
"failed-precondition",
|
|
887
|
+
"Failed to get documents from cache. Persistence is not enabled."
|
|
888
|
+
);
|
|
889
|
+
}
|
|
890
|
+
return getDocsFromLocalCache(queryRef, pm);
|
|
891
|
+
}
|
|
892
|
+
function buildQueryCacheKey(queryRef) {
|
|
893
|
+
const constraints = "constraints" in queryRef ? queryRef.constraints : [];
|
|
894
|
+
return `${queryRef.path}:${JSON.stringify(constraints)}`;
|
|
895
|
+
}
|
|
896
|
+
async function getDocsFromServer(queryRef) {
|
|
897
|
+
const snapshot = await getDocsDirect(queryRef);
|
|
898
|
+
const pm = getPersistenceManager(queryRef.firestore);
|
|
899
|
+
if (pm) {
|
|
900
|
+
for (const doc2 of snapshot.docs) {
|
|
901
|
+
await pm.cacheDocument(doc2.ref.path, doc2.data() ?? null, doc2.exists(), doc2._updateTime);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
return snapshot;
|
|
905
|
+
}
|
|
906
|
+
var INEQUALITY_OPS = /* @__PURE__ */ new Set(["<", "<=", ">", ">=", "!=", "not-in"]);
|
|
907
|
+
var ARRAY_VALUE_OPS = /* @__PURE__ */ new Set(["in", "not-in", "array-contains-any"]);
|
|
908
|
+
function createFirestoreError2(code, message) {
|
|
909
|
+
const error = new Error(message);
|
|
910
|
+
error.code = code;
|
|
911
|
+
return error;
|
|
912
|
+
}
|
|
913
|
+
function validateQueryConstraints(constraints, options) {
|
|
914
|
+
const whereConstraints = collectWhereConstraints(constraints);
|
|
915
|
+
const orderByConstraints = constraints.filter((c) => c.type === "orderBy");
|
|
916
|
+
const allowFlexibleQueries = options?.allowFlexibleQueries ?? false;
|
|
917
|
+
const inequalityFields = /* @__PURE__ */ new Set();
|
|
918
|
+
for (const w of whereConstraints) {
|
|
919
|
+
if (ARRAY_VALUE_OPS.has(w.op)) {
|
|
920
|
+
validateArrayOperator(w);
|
|
921
|
+
}
|
|
922
|
+
if (INEQUALITY_OPS.has(w.op)) {
|
|
923
|
+
inequalityFields.add(w.field);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
if (inequalityFields.size > 1) {
|
|
927
|
+
throw createFirestoreError2(
|
|
928
|
+
"invalid-argument",
|
|
929
|
+
"Firestore requires inequality filters on a single field"
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
if (!allowFlexibleQueries && inequalityFields.size === 1 && orderByConstraints.length > 0) {
|
|
933
|
+
const inequalityField = [...inequalityFields][0];
|
|
934
|
+
const firstOrderBy = orderByConstraints[0];
|
|
935
|
+
if (firstOrderBy.field !== inequalityField) {
|
|
936
|
+
throw createFirestoreError2(
|
|
937
|
+
"invalid-argument",
|
|
938
|
+
`Firestore requires an orderBy on '${inequalityField}' to appear first`
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
function validateArrayOperator(where2) {
|
|
944
|
+
if (!Array.isArray(where2.value)) {
|
|
945
|
+
throw createFirestoreError2(
|
|
946
|
+
"invalid-argument",
|
|
947
|
+
`Operator '${where2.op}' requires an array value`
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
if (where2.value.length === 0 || where2.value.length > 10) {
|
|
951
|
+
throw createFirestoreError2(
|
|
952
|
+
"invalid-argument",
|
|
953
|
+
`Operator '${where2.op}' requires a non-empty array with at most 10 elements`
|
|
954
|
+
);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
function collectWhereConstraints(constraints) {
|
|
958
|
+
const result = [];
|
|
959
|
+
for (const constraint of constraints) {
|
|
960
|
+
if (constraint.type === "where") {
|
|
961
|
+
result.push(constraint);
|
|
962
|
+
} else if (constraint.type === "and" || constraint.type === "or") {
|
|
963
|
+
const composite = constraint;
|
|
964
|
+
result.push(...collectWhereConstraints(composite.constraints));
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
return result;
|
|
968
|
+
}
|
|
969
|
+
function buildStructuredQuery(collectionPath, constraints, isCollectionGroup2 = false) {
|
|
970
|
+
const query2 = {
|
|
971
|
+
from: [{
|
|
972
|
+
collectionId: getCollectionSelectorPath(collectionPath, isCollectionGroup2),
|
|
973
|
+
allDescendants: isCollectionGroup2
|
|
974
|
+
}]
|
|
975
|
+
};
|
|
976
|
+
const wheres = [];
|
|
977
|
+
const compositeFilters = [];
|
|
978
|
+
const orderBys = [];
|
|
979
|
+
let limitValue;
|
|
980
|
+
let limitToLastValue;
|
|
981
|
+
let offsetValue;
|
|
982
|
+
let startCursor;
|
|
983
|
+
let endCursor;
|
|
984
|
+
for (const constraint of constraints) {
|
|
985
|
+
switch (constraint.type) {
|
|
986
|
+
case "where":
|
|
987
|
+
wheres.push(constraint);
|
|
988
|
+
break;
|
|
989
|
+
case "and":
|
|
990
|
+
case "or":
|
|
991
|
+
compositeFilters.push(constraint);
|
|
992
|
+
break;
|
|
993
|
+
case "orderBy":
|
|
994
|
+
orderBys.push(constraint);
|
|
995
|
+
break;
|
|
996
|
+
case "limit":
|
|
997
|
+
limitValue = constraint.limit;
|
|
998
|
+
break;
|
|
999
|
+
case "limitToLast":
|
|
1000
|
+
limitToLastValue = constraint.limit;
|
|
1001
|
+
break;
|
|
1002
|
+
case "offset":
|
|
1003
|
+
offsetValue = constraint.count;
|
|
1004
|
+
break;
|
|
1005
|
+
case "startAt":
|
|
1006
|
+
case "startAfter": {
|
|
1007
|
+
const c = constraint;
|
|
1008
|
+
const cursorValues = extractCursorValues(c.values, orderBys);
|
|
1009
|
+
startCursor = {
|
|
1010
|
+
values: cursorValues.map((v) => toFirestoreValue(v)),
|
|
1011
|
+
before: c.inclusive
|
|
1012
|
+
};
|
|
1013
|
+
break;
|
|
1014
|
+
}
|
|
1015
|
+
case "endAt":
|
|
1016
|
+
case "endBefore": {
|
|
1017
|
+
const c = constraint;
|
|
1018
|
+
const cursorValues = extractCursorValues(c.values, orderBys);
|
|
1019
|
+
endCursor = {
|
|
1020
|
+
values: cursorValues.map((v) => toFirestoreValue(v)),
|
|
1021
|
+
before: !c.inclusive
|
|
1022
|
+
};
|
|
1023
|
+
break;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
const allFilters = [];
|
|
1028
|
+
for (const w of wheres) {
|
|
1029
|
+
const filter = buildWhereFilter(w);
|
|
1030
|
+
if (filter) {
|
|
1031
|
+
allFilters.push(filter);
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
for (const cf of compositeFilters) {
|
|
1035
|
+
allFilters.push(buildCompositeFilterQuery(cf));
|
|
1036
|
+
}
|
|
1037
|
+
if (allFilters.length === 1) {
|
|
1038
|
+
query2.where = allFilters[0];
|
|
1039
|
+
} else if (allFilters.length > 1) {
|
|
1040
|
+
query2.where = {
|
|
1041
|
+
compositeFilter: {
|
|
1042
|
+
op: "AND",
|
|
1043
|
+
filters: allFilters
|
|
1044
|
+
}
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
if (orderBys.length > 0) {
|
|
1048
|
+
query2.orderBy = orderBys.map((ob) => ({
|
|
1049
|
+
field: { fieldPath: ob.field },
|
|
1050
|
+
direction: ob.direction === "desc" ? "DESCENDING" : "ASCENDING"
|
|
1051
|
+
}));
|
|
1052
|
+
}
|
|
1053
|
+
if (limitValue !== void 0) {
|
|
1054
|
+
query2.limit = limitValue;
|
|
1055
|
+
}
|
|
1056
|
+
if (limitToLastValue !== void 0) {
|
|
1057
|
+
query2.limitToLast = limitToLastValue;
|
|
1058
|
+
}
|
|
1059
|
+
if (offsetValue !== void 0) {
|
|
1060
|
+
query2.offset = offsetValue;
|
|
1061
|
+
}
|
|
1062
|
+
if (startCursor) {
|
|
1063
|
+
query2.startAt = startCursor;
|
|
1064
|
+
}
|
|
1065
|
+
if (endCursor) {
|
|
1066
|
+
query2.endAt = endCursor;
|
|
1067
|
+
}
|
|
1068
|
+
return query2;
|
|
1069
|
+
}
|
|
1070
|
+
function buildWhereFilter(where2) {
|
|
1071
|
+
const fieldPath = where2.field;
|
|
1072
|
+
const value = toFirestoreValue(where2.value);
|
|
1073
|
+
const opMap = {
|
|
1074
|
+
"<": "LESS_THAN",
|
|
1075
|
+
"<=": "LESS_THAN_OR_EQUAL",
|
|
1076
|
+
"==": "EQUAL",
|
|
1077
|
+
"!=": "NOT_EQUAL",
|
|
1078
|
+
">": "GREATER_THAN",
|
|
1079
|
+
">=": "GREATER_THAN_OR_EQUAL",
|
|
1080
|
+
"array-contains": "ARRAY_CONTAINS",
|
|
1081
|
+
"array-contains-any": "ARRAY_CONTAINS_ANY",
|
|
1082
|
+
"in": "IN",
|
|
1083
|
+
"not-in": "NOT_IN"
|
|
1084
|
+
};
|
|
1085
|
+
if (where2.op === "array-contains-any" || where2.op === "in" || where2.op === "not-in") {
|
|
1086
|
+
return {
|
|
1087
|
+
fieldFilter: {
|
|
1088
|
+
field: { fieldPath },
|
|
1089
|
+
op: opMap[where2.op],
|
|
1090
|
+
value
|
|
1091
|
+
}
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
return {
|
|
1095
|
+
fieldFilter: {
|
|
1096
|
+
field: { fieldPath },
|
|
1097
|
+
op: opMap[where2.op],
|
|
1098
|
+
value
|
|
1099
|
+
}
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
function buildCompositeFilterQuery(constraint) {
|
|
1103
|
+
const op = constraint.type === "and" ? "AND" : "OR";
|
|
1104
|
+
const filters = constraint.constraints.map((c) => {
|
|
1105
|
+
if (c.type === "and" || c.type === "or") {
|
|
1106
|
+
return buildCompositeFilterQuery(c);
|
|
1107
|
+
}
|
|
1108
|
+
return buildWhereFilter(c);
|
|
1109
|
+
});
|
|
1110
|
+
return {
|
|
1111
|
+
compositeFilter: {
|
|
1112
|
+
op,
|
|
1113
|
+
filters
|
|
1114
|
+
}
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
function getCollectionId(path) {
|
|
1118
|
+
const parts = path.split("/");
|
|
1119
|
+
return parts[parts.length - 1];
|
|
1120
|
+
}
|
|
1121
|
+
function getCollectionSelectorPath(path, isCollectionGroup2) {
|
|
1122
|
+
if (isCollectionGroup2) {
|
|
1123
|
+
return getCollectionId(path);
|
|
1124
|
+
}
|
|
1125
|
+
return path;
|
|
1126
|
+
}
|
|
1127
|
+
function extractCursorValues(values, orderBys) {
|
|
1128
|
+
if (values.length === 0) return values;
|
|
1129
|
+
const first = values[0];
|
|
1130
|
+
if (isDocumentSnapshot(first)) {
|
|
1131
|
+
const snapshot = first;
|
|
1132
|
+
const data = snapshot.data();
|
|
1133
|
+
if (!data) {
|
|
1134
|
+
return values;
|
|
1135
|
+
}
|
|
1136
|
+
if (orderBys.length > 0) {
|
|
1137
|
+
return orderBys.map((ob) => {
|
|
1138
|
+
if (ob.field === "__name__") {
|
|
1139
|
+
return snapshot.id;
|
|
1140
|
+
}
|
|
1141
|
+
return getNestedValue(data, ob.field);
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1144
|
+
return [snapshot.id];
|
|
1145
|
+
}
|
|
1146
|
+
return values;
|
|
1147
|
+
}
|
|
1148
|
+
function isDocumentSnapshot(value) {
|
|
1149
|
+
if (!value || typeof value !== "object") return false;
|
|
1150
|
+
const obj = value;
|
|
1151
|
+
return typeof obj.exists === "function" && typeof obj.data === "function" && typeof obj.id === "string";
|
|
1152
|
+
}
|
|
1153
|
+
function getNestedValue(obj, path) {
|
|
1154
|
+
const parts = path.split(".");
|
|
1155
|
+
let current = obj;
|
|
1156
|
+
for (const part of parts) {
|
|
1157
|
+
if (current === null || current === void 0) return void 0;
|
|
1158
|
+
if (typeof current !== "object") return void 0;
|
|
1159
|
+
current = current[part];
|
|
1160
|
+
}
|
|
1161
|
+
return current;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
// src/transport/websocket.ts
|
|
1165
|
+
function getWebSocketConstructor() {
|
|
1166
|
+
if (typeof globalThis.WebSocket !== "undefined") {
|
|
1167
|
+
return globalThis.WebSocket;
|
|
1168
|
+
}
|
|
1169
|
+
return null;
|
|
1170
|
+
}
|
|
1171
|
+
function normalizeWebSocketHost(rawHost, ssl) {
|
|
1172
|
+
const trimmed = rawHost.trim().replace(/\/+$/, "");
|
|
1173
|
+
if (trimmed.startsWith("ws://") || trimmed.startsWith("wss://")) {
|
|
1174
|
+
return trimmed;
|
|
1175
|
+
}
|
|
1176
|
+
if (trimmed.startsWith("http://")) {
|
|
1177
|
+
return `ws://${trimmed.slice("http://".length)}`;
|
|
1178
|
+
}
|
|
1179
|
+
if (trimmed.startsWith("https://")) {
|
|
1180
|
+
return `wss://${trimmed.slice("https://".length)}`;
|
|
1181
|
+
}
|
|
1182
|
+
const protocol = ssl ? "wss" : "ws";
|
|
1183
|
+
return `${protocol}://${trimmed}`;
|
|
1184
|
+
}
|
|
1185
|
+
var WebSocketClient = class {
|
|
1186
|
+
constructor(firestore) {
|
|
1187
|
+
this.socket = null;
|
|
1188
|
+
this.state = "disconnected";
|
|
1189
|
+
this.reconnectAttempts = 0;
|
|
1190
|
+
this.maxReconnectAttempts = 5;
|
|
1191
|
+
this.reconnectDelay = 1e3;
|
|
1192
|
+
this.authToken = null;
|
|
1193
|
+
this.syncTimer = null;
|
|
1194
|
+
// Suscripciones activas
|
|
1195
|
+
this.subscriptions = /* @__PURE__ */ new Map();
|
|
1196
|
+
// ID para suscripciones
|
|
1197
|
+
this.subscriptionIdCounter = 0;
|
|
1198
|
+
this.firestore = firestore;
|
|
1199
|
+
const host = normalizeWebSocketHost(firestore._config.host, firestore._config.ssl);
|
|
1200
|
+
this.url = `${host}/v1/projects/${firestore._projectId}/databases/${firestore._databaseId}/documents:listen`;
|
|
1201
|
+
}
|
|
1202
|
+
/**
|
|
1203
|
+
* Establece el token de autenticación
|
|
1204
|
+
*/
|
|
1205
|
+
setAuthToken(token) {
|
|
1206
|
+
this.authToken = token;
|
|
1207
|
+
if (this.socket && this.state === "connected") {
|
|
1208
|
+
this.reconnect();
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
/**
|
|
1212
|
+
* Conecta al WebSocket
|
|
1213
|
+
*/
|
|
1214
|
+
async connect() {
|
|
1215
|
+
if (this.state === "connecting" || this.state === "connected") {
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
this.state = "connecting";
|
|
1219
|
+
return new Promise((resolve, reject) => {
|
|
1220
|
+
try {
|
|
1221
|
+
const WS = getWebSocketConstructor();
|
|
1222
|
+
if (!WS) {
|
|
1223
|
+
this.state = "error";
|
|
1224
|
+
reject(new Error("WebSocket not available in this environment"));
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1227
|
+
this.socket = new WS(this.url);
|
|
1228
|
+
this.socket.onopen = () => {
|
|
1229
|
+
this.state = "connected";
|
|
1230
|
+
this.reconnectAttempts = 0;
|
|
1231
|
+
this.sendAuth();
|
|
1232
|
+
this.resubscribeAll();
|
|
1233
|
+
resolve();
|
|
1234
|
+
};
|
|
1235
|
+
this.socket.onclose = () => {
|
|
1236
|
+
this.state = "disconnected";
|
|
1237
|
+
this.handleDisconnect();
|
|
1238
|
+
};
|
|
1239
|
+
this.socket.onerror = () => {
|
|
1240
|
+
this.state = "error";
|
|
1241
|
+
reject(new Error("WebSocket connection failed"));
|
|
1242
|
+
};
|
|
1243
|
+
this.socket.onmessage = (event) => {
|
|
1244
|
+
this.handleMessage(event.data);
|
|
1245
|
+
};
|
|
1246
|
+
} catch (error) {
|
|
1247
|
+
this.state = "error";
|
|
1248
|
+
reject(error);
|
|
1249
|
+
}
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1252
|
+
/**
|
|
1253
|
+
* Maneja desconexión e intenta reconectar
|
|
1254
|
+
*/
|
|
1255
|
+
handleDisconnect() {
|
|
1256
|
+
if (this.reconnectAttempts < this.maxReconnectAttempts && this.subscriptions.size > 0) {
|
|
1257
|
+
this.reconnectAttempts++;
|
|
1258
|
+
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
|
|
1259
|
+
setTimeout(() => this.connect(), delay);
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
/**
|
|
1263
|
+
* Reconecta forzadamente
|
|
1264
|
+
*/
|
|
1265
|
+
reconnect() {
|
|
1266
|
+
this.socket?.close();
|
|
1267
|
+
this.state = "disconnected";
|
|
1268
|
+
this.connect();
|
|
1269
|
+
}
|
|
1270
|
+
/**
|
|
1271
|
+
* Envía el token de autenticación como mensaje (no en URL)
|
|
1272
|
+
*/
|
|
1273
|
+
sendAuth() {
|
|
1274
|
+
if (this.socket && this.state === "connected" && this.authToken) {
|
|
1275
|
+
this.socket.send(JSON.stringify({ type: "auth", token: this.authToken }));
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
/**
|
|
1279
|
+
* Re-suscribe a todas las suscripciones activas
|
|
1280
|
+
*/
|
|
1281
|
+
resubscribeAll() {
|
|
1282
|
+
for (const [id, sub] of this.subscriptions) {
|
|
1283
|
+
this.sendSubscribe(id, sub.path, sub.query, sub.includeMetadataChanges, sub.allDescendants);
|
|
1284
|
+
if (sub.resumeToken && sub.path) {
|
|
1285
|
+
this.sendResume(id, sub.resumeToken, sub.path);
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
/**
|
|
1290
|
+
* Envía mensaje de suscripción
|
|
1291
|
+
*/
|
|
1292
|
+
sendSubscribe(id, path, query2, includeMetadataChanges, allDescendants) {
|
|
1293
|
+
if (this.socket && this.state === "connected") {
|
|
1294
|
+
const message = {
|
|
1295
|
+
type: "subscribe",
|
|
1296
|
+
id,
|
|
1297
|
+
path,
|
|
1298
|
+
query: query2,
|
|
1299
|
+
includeMetadataChanges,
|
|
1300
|
+
allDescendants
|
|
1301
|
+
};
|
|
1302
|
+
this.socket.send(JSON.stringify(message));
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
/**
|
|
1306
|
+
* Envía mensaje de reanudación con resumeToken
|
|
1307
|
+
*/
|
|
1308
|
+
sendResume(id, resumeToken, path) {
|
|
1309
|
+
if (this.socket && this.state === "connected") {
|
|
1310
|
+
const message = {
|
|
1311
|
+
type: "resume",
|
|
1312
|
+
id,
|
|
1313
|
+
resumeToken,
|
|
1314
|
+
path
|
|
1315
|
+
};
|
|
1316
|
+
this.socket.send(JSON.stringify(message));
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
/**
|
|
1320
|
+
* Notifica onSnapshotsInSync con debounce
|
|
1321
|
+
*/
|
|
1322
|
+
scheduleSnapshotsSync() {
|
|
1323
|
+
const firestoreInternal = this.firestore;
|
|
1324
|
+
if (!firestoreInternal._syncCallbacks || firestoreInternal._syncCallbacks.size === 0) {
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1327
|
+
if (this.syncTimer) {
|
|
1328
|
+
clearTimeout(this.syncTimer);
|
|
1329
|
+
}
|
|
1330
|
+
this.syncTimer = setTimeout(() => {
|
|
1331
|
+
firestoreInternal._syncCallbacks?.forEach((cb) => cb());
|
|
1332
|
+
}, 50);
|
|
1333
|
+
}
|
|
1334
|
+
/**
|
|
1335
|
+
* Envía mensaje de cancelación de suscripción
|
|
1336
|
+
*/
|
|
1337
|
+
sendUnsubscribe(id) {
|
|
1338
|
+
if (this.socket && this.state === "connected") {
|
|
1339
|
+
const message = {
|
|
1340
|
+
type: "unsubscribe",
|
|
1341
|
+
id
|
|
1342
|
+
};
|
|
1343
|
+
this.socket.send(JSON.stringify(message));
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
/**
|
|
1347
|
+
* Maneja mensajes entrantes
|
|
1348
|
+
*/
|
|
1349
|
+
handleMessage(data) {
|
|
1350
|
+
try {
|
|
1351
|
+
const message = JSON.parse(data);
|
|
1352
|
+
switch (message.type) {
|
|
1353
|
+
case "document_change":
|
|
1354
|
+
case "query_change":
|
|
1355
|
+
case "metadata_change":
|
|
1356
|
+
case "missed_event": {
|
|
1357
|
+
const sub = this.subscriptions.get(message.id || "");
|
|
1358
|
+
if (sub) {
|
|
1359
|
+
if (message.resumeToken) {
|
|
1360
|
+
sub.resumeToken = message.resumeToken;
|
|
1361
|
+
}
|
|
1362
|
+
sub.callback(message);
|
|
1363
|
+
this.scheduleSnapshotsSync();
|
|
1364
|
+
}
|
|
1365
|
+
break;
|
|
1366
|
+
}
|
|
1367
|
+
case "snapshots_in_sync": {
|
|
1368
|
+
this.scheduleSnapshotsSync();
|
|
1369
|
+
break;
|
|
1370
|
+
}
|
|
1371
|
+
case "error": {
|
|
1372
|
+
const sub = this.subscriptions.get(message.id || "");
|
|
1373
|
+
if (sub?.errorCallback) {
|
|
1374
|
+
sub.errorCallback(new Error(message.error || "Unknown error"));
|
|
1375
|
+
}
|
|
1376
|
+
break;
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
} catch {
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
/**
|
|
1383
|
+
* Suscribe a cambios de un documento
|
|
1384
|
+
*/
|
|
1385
|
+
async subscribeToDocument(path, callback, errorCallback, includeMetadataChanges) {
|
|
1386
|
+
if (this.state !== "connected") {
|
|
1387
|
+
await this.connect();
|
|
1388
|
+
}
|
|
1389
|
+
const id = `doc_${++this.subscriptionIdCounter}`;
|
|
1390
|
+
this.subscriptions.set(id, {
|
|
1391
|
+
path,
|
|
1392
|
+
callback,
|
|
1393
|
+
errorCallback,
|
|
1394
|
+
includeMetadataChanges
|
|
1395
|
+
});
|
|
1396
|
+
this.sendSubscribe(id, path, void 0, includeMetadataChanges);
|
|
1397
|
+
return () => {
|
|
1398
|
+
this.subscriptions.delete(id);
|
|
1399
|
+
this.sendUnsubscribe(id);
|
|
1400
|
+
if (this.subscriptions.size === 0) {
|
|
1401
|
+
this.socket?.close();
|
|
1402
|
+
this.state = "disconnected";
|
|
1403
|
+
}
|
|
1404
|
+
};
|
|
1405
|
+
}
|
|
1406
|
+
/**
|
|
1407
|
+
* Suscribe a cambios de una query
|
|
1408
|
+
*/
|
|
1409
|
+
async subscribeToQuery(query2, callback, errorCallback, path, includeMetadataChanges, allDescendants) {
|
|
1410
|
+
if (this.state !== "connected") {
|
|
1411
|
+
await this.connect();
|
|
1412
|
+
}
|
|
1413
|
+
const id = `query_${++this.subscriptionIdCounter}`;
|
|
1414
|
+
this.subscriptions.set(id, {
|
|
1415
|
+
query: query2,
|
|
1416
|
+
callback,
|
|
1417
|
+
errorCallback,
|
|
1418
|
+
path,
|
|
1419
|
+
includeMetadataChanges,
|
|
1420
|
+
allDescendants
|
|
1421
|
+
});
|
|
1422
|
+
this.sendSubscribe(id, path, query2, includeMetadataChanges, allDescendants);
|
|
1423
|
+
return () => {
|
|
1424
|
+
this.subscriptions.delete(id);
|
|
1425
|
+
this.sendUnsubscribe(id);
|
|
1426
|
+
if (this.subscriptions.size === 0) {
|
|
1427
|
+
this.socket?.close();
|
|
1428
|
+
this.state = "disconnected";
|
|
1429
|
+
}
|
|
1430
|
+
};
|
|
1431
|
+
}
|
|
1432
|
+
/**
|
|
1433
|
+
* Cierra la conexión
|
|
1434
|
+
*/
|
|
1435
|
+
close() {
|
|
1436
|
+
this.subscriptions.clear();
|
|
1437
|
+
this.socket?.close();
|
|
1438
|
+
this.state = "disconnected";
|
|
1439
|
+
}
|
|
1440
|
+
/**
|
|
1441
|
+
* Fuerza desconexión sin limpiar suscripciones (para pruebas de reconexión)
|
|
1442
|
+
*/
|
|
1443
|
+
simulateDisconnect() {
|
|
1444
|
+
if (this.socket) {
|
|
1445
|
+
this.socket.close();
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
};
|
|
1449
|
+
var clients = /* @__PURE__ */ new WeakMap();
|
|
1450
|
+
function getWebSocketClient(firestore) {
|
|
1451
|
+
let client = clients.get(firestore);
|
|
1452
|
+
if (!client) {
|
|
1453
|
+
client = new WebSocketClient(firestore);
|
|
1454
|
+
clients.set(firestore, client);
|
|
1455
|
+
}
|
|
1456
|
+
return client;
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
// src/firestore/realtime.ts
|
|
1460
|
+
function onSnapshot(reference, onNextOrObserverOrOptions, onErrorOrObserver, onError) {
|
|
1461
|
+
let nextCallback;
|
|
1462
|
+
let errorCallback = onError;
|
|
1463
|
+
let options;
|
|
1464
|
+
let onNextOrObserver = onNextOrObserverOrOptions;
|
|
1465
|
+
const maybeOptions = onNextOrObserverOrOptions;
|
|
1466
|
+
if (maybeOptions && typeof maybeOptions === "object" && ("includeMetadataChanges" in maybeOptions || "source" in maybeOptions)) {
|
|
1467
|
+
options = maybeOptions;
|
|
1468
|
+
onNextOrObserver = onErrorOrObserver;
|
|
1469
|
+
errorCallback = onError;
|
|
1470
|
+
} else {
|
|
1471
|
+
errorCallback = onErrorOrObserver;
|
|
1472
|
+
}
|
|
1473
|
+
if (typeof onNextOrObserver === "function") {
|
|
1474
|
+
nextCallback = onNextOrObserver;
|
|
1475
|
+
} else {
|
|
1476
|
+
nextCallback = onNextOrObserver?.next;
|
|
1477
|
+
errorCallback = onNextOrObserver?.error ?? errorCallback;
|
|
1478
|
+
}
|
|
1479
|
+
const isDocument = reference.type === "document";
|
|
1480
|
+
if (isDocument) {
|
|
1481
|
+
return subscribeToDocument(
|
|
1482
|
+
reference,
|
|
1483
|
+
nextCallback,
|
|
1484
|
+
errorCallback,
|
|
1485
|
+
options
|
|
1486
|
+
);
|
|
1487
|
+
} else {
|
|
1488
|
+
return subscribeToQuery(
|
|
1489
|
+
reference,
|
|
1490
|
+
nextCallback,
|
|
1491
|
+
errorCallback,
|
|
1492
|
+
options
|
|
1493
|
+
);
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
function subscribeToDocument(reference, onNext, onError, options) {
|
|
1497
|
+
let unsubscribed = false;
|
|
1498
|
+
let wsUnsubscribe = null;
|
|
1499
|
+
let localUnsubscribe = null;
|
|
1500
|
+
let lastSnapshot = null;
|
|
1501
|
+
const pm = getPersistenceManager(reference.firestore);
|
|
1502
|
+
if (pm) {
|
|
1503
|
+
pm.getCachedDocument(reference.path).then(async (cached) => {
|
|
1504
|
+
if (cached && !unsubscribed) {
|
|
1505
|
+
const cachedSnapshot = new DocumentSnapshotImpl(
|
|
1506
|
+
reference,
|
|
1507
|
+
cached.exists ? cached.data : void 0,
|
|
1508
|
+
cached.exists,
|
|
1509
|
+
{ fromCache: true, hasPendingWrites: await pm.hasPendingWrites(reference.path) }
|
|
1510
|
+
);
|
|
1511
|
+
lastSnapshot = cachedSnapshot;
|
|
1512
|
+
if (!unsubscribed && onNext) {
|
|
1513
|
+
onNext(cachedSnapshot);
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
});
|
|
1517
|
+
}
|
|
1518
|
+
if (options?.source === "cache") {
|
|
1519
|
+
if (!lastSnapshot) {
|
|
1520
|
+
const hasPendingPromise = pm ? Promise.resolve(pm.hasPendingWrites(reference.path)) : Promise.resolve(false);
|
|
1521
|
+
hasPendingPromise.then((hasPending) => {
|
|
1522
|
+
if (unsubscribed || lastSnapshot) return;
|
|
1523
|
+
const emptyCachedSnapshot = new DocumentSnapshotImpl(
|
|
1524
|
+
reference,
|
|
1525
|
+
void 0,
|
|
1526
|
+
false,
|
|
1527
|
+
{
|
|
1528
|
+
fromCache: true,
|
|
1529
|
+
hasPendingWrites: hasPending
|
|
1530
|
+
}
|
|
1531
|
+
);
|
|
1532
|
+
lastSnapshot = emptyCachedSnapshot;
|
|
1533
|
+
if (!unsubscribed && onNext) {
|
|
1534
|
+
onNext(emptyCachedSnapshot);
|
|
1535
|
+
}
|
|
1536
|
+
});
|
|
1537
|
+
}
|
|
1538
|
+
if (options?.includeMetadataChanges) {
|
|
1539
|
+
localUnsubscribe = registerLocalWriteListener(reference.path, () => {
|
|
1540
|
+
if (unsubscribed || !onNext) return;
|
|
1541
|
+
const data = lastSnapshot?.data();
|
|
1542
|
+
const pendingSnapshot = new DocumentSnapshotImpl(
|
|
1543
|
+
reference,
|
|
1544
|
+
data,
|
|
1545
|
+
lastSnapshot?.exists() ?? false,
|
|
1546
|
+
{ fromCache: true, hasPendingWrites: true }
|
|
1547
|
+
);
|
|
1548
|
+
onNext(pendingSnapshot);
|
|
1549
|
+
});
|
|
1550
|
+
}
|
|
1551
|
+
return () => {
|
|
1552
|
+
unsubscribed = true;
|
|
1553
|
+
if (localUnsubscribe) {
|
|
1554
|
+
localUnsubscribe();
|
|
1555
|
+
}
|
|
1556
|
+
};
|
|
1557
|
+
}
|
|
1558
|
+
getDoc(reference).then(async (snapshot) => {
|
|
1559
|
+
if (!unsubscribed && onNext) {
|
|
1560
|
+
lastSnapshot = snapshot;
|
|
1561
|
+
if (pm) {
|
|
1562
|
+
await pm.cacheDocument(reference.path, snapshot.data() ?? null, snapshot.exists(), snapshot._updateTime);
|
|
1563
|
+
}
|
|
1564
|
+
onNext(snapshot);
|
|
1565
|
+
}
|
|
1566
|
+
}).catch((error) => {
|
|
1567
|
+
if (!unsubscribed && onError) {
|
|
1568
|
+
onError(error);
|
|
1569
|
+
}
|
|
1570
|
+
});
|
|
1571
|
+
if (options?.includeMetadataChanges) {
|
|
1572
|
+
localUnsubscribe = registerLocalWriteListener(reference.path, () => {
|
|
1573
|
+
if (unsubscribed || !onNext) return;
|
|
1574
|
+
const data = lastSnapshot?.data();
|
|
1575
|
+
const pendingSnapshot = new DocumentSnapshotImpl(
|
|
1576
|
+
reference,
|
|
1577
|
+
data,
|
|
1578
|
+
lastSnapshot?.exists() ?? false,
|
|
1579
|
+
{ fromCache: true, hasPendingWrites: true }
|
|
1580
|
+
);
|
|
1581
|
+
onNext(pendingSnapshot);
|
|
1582
|
+
});
|
|
1583
|
+
}
|
|
1584
|
+
try {
|
|
1585
|
+
const wsClient = getWebSocketClient(reference.firestore);
|
|
1586
|
+
wsClient.subscribeToDocument(
|
|
1587
|
+
reference.path,
|
|
1588
|
+
() => {
|
|
1589
|
+
if (!unsubscribed && onNext) {
|
|
1590
|
+
getDoc(reference).then(async (snapshot) => {
|
|
1591
|
+
if (!unsubscribed) {
|
|
1592
|
+
lastSnapshot = snapshot;
|
|
1593
|
+
if (pm) {
|
|
1594
|
+
await pm.cacheDocument(reference.path, snapshot.data() ?? null, snapshot.exists(), snapshot._updateTime);
|
|
1595
|
+
}
|
|
1596
|
+
onNext(snapshot);
|
|
1597
|
+
}
|
|
1598
|
+
}).catch((error) => {
|
|
1599
|
+
if (!unsubscribed && onError) {
|
|
1600
|
+
onError(error);
|
|
1601
|
+
}
|
|
1602
|
+
});
|
|
1603
|
+
}
|
|
1604
|
+
},
|
|
1605
|
+
(error) => {
|
|
1606
|
+
if (!unsubscribed && onError) {
|
|
1607
|
+
onError(error);
|
|
1608
|
+
}
|
|
1609
|
+
},
|
|
1610
|
+
options?.includeMetadataChanges
|
|
1611
|
+
).then((unsub) => {
|
|
1612
|
+
wsUnsubscribe = unsub;
|
|
1613
|
+
if (unsubscribed) {
|
|
1614
|
+
unsub();
|
|
1615
|
+
}
|
|
1616
|
+
}).catch((error) => {
|
|
1617
|
+
if (!unsubscribed && onError) {
|
|
1618
|
+
onError(error);
|
|
1619
|
+
}
|
|
1620
|
+
});
|
|
1621
|
+
} catch (error) {
|
|
1622
|
+
if (!unsubscribed && onError) {
|
|
1623
|
+
onError(error);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
return () => {
|
|
1627
|
+
unsubscribed = true;
|
|
1628
|
+
if (wsUnsubscribe) {
|
|
1629
|
+
wsUnsubscribe();
|
|
1630
|
+
}
|
|
1631
|
+
if (localUnsubscribe) {
|
|
1632
|
+
localUnsubscribe();
|
|
1633
|
+
}
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
function subscribeToQuery(queryRef, onNext, onError, options) {
|
|
1637
|
+
let unsubscribed = false;
|
|
1638
|
+
let previousSnapshot = null;
|
|
1639
|
+
let wsUnsubscribe = null;
|
|
1640
|
+
if (options?.source === "cache") {
|
|
1641
|
+
getDocsFromCache(queryRef).then((snapshot) => {
|
|
1642
|
+
if (!unsubscribed && onNext) {
|
|
1643
|
+
onNext(snapshot);
|
|
1644
|
+
}
|
|
1645
|
+
}).catch((error) => {
|
|
1646
|
+
if (!unsubscribed && onError) {
|
|
1647
|
+
onError(error);
|
|
1648
|
+
}
|
|
1649
|
+
});
|
|
1650
|
+
return () => {
|
|
1651
|
+
unsubscribed = true;
|
|
1652
|
+
};
|
|
1653
|
+
}
|
|
1654
|
+
const refresh = () => {
|
|
1655
|
+
getDocsWithPreviousState(queryRef, previousSnapshot).then((snapshot) => {
|
|
1656
|
+
if (!unsubscribed && onNext) {
|
|
1657
|
+
previousSnapshot = snapshot;
|
|
1658
|
+
onNext(snapshot);
|
|
1659
|
+
}
|
|
1660
|
+
}).catch((error) => {
|
|
1661
|
+
if (!unsubscribed && onError) {
|
|
1662
|
+
onError(error);
|
|
1663
|
+
}
|
|
1664
|
+
});
|
|
1665
|
+
};
|
|
1666
|
+
refresh();
|
|
1667
|
+
try {
|
|
1668
|
+
const wsClient = getWebSocketClient(queryRef.firestore);
|
|
1669
|
+
const constraints = "constraints" in queryRef ? queryRef.constraints : [];
|
|
1670
|
+
const isCollectionGroup2 = queryRef.type === "collectionGroup";
|
|
1671
|
+
wsClient.subscribeToQuery(
|
|
1672
|
+
constraints,
|
|
1673
|
+
() => {
|
|
1674
|
+
if (!unsubscribed) {
|
|
1675
|
+
refresh();
|
|
1676
|
+
}
|
|
1677
|
+
},
|
|
1678
|
+
(error) => {
|
|
1679
|
+
if (!unsubscribed && onError) {
|
|
1680
|
+
onError(error);
|
|
1681
|
+
}
|
|
1682
|
+
},
|
|
1683
|
+
queryRef.path,
|
|
1684
|
+
options?.includeMetadataChanges,
|
|
1685
|
+
isCollectionGroup2
|
|
1686
|
+
// allDescendants para collectionGroup
|
|
1687
|
+
).then((unsub) => {
|
|
1688
|
+
wsUnsubscribe = unsub;
|
|
1689
|
+
if (unsubscribed) {
|
|
1690
|
+
unsub();
|
|
1691
|
+
}
|
|
1692
|
+
}).catch((error) => {
|
|
1693
|
+
if (!unsubscribed && onError) {
|
|
1694
|
+
onError(error);
|
|
1695
|
+
}
|
|
1696
|
+
});
|
|
1697
|
+
} catch (error) {
|
|
1698
|
+
if (!unsubscribed && onError) {
|
|
1699
|
+
onError(error);
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
return () => {
|
|
1703
|
+
unsubscribed = true;
|
|
1704
|
+
if (wsUnsubscribe) {
|
|
1705
|
+
wsUnsubscribe();
|
|
1706
|
+
}
|
|
1707
|
+
};
|
|
1708
|
+
}
|
|
1709
|
+
async function getDocsWithPreviousState(queryRef, previousSnapshot) {
|
|
1710
|
+
const { getHttpClient: getHttpClient2 } = await import("./http-SZFONH6Z.mjs");
|
|
1711
|
+
const { createQuerySnapshot: createQuerySnapshot2, toFirestoreValue: toFirestoreValue3 } = await import("./snapshot-ZWZFIFZD.mjs");
|
|
1712
|
+
const client = getHttpClient2(queryRef.firestore);
|
|
1713
|
+
const constraints = "constraints" in queryRef ? queryRef.constraints : [];
|
|
1714
|
+
const isCollectionGroupQuery = queryRef.type === "collectionGroup";
|
|
1715
|
+
const structuredQuery = buildStructuredQueryForRealtime(queryRef.path, constraints, isCollectionGroupQuery, toFirestoreValue3);
|
|
1716
|
+
const response = await client.post(
|
|
1717
|
+
"/documents:runQuery",
|
|
1718
|
+
{ structuredQuery }
|
|
1719
|
+
);
|
|
1720
|
+
const documents = response.filter((r) => r.document).map((r) => r.document);
|
|
1721
|
+
const previousDocs = previousSnapshot ? previousSnapshot.docs : [];
|
|
1722
|
+
return createQuerySnapshot2(queryRef, documents, previousDocs);
|
|
1723
|
+
}
|
|
1724
|
+
function buildStructuredQueryForRealtime(collectionPath, constraints, isCollectionGroup2, toFirestoreValue3) {
|
|
1725
|
+
const query2 = {
|
|
1726
|
+
from: [{
|
|
1727
|
+
collectionId: isCollectionGroup2 ? collectionPath.split("/").pop() || collectionPath : collectionPath,
|
|
1728
|
+
allDescendants: isCollectionGroup2
|
|
1729
|
+
}]
|
|
1730
|
+
};
|
|
1731
|
+
const allFilters = [];
|
|
1732
|
+
const orderBys = [];
|
|
1733
|
+
let startCursor;
|
|
1734
|
+
let endCursor;
|
|
1735
|
+
for (const raw of constraints) {
|
|
1736
|
+
if (raw.type === "where") {
|
|
1737
|
+
const w = raw;
|
|
1738
|
+
const filter = buildWhereFilterRealtime(w, toFirestoreValue3);
|
|
1739
|
+
if (filter) {
|
|
1740
|
+
allFilters.push(filter);
|
|
1741
|
+
}
|
|
1742
|
+
} else if (raw.type === "and" || raw.type === "or") {
|
|
1743
|
+
allFilters.push(buildCompositeFilterRealtime(raw, toFirestoreValue3));
|
|
1744
|
+
} else if (raw.type === "orderBy") {
|
|
1745
|
+
const o = raw;
|
|
1746
|
+
if (!query2.orderBy) query2.orderBy = [];
|
|
1747
|
+
query2.orderBy.push({
|
|
1748
|
+
field: { fieldPath: o.field },
|
|
1749
|
+
direction: o.direction === "desc" ? "DESCENDING" : "ASCENDING"
|
|
1750
|
+
});
|
|
1751
|
+
orderBys.push(o);
|
|
1752
|
+
} else if (raw.type === "limit") {
|
|
1753
|
+
query2.limit = raw.limit;
|
|
1754
|
+
} else if (raw.type === "limitToLast") {
|
|
1755
|
+
query2.limitToLast = raw.limit;
|
|
1756
|
+
} else if (raw.type === "startAt" || raw.type === "startAfter") {
|
|
1757
|
+
const c = raw;
|
|
1758
|
+
const cursorValues = extractCursorValuesRealtime(c.values, orderBys);
|
|
1759
|
+
startCursor = {
|
|
1760
|
+
values: cursorValues.map((v) => toFirestoreValue3(v)),
|
|
1761
|
+
before: c.inclusive
|
|
1762
|
+
};
|
|
1763
|
+
} else if (raw.type === "endAt" || raw.type === "endBefore") {
|
|
1764
|
+
const c = raw;
|
|
1765
|
+
const cursorValues = extractCursorValuesRealtime(c.values, orderBys);
|
|
1766
|
+
endCursor = {
|
|
1767
|
+
values: cursorValues.map((v) => toFirestoreValue3(v)),
|
|
1768
|
+
before: !c.inclusive
|
|
1769
|
+
};
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
if (allFilters.length === 1) {
|
|
1773
|
+
query2.where = allFilters[0];
|
|
1774
|
+
} else if (allFilters.length > 1) {
|
|
1775
|
+
query2.where = {
|
|
1776
|
+
compositeFilter: {
|
|
1777
|
+
op: "AND",
|
|
1778
|
+
filters: allFilters
|
|
1779
|
+
}
|
|
1780
|
+
};
|
|
1781
|
+
}
|
|
1782
|
+
if (startCursor) {
|
|
1783
|
+
query2.startAt = startCursor;
|
|
1784
|
+
}
|
|
1785
|
+
if (endCursor) {
|
|
1786
|
+
query2.endAt = endCursor;
|
|
1787
|
+
}
|
|
1788
|
+
return query2;
|
|
1789
|
+
}
|
|
1790
|
+
function extractCursorValuesRealtime(values, orderBys) {
|
|
1791
|
+
if (!values || values.length === 0) return values;
|
|
1792
|
+
const first = values[0];
|
|
1793
|
+
if (first && typeof first.data === "function") {
|
|
1794
|
+
const data = first.data();
|
|
1795
|
+
if (!data) return values;
|
|
1796
|
+
if (orderBys.length > 0) {
|
|
1797
|
+
return orderBys.map((ob) => {
|
|
1798
|
+
if (ob.field === "__name__") {
|
|
1799
|
+
return first.id ?? "";
|
|
1800
|
+
}
|
|
1801
|
+
return getNestedValue2(data, ob.field);
|
|
1802
|
+
});
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
return values;
|
|
1806
|
+
}
|
|
1807
|
+
function getNestedValue2(obj, path) {
|
|
1808
|
+
const parts = path.split(".");
|
|
1809
|
+
let current = obj;
|
|
1810
|
+
for (const part of parts) {
|
|
1811
|
+
if (current === null || current === void 0) return void 0;
|
|
1812
|
+
if (typeof current !== "object") return void 0;
|
|
1813
|
+
current = current[part];
|
|
1814
|
+
}
|
|
1815
|
+
return current;
|
|
1816
|
+
}
|
|
1817
|
+
function buildWhereFilterRealtime(where2, toFirestoreValue3) {
|
|
1818
|
+
const opMap = {
|
|
1819
|
+
"<": "LESS_THAN",
|
|
1820
|
+
"<=": "LESS_THAN_OR_EQUAL",
|
|
1821
|
+
"==": "EQUAL",
|
|
1822
|
+
"!=": "NOT_EQUAL",
|
|
1823
|
+
">": "GREATER_THAN",
|
|
1824
|
+
">=": "GREATER_THAN_OR_EQUAL",
|
|
1825
|
+
"array-contains": "ARRAY_CONTAINS",
|
|
1826
|
+
"array-contains-any": "ARRAY_CONTAINS_ANY",
|
|
1827
|
+
"in": "IN",
|
|
1828
|
+
"not-in": "NOT_IN"
|
|
1829
|
+
};
|
|
1830
|
+
return {
|
|
1831
|
+
fieldFilter: {
|
|
1832
|
+
field: { fieldPath: where2.field },
|
|
1833
|
+
op: opMap[where2.op],
|
|
1834
|
+
value: toFirestoreValue3(where2.value)
|
|
1835
|
+
}
|
|
1836
|
+
};
|
|
1837
|
+
}
|
|
1838
|
+
function buildCompositeFilterRealtime(constraint, toFirestoreValue3) {
|
|
1839
|
+
const op = constraint.type === "and" ? "AND" : "OR";
|
|
1840
|
+
const constraints = constraint.constraints || [];
|
|
1841
|
+
const filters = constraints.map((c) => {
|
|
1842
|
+
const item = c;
|
|
1843
|
+
if (item.type === "and" || item.type === "or") {
|
|
1844
|
+
return buildCompositeFilterRealtime(item, toFirestoreValue3);
|
|
1845
|
+
}
|
|
1846
|
+
return buildWhereFilterRealtime(item, toFirestoreValue3);
|
|
1847
|
+
});
|
|
1848
|
+
return {
|
|
1849
|
+
compositeFilter: {
|
|
1850
|
+
op,
|
|
1851
|
+
filters
|
|
1852
|
+
}
|
|
1853
|
+
};
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
// src/firestore/batch.ts
|
|
1857
|
+
var WriteBatchImpl = class {
|
|
1858
|
+
constructor(firestore) {
|
|
1859
|
+
this.firestore = firestore;
|
|
1860
|
+
this.operations = [];
|
|
1861
|
+
this.committed = false;
|
|
1862
|
+
}
|
|
1863
|
+
set(reference, data, options) {
|
|
1864
|
+
this.verifyNotCommitted();
|
|
1865
|
+
this.operations.push({
|
|
1866
|
+
type: "set",
|
|
1867
|
+
reference,
|
|
1868
|
+
data,
|
|
1869
|
+
options
|
|
1870
|
+
});
|
|
1871
|
+
return this;
|
|
1872
|
+
}
|
|
1873
|
+
update(reference, data) {
|
|
1874
|
+
this.verifyNotCommitted();
|
|
1875
|
+
this.operations.push({
|
|
1876
|
+
type: "update",
|
|
1877
|
+
reference,
|
|
1878
|
+
data
|
|
1879
|
+
});
|
|
1880
|
+
return this;
|
|
1881
|
+
}
|
|
1882
|
+
delete(reference) {
|
|
1883
|
+
this.verifyNotCommitted();
|
|
1884
|
+
this.operations.push({ type: "delete", reference });
|
|
1885
|
+
return this;
|
|
1886
|
+
}
|
|
1887
|
+
async commit() {
|
|
1888
|
+
this.verifyNotCommitted();
|
|
1889
|
+
this.committed = true;
|
|
1890
|
+
if (this.operations.length === 0) {
|
|
1891
|
+
return;
|
|
1892
|
+
}
|
|
1893
|
+
if (this.operations.length > 500) {
|
|
1894
|
+
throw createFirestoreError3("invalid-argument", "Batch write exceeds 500 operations");
|
|
1895
|
+
}
|
|
1896
|
+
const client = getHttpClient(this.firestore);
|
|
1897
|
+
const writes = this.operations.map((op) => {
|
|
1898
|
+
switch (op.type) {
|
|
1899
|
+
case "set": {
|
|
1900
|
+
const fields = toFirestoreFields(op.data);
|
|
1901
|
+
if (op.options?.merge || op.options?.mergeFields) {
|
|
1902
|
+
const fieldPaths = op.options.mergeFields || Object.keys(op.data);
|
|
1903
|
+
return {
|
|
1904
|
+
update: {
|
|
1905
|
+
name: `projects/${this.firestore.app.options.projectId}/databases/(default)/documents/${op.reference.path}`,
|
|
1906
|
+
fields
|
|
1907
|
+
},
|
|
1908
|
+
updateMask: { fieldPaths }
|
|
1909
|
+
};
|
|
1910
|
+
}
|
|
1911
|
+
return {
|
|
1912
|
+
update: {
|
|
1913
|
+
name: `projects/${this.firestore.app.options.projectId}/databases/(default)/documents/${op.reference.path}`,
|
|
1914
|
+
fields
|
|
1915
|
+
}
|
|
1916
|
+
};
|
|
1917
|
+
}
|
|
1918
|
+
case "update": {
|
|
1919
|
+
const fields = toFirestoreFields(op.data);
|
|
1920
|
+
const fieldPaths = Object.keys(op.data);
|
|
1921
|
+
return {
|
|
1922
|
+
update: {
|
|
1923
|
+
name: `projects/${this.firestore.app.options.projectId}/databases/(default)/documents/${op.reference.path}`,
|
|
1924
|
+
fields
|
|
1925
|
+
},
|
|
1926
|
+
updateMask: { fieldPaths }
|
|
1927
|
+
};
|
|
1928
|
+
}
|
|
1929
|
+
case "delete":
|
|
1930
|
+
return {
|
|
1931
|
+
delete: `projects/${this.firestore.app.options.projectId}/databases/(default)/documents/${op.reference.path}`
|
|
1932
|
+
};
|
|
1933
|
+
}
|
|
1934
|
+
});
|
|
1935
|
+
await client.post(":batchWrite", { writes });
|
|
1936
|
+
}
|
|
1937
|
+
verifyNotCommitted() {
|
|
1938
|
+
if (this.committed) {
|
|
1939
|
+
throw new Error("WriteBatch ya ha sido committed");
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
};
|
|
1943
|
+
function writeBatch(firestore) {
|
|
1944
|
+
return new WriteBatchImpl(firestore);
|
|
1945
|
+
}
|
|
1946
|
+
var TransactionImpl = class {
|
|
1947
|
+
constructor(firestore, client) {
|
|
1948
|
+
this.firestore = firestore;
|
|
1949
|
+
this.client = client;
|
|
1950
|
+
this.reads = /* @__PURE__ */ new Map();
|
|
1951
|
+
this.operations = [];
|
|
1952
|
+
this.id = createTransactionId();
|
|
1953
|
+
this.readVersions = /* @__PURE__ */ new Map();
|
|
1954
|
+
this.preconditions = /* @__PURE__ */ new Map();
|
|
1955
|
+
}
|
|
1956
|
+
/**
|
|
1957
|
+
* Inicia la transacción
|
|
1958
|
+
*/
|
|
1959
|
+
async begin() {
|
|
1960
|
+
const response = await this.client.post(
|
|
1961
|
+
":beginTransaction",
|
|
1962
|
+
{}
|
|
1963
|
+
);
|
|
1964
|
+
this.transactionId = response.transaction;
|
|
1965
|
+
}
|
|
1966
|
+
async get(reference) {
|
|
1967
|
+
const cached = this.reads.get(reference.path);
|
|
1968
|
+
if (cached) {
|
|
1969
|
+
return cached;
|
|
1970
|
+
}
|
|
1971
|
+
const snapshot = await getDoc(reference);
|
|
1972
|
+
this.readVersions.set(reference.path, getDocumentVersion(reference.path));
|
|
1973
|
+
const snapImpl = snapshot;
|
|
1974
|
+
this.preconditions.set(reference.path, {
|
|
1975
|
+
exists: snapshot.exists(),
|
|
1976
|
+
updateTime: snapImpl._updateTime
|
|
1977
|
+
});
|
|
1978
|
+
this.reads.set(reference.path, snapshot);
|
|
1979
|
+
return snapshot;
|
|
1980
|
+
}
|
|
1981
|
+
set(reference, data, options) {
|
|
1982
|
+
this.operations.push({
|
|
1983
|
+
type: "set",
|
|
1984
|
+
reference,
|
|
1985
|
+
data,
|
|
1986
|
+
options
|
|
1987
|
+
});
|
|
1988
|
+
return this;
|
|
1989
|
+
}
|
|
1990
|
+
update(reference, data) {
|
|
1991
|
+
this.operations.push({
|
|
1992
|
+
type: "update",
|
|
1993
|
+
reference,
|
|
1994
|
+
data
|
|
1995
|
+
});
|
|
1996
|
+
return this;
|
|
1997
|
+
}
|
|
1998
|
+
delete(reference) {
|
|
1999
|
+
this.operations.push({ type: "delete", reference });
|
|
2000
|
+
return this;
|
|
2001
|
+
}
|
|
2002
|
+
/**
|
|
2003
|
+
* Ejecuta la transacción
|
|
2004
|
+
*/
|
|
2005
|
+
async commit() {
|
|
2006
|
+
if (this.operations.length > 500) {
|
|
2007
|
+
throw createFirestoreError3("invalid-argument", "Transaction exceeds 500 operations");
|
|
2008
|
+
}
|
|
2009
|
+
if (this.operations.length > 0) {
|
|
2010
|
+
for (const [path, version] of this.readVersions.entries()) {
|
|
2011
|
+
if (getDocumentVersion(path) !== version) {
|
|
2012
|
+
throw createFirestoreError3("aborted", "Transaction read conflict");
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
const paths = this.operations.map((op) => op.reference.path);
|
|
2017
|
+
if (hasWriteConflict(this.id, paths)) {
|
|
2018
|
+
throw createFirestoreError3("aborted", "Transaction conflicted with another in-flight write");
|
|
2019
|
+
}
|
|
2020
|
+
acquireWriteLocks(this.id, paths);
|
|
2021
|
+
if (this.operations.length === 0) {
|
|
2022
|
+
if (this.transactionId) {
|
|
2023
|
+
await this.client.post(":rollback", {
|
|
2024
|
+
transaction: this.transactionId
|
|
2025
|
+
});
|
|
2026
|
+
}
|
|
2027
|
+
releaseWriteLocks(this.id);
|
|
2028
|
+
return;
|
|
2029
|
+
}
|
|
2030
|
+
const writes = this.operations.map((op) => {
|
|
2031
|
+
const baseName = `projects/${this.firestore.app.options.projectId}/databases/(default)/documents/${op.reference.path}`;
|
|
2032
|
+
switch (op.type) {
|
|
2033
|
+
case "set": {
|
|
2034
|
+
const fields = toFirestoreFields(op.data);
|
|
2035
|
+
if (op.options?.merge || op.options?.mergeFields) {
|
|
2036
|
+
const fieldPaths = op.options.mergeFields || Object.keys(op.data);
|
|
2037
|
+
return {
|
|
2038
|
+
update: { name: baseName, fields },
|
|
2039
|
+
updateMask: { fieldPaths },
|
|
2040
|
+
currentDocument: this.preconditions.get(op.reference.path)
|
|
2041
|
+
};
|
|
2042
|
+
}
|
|
2043
|
+
return {
|
|
2044
|
+
update: { name: baseName, fields },
|
|
2045
|
+
currentDocument: this.preconditions.get(op.reference.path)
|
|
2046
|
+
};
|
|
2047
|
+
}
|
|
2048
|
+
case "update": {
|
|
2049
|
+
const fields = toFirestoreFields(op.data);
|
|
2050
|
+
const fieldPaths = Object.keys(op.data);
|
|
2051
|
+
return {
|
|
2052
|
+
update: { name: baseName, fields },
|
|
2053
|
+
updateMask: { fieldPaths },
|
|
2054
|
+
currentDocument: this.preconditions.get(op.reference.path)
|
|
2055
|
+
};
|
|
2056
|
+
}
|
|
2057
|
+
case "delete":
|
|
2058
|
+
return {
|
|
2059
|
+
delete: baseName,
|
|
2060
|
+
currentDocument: this.preconditions.get(op.reference.path)
|
|
2061
|
+
};
|
|
2062
|
+
}
|
|
2063
|
+
});
|
|
2064
|
+
try {
|
|
2065
|
+
await this.client.post(":commit", {
|
|
2066
|
+
transaction: this.transactionId,
|
|
2067
|
+
writes
|
|
2068
|
+
});
|
|
2069
|
+
bumpDocumentVersions(paths);
|
|
2070
|
+
} finally {
|
|
2071
|
+
releaseWriteLocks(this.id);
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
/**
|
|
2075
|
+
* Rollback de la transacción
|
|
2076
|
+
*/
|
|
2077
|
+
async rollback() {
|
|
2078
|
+
if (this.transactionId) {
|
|
2079
|
+
await this.client.post(":rollback", {
|
|
2080
|
+
transaction: this.transactionId
|
|
2081
|
+
});
|
|
2082
|
+
}
|
|
2083
|
+
releaseWriteLocks(this.id);
|
|
2084
|
+
}
|
|
2085
|
+
};
|
|
2086
|
+
async function runTransaction(firestore, updateFunction, options) {
|
|
2087
|
+
const maxAttempts = options?.maxAttempts ?? 5;
|
|
2088
|
+
const client = getHttpClient(firestore);
|
|
2089
|
+
let lastError;
|
|
2090
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
2091
|
+
const transaction = new TransactionImpl(firestore, client);
|
|
2092
|
+
try {
|
|
2093
|
+
await transaction.begin();
|
|
2094
|
+
const result = await updateFunction(transaction);
|
|
2095
|
+
await transaction.commit();
|
|
2096
|
+
return result;
|
|
2097
|
+
} catch (error) {
|
|
2098
|
+
lastError = error;
|
|
2099
|
+
try {
|
|
2100
|
+
await transaction.rollback();
|
|
2101
|
+
} catch {
|
|
2102
|
+
}
|
|
2103
|
+
if (error.code === "aborted") {
|
|
2104
|
+
await new Promise(
|
|
2105
|
+
(resolve) => setTimeout(resolve, Math.pow(2, attempt) * 100)
|
|
2106
|
+
);
|
|
2107
|
+
continue;
|
|
2108
|
+
}
|
|
2109
|
+
throw error;
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
throw lastError || new Error("Transaction failed after max attempts");
|
|
2113
|
+
}
|
|
2114
|
+
function createFirestoreError3(code, message) {
|
|
2115
|
+
const error = new Error(message);
|
|
2116
|
+
error.code = code;
|
|
2117
|
+
return error;
|
|
2118
|
+
}
|
|
2119
|
+
var writeLocks = /* @__PURE__ */ new Map();
|
|
2120
|
+
var documentVersions = /* @__PURE__ */ new Map();
|
|
2121
|
+
function createTransactionId() {
|
|
2122
|
+
return `tx_${Math.random().toString(36).slice(2)}_${Date.now()}`;
|
|
2123
|
+
}
|
|
2124
|
+
function hasWriteConflict(txId, paths) {
|
|
2125
|
+
return paths.some((path) => {
|
|
2126
|
+
const owner = writeLocks.get(path);
|
|
2127
|
+
return owner !== void 0 && owner !== txId;
|
|
2128
|
+
});
|
|
2129
|
+
}
|
|
2130
|
+
function getDocumentVersion(path) {
|
|
2131
|
+
return documentVersions.get(path) ?? 0;
|
|
2132
|
+
}
|
|
2133
|
+
function bumpDocumentVersions(paths) {
|
|
2134
|
+
paths.forEach((path) => {
|
|
2135
|
+
const current = getDocumentVersion(path);
|
|
2136
|
+
documentVersions.set(path, current + 1);
|
|
2137
|
+
});
|
|
2138
|
+
}
|
|
2139
|
+
function acquireWriteLocks(txId, paths) {
|
|
2140
|
+
paths.forEach((path) => {
|
|
2141
|
+
writeLocks.set(path, txId);
|
|
2142
|
+
});
|
|
2143
|
+
}
|
|
2144
|
+
function releaseWriteLocks(txId) {
|
|
2145
|
+
for (const [path, owner] of writeLocks.entries()) {
|
|
2146
|
+
if (owner === txId) {
|
|
2147
|
+
writeLocks.delete(path);
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
// src/firestore/advanced-query.ts
|
|
2153
|
+
function and(...constraints) {
|
|
2154
|
+
if (constraints.length === 0) {
|
|
2155
|
+
throw new Error("and() requires at least one constraint");
|
|
2156
|
+
}
|
|
2157
|
+
return {
|
|
2158
|
+
type: "and",
|
|
2159
|
+
constraints
|
|
2160
|
+
};
|
|
2161
|
+
}
|
|
2162
|
+
function or(...constraints) {
|
|
2163
|
+
if (constraints.length === 0) {
|
|
2164
|
+
throw new Error("or() requires at least one constraint");
|
|
2165
|
+
}
|
|
2166
|
+
return {
|
|
2167
|
+
type: "or",
|
|
2168
|
+
constraints
|
|
2169
|
+
};
|
|
2170
|
+
}
|
|
2171
|
+
var CollectionGroupImpl = class {
|
|
2172
|
+
constructor(firestore, collectionId, path, constraints = []) {
|
|
2173
|
+
this.firestore = firestore;
|
|
2174
|
+
this.collectionId = collectionId;
|
|
2175
|
+
this.path = path;
|
|
2176
|
+
this.constraints = constraints;
|
|
2177
|
+
this.type = "collectionGroup";
|
|
2178
|
+
}
|
|
2179
|
+
};
|
|
2180
|
+
function collectionGroup(firestore, collectionId) {
|
|
2181
|
+
if (!collectionId || typeof collectionId !== "string") {
|
|
2182
|
+
throw new Error("collectionGroup requires a valid collection ID");
|
|
2183
|
+
}
|
|
2184
|
+
if (collectionId.includes("/")) {
|
|
2185
|
+
throw new Error("collectionGroup ID cannot contain slashes");
|
|
2186
|
+
}
|
|
2187
|
+
return new CollectionGroupImpl(
|
|
2188
|
+
firestore,
|
|
2189
|
+
collectionId,
|
|
2190
|
+
collectionId,
|
|
2191
|
+
// path es solo el ID para collection groups
|
|
2192
|
+
[]
|
|
2193
|
+
);
|
|
2194
|
+
}
|
|
2195
|
+
function isAndConstraint(constraint) {
|
|
2196
|
+
return constraint.type === "and";
|
|
2197
|
+
}
|
|
2198
|
+
function isOrConstraint(constraint) {
|
|
2199
|
+
return constraint.type === "or";
|
|
2200
|
+
}
|
|
2201
|
+
function isCompositeFilter(constraint) {
|
|
2202
|
+
return isAndConstraint(constraint) || isOrConstraint(constraint);
|
|
2203
|
+
}
|
|
2204
|
+
function isCollectionGroup(ref) {
|
|
2205
|
+
return ref.type === "collectionGroup";
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
// src/firestore/aggregations.ts
|
|
2209
|
+
function count() {
|
|
2210
|
+
return {
|
|
2211
|
+
_aggregateType: "count"
|
|
2212
|
+
};
|
|
2213
|
+
}
|
|
2214
|
+
function sum(field) {
|
|
2215
|
+
return {
|
|
2216
|
+
_aggregateType: "sum",
|
|
2217
|
+
_field: field
|
|
2218
|
+
};
|
|
2219
|
+
}
|
|
2220
|
+
function average(field) {
|
|
2221
|
+
return {
|
|
2222
|
+
_aggregateType: "average",
|
|
2223
|
+
_field: field
|
|
2224
|
+
};
|
|
2225
|
+
}
|
|
2226
|
+
async function getCountFromServer(query2) {
|
|
2227
|
+
return getAggregateFromServer(query2, { count: count() });
|
|
2228
|
+
}
|
|
2229
|
+
async function getAggregateFromServer(query2, aggregateSpec) {
|
|
2230
|
+
const firestore = query2.firestore;
|
|
2231
|
+
const client = getHttpClient(firestore);
|
|
2232
|
+
const structuredQuery = buildAggregateQuery(query2, aggregateSpec);
|
|
2233
|
+
const response = await client.post("/documents:runAggregationQuery", {
|
|
2234
|
+
structuredAggregationQuery: structuredQuery
|
|
2235
|
+
});
|
|
2236
|
+
let aggregateFields = {};
|
|
2237
|
+
if (Array.isArray(response)) {
|
|
2238
|
+
aggregateFields = response[0]?.result?.aggregateFields || {};
|
|
2239
|
+
} else {
|
|
2240
|
+
aggregateFields = response?.result?.aggregateFields || {};
|
|
2241
|
+
}
|
|
2242
|
+
const resultData = {};
|
|
2243
|
+
for (const [alias, spec] of Object.entries(aggregateSpec)) {
|
|
2244
|
+
const value = aggregateFields[alias];
|
|
2245
|
+
if (value) {
|
|
2246
|
+
if ("nullValue" in value) {
|
|
2247
|
+
resultData[alias] = null;
|
|
2248
|
+
} else if (value.integerValue !== void 0) {
|
|
2249
|
+
resultData[alias] = parseInt(value.integerValue, 10);
|
|
2250
|
+
} else if (value.doubleValue !== void 0) {
|
|
2251
|
+
resultData[alias] = value.doubleValue;
|
|
2252
|
+
} else {
|
|
2253
|
+
resultData[alias] = null;
|
|
2254
|
+
}
|
|
2255
|
+
} else {
|
|
2256
|
+
resultData[alias] = spec._aggregateType === "average" ? null : 0;
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
return {
|
|
2260
|
+
data: () => resultData
|
|
2261
|
+
};
|
|
2262
|
+
}
|
|
2263
|
+
function buildAggregateQuery(query2, aggregateSpec) {
|
|
2264
|
+
const pathParts = query2.path.split("/");
|
|
2265
|
+
const collectionId = pathParts[pathParts.length - 1];
|
|
2266
|
+
const structuredQuery = {
|
|
2267
|
+
from: [{ collectionId }]
|
|
2268
|
+
};
|
|
2269
|
+
if ("constraints" in query2) {
|
|
2270
|
+
const constraints = query2.constraints || [];
|
|
2271
|
+
const wheres = constraints.filter((c) => c.type === "where");
|
|
2272
|
+
if (wheres.length > 0) {
|
|
2273
|
+
structuredQuery.where = buildWhereFromConstraints(wheres);
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
const aggregations = Object.entries(aggregateSpec).map(([alias, spec]) => {
|
|
2277
|
+
const aggregation = { alias };
|
|
2278
|
+
switch (spec._aggregateType) {
|
|
2279
|
+
case "count":
|
|
2280
|
+
aggregation.count = {};
|
|
2281
|
+
break;
|
|
2282
|
+
case "sum":
|
|
2283
|
+
aggregation.sum = { field: { fieldPath: spec._field } };
|
|
2284
|
+
break;
|
|
2285
|
+
case "average":
|
|
2286
|
+
aggregation.average = { field: { fieldPath: spec._field } };
|
|
2287
|
+
break;
|
|
2288
|
+
}
|
|
2289
|
+
return aggregation;
|
|
2290
|
+
});
|
|
2291
|
+
return {
|
|
2292
|
+
structuredQuery,
|
|
2293
|
+
aggregations
|
|
2294
|
+
};
|
|
2295
|
+
}
|
|
2296
|
+
function buildWhereFromConstraints(constraints) {
|
|
2297
|
+
const filters = constraints.map((c) => {
|
|
2298
|
+
const constraint = c;
|
|
2299
|
+
return {
|
|
2300
|
+
fieldFilter: {
|
|
2301
|
+
field: { fieldPath: constraint.field },
|
|
2302
|
+
op: mapOperator(constraint.op),
|
|
2303
|
+
value: toFirestoreValue2(constraint.value)
|
|
2304
|
+
}
|
|
2305
|
+
};
|
|
2306
|
+
});
|
|
2307
|
+
if (filters.length === 1) {
|
|
2308
|
+
return filters[0];
|
|
2309
|
+
}
|
|
2310
|
+
return {
|
|
2311
|
+
compositeFilter: {
|
|
2312
|
+
op: "AND",
|
|
2313
|
+
filters
|
|
2314
|
+
}
|
|
2315
|
+
};
|
|
2316
|
+
}
|
|
2317
|
+
function mapOperator(op) {
|
|
2318
|
+
const opMap = {
|
|
2319
|
+
"<": "LESS_THAN",
|
|
2320
|
+
"<=": "LESS_THAN_OR_EQUAL",
|
|
2321
|
+
"==": "EQUAL",
|
|
2322
|
+
"!=": "NOT_EQUAL",
|
|
2323
|
+
">": "GREATER_THAN",
|
|
2324
|
+
">=": "GREATER_THAN_OR_EQUAL",
|
|
2325
|
+
"array-contains": "ARRAY_CONTAINS",
|
|
2326
|
+
"array-contains-any": "ARRAY_CONTAINS_ANY",
|
|
2327
|
+
"in": "IN",
|
|
2328
|
+
"not-in": "NOT_IN"
|
|
2329
|
+
};
|
|
2330
|
+
return opMap[op] || "EQUAL";
|
|
2331
|
+
}
|
|
2332
|
+
function toFirestoreValue2(value) {
|
|
2333
|
+
if (value === null) return { nullValue: null };
|
|
2334
|
+
if (typeof value === "boolean") return { booleanValue: value };
|
|
2335
|
+
if (typeof value === "number") {
|
|
2336
|
+
return Number.isInteger(value) ? { integerValue: String(value) } : { doubleValue: value };
|
|
2337
|
+
}
|
|
2338
|
+
if (typeof value === "string") return { stringValue: value };
|
|
2339
|
+
if (Array.isArray(value)) {
|
|
2340
|
+
return { arrayValue: { values: value.map(toFirestoreValue2) } };
|
|
2341
|
+
}
|
|
2342
|
+
if (typeof value === "object") {
|
|
2343
|
+
const fields = {};
|
|
2344
|
+
for (const [k, v] of Object.entries(value)) {
|
|
2345
|
+
fields[k] = toFirestoreValue2(v);
|
|
2346
|
+
}
|
|
2347
|
+
return { mapValue: { fields } };
|
|
2348
|
+
}
|
|
2349
|
+
return { stringValue: String(value) };
|
|
2350
|
+
}
|
|
2351
|
+
|
|
2352
|
+
// src/firestore/advanced-search.ts
|
|
2353
|
+
function whereSearch(field, value) {
|
|
2354
|
+
return {
|
|
2355
|
+
type: "where",
|
|
2356
|
+
field: `${field}_search`,
|
|
2357
|
+
op: "==",
|
|
2358
|
+
value
|
|
2359
|
+
};
|
|
2360
|
+
}
|
|
2361
|
+
function whereContains(field, value) {
|
|
2362
|
+
return {
|
|
2363
|
+
type: "where",
|
|
2364
|
+
field: `${field}_contains`,
|
|
2365
|
+
op: "==",
|
|
2366
|
+
value
|
|
2367
|
+
};
|
|
2368
|
+
}
|
|
2369
|
+
function whereFuzzy(field, value) {
|
|
2370
|
+
return {
|
|
2371
|
+
type: "where",
|
|
2372
|
+
field: `${field}_fuzzy`,
|
|
2373
|
+
op: "==",
|
|
2374
|
+
value
|
|
2375
|
+
// El threshold se maneja en el backend (default 0.3)
|
|
2376
|
+
};
|
|
2377
|
+
}
|
|
2378
|
+
function whereSimilar(field, value) {
|
|
2379
|
+
return {
|
|
2380
|
+
type: "where",
|
|
2381
|
+
field: `${field}_similar`,
|
|
2382
|
+
op: "==",
|
|
2383
|
+
value
|
|
2384
|
+
};
|
|
2385
|
+
}
|
|
2386
|
+
function orderByRelevance(direction = "desc") {
|
|
2387
|
+
return {
|
|
2388
|
+
type: "orderBy",
|
|
2389
|
+
field: "__relevance__",
|
|
2390
|
+
direction
|
|
2391
|
+
};
|
|
2392
|
+
}
|
|
2393
|
+
function offset(count2) {
|
|
2394
|
+
return {
|
|
2395
|
+
type: "offset",
|
|
2396
|
+
count: count2
|
|
2397
|
+
};
|
|
2398
|
+
}
|
|
2399
|
+
async function fastSearch(firestore, collectionPath, term, options = {}) {
|
|
2400
|
+
if (!collectionPath) {
|
|
2401
|
+
throw new Error("La colecci\xF3n es obligatoria.");
|
|
2402
|
+
}
|
|
2403
|
+
if (!term) {
|
|
2404
|
+
throw new Error("El t\xE9rmino es obligatorio.");
|
|
2405
|
+
}
|
|
2406
|
+
const params = new URLSearchParams({
|
|
2407
|
+
q: term,
|
|
2408
|
+
limit: String(options.limit ?? 50),
|
|
2409
|
+
fuzzy: options.fuzzy ? "true" : "false"
|
|
2410
|
+
});
|
|
2411
|
+
if (options.field) {
|
|
2412
|
+
params.set("field", options.field);
|
|
2413
|
+
}
|
|
2414
|
+
const searchPath = `/search/${encodeURIComponent(collectionPath)}?${params.toString()}`;
|
|
2415
|
+
const client = getHttpClient(firestore);
|
|
2416
|
+
return await client.get(searchPath);
|
|
2417
|
+
}
|
|
2418
|
+
|
|
2419
|
+
export {
|
|
2420
|
+
doc,
|
|
2421
|
+
collection,
|
|
2422
|
+
withConverter,
|
|
2423
|
+
refEqual,
|
|
2424
|
+
queryEqual,
|
|
2425
|
+
snapshotEqual,
|
|
2426
|
+
onSnapshotsInSync,
|
|
2427
|
+
getDoc,
|
|
2428
|
+
getDocFromCache,
|
|
2429
|
+
setDoc,
|
|
2430
|
+
getDocFromServer,
|
|
2431
|
+
updateDoc,
|
|
2432
|
+
deleteDoc,
|
|
2433
|
+
addDoc,
|
|
2434
|
+
query,
|
|
2435
|
+
where,
|
|
2436
|
+
orderBy,
|
|
2437
|
+
limit,
|
|
2438
|
+
limitToLast,
|
|
2439
|
+
startAt,
|
|
2440
|
+
startAfter,
|
|
2441
|
+
endAt,
|
|
2442
|
+
endBefore,
|
|
2443
|
+
getDocs,
|
|
2444
|
+
getDocsFromCache,
|
|
2445
|
+
getDocsFromServer,
|
|
2446
|
+
onSnapshot,
|
|
2447
|
+
writeBatch,
|
|
2448
|
+
runTransaction,
|
|
2449
|
+
and,
|
|
2450
|
+
or,
|
|
2451
|
+
collectionGroup,
|
|
2452
|
+
isAndConstraint,
|
|
2453
|
+
isOrConstraint,
|
|
2454
|
+
isCompositeFilter,
|
|
2455
|
+
isCollectionGroup,
|
|
2456
|
+
count,
|
|
2457
|
+
sum,
|
|
2458
|
+
average,
|
|
2459
|
+
getCountFromServer,
|
|
2460
|
+
getAggregateFromServer,
|
|
2461
|
+
whereSearch,
|
|
2462
|
+
whereContains,
|
|
2463
|
+
whereFuzzy,
|
|
2464
|
+
whereSimilar,
|
|
2465
|
+
orderByRelevance,
|
|
2466
|
+
offset,
|
|
2467
|
+
fastSearch
|
|
2468
|
+
};
|
|
2469
|
+
//# sourceMappingURL=chunk-DXPQJR5D.mjs.map
|