{"id":13719384,"url":"https://github.com/RxSwiftCommunity/RxGRDB","last_synced_at":"2025-05-07T11:31:31.567Z","repository":{"id":45947523,"uuid":"87106676","full_name":"RxSwiftCommunity/RxGRDB","owner":"RxSwiftCommunity","description":"Reactive extensions for SQLite","archived":false,"fork":false,"pushed_at":"2025-03-15T10:12:52.000Z","size":3040,"stargazers_count":219,"open_issues_count":1,"forks_count":36,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-04-12T08:21:46.113Z","etag":null,"topics":["database","grdb","reactive","rxswift","sqlite"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/RxSwiftCommunity.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2017-04-03T18:24:45.000Z","updated_at":"2025-03-15T10:12:44.000Z","dependencies_parsed_at":"2025-02-27T09:20:42.600Z","dependency_job_id":"c3e67311-7cc4-498a-8c1f-0d974496435a","html_url":"https://github.com/RxSwiftCommunity/RxGRDB","commit_stats":null,"previous_names":[],"tags_count":31,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RxSwiftCommunity%2FRxGRDB","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RxSwiftCommunity%2FRxGRDB/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RxSwiftCommunity%2FRxGRDB/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RxSwiftCommunity%2FRxGRDB/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/RxSwiftCommunity","download_url":"https://codeload.github.com/RxSwiftCommunity/RxGRDB/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252868849,"owners_count":21816925,"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":["database","grdb","reactive","rxswift","sqlite"],"created_at":"2024-08-03T01:00:47.600Z","updated_at":"2025-05-07T11:31:31.532Z","avatar_url":"https://github.com/RxSwiftCommunity.png","language":"Swift","funding_links":[],"categories":["Libraries"],"sub_categories":[],"readme":"RxGRDB [![Swift 6](https://img.shields.io/badge/swift-6-orange.svg?style=flat)](https://developer.apple.com/swift/)  [![License](https://img.shields.io/github/license/RxSwiftCommunity/RxGRDB.svg?maxAge=2592000)](/LICENSE)\n======\n\n### A set of extensions for [SQLite], [GRDB.swift], and [RxSwift]\n\n**Latest release**: March 25, 2025 • [version 4.0.0](https://github.com/RxSwiftCommunity/RxGRDB/tree/v4.0.0) • [Release Notes]\n\n**Requirements**: iOS 11.0+ / macOS 10.13+ / tvOS 11.0+ / watchOS 4.0+ • Swift 6+ / Xcode 16+\n\n---\n\n## Usage\n\nTo connect to the database, please refer to [GRDB](https://github.com/groue/GRDB.swift), the database library that supports RxGRDB.\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cstrong\u003eAsynchronously read from the database\u003c/strong\u003e\u003c/summary\u003e\n\nThis observable reads a single value and delivers it.\n\n```swift\n// Single\u003c[Player]\u003e\nlet players = dbQueue.rx.read { db in\n    try Player.fetchAll(db)\n}\n\nplayers.subscribe(\n    onSuccess: { (players: [Player]) in\n        print(\"Players: \\(players)\")\n    },\n    onError: { error in ... })\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cstrong\u003eAsynchronously write in the database\u003c/strong\u003e\u003c/summary\u003e\n\nThis observable completes after the database has been updated.\n\n```swift\n// Single\u003cVoid\u003e\nlet write = dbQueue.rx.write { db in \n    try Player(...).insert(db)\n}\n\nwrite.subscribe(\n    onSuccess: { _ in\n        print(\"Updates completed\")\n    },\n    onError: { error in ... })\n\n// Single\u003cInt\u003e\nlet newPlayerCount = dbQueue.rx.write { db -\u003e Int in\n    try Player(...).insert(db)\n    return try Player.fetchCount(db)\n}\n\nnewPlayerCount.subscribe(\n    onSuccess: { (playerCount: Int) in\n        print(\"New players count: \\(playerCount)\")\n    },\n    onError: { error in ... })\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cstrong\u003eObserve changes in database values\u003c/strong\u003e\u003c/summary\u003e\n\nThis observable delivers fresh values whenever the database changes:\n\n```swift\n// Observable\u003c[Player]\u003e\nlet observable = ValueObservation\n    .tracking { db in try Player.fetchAll(db) }\n    .rx.observe(in: dbQueue)\n\nobservable.subscribe(\n    onNext: { (players: [Player]) in\n        print(\"Fresh players: \\(players)\")\n    },\n    onError: { error in ... })\n\n// Observable\u003cInt?\u003e\nlet observable = ValueObservation\n    .tracking { db in try Int.fetchOne(db, sql: \"SELECT MAX(score) FROM player\") }\n    .rx.observe(in: dbQueue)\n\nobservable.subscribe(\n    onNext: { (maxScore: Int?) in\n        print(\"Fresh maximum score: \\(maxScore)\")\n    },\n    onError: { error in ... })\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cstrong\u003eObserve database transactions\u003c/strong\u003e\u003c/summary\u003e\n\nThis observable delivers database connections whenever a database transaction has impacted an observed region:\n\n```swift\n// Observable\u003cDatabase\u003e\nlet observable = DatabaseRegionObservation\n    .tracking(Player.all())\n    .rx.changes(in: dbQueue)\n\nobservable.subscribe(\n    onNext: { (db: Database) in\n        print(\"Exclusive write access to the database after players have been impacted\")\n    },\n    onError: { error in ... })\n\n// Observable\u003cDatabase\u003e\nlet observable = DatabaseRegionObservation\n    .tracking(SQLRequest\u003cInt\u003e(sql: \"SELECT MAX(score) FROM player\"))\n    .rx.changes(in: dbQueue)\n\nobservable.subscribe(\n    onNext: { (db: Database) in\n        print(\"Exclusive write access to the database after maximum score has been impacted\")\n    },\n    onError: { error in ... })\n```\n\n\u003c/details\u003e\n\nDocumentation\n=============\n\n- [Installation]\n- [Demo Application]\n- [Asynchronous Database Access]\n- [Database Observation]\n\n## Installation\n\nTo use RxGRDB with the [Swift Package Manager], add a dependency to your `Package.swift` file:\n\n```swift\nlet package = Package(\n    dependencies: [\n        .package(url: \"https://github.com/RxSwiftCommunity/RxGRDB.git\", ...)\n    ]\n)\n```\n\nTo use RxGRDB with [CocoaPods](http://cocoapods.org/), specify in your `Podfile`:\n\n```ruby\n# Pick only one\npod 'RxGRDB'\npod 'RxGRDB/SQLCipher'\n```\n\n\n# Asynchronous Database Access\n\nRxGRDB provide observables that perform asynchronous database accesses.\n\n- [`rx.read(observeOn:value:)`]\n- [`rx.write(observeOn:updates:)`]\n- [`rx.write(observeOn:updates:thenRead:)`]\n\n\n#### `DatabaseReader.rx.read(observeOn:value:)`\n\nThis methods returns a [Single] that completes after database values have been asynchronously fetched.\n\n```swift\n// Single\u003c[Player]\u003e\nlet players = dbQueue.rx.read { db in\n    try Player.fetchAll(db)\n}\n```\n\nAny attempt at modifying the database completes subscriptions with an error.\n\nWhen you use a [database queue] or a [database snapshot], the read has to wait for any eventual concurrent database access performed by this queue or snapshot to complete.\n\nWhen you use a [database pool], reads are generally non-blocking, unless the maximum number of concurrent reads has been reached. In this case, a read has to wait for another read to complete. That maximum number can be [configured].\n\nThis observable can be subscribed from any thread. A new database access starts on every subscription.\n\nThe fetched value is published on the main queue, unless you provide a specific scheduler to the `observeOn` argument.\n\n\n#### `DatabaseWriter.rx.write(observeOn:updates:)`\n\nThis method returns a [Single] that completes after database updates have been successfully executed inside a database transaction.\n\n```swift\n// Single\u003cVoid\u003e\nlet write = dbQueue.rx.write { db in\n    try Player(...).insert(db)\n}\n\n// Single\u003cInt\u003e\nlet newPlayerCount = dbQueue.rx.write { db -\u003e Int in\n    try Player(...).insert(db)\n    return try Player.fetchCount(db)\n}\n```\n\nThis observable can be subscribed from any thread. A new database access starts on every subscription.\n\nIt completes on the main queue, unless you provide a specific [scheduler] to the `observeOn` argument.\n\nYou can ignore its value and turn it into a [Completable] with the `asCompletable` operator:\n\n```swift\n// Completable\nlet write = dbQueue.rx\n    .write { db in try Player(...).insert(db) }\n    .asCompletable()\n```\n\nWhen you use a [database pool], and your app executes some database updates followed by some slow fetches, you may profit from optimized scheduling with [`rx.write(observeOn:updates:thenRead:)`]. See below.\n\n\n#### `DatabaseWriter.rx.write(observeOn:updates:thenRead:)`\n\nThis method returns a [Single] that completes after database updates have been successfully executed inside a database transaction, and values have been subsequently fetched:\n\n```swift\n// Single\u003cInt\u003e\nlet newPlayerCount = dbQueue.rx.write(\n    updates: { db in try Player(...).insert(db) }\n    thenRead: { db, _ in try Player.fetchCount(db) })\n}\n```\n\nIt publishes exactly the same values as [`rx.write(observeOn:updates:)`]:\n\n```swift\n// Single\u003cInt\u003e\nlet newPlayerCount = dbQueue.rx.write { db -\u003e Int in\n    try Player(...).insert(db)\n    return try Player.fetchCount(db)\n}\n```\n\nThe difference is that the last fetches are performed in the `thenRead` function. This function accepts two arguments: a readonly database connection, and the result of the `updates` function. This allows you to pass information from a function to the other (it is ignored in the sample code above).\n\nWhen you use a [database pool], this method applies a scheduling optimization: the `thenRead` function sees the database in the state left by the `updates` function, and yet does not block any concurrent writes. This can reduce database write contention. See [Advanced DatabasePool](https://github.com/groue/GRDB.swift/blob/master/README.md#advanced-databasepool) for more information.\n\nWhen you use a [database queue], the results are guaranteed to be identical, but no scheduling optimization is applied.\n\nThis observable can be subscribed from any thread. A new database access starts on every subscription.\n\nIt completes on the main queue, unless you provide a specific [scheduler] to the `observeOn` argument.\n\n\n# Database Observation\n\nDatabase Observation observables are based on GRDB's [ValueObservation] and [DatabaseRegionObservation]. Please refer to their documentation for more information. If your application needs change notifications that are not built in RxGRDB, check the general [Database Changes Observation] chapter.\n\n- [`ValueObservation.rx.observe(in:scheduling:)`]\n- [`DatabaseRegionObservation.rx.changes(in:)`]\n\n\n#### `ValueObservation.rx.observe(in:scheduling:)`\n\nGRDB's [ValueObservation] tracks changes in database values. You can turn it into an RxSwift observable:\n\n```swift\nlet observation = ValueObservation.tracking { db in\n    try Player.fetchAll(db)\n}\n\n// Observable\u003c[Player]\u003e\nlet observable = observation.rx.observe(in: dbQueue)\n```\n\nThis observable has the same behavior as ValueObservation:\n\n- It notifies an initial value before the eventual changes.\n- It may coalesce subsequent changes into a single notification.\n- It may notify consecutive identical values. You can filter out the undesired duplicates with the `distinctUntilChanged()` RxSwift operator, but we suggest you have a look at the [removeDuplicates()](https://github.com/groue/GRDB.swift/blob/master/README.md#valueobservationremoveduplicates) GRDB operator also.\n- It stops emitting any value after the database connection is closed. But it never completes.\n- By default, it notifies the initial value, as well as eventual changes and errors, on the main thread, asynchronously.\n    \n    This can be configured with the `scheduling` argument. It does not accept an RxSwift scheduler, but a [GRDB scheduler](https://github.com/groue/GRDB.swift/blob/master/README.md#valueobservation-scheduling).\n    \n    For example, the `.immediate` scheduler makes sure the initial value is notified immediately when the observable is subscribed. It can help your application update the user interface without having to wait for any asynchronous notifications:\n    \n    ```swift\n    // Immediate notification of the initial value\n    let disposable = observation.rx\n        .observe(\n            in: dbQueue,\n            scheduling: .immediate) // \u003c-\n        .subscribe(\n            onNext: { players: [Player] in print(\"fresh players: \\(players)\") },\n            onError: { error in ... })\n    // \u003c- here \"fresh players\" is already printed.\n    ```\n    \n    Note that the `.immediate` scheduler requires that the observable is subscribed from the main thread. It raises a fatal error otherwise.\n\nSee [ValueObservation Scheduling](https://github.com/groue/GRDB.swift/blob/master/README.md#valueobservation-scheduling) for more information.\n\n:warning: **ValueObservation and Data Consistency**\n\nWhen you compose ValueObservation observables together with the [combineLatest](http://reactivex.io/documentation/operators/combinelatest.html) operator, you lose all guarantees of [data consistency](https://en.wikipedia.org/wiki/Consistency_(database_systems)).\n\nInstead, compose requests together into **one single** ValueObservation, as below:\n\n```swift\n// DATA CONSISTENCY GUARANTEED\nlet hallOfFameObservable = ValueObservation\n    .tracking { db -\u003e HallOfFame in\n        let playerCount = try Player.fetchCount(db)\n        let bestPlayers = try Player.limit(10).orderedByScore().fetchAll(db)\n        return HallOfFame(playerCount:playerCount, bestPlayers:bestPlayers)\n    }\n    .rx.observe(in: dbQueue)\n```\n\nSee [ValueObservation] for more information.\n\n\n#### `DatabaseRegionObservation.rx.changes(in:)`\n\nGRDB's [DatabaseRegionObservation] notifies all transactions that impact a tracked database region. You can turn it into an RxSwift observable:\n\n```swift\nlet request = Player.all()\nlet observation = DatabaseRegionObservation.tracking(request)\n\n// Observable\u003cDatabase\u003e\nlet observable = observation.rx.changes(in: dbQueue)\n```\n\nThis observable can be created and subscribed from any thread. It delivers database connections in a \"protected dispatch queue\", serialized with all database updates. It only completes when a database error happens.\n\n```swift\nlet request = Player.all()\nlet disposable = DatabaseRegionObservation\n    .tracking(request)\n    .rx.changes(in: dbQueue)\n    .subscribe(\n        onNext: { (db: Database) in\n            print(\"Players have changed.\")\n        },\n        onError: { error in ... })\n\ntry dbQueue.write { db in\n    try Player(name: \"Arthur\").insert(db)\n    try Player(name: \"Barbara\").insert(db)\n} \n// Prints \"Players have changed.\"\n\ntry dbQueue.write { db in\n    try Player.deleteAll(db)\n}\n// Prints \"Players have changed.\"\n```\n\nSee [DatabaseRegionObservation] for more information.\n\n\n[Asynchronous Database Access]: #asynchronous-database-access\n[RxSwift]: https://github.com/ReactiveX/RxSwift\n[Database Changes Observation]: https://github.com/groue/GRDB.swift/blob/master/README.md#database-changes-observation\n[Database Observation]: #database-observation\n[DatabaseRegionObservation]: https://github.com/groue/GRDB.swift/blob/master/README.md#databaseregionobservation\n[Demo Application]: Documentation/RxGRDBDemo/README.md\n[GRDB.swift]: https://github.com/groue/GRDB.swift\n[Installation]: #installation\n[Release Notes]: CHANGELOG.md\n[SQLite]: http://sqlite.org\n[Swift Package Manager]: https://swift.org/package-manager/\n[ValueObservation]: https://github.com/groue/GRDB.swift/blob/master/README.md#valueobservation\n[`DatabaseRegionObservation.rx.changes(in:)`]: #databaseregionobservationrxchangesin\n[`ValueObservation.rx.observe(in:scheduling:)`]: #valueobservationrxobserveinscheduling\n[`rx.read(observeOn:value:)`]: #databasereaderrxreadobserveonvalue\n[`rx.write(observeOn:updates:)`]: #databasewriterrxwriteobserveonupdates\n[`rx.write(observeOn:updates:thenRead:)`]: #databasewriterrxwriteobserveonupdatesthenread\n[configured]: https://github.com/groue/GRDB.swift/blob/master/README.md#databasepool-configuration\n[database pool]: https://github.com/groue/GRDB.swift/blob/master/README.md#database-pools\n[database queue]: https://github.com/groue/GRDB.swift/blob/master/README.md#database-queues\n[database snapshot]: https://github.com/groue/GRDB.swift/blob/master/README.md#database-snapshots\n[scheduler]: https://github.com/ReactiveX/RxSwift/blob/master/Documentation/Schedulers.md\n[Single]: https://github.com/ReactiveX/RxSwift/blob/master/Documentation/Traits.md#single\n[Completable]: https://github.com/ReactiveX/RxSwift/blob/master/Documentation/Traits.md#completable\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FRxSwiftCommunity%2FRxGRDB","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FRxSwiftCommunity%2FRxGRDB","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FRxSwiftCommunity%2FRxGRDB/lists"}