fire2mongo 1.0.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/README.md +651 -0
- package/dist/index.d.mts +317 -0
- package/dist/index.d.ts +317 -0
- package/dist/index.js +576 -0
- package/dist/index.mjs +518 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,651 @@
|
|
|
1
|
+
# fire2mongo
|
|
2
|
+
|
|
3
|
+
Drop-in TypeScript replacement for `firebase/firestore` backed by MongoDB.
|
|
4
|
+
Change one import line — the rest of your code stays identical.
|
|
5
|
+
|
|
6
|
+
```ts
|
|
7
|
+
// Before
|
|
8
|
+
import { doc, getDoc, query, where } from 'firebase/firestore';
|
|
9
|
+
|
|
10
|
+
// After
|
|
11
|
+
import { doc, getDoc, query, where } from 'fire2mongo';
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Table of Contents
|
|
17
|
+
|
|
18
|
+
1. [Installation](#installation)
|
|
19
|
+
2. [Setup](#setup)
|
|
20
|
+
3. [Quick Migration](#quick-migration)
|
|
21
|
+
4. [API Reference](#api-reference)
|
|
22
|
+
- [References](#references)
|
|
23
|
+
- [Reading Data](#reading-data)
|
|
24
|
+
- [Writing Data](#writing-data)
|
|
25
|
+
- [Querying](#querying)
|
|
26
|
+
- [FieldValue](#fieldvalue)
|
|
27
|
+
- [Timestamp](#timestamp)
|
|
28
|
+
- [Batch Writes](#batch-writes)
|
|
29
|
+
- [Transactions](#transactions)
|
|
30
|
+
5. [Subcollections](#subcollections)
|
|
31
|
+
6. [Collection Registry](#collection-registry)
|
|
32
|
+
7. [Connection Management](#connection-management)
|
|
33
|
+
8. [Known Limitations](#known-limitations)
|
|
34
|
+
9. [Publishing](#publishing)
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm install fire2mongo mongodb
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
> `mongodb` is a peer dependency — install it alongside fire2mongo.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Setup
|
|
49
|
+
|
|
50
|
+
### 1. Initialize the connection
|
|
51
|
+
|
|
52
|
+
Call `initMongoDB()` once at app startup. It is safe to call multiple times — the connection is cached after the first call.
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import { initMongoDB } from 'fire2mongo';
|
|
56
|
+
|
|
57
|
+
await initMongoDB({
|
|
58
|
+
uri: process.env.MONGODB_URI!,
|
|
59
|
+
dbName: process.env.MONGODB_DB!,
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Next.js App Router** — call it in a shared singleton file (e.g. `lib/mongodb.ts`) imported by your route handlers:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
// lib/mongodb.ts
|
|
67
|
+
import { initMongoDB } from 'fire2mongo';
|
|
68
|
+
|
|
69
|
+
let initialized = false;
|
|
70
|
+
|
|
71
|
+
export async function ensureDb() {
|
|
72
|
+
if (initialized) return;
|
|
73
|
+
await initMongoDB({ uri: process.env.MONGODB_URI!, dbName: process.env.MONGODB_DB! });
|
|
74
|
+
initialized = true;
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 2. Register your collections
|
|
79
|
+
|
|
80
|
+
Every Firebase collection name must be registered before use. Do this once — in the same startup file or a dedicated `collections.ts`.
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import { registerCollections } from 'fire2mongo';
|
|
84
|
+
|
|
85
|
+
registerCollections({
|
|
86
|
+
users: 'users',
|
|
87
|
+
orders: 'orders',
|
|
88
|
+
products: 'inventory_items', // Firebase name → actual MongoDB collection name
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Or one at a time:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { registerCollection } from 'fire2mongo';
|
|
96
|
+
|
|
97
|
+
registerCollection('users');
|
|
98
|
+
registerCollection('products', { mongoCollection: 'inventory_items' });
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
If you call `getDoc` on an unregistered collection, fire2mongo throws a clear error listing every registered name:
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
[fire2mongo] No collection registered for "items".
|
|
105
|
+
Registered: users, orders, products.
|
|
106
|
+
Call registerCollection("items") during app startup.
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Quick Migration
|
|
112
|
+
|
|
113
|
+
The only thing you need to change in existing Firebase code is the import path (and remove the Firebase `db` import).
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
// ── BEFORE ──────────────────────────────────────────────────────────────────
|
|
117
|
+
import {
|
|
118
|
+
doc, collection, getDoc, getDocs, setDoc, addDoc, updateDoc, deleteDoc,
|
|
119
|
+
query, where, orderBy, limit,
|
|
120
|
+
Timestamp, arrayUnion, arrayRemove, increment,
|
|
121
|
+
} from 'firebase/firestore';
|
|
122
|
+
import { db } from '@/lib/firebase';
|
|
123
|
+
|
|
124
|
+
// ── AFTER ────────────────────────────────────────────────────────────────────
|
|
125
|
+
import {
|
|
126
|
+
doc, collection, getDoc, getDocs, setDoc, addDoc, updateDoc, deleteDoc,
|
|
127
|
+
query, where, orderBy, limit,
|
|
128
|
+
Timestamp, arrayUnion, arrayRemove, increment,
|
|
129
|
+
} from 'fire2mongo';
|
|
130
|
+
import { db } from 'fire2mongo'; // db = {} no-op — kept so existing code compiles
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Everything below the import line is unchanged.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## API Reference
|
|
138
|
+
|
|
139
|
+
### References
|
|
140
|
+
|
|
141
|
+
#### `doc(db, collection, id)`
|
|
142
|
+
|
|
143
|
+
Returns a `DocumentReference` for a specific document.
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
import { doc, db } from 'fire2mongo';
|
|
147
|
+
|
|
148
|
+
const userRef = doc(db, 'users', userId);
|
|
149
|
+
const orderRef = doc(db, 'orders', orderId);
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
#### `doc(collectionRef)` — auto-generate ID
|
|
153
|
+
|
|
154
|
+
Pass a `CollectionReference` (no ID) to get a reference with an auto-generated ObjectId.
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
const newRef = doc(collection(db, 'users'));
|
|
158
|
+
console.log(newRef.id); // auto-generated ObjectId string
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
#### `collection(db, path)`
|
|
162
|
+
|
|
163
|
+
Returns a `CollectionReference`.
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
import { collection, db } from 'fire2mongo';
|
|
167
|
+
|
|
168
|
+
const usersCol = collection(db, 'users');
|
|
169
|
+
const ordersCol = collection(db, 'orders');
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
### Reading Data
|
|
175
|
+
|
|
176
|
+
#### `getDoc(ref)` — single document
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
import { doc, getDoc, db } from 'fire2mongo';
|
|
180
|
+
|
|
181
|
+
const snap = await getDoc(doc(db, 'users', userId));
|
|
182
|
+
|
|
183
|
+
if (snap.exists()) {
|
|
184
|
+
const user = snap.data(); // typed as your document type
|
|
185
|
+
console.log(snap.id); // document id string
|
|
186
|
+
} else {
|
|
187
|
+
console.log('Not found');
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
With generics:
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
interface User { name: string; email: string; role: string; }
|
|
195
|
+
|
|
196
|
+
const snap = await getDoc<User>(doc(db, 'users', userId));
|
|
197
|
+
const user = snap.data(); // User | undefined
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
#### `getDocs(queryOrRef)` — multiple documents
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
import { collection, getDocs, db } from 'fire2mongo';
|
|
204
|
+
|
|
205
|
+
// Entire collection
|
|
206
|
+
const snap = await getDocs(collection(db, 'users'));
|
|
207
|
+
|
|
208
|
+
snap.forEach(doc => {
|
|
209
|
+
console.log(doc.id, doc.data());
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
console.log(snap.size); // number of documents
|
|
213
|
+
console.log(snap.empty); // boolean
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
With a query:
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
import { query, where, orderBy, limit, getDocs, collection, db } from 'fire2mongo';
|
|
220
|
+
|
|
221
|
+
const q = query(
|
|
222
|
+
collection(db, 'orders'),
|
|
223
|
+
where('status', '==', 'PENDING'),
|
|
224
|
+
orderBy('createdAt', 'desc'),
|
|
225
|
+
limit(20),
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
const snap = await getDocs(q);
|
|
229
|
+
const orders = snap.docs.map(d => d.data());
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
### Writing Data
|
|
235
|
+
|
|
236
|
+
#### `setDoc(ref, data)` — create or overwrite
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
import { doc, setDoc, db } from 'fire2mongo';
|
|
240
|
+
|
|
241
|
+
await setDoc(doc(db, 'users', userId), {
|
|
242
|
+
name: 'Alice',
|
|
243
|
+
email: 'alice@example.com',
|
|
244
|
+
createdAt: Timestamp.now(),
|
|
245
|
+
});
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
#### `setDoc(ref, data, { merge: true })` — partial update (merge)
|
|
249
|
+
|
|
250
|
+
Only the provided fields are updated. Existing fields not in `data` are left unchanged.
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
await setDoc(doc(db, 'users', userId), { name: 'Alice Updated' }, { merge: true });
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
#### `addDoc(collectionRef, data)` — create with auto-generated ID
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
import { collection, addDoc, db } from 'fire2mongo';
|
|
260
|
+
|
|
261
|
+
const ref = await addDoc(collection(db, 'orders'), {
|
|
262
|
+
customerId: 'cust_123',
|
|
263
|
+
total: 4500,
|
|
264
|
+
status: 'PENDING',
|
|
265
|
+
createdAt: Timestamp.now(),
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
console.log('New order ID:', ref.id);
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
#### `updateDoc(ref, partialData)` — partial update
|
|
272
|
+
|
|
273
|
+
Only the provided fields are updated. Does not overwrite the entire document.
|
|
274
|
+
|
|
275
|
+
```ts
|
|
276
|
+
import { doc, updateDoc, db } from 'fire2mongo';
|
|
277
|
+
|
|
278
|
+
await updateDoc(doc(db, 'orders', orderId), {
|
|
279
|
+
status: 'SHIPPED',
|
|
280
|
+
shippedAt: Timestamp.now(),
|
|
281
|
+
});
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
#### `deleteDoc(ref)` — delete a document
|
|
285
|
+
|
|
286
|
+
```ts
|
|
287
|
+
import { doc, deleteDoc, db } from 'fire2mongo';
|
|
288
|
+
|
|
289
|
+
await deleteDoc(doc(db, 'orders', orderId));
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
### Querying
|
|
295
|
+
|
|
296
|
+
Build queries with `query()` and any combination of `where`, `orderBy`, `limit`, and `offset`.
|
|
297
|
+
|
|
298
|
+
```ts
|
|
299
|
+
import { query, where, orderBy, limit, offset } from 'fire2mongo';
|
|
300
|
+
|
|
301
|
+
const q = query(
|
|
302
|
+
collection(db, 'orders'),
|
|
303
|
+
where('status', '==', 'ACTIVE'),
|
|
304
|
+
orderBy('createdAt', 'desc'),
|
|
305
|
+
limit(10),
|
|
306
|
+
offset(20), // skip first 20
|
|
307
|
+
);
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
#### `where(field, operator, value)`
|
|
311
|
+
|
|
312
|
+
| Operator | MongoDB equivalent |
|
|
313
|
+
|---|---|
|
|
314
|
+
| `'=='` | `{ $eq }` |
|
|
315
|
+
| `'!='` | `{ $ne }` |
|
|
316
|
+
| `'<'` | `{ $lt }` |
|
|
317
|
+
| `'<='` | `{ $lte }` |
|
|
318
|
+
| `'>'` | `{ $gt }` |
|
|
319
|
+
| `'>='` | `{ $gte }` |
|
|
320
|
+
| `'in'` | `{ $in }` |
|
|
321
|
+
| `'not-in'` | `{ $nin }` |
|
|
322
|
+
| `'array-contains'` | `{ $elemMatch: { $eq } }` |
|
|
323
|
+
| `'array-contains-any'` | `{ $in }` |
|
|
324
|
+
|
|
325
|
+
```ts
|
|
326
|
+
where('status', '==', 'ACTIVE')
|
|
327
|
+
where('age', '>=', 18)
|
|
328
|
+
where('role', 'in', ['admin', 'manager'])
|
|
329
|
+
where('tags', 'array-contains', 'premium')
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
#### `and(...constraints)` — explicit AND grouping
|
|
333
|
+
|
|
334
|
+
```ts
|
|
335
|
+
import { and } from 'fire2mongo';
|
|
336
|
+
|
|
337
|
+
const q = query(
|
|
338
|
+
collection(db, 'users'),
|
|
339
|
+
and(
|
|
340
|
+
where('role', '==', 'staff'),
|
|
341
|
+
where('branch', '==', 'HQ'),
|
|
342
|
+
),
|
|
343
|
+
);
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
#### `or(...constraints)` — OR grouping
|
|
347
|
+
|
|
348
|
+
```ts
|
|
349
|
+
import { or } from 'fire2mongo';
|
|
350
|
+
|
|
351
|
+
const q = query(
|
|
352
|
+
collection(db, 'orders'),
|
|
353
|
+
or(
|
|
354
|
+
where('status', '==', 'PENDING'),
|
|
355
|
+
where('status', '==', 'PROCESSING'),
|
|
356
|
+
),
|
|
357
|
+
);
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
#### `orderBy(field, direction?)`
|
|
361
|
+
|
|
362
|
+
```ts
|
|
363
|
+
orderBy('createdAt', 'desc') // newest first
|
|
364
|
+
orderBy('name', 'asc') // alphabetical (default)
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
#### `limit(n)` / `offset(n)`
|
|
368
|
+
|
|
369
|
+
```ts
|
|
370
|
+
limit(10) // return max 10 documents
|
|
371
|
+
offset(20) // skip first 20 documents (use for pagination)
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
### FieldValue
|
|
377
|
+
|
|
378
|
+
Atomic write operations — use inside `updateDoc` or `setDoc`.
|
|
379
|
+
|
|
380
|
+
#### `arrayUnion(...elements)` — add to array (no duplicates)
|
|
381
|
+
|
|
382
|
+
```ts
|
|
383
|
+
import { arrayUnion, updateDoc, doc, db } from 'fire2mongo';
|
|
384
|
+
|
|
385
|
+
await updateDoc(doc(db, 'users', userId), {
|
|
386
|
+
tags: arrayUnion('premium', 'verified'),
|
|
387
|
+
});
|
|
388
|
+
// MongoDB: $addToSet: { $each: ['premium', 'verified'] }
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
#### `arrayRemove(...elements)` — remove from array
|
|
392
|
+
|
|
393
|
+
```ts
|
|
394
|
+
import { arrayRemove } from 'fire2mongo';
|
|
395
|
+
|
|
396
|
+
await updateDoc(doc(db, 'users', userId), {
|
|
397
|
+
tags: arrayRemove('trial'),
|
|
398
|
+
});
|
|
399
|
+
// MongoDB: $pull: { $in: ['trial'] }
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
#### `increment(n)` — increment a numeric field
|
|
403
|
+
|
|
404
|
+
```ts
|
|
405
|
+
import { increment } from 'fire2mongo';
|
|
406
|
+
|
|
407
|
+
await updateDoc(doc(db, 'posts', postId), {
|
|
408
|
+
viewCount: increment(1),
|
|
409
|
+
likeCount: increment(-1),
|
|
410
|
+
});
|
|
411
|
+
// MongoDB: $inc: { viewCount: 1, likeCount: -1 }
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
You can mix FieldValue operations with regular field updates in a single call:
|
|
415
|
+
|
|
416
|
+
```ts
|
|
417
|
+
await updateDoc(ref, {
|
|
418
|
+
status: 'ACTIVE', // regular $set
|
|
419
|
+
tags: arrayUnion('vip'), // $addToSet
|
|
420
|
+
loginCount: increment(1), // $inc
|
|
421
|
+
});
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
---
|
|
425
|
+
|
|
426
|
+
### Timestamp
|
|
427
|
+
|
|
428
|
+
Firebase-compatible `Timestamp` class. Stored as a JavaScript `Date` in MongoDB.
|
|
429
|
+
|
|
430
|
+
```ts
|
|
431
|
+
import { Timestamp } from 'fire2mongo';
|
|
432
|
+
|
|
433
|
+
// Create
|
|
434
|
+
const now = Timestamp.now();
|
|
435
|
+
const fromDate = Timestamp.fromDate(new Date('2025-01-01'));
|
|
436
|
+
const fromMs = Timestamp.fromMillis(Date.now());
|
|
437
|
+
|
|
438
|
+
// Convert
|
|
439
|
+
now.toDate() // → Date
|
|
440
|
+
now.toMillis() // → number (epoch ms)
|
|
441
|
+
now.seconds // → number
|
|
442
|
+
now.nanoseconds // → number
|
|
443
|
+
|
|
444
|
+
// Compare
|
|
445
|
+
now.isEqual(fromMs) // → boolean
|
|
446
|
+
|
|
447
|
+
// Use in writes
|
|
448
|
+
await setDoc(ref, {
|
|
449
|
+
createdAt: Timestamp.now(),
|
|
450
|
+
scheduledAt: Timestamp.fromDate(new Date('2025-12-31')),
|
|
451
|
+
});
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
---
|
|
455
|
+
|
|
456
|
+
### Batch Writes
|
|
457
|
+
|
|
458
|
+
Queue multiple write operations and execute them together. All ops run in parallel via `Promise.all`.
|
|
459
|
+
|
|
460
|
+
> **Note:** Batch writes are **not atomic**. If one operation fails, others may already have applied. For atomic operations, use MongoDB sessions directly.
|
|
461
|
+
|
|
462
|
+
```ts
|
|
463
|
+
import { writeBatch, doc, db } from 'fire2mongo';
|
|
464
|
+
|
|
465
|
+
const batch = writeBatch(db);
|
|
466
|
+
|
|
467
|
+
batch.set(doc(db, 'users', 'user1'), { name: 'Alice', status: 'ACTIVE' });
|
|
468
|
+
batch.update(doc(db, 'orders', 'order1'), { status: 'SHIPPED' });
|
|
469
|
+
batch.delete(doc(db, 'drafts', 'draft1'));
|
|
470
|
+
|
|
471
|
+
await batch.commit();
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
---
|
|
475
|
+
|
|
476
|
+
### Transactions
|
|
477
|
+
|
|
478
|
+
Best-effort transaction — reads are immediate, writes are collected and executed in parallel after the callback resolves.
|
|
479
|
+
|
|
480
|
+
> **Note:** This is **not true ACID**. There is no rollback on failure. For true atomic transactions use the MongoDB driver directly with sessions.
|
|
481
|
+
|
|
482
|
+
```ts
|
|
483
|
+
import { runTransaction, doc, db } from 'fire2mongo';
|
|
484
|
+
|
|
485
|
+
await runTransaction(db, async (tx) => {
|
|
486
|
+
const snap = await tx.get(doc(db, 'counters', 'global'));
|
|
487
|
+
const current = snap.data()?.value ?? 0;
|
|
488
|
+
|
|
489
|
+
tx.update(doc(db, 'counters', 'global'), { value: current + 1 });
|
|
490
|
+
});
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
---
|
|
494
|
+
|
|
495
|
+
## Subcollections
|
|
496
|
+
|
|
497
|
+
Firebase subcollections are mapped to flat MongoDB collections using `_` as a separator. Parent IDs are automatically injected into documents on every write — no manual wiring needed.
|
|
498
|
+
|
|
499
|
+
### Naming rule
|
|
500
|
+
|
|
501
|
+
```
|
|
502
|
+
Firebase path MongoDB collection Auto-injected fields
|
|
503
|
+
────────────────────────────────────── ──────────────────────── ────────────────────
|
|
504
|
+
orders/{orderId}/items orders_items ordersId
|
|
505
|
+
users/{userId}/sessions users_sessions usersId
|
|
506
|
+
users/{userId}/sessions/{id}/logs users_sessions_logs usersId, sessionsId
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### Reading a subcollection
|
|
510
|
+
|
|
511
|
+
```ts
|
|
512
|
+
import { collection, getDocs, query, where, db } from 'fire2mongo';
|
|
513
|
+
|
|
514
|
+
// All items for a specific order
|
|
515
|
+
const itemsCol = collection(db, 'orders', orderId, 'items');
|
|
516
|
+
const snap = await getDocs(itemsCol);
|
|
517
|
+
|
|
518
|
+
// With a filter
|
|
519
|
+
const q = query(itemsCol, where('status', '==', 'ACTIVE'));
|
|
520
|
+
const snap = await getDocs(q);
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
### Writing to a subcollection
|
|
524
|
+
|
|
525
|
+
```ts
|
|
526
|
+
import { collection, addDoc, doc, setDoc, db } from 'fire2mongo';
|
|
527
|
+
|
|
528
|
+
// addDoc — auto ID, ordersId injected automatically
|
|
529
|
+
const ref = await addDoc(collection(db, 'orders', orderId, 'items'), {
|
|
530
|
+
productId: 'prod_123',
|
|
531
|
+
quantity: 2,
|
|
532
|
+
price: 1500,
|
|
533
|
+
});
|
|
534
|
+
// Stored as: { _id: <ObjectId>, ordersId: orderId, productId: ..., quantity: ..., price: ... }
|
|
535
|
+
|
|
536
|
+
// setDoc — specific ID
|
|
537
|
+
await setDoc(doc(db, 'orders', orderId, 'items', itemId), {
|
|
538
|
+
productId: 'prod_456',
|
|
539
|
+
quantity: 1,
|
|
540
|
+
});
|
|
541
|
+
// Stored as: { _id: itemId, ordersId: orderId, ... }
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
### Register subcollection names
|
|
545
|
+
|
|
546
|
+
Subcollections use the flattened MongoDB collection name for registration:
|
|
547
|
+
|
|
548
|
+
```ts
|
|
549
|
+
registerCollections({
|
|
550
|
+
orders: 'orders',
|
|
551
|
+
orders_items: 'orders_items', // subcollection
|
|
552
|
+
});
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
---
|
|
556
|
+
|
|
557
|
+
## Collection Registry
|
|
558
|
+
|
|
559
|
+
The registry maps Firebase collection names (used in `doc()` / `collection()` calls) to actual MongoDB collection names. This allows your Firebase-style code to stay unchanged even if MongoDB uses different collection names.
|
|
560
|
+
|
|
561
|
+
```ts
|
|
562
|
+
import { registerCollection, registerCollections, hasCollection, clearRegistry } from 'fire2mongo';
|
|
563
|
+
|
|
564
|
+
// Register one
|
|
565
|
+
registerCollection('users');
|
|
566
|
+
registerCollection('products', { mongoCollection: 'inventory_items' });
|
|
567
|
+
|
|
568
|
+
// Register many at once
|
|
569
|
+
registerCollections({
|
|
570
|
+
users: 'users',
|
|
571
|
+
orders: 'kms_orders',
|
|
572
|
+
products: 'inventory_items',
|
|
573
|
+
orders_items: 'orders_items',
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
// Check if registered
|
|
577
|
+
hasCollection('users') // true
|
|
578
|
+
hasCollection('unknown') // false
|
|
579
|
+
|
|
580
|
+
// Clear all (useful in tests)
|
|
581
|
+
clearRegistry();
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
---
|
|
585
|
+
|
|
586
|
+
## Connection Management
|
|
587
|
+
|
|
588
|
+
```ts
|
|
589
|
+
import { initMongoDB, getDb, closeMongoDB } from 'fire2mongo';
|
|
590
|
+
|
|
591
|
+
// Initialize (call once at startup)
|
|
592
|
+
await initMongoDB({ uri: 'mongodb://...', dbName: 'mydb' });
|
|
593
|
+
|
|
594
|
+
// Access the raw Db instance (for advanced MongoDB operations)
|
|
595
|
+
const db = getDb();
|
|
596
|
+
const col = db.collection('my_collection');
|
|
597
|
+
|
|
598
|
+
// Close (graceful shutdown / tests)
|
|
599
|
+
await closeMongoDB();
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
`getDb()` throws if `initMongoDB()` has not been called:
|
|
603
|
+
|
|
604
|
+
```
|
|
605
|
+
[fire2mongo] MongoDB not initialized. Call initMongoDB() before using any firestore functions.
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
---
|
|
609
|
+
|
|
610
|
+
## Known Limitations
|
|
611
|
+
|
|
612
|
+
| Limitation | Detail |
|
|
613
|
+
|---|---|
|
|
614
|
+
| **Server-side only** | Uses the `mongodb` Node.js driver. Not compatible with browser or edge runtimes. |
|
|
615
|
+
| **No real-time listeners** | `onSnapshot` is not implemented. |
|
|
616
|
+
| **Batch is not atomic** | `writeBatch.commit()` runs ops via `Promise.all` — no session or rollback. |
|
|
617
|
+
| **Transaction is not ACID** | `runTransaction` collects writes and runs them in parallel. No rollback on failure. |
|
|
618
|
+
| **`setDoc` checks existence** | Without `{ merge: true }`, `setDoc` calls `findOne` first to decide between insert and replace. |
|
|
619
|
+
| **No cursor pagination** | `startAfter()` is not supported. Use `offset()` for page-based pagination. |
|
|
620
|
+
| **ObjectId IDs** | Auto-generated IDs use MongoDB `ObjectId`. Firebase UID strings are stored as-is in the `id` field. |
|
|
621
|
+
|
|
622
|
+
---
|
|
623
|
+
|
|
624
|
+
## Publishing
|
|
625
|
+
|
|
626
|
+
```bash
|
|
627
|
+
# Build CJS + ESM + type declarations
|
|
628
|
+
npm run build
|
|
629
|
+
|
|
630
|
+
# Verify types
|
|
631
|
+
npm run typecheck
|
|
632
|
+
|
|
633
|
+
# Publish to npm
|
|
634
|
+
npm publish --access public
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
Build output in `dist/`:
|
|
638
|
+
|
|
639
|
+
```
|
|
640
|
+
dist/
|
|
641
|
+
index.js — CommonJS
|
|
642
|
+
index.mjs — ES Module
|
|
643
|
+
index.d.ts — TypeScript declarations (CJS)
|
|
644
|
+
index.d.mts — TypeScript declarations (ESM)
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
---
|
|
648
|
+
|
|
649
|
+
## License
|
|
650
|
+
|
|
651
|
+
MIT
|