{"id":23152476,"url":"https://github.com/1amageek/firebaseapi","last_synced_at":"2025-10-19T22:17:05.226Z","repository":{"id":159712840,"uuid":"634799135","full_name":"1amageek/FirebaseAPI","owner":"1amageek","description":"Lightweight Cloud Firestore Client API using googleapis gRPC.","archived":false,"fork":false,"pushed_at":"2024-10-30T05:00:41.000Z","size":172,"stargazers_count":4,"open_issues_count":0,"forks_count":3,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-11-24T16:42:49.137Z","etag":null,"topics":["cloudfirestore","firebase"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/1amageek.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2023-05-01T08:14:47.000Z","updated_at":"2024-10-30T05:00:45.000Z","dependencies_parsed_at":"2023-12-15T10:28:58.976Z","dependency_job_id":null,"html_url":"https://github.com/1amageek/FirebaseAPI","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/1amageek%2FFirebaseAPI","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/1amageek%2FFirebaseAPI/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/1amageek%2FFirebaseAPI/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/1amageek%2FFirebaseAPI/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/1amageek","download_url":"https://codeload.github.com/1amageek/FirebaseAPI/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230167887,"owners_count":18183846,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cloudfirestore","firebase"],"created_at":"2024-12-17T19:14:50.257Z","updated_at":"2025-10-19T22:17:05.221Z","avatar_url":"https://github.com/1amageek.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# FirebaseAPI\n\nFirebaseAPI for Swift is a Swift package that provides a simple interface to interact with Firebase services using gRPC.\n\nThis repository includes the [googleapis](https://github.com/googleapis/googleapis) repository as a submodule, which is used to generate the API client code for Firebase.\n\n## Features\n\n- ✅ **Firestore API**: Full support for Firestore operations (CRUD, queries, transactions, batches)\n- ✅ **Generic Transport**: Support for any `ClientTransport` implementation from grpc-swift-2\n- ✅ **Swift 6 Ready**: Full concurrency support with `async/await` and `Sendable`\n- ✅ **Type-safe Encoding/Decoding**: FirestoreEncoder and FirestoreDecoder for seamless Swift type conversion\n- ✅ **Property Wrappers**: `@DocumentID`, `@ReferencePath`, `@ExplicitNull` for Firestore-specific behaviors\n- ✅ **Retry Strategy**: Built-in retry handling with exponential backoff\n\n## Requirements\n\n- Swift 6.2+\n- macOS 15.0+ / iOS 18.0+ / watchOS 11.0+ / tvOS 18.0+ / visionOS 2.0+\n\n## Installation\n\nAdd FirebaseAPI to your `Package.swift`:\n\n```swift\ndependencies: [\n    .package(url: \"https://github.com/1amageek/FirebaseAPI.git\", from: \"0.1.0\")\n]\n```\n\nThen add it to your target dependencies:\n\n```swift\n.target(\n    name: \"YourTarget\",\n    dependencies: [\n        .product(name: \"FirestoreAPI\", package: \"FirebaseAPI\")\n    ]\n)\n```\n\n## Usage\n\n### Initialize Firestore\n\n```swift\nimport FirestoreAPI\nimport GRPCHTTP2TransportNIOPosix // or your preferred transport\n\n// Create a transport (example using HTTP/2 with NIO)\nlet transport = HTTP2ClientTransport.Posix(\n    target: .ipv4(host: \"firestore.googleapis.com\", port: 443),\n    config: .defaults(transportSecurity: .tls)\n)\n\n// Initialize Firestore with generic transport\nlet firestore = Firestore(\n    projectId: \"your-project-id\",\n    transport: transport,\n    accessTokenProvider: yourAccessTokenProvider\n)\n```\n\n### Basic CRUD Operations\n\n```swift\n// Define your model\nstruct User: Codable {\n    @DocumentID var id: String\n    var name: String\n    var email: String\n    var createdAt: Timestamp\n}\n\n// Create a document\nlet userRef = firestore.collection(\"users\").document(\"user123\")\ntry await userRef.setData([\n    \"name\": \"John Doe\",\n    \"email\": \"john@example.com\",\n    \"createdAt\": Timestamp.now()\n], firestore: firestore)\n\n// Or use Codable\nlet user = User(id: \"user123\", name: \"John Doe\", email: \"john@example.com\", createdAt: .now())\ntry await userRef.setData(user, firestore: firestore)\n\n// Read a document\nlet snapshot = try await userRef.getDocument(firestore: firestore)\nif let data = snapshot.data() {\n    print(\"User data: \\(data)\")\n}\n\n// Or decode to Codable\nlet user: User? = try await userRef.getDocument(type: User.self, firestore: firestore)\n\n// Update a document\ntry await userRef.updateData([\"name\": \"Jane Doe\"], firestore: firestore)\n\n// Delete a document\ntry await userRef.delete(firestore: firestore)\n```\n\n### Queries\n\n```swift\n// Simple query\nlet usersRef = firestore.collection(\"users\")\nlet snapshot = try await usersRef\n    .where(\"age\" \u003e= 18)\n    .where(\"city\" == \"Tokyo\")\n    .orderBy(\"name\", descending: false)\n    .limit(10)\n    .getDocuments(firestore: firestore)\n\nfor doc in snapshot.documents {\n    print(doc.data())\n}\n\n// Query with Codable\nlet users: [User] = try await usersRef\n    .where(\"age\" \u003e= 18)\n    .getDocuments(type: User.self, firestore: firestore)\n```\n\n### Transactions\n\n```swift\ntry await firestore.runTransaction { transaction in\n    // Read documents\n    let userDoc = firestore.document(\"users/user123\")\n    let snapshot = try await transaction.get(documentReference: userDoc)\n\n    guard let balance = snapshot.data()?[\"balance\"] as? Int else {\n        throw FirestoreError.notFound\n    }\n\n    // Write operations\n    transaction.updateData([\"balance\": balance - 100], forDocument: userDoc)\n\n    return balance - 100\n}\n```\n\n### Batch Writes\n\n```swift\nlet batch = firestore.batch()\n\nlet user1 = firestore.document(\"users/user1\")\nlet user2 = firestore.document(\"users/user2\")\n\nbatch.setData([\"name\": \"Alice\"], forDocument: user1)\nbatch.updateData([\"lastLogin\": Timestamp.now()], forDocument: user2)\nbatch.deleteDocument(document: firestore.document(\"users/user3\"))\n\ntry await batch.commit()\n```\n\n### Property Wrappers\n\n```swift\nstruct Post: Codable {\n    @DocumentID var id: String\n    @ReferencePath var path: String\n    @ExplicitNull var deletedAt: Date?\n\n    var title: String\n    var content: String\n    var authorRef: DocumentReference\n}\n\n// @DocumentID: Automatically populated with document ID during decoding\n// @ReferencePath: Automatically populated with document path\n// @ExplicitNull: Encodes as NSNull instead of omitting the field\n```\n\n## Architecture\n\n### Generic Transport Design\n\nFirebaseAPI uses a generic `Transport` parameter that conforms to `ClientTransport` from grpc-swift-2. This design allows:\n\n- **Flexibility**: Use any transport implementation (HTTP/2, NIO-based, custom)\n- **Type Safety**: Transport type is known at compile time for optimal performance\n- **Testability**: Easy to mock transport for unit tests\n\n```swift\npublic final class Firestore\u003cTransport: ClientTransport\u003e: Sendable {\n    internal let transport: Transport\n    // ...\n}\n```\n\n### Why Generic Instead of Protocol?\n\nThe library uses `Firestore\u003cTransport: ClientTransport\u003e` instead of `any ClientTransport` because:\n\n1. **gRPC Client Requirements**: `GRPCClient\u003cTransport\u003e` requires a concrete type parameter\n2. **Swift Type System**: Existential types (`any Protocol`) cannot conform to protocols with `Self` requirements\n3. **Performance**: Generic types are resolved at compile time, avoiding runtime overhead\n\n## Development\n\n### Prerequisites\n\nTo develop this library, you need:\n1. Swift 6.2+\n2. Protocol Buffer compiler (`protoc`)\n3. gRPC Swift plugins\n\n### Generating Proto Files\n\nThis repository includes the googleapis as a submodule. To regenerate the Firestore proto files:\n\n```bash\nmkdir -p Sources/FirestoreAPI/Proto\ncd googleapis\nprotoc \\\n  ./google/firestore/v1/*.proto \\\n  ./google/api/field_behavior.proto \\\n  ./google/api/resource.proto \\\n  ./google/longrunning/operations.proto \\\n  ./google/rpc/status.proto \\\n  ./google/type/latlng.proto \\\n  --swift_out=../Sources/FirestoreAPI/Proto \\\n  --grpc-swift_out=../Sources/FirestoreAPI/Proto \\\n  --swift_opt=Visibility=Public \\\n  --grpc-swift_opt=Visibility=Public\n```\n\n### Running Tests\n\nThe test suite uses **Swift Testing** framework (not XCTest):\n\n```bash\nswift test\n```\n\nAll 67 tests should pass:\n- Reference Path Tests: 9 tests\n- Query Predicate Tests: 6 tests\n- Firestore Encoder Tests: 21 tests\n- Firestore Decoder Tests: 23 tests\n- Listen API Tests: 8 tests\n\n### Test Coverage\n\n- ✅ Firestore Encoder/Decoder for all supported types\n- ✅ Document reference path generation\n- ✅ Query predicates and operators\n- ✅ Property wrappers (@DocumentID, @ReferencePath, @ExplicitNull)\n- ✅ Real-time listener response processing\n- ✅ Mock transport for testing without network calls\n\n## Dependencies\n\n- [grpc-swift-2](https://github.com/grpc/grpc-swift-2): gRPC core and protocols\n- [grpc-swift-protobuf](https://github.com/grpc/grpc-swift-protobuf): Protobuf serialization\n- [swift-protobuf](https://github.com/apple/swift-protobuf): Protocol Buffer runtime\n- [swift-log](https://github.com/apple/swift-log): Logging infrastructure\n\n## Migration from grpc-swift 1.x\n\nThis library has been migrated to **grpc-swift-2.x**. Key changes:\n\n- `HPACKHeaders` → `Metadata`\n- `ClientCall` → `ClientRequest` with new API\n- Direct `GRPCClient` creation instead of connection pooling\n- Bidirectional streaming now fully supported for real-time listeners\n\n## Real-time Listeners\n\nThe library now supports real-time listeners for documents and queries using bidirectional streaming:\n\n```swift\n// Listen to document changes\nlet docRef = firestore.collection(\"users\").document(\"user123\")\nlet stream = try await docRef.addSnapshotListener(firestore: firestore)\n\nfor try await snapshot in stream {\n    if snapshot.exists {\n        print(\"Document updated: \\(snapshot.data())\")\n    } else {\n        print(\"Document deleted or doesn't exist\")\n    }\n}\n```\n\n```swift\n// Listen to query changes\nlet query = firestore.collection(\"users\").where(\"age\" \u003e= 18)\nlet stream = try await query.addSnapshotListener(firestore: firestore)\n\nfor try await snapshot in stream {\n    print(\"Query results updated: \\(snapshot.documents.count) documents\")\n    for doc in snapshot.documents {\n        print(doc.data())\n    }\n}\n```\n\nNote: The stream will continue until cancelled or an error occurs. Use task cancellation to stop listening:\n\n```swift\nlet task = Task {\n    for try await snapshot in stream {\n        // Process snapshot\n    }\n}\n\n// Later: stop listening\ntask.cancel()\n```\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F1amageek%2Ffirebaseapi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F1amageek%2Ffirebaseapi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F1amageek%2Ffirebaseapi/lists"}