@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.
Files changed (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +692 -0
  3. package/dist/app.d.mts +51 -0
  4. package/dist/app.d.ts +51 -0
  5. package/dist/app.js +16 -0
  6. package/dist/app.js.map +1 -0
  7. package/dist/app.mjs +16 -0
  8. package/dist/app.mjs.map +1 -0
  9. package/dist/auth/index.d.mts +43 -0
  10. package/dist/auth/index.d.ts +43 -0
  11. package/dist/auth/index.js +18 -0
  12. package/dist/auth/index.js.map +1 -0
  13. package/dist/auth/index.mjs +18 -0
  14. package/dist/auth/index.mjs.map +1 -0
  15. package/dist/chunk-2RQUHE2K.js +719 -0
  16. package/dist/chunk-2RQUHE2K.js.map +1 -0
  17. package/dist/chunk-4CV4JOE5.js +27 -0
  18. package/dist/chunk-4CV4JOE5.js.map +1 -0
  19. package/dist/chunk-57XXMSJA.js +65 -0
  20. package/dist/chunk-57XXMSJA.js.map +1 -0
  21. package/dist/chunk-6J3LNKUQ.js +213 -0
  22. package/dist/chunk-6J3LNKUQ.js.map +1 -0
  23. package/dist/chunk-BXV7KTHB.js +645 -0
  24. package/dist/chunk-BXV7KTHB.js.map +1 -0
  25. package/dist/chunk-C3PCJJX4.mjs +645 -0
  26. package/dist/chunk-C3PCJJX4.mjs.map +1 -0
  27. package/dist/chunk-C6SKWUQV.mjs +213 -0
  28. package/dist/chunk-C6SKWUQV.mjs.map +1 -0
  29. package/dist/chunk-DXPQJR5D.mjs +2469 -0
  30. package/dist/chunk-DXPQJR5D.mjs.map +1 -0
  31. package/dist/chunk-MRVKMKSO.mjs +65 -0
  32. package/dist/chunk-MRVKMKSO.mjs.map +1 -0
  33. package/dist/chunk-NFEGQTCC.mjs +27 -0
  34. package/dist/chunk-NFEGQTCC.mjs.map +1 -0
  35. package/dist/chunk-RSBBZLDE.js +128 -0
  36. package/dist/chunk-RSBBZLDE.js.map +1 -0
  37. package/dist/chunk-RZWTSZSJ.js +2469 -0
  38. package/dist/chunk-RZWTSZSJ.js.map +1 -0
  39. package/dist/chunk-SZKHE2TQ.mjs +719 -0
  40. package/dist/chunk-SZKHE2TQ.mjs.map +1 -0
  41. package/dist/chunk-ZJ4A4Y2T.mjs +128 -0
  42. package/dist/chunk-ZJ4A4Y2T.mjs.map +1 -0
  43. package/dist/firestore/index.d.mts +1476 -0
  44. package/dist/firestore/index.d.ts +1476 -0
  45. package/dist/firestore/index.js +156 -0
  46. package/dist/firestore/index.js.map +1 -0
  47. package/dist/firestore/index.mjs +156 -0
  48. package/dist/firestore/index.mjs.map +1 -0
  49. package/dist/http-A2S5CWEV.js +10 -0
  50. package/dist/http-A2S5CWEV.js.map +1 -0
  51. package/dist/http-SZFONH6Z.mjs +10 -0
  52. package/dist/http-SZFONH6Z.mjs.map +1 -0
  53. package/dist/index.d.mts +4 -0
  54. package/dist/index.d.ts +4 -0
  55. package/dist/index.js +171 -0
  56. package/dist/index.js.map +1 -0
  57. package/dist/index.mjs +171 -0
  58. package/dist/index.mjs.map +1 -0
  59. package/dist/indexeddb-mutation-queue-5EB7C2D5.js +192 -0
  60. package/dist/indexeddb-mutation-queue-5EB7C2D5.js.map +1 -0
  61. package/dist/indexeddb-mutation-queue-M2MAH4E4.mjs +192 -0
  62. package/dist/indexeddb-mutation-queue-M2MAH4E4.mjs.map +1 -0
  63. package/dist/indexeddb-store-D23ZY3PR.mjs +162 -0
  64. package/dist/indexeddb-store-D23ZY3PR.mjs.map +1 -0
  65. package/dist/indexeddb-store-DNWBZUQE.js +162 -0
  66. package/dist/indexeddb-store-DNWBZUQE.js.map +1 -0
  67. package/dist/snapshot-MCQVLVHL.js +22 -0
  68. package/dist/snapshot-MCQVLVHL.js.map +1 -0
  69. package/dist/snapshot-ZWZFIFZD.mjs +22 -0
  70. package/dist/snapshot-ZWZFIFZD.mjs.map +1 -0
  71. package/dist/types-meoR-Ecp.d.mts +269 -0
  72. package/dist/types-meoR-Ecp.d.ts +269 -0
  73. package/package.json +78 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ponceca
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,692 @@
1
+ # @ponceca/firestore-sdk
2
+
3
+ SDK JavaScript/TypeScript 100% compatible con Firebase Firestore v9+ (API modular).
4
+
5
+ **Migrar desde Firebase es tan simple como cambiar el import.**
6
+
7
+ ## Instalación
8
+
9
+ ```bash
10
+ npm install @ponceca/firestore-sdk
11
+ ```
12
+
13
+ ## Importación “igual” al SDK oficial (estilo Firebase)
14
+
15
+ Ahora puedes importar igual que Firebase:
16
+
17
+ - `@ponceca/firestore-sdk/app` (equivalente a `firebase/app`)
18
+ - `@ponceca/firestore-sdk/firestore` (equivalente a `firebase/firestore`)
19
+
20
+ ## Configuración - Idéntica a Firebase
21
+
22
+ ### Opción 1: Conectar al Emulador (Recomendado para desarrollo)
23
+
24
+ ```typescript
25
+ import { initializeApp } from '@ponceca/firestore-sdk/app';
26
+ import {
27
+ getFirestore,
28
+ connectFirestoreEmulator
29
+ } from '@ponceca/firestore-sdk/firestore';
30
+
31
+ // Inicializar app (igual que Firebase)
32
+ const app = initializeApp({
33
+ projectId: 'my-project',
34
+ });
35
+
36
+ // Obtener Firestore
37
+ const db = getFirestore(app);
38
+
39
+ // Conectar al servidor local (igual que Firebase emulator)
40
+ connectFirestoreEmulator(db, 'localhost', 3000);
41
+ ```
42
+
43
+ ### Opción 2: Configuración en initializeFirestore
44
+
45
+ ```typescript
46
+ import { initializeApp } from '@ponceca/firestore-sdk/app';
47
+ import { initializeFirestore } from '@ponceca/firestore-sdk/firestore';
48
+
49
+ const app = initializeApp({
50
+ projectId: 'my-project',
51
+ });
52
+
53
+ // Configuración avanzada
54
+ const db = initializeFirestore(app, {
55
+ host: 'localhost:3000',
56
+ ssl: false,
57
+ });
58
+ ```
59
+
60
+ ### Perfil recomendado para producción (balanceado)
61
+
62
+ ```typescript
63
+ import { initializeApp } from '@ponceca/firestore-sdk/app';
64
+ import { initializeFirestore } from '@ponceca/firestore-sdk/firestore';
65
+
66
+ const app = initializeApp({
67
+ projectId: 'my-project',
68
+ });
69
+
70
+ const db = initializeFirestore(app, {
71
+ host: 'api.my-firestore-clone.com',
72
+ ssl: true,
73
+ timeout: 12000,
74
+ maxRetries: 2,
75
+ retryInitialDelayMs: 150,
76
+ retryMaxDelayMs: 2000,
77
+ });
78
+ ```
79
+
80
+ ### Opción 3: Configuración con baseUrl (Conveniente)
81
+
82
+ ```typescript
83
+ import { initializeApp } from '@ponceca/firestore-sdk/app';
84
+
85
+ const app = initializeApp({
86
+ projectId: 'my-project',
87
+ baseUrl: 'http://localhost:3000', // Auto-configura host y ssl
88
+ });
89
+
90
+ const db = getFirestore(app);
91
+ ```
92
+
93
+ ## Migrar desde Firebase
94
+
95
+ ```diff
96
+ - import { initializeApp } from 'firebase/app';
97
+ - import { getFirestore, doc, setDoc } from 'firebase/firestore';
98
+ + import { initializeApp } from '@ponceca/firestore-sdk/app';
99
+ + import { getFirestore, doc, setDoc } from '@ponceca/firestore-sdk/firestore';
100
+
101
+ const app = initializeApp({ projectId: 'my-project' });
102
+ const db = getFirestore(app);
103
+
104
+ + // Conectar a tu servidor (en desarrollo)
105
+ + connectFirestoreEmulator(db, 'localhost', 3000);
106
+
107
+ // ¡El resto del código es IDÉNTICO!
108
+ await setDoc(doc(db, 'users', 'user1'), { name: 'Juan' });
109
+ ```
110
+
111
+ ## Uso Básico
112
+
113
+ ```typescript
114
+ import { initializeApp } from '@ponceca/firestore-sdk/app';
115
+ import {
116
+ getFirestore,
117
+ connectFirestoreEmulator,
118
+ collection,
119
+ doc,
120
+ getDoc,
121
+ setDoc,
122
+ updateDoc,
123
+ deleteDoc,
124
+ addDoc,
125
+ query,
126
+ where,
127
+ orderBy,
128
+ limit,
129
+ getDocs,
130
+ onSnapshot,
131
+ serverTimestamp,
132
+ } from '@ponceca/firestore-sdk/firestore';
133
+
134
+ // 1. Inicializar app
135
+ const app = initializeApp({
136
+ projectId: 'my-project',
137
+ });
138
+
139
+ // 2. Obtener instancia de Firestore
140
+ const db = getFirestore(app);
141
+
142
+ // 3. Conectar al servidor (desarrollo local)
143
+ connectFirestoreEmulator(db, 'localhost', 3000);
144
+
145
+ // 4. Crear referencia a documento
146
+ const userRef = doc(db, 'users', 'user123');
147
+
148
+ // 5. Escribir documento
149
+ await setDoc(userRef, {
150
+ name: 'Juan García',
151
+ email: 'juan@example.com',
152
+ age: 25,
153
+ createdAt: serverTimestamp(),
154
+ });
155
+
156
+ // 6. Leer documento
157
+ const snapshot = await getDoc(userRef);
158
+ if (snapshot.exists()) {
159
+ console.log('Usuario:', snapshot.data());
160
+ }
161
+
162
+ // 7. Actualizar documento
163
+ await updateDoc(userRef, {
164
+ age: 26,
165
+ 'address.city': 'Madrid', // Actualización anidada
166
+ });
167
+
168
+ // 8. Eliminar documento
169
+ await deleteDoc(userRef);
170
+
171
+ // 9. Añadir documento con ID automático
172
+ const newDocRef = await addDoc(collection(db, 'posts'), {
173
+ title: 'Mi primer post',
174
+ content: 'Hola mundo!',
175
+ createdAt: serverTimestamp(),
176
+ });
177
+ console.log('Nuevo ID:', newDocRef.id);
178
+ ```
179
+
180
+ ## Autenticación
181
+
182
+ El SDK soporta múltiples formas de autenticación, idéntico a Firebase:
183
+
184
+ ### Opción 1: Token Manual (Simple)
185
+
186
+ ```typescript
187
+ import {
188
+ getFirestore,
189
+ setAuthToken,
190
+ getAuthToken
191
+ } from '@ponceca/firestore-sdk/firestore';
192
+
193
+ const db = getFirestore(app);
194
+
195
+ // Establecer token JWT
196
+ const token = await myAuthService.getToken();
197
+ setAuthToken(db, token);
198
+
199
+ // Verificar token actual
200
+ console.log('Token:', getAuthToken(db));
201
+
202
+ // Logout - eliminar token
203
+ setAuthToken(db, null);
204
+ ```
205
+
206
+ ### Opción 2: AuthTokenProvider (Automático - Recomendado)
207
+
208
+ Igual que Firebase Auth se integra automáticamente con Firestore:
209
+
210
+ ```typescript
211
+ import {
212
+ initializeFirestore,
213
+ type AuthTokenProvider
214
+ } from '@ponceca/firestore-sdk/firestore';
215
+
216
+ // Crear un provider que obtiene tokens automáticamente
217
+ const authProvider: AuthTokenProvider = {
218
+ // Obtiene el token actual (llamado en cada request)
219
+ async getToken() {
220
+ const user = myAuthService.currentUser;
221
+ return user ? await user.getIdToken() : null;
222
+ },
223
+
224
+ // Opcional: escucha cambios de auth
225
+ onAuthStateChanged(callback) {
226
+ return myAuthService.onAuthStateChanged((user) => {
227
+ callback(user ? user.token : null);
228
+ });
229
+ }
230
+ };
231
+
232
+ // Configurar Firestore con el provider
233
+ const db = initializeFirestore(app, {
234
+ host: 'localhost:3000',
235
+ ssl: false,
236
+ authTokenProvider: authProvider
237
+ });
238
+
239
+ // Ahora cada request obtiene el token automáticamente
240
+ await setDoc(doc(db, 'users', 'user1'), { name: 'Juan' }); // Token incluido!
241
+ ```
242
+
243
+ ### Opción 3: Integración con Firebase Auth
244
+
245
+ ```typescript
246
+ import { getAuth, onAuthStateChanged } from 'firebase/auth';
247
+ import { initializeFirestore, type AuthTokenProvider } from '@ponceca/firestore-sdk/firestore';
248
+
249
+ const firebaseAuth = getAuth();
250
+
251
+ // Provider que usa Firebase Auth
252
+ const firebaseAuthProvider: AuthTokenProvider = {
253
+ async getToken() {
254
+ const user = firebaseAuth.currentUser;
255
+ return user ? await user.getIdToken() : null;
256
+ },
257
+ onAuthStateChanged(callback) {
258
+ return onAuthStateChanged(firebaseAuth, async (user) => {
259
+ callback(user ? await user.getIdToken() : null);
260
+ });
261
+ }
262
+ };
263
+
264
+ const db = initializeFirestore(app, {
265
+ host: 'localhost:3000',
266
+ authTokenProvider: firebaseAuthProvider
267
+ });
268
+ ```
269
+
270
+ ### Opción 4: Mock Token para Testing
271
+
272
+ ```typescript
273
+ connectFirestoreEmulator(db, 'localhost', 3000, {
274
+ mockUserToken: {
275
+ sub: 'user123',
276
+ email: 'test@example.com',
277
+ // Simular claims personalizados
278
+ admin: true
279
+ }
280
+ });
281
+ ```
282
+
283
+ ## Queries
284
+
285
+ ```typescript
286
+ // Query con filtros
287
+ const q = query(
288
+ collection(db, 'users'),
289
+ where('age', '>=', 18),
290
+ where('status', '==', 'active'),
291
+ orderBy('age', 'desc'),
292
+ limit(10)
293
+ );
294
+
295
+ const querySnapshot = await getDocs(q);
296
+ querySnapshot.forEach((doc) => {
297
+ console.log(doc.id, '=>', doc.data());
298
+ });
299
+
300
+ // Operadores disponibles
301
+ where('field', '==', value); // Igual
302
+ where('field', '!=', value); // No igual
303
+ where('field', '<', value); // Menor que
304
+ where('field', '<=', value); // Menor o igual
305
+ where('field', '>', value); // Mayor que
306
+ where('field', '>=', value); // Mayor o igual
307
+ where('field', 'in', [values]); // En array
308
+ where('field', 'not-in', [values]); // No en array
309
+ where('field', 'array-contains', value); // Array contiene
310
+ where('field', 'array-contains-any', [values]); // Array contiene cualquiera
311
+ ```
312
+
313
+ ### Modo flexible (opt-in)
314
+
315
+ En Firestore original, si usas un filtro de rango (ej: `where('price', '>=', 600)`),
316
+ el primer `orderBy` debe ser sobre ese mismo campo. En PostgreSQL no es necesario.
317
+
318
+ Si quieres permitir queries más flexibles, activa el flag `allowFlexibleQueries`:
319
+
320
+ ```typescript
321
+ const db = initializeFirestore(app, {
322
+ host: 'localhost:3000',
323
+ ssl: false,
324
+ allowFlexibleQueries: true,
325
+ });
326
+
327
+ // Ahora es válido:
328
+ const q = query(
329
+ collection(db, 'products'),
330
+ where('price', '>=', 600),
331
+ orderBy('rating', 'desc') // Ordenar por campo diferente al del filtro de rango
332
+ );
333
+ ```
334
+
335
+ 📖 Ver [documentación completa del modo flexible](docs/FLEXIBLE_QUERIES.md) con más ejemplos y casos de uso.
336
+
337
+ ## Real-time Listeners
338
+
339
+ ```typescript
340
+ // Escuchar cambios en documento
341
+ const unsubscribe = onSnapshot(userRef, (snapshot) => {
342
+ if (snapshot.exists()) {
343
+ console.log('Datos actualizados:', snapshot.data());
344
+ }
345
+ });
346
+
347
+ // Escuchar cambios en query
348
+ const q = query(collection(db, 'messages'), orderBy('timestamp', 'desc'), limit(50));
349
+ const unsubscribeQuery = onSnapshot(q, (snapshot) => {
350
+ snapshot.docChanges().forEach((change) => {
351
+ if (change.type === 'added') {
352
+ console.log('Nuevo mensaje:', change.doc.data());
353
+ }
354
+ if (change.type === 'modified') {
355
+ console.log('Mensaje modificado:', change.doc.data());
356
+ }
357
+ if (change.type === 'removed') {
358
+ console.log('Mensaje eliminado:', change.doc.id);
359
+ }
360
+ });
361
+ });
362
+
363
+ // Cancelar suscripción
364
+ unsubscribe();
365
+ unsubscribeQuery();
366
+ ```
367
+
368
+ ## Field Values Especiales
369
+
370
+ ```typescript
371
+ import {
372
+ serverTimestamp,
373
+ increment,
374
+ arrayUnion,
375
+ arrayRemove,
376
+ deleteField,
377
+ } from '@ponceca/firestore-sdk/firestore';
378
+
379
+ // Timestamp del servidor
380
+ await setDoc(ref, { createdAt: serverTimestamp() });
381
+
382
+ // Incrementar valor numérico
383
+ await updateDoc(ref, { views: increment(1) });
384
+
385
+ // Añadir elementos a array (sin duplicados)
386
+ await updateDoc(ref, { tags: arrayUnion('new-tag') });
387
+
388
+ // Eliminar elementos de array
389
+ await updateDoc(ref, { tags: arrayRemove('old-tag') });
390
+
391
+ // Eliminar campo
392
+ await updateDoc(ref, { oldField: deleteField() });
393
+ ```
394
+
395
+ ## Batch Writes
396
+
397
+ ```typescript
398
+ import { writeBatch } from '@ponceca/firestore-sdk/firestore';
399
+
400
+ const batch = writeBatch(db);
401
+
402
+ batch.set(doc(db, 'cities', 'NYC'), { name: 'New York' });
403
+ batch.update(doc(db, 'cities', 'LA'), { population: 4000000 });
404
+ batch.delete(doc(db, 'cities', 'OLD'));
405
+
406
+ await batch.commit();
407
+ ```
408
+
409
+ ## Transacciones
410
+
411
+ ```typescript
412
+ import { runTransaction } from '@ponceca/firestore-sdk/firestore';
413
+
414
+ const newBalance = await runTransaction(db, async (transaction) => {
415
+ const accountRef = doc(db, 'accounts', 'account1');
416
+ const accountDoc = await transaction.get(accountRef);
417
+
418
+ if (!accountDoc.exists()) {
419
+ throw new Error('Account does not exist');
420
+ }
421
+
422
+ const currentBalance = accountDoc.data()!.balance;
423
+ const newBalance = currentBalance - 100;
424
+
425
+ if (newBalance < 0) {
426
+ throw new Error('Insufficient funds');
427
+ }
428
+
429
+ transaction.update(accountRef, { balance: newBalance });
430
+ return newBalance;
431
+ });
432
+ ```
433
+
434
+ ## Subcolecciones
435
+
436
+ ```typescript
437
+ // Crear referencia a subcolección
438
+ const postsRef = collection(db, 'users', 'user123', 'posts');
439
+
440
+ // O desde documento
441
+ const userRef = doc(db, 'users', 'user123');
442
+ const postsRef2 = collection(userRef, 'posts');
443
+
444
+ // Añadir documento a subcolección
445
+ await addDoc(postsRef, {
446
+ title: 'Mi post',
447
+ content: 'Contenido...',
448
+ });
449
+
450
+ // Query en subcolección
451
+ const recentPosts = await getDocs(
452
+ query(postsRef, orderBy('createdAt', 'desc'), limit(5))
453
+ );
454
+ ```
455
+
456
+ ## Tipos TypeScript
457
+
458
+ El SDK incluye tipos completos para TypeScript:
459
+
460
+ ```typescript
461
+ import type {
462
+ DocumentData,
463
+ DocumentReference,
464
+ CollectionReference,
465
+ Query,
466
+ DocumentSnapshot,
467
+ QuerySnapshot,
468
+ Firestore,
469
+ FieldValue,
470
+ } from '@ponceca/firestore-sdk/firestore';
471
+
472
+ // Tipos personalizados
473
+ interface User {
474
+ name: string;
475
+ email: string;
476
+ age: number;
477
+ }
478
+
479
+ const userRef = doc(db, 'users', 'user123') as DocumentReference<User>;
480
+ const snapshot = await getDoc(userRef);
481
+
482
+ if (snapshot.exists()) {
483
+ const user: User = snapshot.data()!;
484
+ console.log(user.name); // TypeScript conoce el tipo
485
+ }
486
+ ```
487
+
488
+ ## Compatibilidad con Firebase
489
+
490
+ Este SDK es un drop-in replacement para Firebase Firestore v9. Para migrar:
491
+
492
+ ```typescript
493
+ // Antes (Firebase)
494
+ import { initializeApp } from 'firebase/app';
495
+ import { getFirestore, doc, getDoc } from 'firebase/firestore';
496
+
497
+ // Después (Este SDK)
498
+ import { initializeApp } from '@ponceca/firestore-sdk/app';
499
+ import { getFirestore, doc, getDoc } from '@ponceca/firestore-sdk/firestore';
500
+ ```
501
+
502
+ ## Queries Avanzadas
503
+
504
+ ```typescript
505
+ import { and, or, collectionGroup } from '@ponceca/firestore-sdk/firestore';
506
+
507
+ // Filtros compuestos con AND/OR
508
+ const q = query(
509
+ collection(db, 'products'),
510
+ and(
511
+ where('category', '==', 'electronics'),
512
+ or(
513
+ where('price', '<', 100),
514
+ where('onSale', '==', true)
515
+ )
516
+ )
517
+ );
518
+
519
+ // Collection Group Query (buscar en todas las subcolecciones con mismo ID)
520
+ const allComments = query(
521
+ collectionGroup(db, 'comments'),
522
+ where('author', '==', 'john'),
523
+ orderBy('createdAt', 'desc'),
524
+ limit(10)
525
+ );
526
+ ```
527
+
528
+ ## Agregaciones
529
+
530
+ ```typescript
531
+ import { getCountFromServer, getAggregateFromServer, count, sum, average } from '@ponceca/firestore-sdk/firestore';
532
+
533
+ // Contar documentos
534
+ const countSnapshot = await getCountFromServer(query(collection(db, 'users')));
535
+ console.log('Total users:', countSnapshot.data().count);
536
+
537
+ // Múltiples agregaciones
538
+ const snapshot = await getAggregateFromServer(
539
+ query(collection(db, 'orders'), where('status', '==', 'completed')),
540
+ {
541
+ totalOrders: count(),
542
+ totalRevenue: sum('amount'),
543
+ avgOrderValue: average('amount')
544
+ }
545
+ );
546
+
547
+ console.log('Total orders:', snapshot.data().totalOrders);
548
+ console.log('Total revenue:', snapshot.data().totalRevenue);
549
+ console.log('Average order:', snapshot.data().avgOrderValue);
550
+ ```
551
+
552
+ ## Tipos Especiales
553
+
554
+ ```typescript
555
+ import { Timestamp, GeoPoint, Bytes, FieldPath, documentId, vector } from '@ponceca/firestore-sdk/firestore';
556
+
557
+ // Timestamp
558
+ const ts = Timestamp.now();
559
+ const tsFromDate = Timestamp.fromDate(new Date());
560
+ console.log(ts.toDate());
561
+
562
+ // GeoPoint
563
+ const location = new GeoPoint(37.7749, -122.4194);
564
+ console.log(location.latitude, location.longitude);
565
+
566
+ // Bytes
567
+ const bytes = Bytes.fromBase64String('SGVsbG8gV29ybGQ=');
568
+ console.log(bytes.toUint8Array());
569
+
570
+ // FieldPath para campos anidados
571
+ const path = new FieldPath('user', 'address', 'city');
572
+
573
+ // documentId() para filtrar por ID de documento
574
+ const q = query(
575
+ collection(db, 'users'),
576
+ where(documentId(), 'in', ['user1', 'user2', 'user3'])
577
+ );
578
+
579
+ // Vector para embeddings/ML
580
+ const embedding = vector([0.1, 0.2, 0.3, 0.4]);
581
+ await setDoc(docRef, { embedding });
582
+ ```
583
+
584
+ ## Utilidades
585
+
586
+ ```typescript
587
+ import { refEqual, queryEqual, snapshotEqual, onSnapshotsInSync } from '@ponceca/firestore-sdk/firestore';
588
+
589
+ // Comparar referencias
590
+ const ref1 = doc(db, 'users', '123');
591
+ const ref2 = doc(db, 'users', '123');
592
+ console.log(refEqual(ref1, ref2)); // true
593
+
594
+ // Comparar queries
595
+ const q1 = query(collection(db, 'users'), where('age', '>=', 18));
596
+ const q2 = query(collection(db, 'users'), where('age', '>=', 18));
597
+ console.log(queryEqual(q1, q2)); // true
598
+
599
+ // Notificación cuando todos los listeners están sincronizados
600
+ const unsubSync = onSnapshotsInSync(db, () => {
601
+ console.log('All snapshots are in sync');
602
+ });
603
+ ```
604
+
605
+ ## API Reference
606
+
607
+ ### App
608
+
609
+ - `initializeApp(options, name?)` - Inicializa la app
610
+ - `getApp(name?)` - Obtiene app existente
611
+ - `getApps()` - Lista todas las apps
612
+ - `deleteApp(app)` - Elimina una app
613
+
614
+ ### Firestore
615
+
616
+ - `getFirestore(app?)` - Obtiene instancia de Firestore
617
+ - `initializeFirestore(app, settings)` - Inicializa con configuración
618
+ - `terminate(firestore)` - Termina instancia
619
+
620
+ ### Referencias
621
+
622
+ - `doc(firestore, path, ...segments)` - Referencia a documento
623
+ - `collection(firestore, path, ...segments)` - Referencia a colección
624
+ - `collectionGroup(firestore, collectionId)` - Query sobre grupo de colecciones
625
+
626
+ ### CRUD
627
+
628
+ - `getDoc(ref)` - Lee documento
629
+ - `setDoc(ref, data, options?)` - Escribe documento
630
+ - `updateDoc(ref, data)` - Actualiza campos
631
+ - `deleteDoc(ref)` - Elimina documento
632
+ - `addDoc(collectionRef, data)` - Añade con ID auto
633
+
634
+ ### Queries
635
+
636
+ - `query(ref, ...constraints)` - Crea query
637
+ - `where(field, op, value)` - Filtro
638
+ - `orderBy(field, direction?)` - Ordenar
639
+ - `limit(n)` - Limitar resultados
640
+ - `limitToLast(n)` - Últimos N
641
+ - `startAt(...values)` - Cursor inicio inclusivo
642
+ - `startAfter(...values)` - Cursor inicio exclusivo
643
+ - `endAt(...values)` - Cursor fin inclusivo
644
+ - `endBefore(...values)` - Cursor fin exclusivo
645
+ - `and(...constraints)` - Filtro AND compuesto
646
+ - `or(...constraints)` - Filtro OR compuesto
647
+ - `getDocs(query)` - Ejecuta query
648
+
649
+ ### Real-time
650
+
651
+ - `onSnapshot(ref, onNext, onError?)` - Suscribe a cambios
652
+ - `onSnapshotsInSync(firestore, callback)` - Sincronización de listeners
653
+
654
+ ### Agregaciones
655
+
656
+ - `getCountFromServer(query)` - Cuenta documentos
657
+ - `getAggregateFromServer(query, spec)` - Múltiples agregaciones
658
+ - `count()` - Campo de conteo
659
+ - `sum(field)` - Campo de suma
660
+ - `average(field)` - Campo de promedio
661
+
662
+ ### Field Values
663
+
664
+ - `serverTimestamp()` - Timestamp del servidor
665
+ - `increment(n)` - Incrementar número
666
+ - `arrayUnion(...values)` - Añadir a array
667
+ - `arrayRemove(...values)` - Eliminar de array
668
+ - `deleteField()` - Eliminar campo
669
+
670
+ ### Tipos Especiales
671
+
672
+ - `Timestamp` - Clase para timestamps
673
+ - `GeoPoint` - Clase para coordenadas
674
+ - `Bytes` - Clase para datos binarios
675
+ - `FieldPath` - Clase para paths de campos
676
+ - `VectorValue` / `vector()` - Para embeddings/ML
677
+ - `documentId()` - Referencia al ID de documento
678
+
679
+ ### Batch & Transactions
680
+
681
+ - `writeBatch(firestore)` - Crea batch
682
+ - `runTransaction(firestore, fn, options?)` - Ejecuta transacción
683
+
684
+ ### Utilidades
685
+
686
+ - `refEqual(ref1, ref2)` - Compara referencias
687
+ - `queryEqual(q1, q2)` - Compara queries
688
+ - `snapshotEqual(s1, s2)` - Compara snapshots
689
+
690
+ ## Licencia
691
+
692
+ MIT