@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
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
|