|
1 | 1 | # Firestore DB ORM |
2 | 2 |
|
3 | | -`firestore-db-orm` is a simple ORM library for Firestore, designed to provide easy-to-use, TypeScript-typed CRUD |
4 | | -operations. |
| 3 | +`firestore-db-orm` is a lightweight, TypeScript-first Object-Relational Mapper (ORM) for Google Firestore. It simplifies interactions with your Firestore database by providing an intuitive, promise-based API for common CRUD (Create, Read, Update, Delete) operations and flexible querying. |
| 4 | + |
| 5 | +## Table of Contents |
| 6 | + |
| 7 | +- [Features](#features) |
| 8 | +- [Installation](#installation) |
| 9 | +- [Instantiate the ORM](#instantiate-the-orm) |
| 10 | +- [CRUD Operations](#crud-operations) |
| 11 | + - [`add(data: T): Promise<T>`](#adddata-t-promiset) |
| 12 | + - [`get(id: string): Promise<T | undefined>`](#getid-string-promiset--undefined) |
| 13 | + - [`update(id: string, data: Partial<T>): Promise<void>`](#updateid-string-data-partialt-promisevoid) |
| 14 | + - [`delete(id: string): Promise<void>`](#deleteid-string-promisevoid) |
| 15 | + - [`findOne(searchParams: SearchParams): Promise<T | undefined>`](#findonesearchparams-searchparams-promiset--undefined) |
| 16 | +- [Finding Multiple Documents (`finds` Method)](#finding-multiple-documents-finds-method) |
| 17 | + - [Examples of `finds` Method](#examples-of-finds-method) |
| 18 | +- [Advanced Usage (Future Enhancements)](#advanced-usage-future-enhancements) |
| 19 | +- [Contributing](#contributing) |
| 20 | +- [License](#license) |
| 21 | + |
| 22 | +## Features |
| 23 | + |
| 24 | +* **Type-Safe:** Leverages TypeScript for strong typing of your data models. |
| 25 | +* **Simple API:** Easy-to-understand methods for CRUD operations. |
| 26 | +* **Flexible Querying:** Supports various query conditions for finding documents. |
| 27 | +* **Automatic ID Generation:** Automatically handles `id` generation for new documents (using `uuid`). |
| 28 | +* **Promise-based:** Asynchronous operations using Promises for modern JavaScript. |
5 | 29 |
|
6 | 30 | ## Installation |
7 | 31 |
|
8 | | -Install firebase-db-orm with bun |
| 32 | +Install `firestore-db-orm` using your preferred package manager: |
9 | 33 |
|
| 34 | +**bun** |
10 | 35 | ```bash |
11 | 36 | bun add firestore-db-orm |
12 | 37 | ``` |
13 | 38 |
|
| 39 | +**npm** |
| 40 | +```bash |
| 41 | +npm install firestore-db-orm |
| 42 | +``` |
| 43 | + |
| 44 | +**yarn** |
| 45 | +```bash |
| 46 | +yarn add firestore-db-orm |
| 47 | +``` |
| 48 | + |
14 | 49 | ## Instantiate the ORM |
15 | 50 |
|
16 | 51 | ```typescript |
| 52 | +import { initializeApp, cert } from 'firebase-admin/app'; |
| 53 | +import { getFirestore } from 'firebase-admin/firestore'; |
17 | 54 | import { FirestoreORM } from "firestore-db-orm"; |
18 | | -import { db } from "./firestore"; |
19 | | -import type { IUser } from "./user.interface"; |
20 | | - |
| 55 | +import type { IUser } from "./user.interface"; // Your data model interface |
| 56 | + |
| 57 | +// Initialize Firebase Admin SDK |
| 58 | +// Ensure you have your service account key JSON file |
| 59 | +// (e.g., serviceAccountKey.json) in your project. |
| 60 | +// Replace with your actual service account key path and project details. |
| 61 | +const serviceAccount = require('./path/to/your/serviceAccountKey.json'); |
| 62 | + |
| 63 | +initializeApp({ |
| 64 | + credential: cert(serviceAccount), |
| 65 | + // databaseURL: 'https://<YOUR_PROJECT_ID>.firebaseio.com' // Optional: if not using default |
| 66 | +}); |
| 67 | + |
| 68 | +// Get Firestore instance |
| 69 | +const db = getFirestore(); |
| 70 | + |
| 71 | +// Instantiate the ORM |
| 72 | +// Parameters: |
| 73 | +// 1. db: The Firestore instance. |
| 74 | +// 2. modelCollection: The name of the Firestore collection (e.g., "users"). |
21 | 75 | const userORM = new FirestoreORM<IUser>(db, "users"); |
| 76 | + |
22 | 77 | export default userORM; |
23 | 78 | ``` |
24 | 79 |
|
25 | | -## Complete Example |
| 80 | +**Explanation:** |
| 81 | + |
| 82 | +* **`IUser`**: This is a TypeScript interface defining the structure of your data (e.g., for a "users" collection). You should replace this with your actual data model. |
| 83 | +* **`db`**: This is your initialized Firestore database instance. The example shows how to initialize it using `firebase-admin`. Make sure you have the `firebase-admin` package installed and configured with your Firebase project credentials. |
| 84 | +* **`"users"`**: This string is the name of the Firestore collection where your data will be stored. Replace `"users"` with the actual name of your collection. |
| 85 | + |
| 86 | +## CRUD Operations |
| 87 | + |
| 88 | +The ORM provides methods for common Create, Read, Update, and Delete (CRUD) operations. |
| 89 | + |
| 90 | +### `add(data: T): Promise<T>` |
| 91 | + |
| 92 | +Adds a new document to the collection. The `id` field is automatically generated (using `uuid`) and added to the data before saving. |
| 93 | + |
| 94 | +```typescript |
| 95 | +import userORM from "./user.orm"; // Assuming user.orm.ts is set up as shown above |
| 96 | +import { v4 } from "uuid"; // Or use any ID generation strategy |
| 97 | + |
| 98 | +(async () => { |
| 99 | + try { |
| 100 | + const newUser = await userORM.add({ |
| 101 | + // id: v4(), // ID is auto-generated, but you can pre-define if needed for your model |
| 102 | + email: "test@example.com", |
| 103 | + password: "securepassword123", |
| 104 | + // Add other fields as per your IUser interface |
| 105 | + }); |
| 106 | + console.log("New user created:", newUser); |
| 107 | + } catch (error) { |
| 108 | + console.error("Error adding user:", error); |
| 109 | + } |
| 110 | +})(); |
| 111 | +``` |
| 112 | + |
| 113 | +### `get(id: string): Promise<T | undefined>` |
| 114 | + |
| 115 | +Retrieves a document by its ID. Returns the document data if found, otherwise `undefined`. |
26 | 116 |
|
27 | 117 | ```typescript |
28 | 118 | import userORM from "./user.orm"; |
29 | | -import { v4 } from "uuid"; |
30 | 119 |
|
31 | 120 | (async () => { |
32 | | - const newUser = await userORM.add({ |
33 | | - id: v4(), |
34 | | - email: "test@example.com", |
35 | | - password: "securepassword", |
36 | | - }); |
37 | | - console.log("New user created:", newUser); |
38 | | - |
39 | | - const user = await userORM.get(newUser.id); |
40 | | - if (user) { |
41 | | - console.log("User found:", user); |
| 121 | + try { |
| 122 | + const userId = "some-user-id"; // Replace with an actual ID |
| 123 | + const user = await userORM.get(userId); |
| 124 | + |
| 125 | + if (user) { |
| 126 | + console.log("User found:", user); |
| 127 | + } else { |
| 128 | + console.log("User not found."); |
| 129 | + } |
| 130 | + } catch (error) { |
| 131 | + console.error("Error getting user:", error); |
42 | 132 | } |
| 133 | +})(); |
| 134 | +``` |
43 | 135 |
|
44 | | - await userORM.update(newUser.id, { email: "newemail@example.com" }); |
45 | | - await userORM.delete(newUser.id); |
| 136 | +### `update(id: string, data: Partial<T>): Promise<void>` |
46 | 137 |
|
47 | | - const otherUser = await userORM.findOne({ |
48 | | - email: { where: "==", value: "email@asd.com" }, |
49 | | - }); |
| 138 | +Updates an existing document with the provided data. `Partial<T>` means you can provide only the fields you want to change. |
| 139 | + |
| 140 | +```typescript |
| 141 | +import userORM from "./user.orm"; |
| 142 | + |
| 143 | +(async () => { |
| 144 | + try { |
| 145 | + const userIdToUpdate = "some-user-id"; // Replace with an actual ID |
| 146 | + await userORM.update(userIdToUpdate, { email: "updated.email@example.com" }); |
| 147 | + console.log("User updated successfully."); |
| 148 | + |
| 149 | + // You can verify by getting the user |
| 150 | + const updatedUser = await userORM.get(userIdToUpdate); |
| 151 | + if (updatedUser) { |
| 152 | + console.log("Updated user data:", updatedUser); |
| 153 | + } |
| 154 | + } catch (error) { |
| 155 | + console.error("Error updating user:", error); |
| 156 | + } |
50 | 157 | })(); |
51 | 158 | ``` |
52 | 159 |
|
53 | | -## Examples of `finds` Method |
| 160 | +### `delete(id: string): Promise<void>` |
| 161 | + |
| 162 | +Deletes a document by its ID. |
| 163 | + |
| 164 | +```typescript |
| 165 | +import userORM from "./user.orm"; |
| 166 | + |
| 167 | +(async () => { |
| 168 | + try { |
| 169 | + const userIdToDelete = "some-user-id"; // Replace with an actual ID |
| 170 | + await userORM.delete(userIdToDelete); |
| 171 | + console.log("User deleted successfully."); |
| 172 | + |
| 173 | + // You can verify by trying to get the user |
| 174 | + const deletedUser = await userORM.get(userIdToDelete); |
| 175 | + if (!deletedUser) { |
| 176 | + console.log("User confirmed deleted."); |
| 177 | + } |
| 178 | + } catch (error) { |
| 179 | + console.error("Error deleting user:", error); |
| 180 | + } |
| 181 | +})(); |
| 182 | +``` |
| 183 | + |
| 184 | +### `findOne(searchParams: SearchParams): Promise<T | undefined>` |
| 185 | + |
| 186 | +Finds a single document that matches the search criteria. Returns the first matching document or `undefined`. |
| 187 | +The `searchParams` object allows you to specify field conditions. |
| 188 | + |
| 189 | +```typescript |
| 190 | +import userORM from "./user.orm"; |
| 191 | + |
| 192 | +(async () => { |
| 193 | + try { |
| 194 | + const user = await userORM.findOne({ |
| 195 | + email: { where: "==", value: "test@example.com" }, |
| 196 | + }); |
| 197 | + |
| 198 | + if (user) { |
| 199 | + console.log("User found by email:", user); |
| 200 | + } else { |
| 201 | + console.log("User with that email not found."); |
| 202 | + } |
| 203 | + } catch (error) { |
| 204 | + console.error("Error finding one user:", error); |
| 205 | + } |
| 206 | +})(); |
| 207 | +``` |
| 208 | + |
| 209 | +## Finding Multiple Documents (`finds` Method) |
| 210 | + |
| 211 | +The `finds(searchParams: SearchParams = {}): Promise<T[]>` method allows you to retrieve multiple documents based on complex query conditions. |
| 212 | + |
| 213 | +**`SearchParams` Type:** |
| 214 | + |
| 215 | +The `searchParams` object is a key-value map where: |
| 216 | +* **Key**: The field name in your document (e.g., `age`, `name`). |
| 217 | +* **Value**: Can be one of the following: |
| 218 | + * A direct value (e.g., `25`, `"Juan"`): This implies an "equals" (`==`) condition. |
| 219 | + * A `SearchParam` object: `{ value: any; where: WhereFilterOp }` for specifying conditions like greater than (`>`), less than (`<`), etc. `WhereFilterOp` is a string type from Firebase (`<`, `<=`, `==`, `!=`, `>=`, `>`, `array-contains`, `in`, `not-in`, `array-contains-any`). |
| 220 | + * An array of `SearchParam` objects: For applying multiple conditions to the same field (e.g., age > 10 AND age < 30). |
54 | 221 |
|
55 | 222 | ### Example 1: Simple Search |
56 | 223 |
|
@@ -167,6 +334,30 @@ const result6 = await userORM.finds(searchParams6); |
167 | 334 | console.log(result6); |
168 | 335 | ``` |
169 | 336 |
|
| 337 | +## Advanced Usage (Future Enhancements) |
| 338 | + |
| 339 | +Currently, the ORM focuses on providing straightforward CRUD operations. Future enhancements could include: |
| 340 | + |
| 341 | +* **Transactions:** Support for Firestore transactions to perform multiple operations atomically. |
| 342 | +* **Batch Writes:** Enabling batch operations (e.g., multiple `set`, `update`, or `delete` calls in a single request) for efficiency. |
| 343 | +* **Custom Data Transformers:** Hooks or methods to transform data when reading from or writing to Firestore (e.g., for date conversions, encryption/decryption). |
| 344 | +* **Real-time Listeners:** Integration with Firestore's real-time data synchronization capabilities. |
| 345 | + |
| 346 | +If you have specific needs or ideas for advanced features, please feel free to open an issue or contribute to the project! |
| 347 | + |
| 348 | +## Contributing |
| 349 | + |
| 350 | +Contributions are welcome! If you'd like to contribute, please follow these steps: |
| 351 | + |
| 352 | +1. **Fork the repository.** |
| 353 | +2. **Create a new branch:** `git checkout -b my-feature-branch` |
| 354 | +3. **Make your changes.** |
| 355 | +4. **Commit your changes:** `git commit -m 'Add some feature'` |
| 356 | +5. **Push to the branch:** `git push origin my-feature-branch` |
| 357 | +6. **Open a pull request.** |
| 358 | + |
| 359 | +Please make sure to update tests as appropriate and follow the existing code style. |
| 360 | + |
170 | 361 | ## License |
171 | 362 |
|
172 | | -[MPL-2.0](https://www.mozilla.org/en-US/MPL/2.0/) |
| 363 | +This project is licensed under the MPL-2.0 License. See the [LICENSE](LICENSE) file for details. |
0 commit comments