Full Code of RxSwiftCommunity/RxGRDB for AI

master 7dc5a6a00012 cached
43 files
173.5 KB
45.9k tokens
1 requests
Download .txt
Repository: RxSwiftCommunity/RxGRDB
Branch: master
Commit: 7dc5a6a00012
Files: 43
Total size: 173.5 KB

Directory structure:
gitextract_sqrfypq0/

├── .ci/
│   └── gemfiles/
│       └── Gemfile.travis
├── .gitignore
├── .swiftpm/
│   └── xcode/
│       ├── package.xcworkspace/
│       │   └── contents.xcworkspacedata
│       └── xcshareddata/
│           └── xcschemes/
│               ├── RxGRDB.xcscheme
│               └── RxGRDBTests.xcscheme
├── .travis.yml
├── CHANGELOG.md
├── Documentation/
│   └── RxGRDBDemo/
│       ├── README.md
│       ├── RxGRDBDemo/
│       │   ├── AppDatabase.swift
│       │   ├── AppDelegate.swift
│       │   ├── Info.plist
│       │   ├── Models/
│       │   │   ├── Player.swift
│       │   │   └── Players.swift
│       │   ├── Resources/
│       │   │   ├── Assets.xcassets/
│       │   │   │   └── AppIcon.appiconset/
│       │   │   │       └── Contents.json
│       │   │   └── Base.lproj/
│       │   │       ├── LaunchScreen.storyboard
│       │   │       └── Main.storyboard
│       │   ├── UI/
│       │   │   ├── PlayersViewController.swift
│       │   │   └── PlayersViewModel.swift
│       │   └── World.swift
│       ├── RxGRDBDemo.xcodeproj/
│       │   ├── project.pbxproj
│       │   ├── project.xcworkspace/
│       │   │   ├── contents.xcworkspacedata
│       │   │   └── xcshareddata/
│       │   │       └── IDEWorkspaceChecks.plist
│       │   └── xcshareddata/
│       │       └── xcschemes/
│       │           └── RxGRDBDemo.xcscheme
│       └── RxGRDBDemoTests/
│           ├── AppDatabaseTests.swift
│           ├── Info.plist
│           ├── PlayerTests.swift
│           ├── PlayersTests.swift
│           └── PlayersViewModelTests.swift
├── LICENSE
├── Makefile
├── Package.swift
├── README.md
├── Sources/
│   └── RxGRDB/
│       ├── DatabaseReader+Rx.swift
│       ├── DatabaseRegionObservation+Rx.swift
│       ├── DatabaseWriter+Rx.swift
│       ├── GRDBReactive.swift
│       └── ValueObservation+Rx.swift
├── TODO.md
└── Tests/
    └── RxGRDBTests/
        ├── DatabaseReaderReadTests.swift
        ├── DatabaseRegionObservationTests.swift
        ├── DatabaseWriterWriteTests.swift
        ├── Support.swift
        └── ValueObservationTests.swift

================================================
FILE CONTENTS
================================================

================================================
FILE: .ci/gemfiles/Gemfile.travis
================================================
source 'https://rubygems.org'

gem 'xcpretty'
gem 'xcpretty-travis-formatter'
gem 'cocoapods', '~> 1.7'


================================================
FILE: .gitignore
================================================
## https://github.com/github/gitignore/blob/master/Global/macOS.gitignore

*.DS_Store
.AppleDouble
.LSOverride

# Icon must end with two \r
Icon


# Thumbnails
._*

# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk


## https://github.com/github/gitignore/blob/master/Swift.gitignore

# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore

## Build generated
build/
DerivedData/

## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/

## Other
*.moved-aside
*.xccheckout
*.xcscmblueprint

## Obj-C/Swift specific
*.hmap
*.ipa
*.dSYM.zip
*.dSYM

## Playgrounds
timeline.xctimeline
playground.xcworkspace

# Swift Package Manager
#
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
# Packages/
# Package.pins
.build/
Package.resolved

##
Pods/

================================================
FILE: .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
   version = "1.0">
   <FileRef
      location = "self:">
   </FileRef>
</Workspace>


================================================
FILE: .swiftpm/xcode/xcshareddata/xcschemes/RxGRDB.xcscheme
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
   LastUpgradeVersion = "1230"
   version = "1.3">
   <BuildAction
      parallelizeBuildables = "YES"
      buildImplicitDependencies = "YES">
      <BuildActionEntries>
         <BuildActionEntry
            buildForTesting = "YES"
            buildForRunning = "YES"
            buildForProfiling = "YES"
            buildForArchiving = "YES"
            buildForAnalyzing = "YES">
            <BuildableReference
               BuildableIdentifier = "primary"
               BlueprintIdentifier = "RxGRDB"
               BuildableName = "RxGRDB"
               BlueprintName = "RxGRDB"
               ReferencedContainer = "container:">
            </BuildableReference>
         </BuildActionEntry>
         <BuildActionEntry
            buildForTesting = "YES"
            buildForRunning = "YES"
            buildForProfiling = "NO"
            buildForArchiving = "NO"
            buildForAnalyzing = "YES">
            <BuildableReference
               BuildableIdentifier = "primary"
               BlueprintIdentifier = "RxGRDBTests"
               BuildableName = "RxGRDBTests"
               BlueprintName = "RxGRDBTests"
               ReferencedContainer = "container:">
            </BuildableReference>
         </BuildActionEntry>
      </BuildActionEntries>
   </BuildAction>
   <TestAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      shouldUseLaunchSchemeArgsEnv = "YES"
      codeCoverageEnabled = "YES">
      <Testables>
         <TestableReference
            skipped = "NO">
            <BuildableReference
               BuildableIdentifier = "primary"
               BlueprintIdentifier = "RxGRDBTests"
               BuildableName = "RxGRDBTests"
               BlueprintName = "RxGRDBTests"
               ReferencedContainer = "container:">
            </BuildableReference>
         </TestableReference>
      </Testables>
   </TestAction>
   <LaunchAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      launchStyle = "0"
      useCustomWorkingDirectory = "NO"
      ignoresPersistentStateOnLaunch = "NO"
      debugDocumentVersioning = "YES"
      debugServiceExtension = "internal"
      allowLocationSimulation = "YES">
   </LaunchAction>
   <ProfileAction
      buildConfiguration = "Release"
      shouldUseLaunchSchemeArgsEnv = "YES"
      savedToolIdentifier = ""
      useCustomWorkingDirectory = "NO"
      debugDocumentVersioning = "YES">
      <MacroExpansion>
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "RxGRDB"
            BuildableName = "RxGRDB"
            BlueprintName = "RxGRDB"
            ReferencedContainer = "container:">
         </BuildableReference>
      </MacroExpansion>
   </ProfileAction>
   <AnalyzeAction
      buildConfiguration = "Debug">
   </AnalyzeAction>
   <ArchiveAction
      buildConfiguration = "Release"
      revealArchiveInOrganizer = "YES">
   </ArchiveAction>
</Scheme>


================================================
FILE: .swiftpm/xcode/xcshareddata/xcschemes/RxGRDBTests.xcscheme
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
   LastUpgradeVersion = "1230"
   version = "1.3">
   <BuildAction
      parallelizeBuildables = "YES"
      buildImplicitDependencies = "YES">
   </BuildAction>
   <TestAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      shouldUseLaunchSchemeArgsEnv = "YES">
      <Testables>
         <TestableReference
            skipped = "NO">
            <BuildableReference
               BuildableIdentifier = "primary"
               BlueprintIdentifier = "RxGRDBTests"
               BuildableName = "RxGRDBTests"
               BlueprintName = "RxGRDBTests"
               ReferencedContainer = "container:">
            </BuildableReference>
         </TestableReference>
      </Testables>
   </TestAction>
   <LaunchAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      launchStyle = "0"
      useCustomWorkingDirectory = "NO"
      ignoresPersistentStateOnLaunch = "NO"
      debugDocumentVersioning = "YES"
      debugServiceExtension = "internal"
      allowLocationSimulation = "YES">
   </LaunchAction>
   <ProfileAction
      buildConfiguration = "Release"
      shouldUseLaunchSchemeArgsEnv = "YES"
      savedToolIdentifier = ""
      useCustomWorkingDirectory = "NO"
      debugDocumentVersioning = "YES">
   </ProfileAction>
   <AnalyzeAction
      buildConfiguration = "Debug">
   </AnalyzeAction>
   <ArchiveAction
      buildConfiguration = "Release"
      revealArchiveInOrganizer = "YES">
   </ArchiveAction>
</Scheme>


================================================
FILE: .travis.yml
================================================
# The OS X Build Environment
# https://docs.travis-ci.com/user/reference/osx/#Xcode-version

language: objective-c
xcode_project: RxGRDB.xcodeproj

# Caches
cache:
    - bundler
    - cocoapods

# Custom CocoaPods installation
install:
    - bundle install
    - bundle exec pod repo update
  
# Disable the default Travis-CI submodule logic
# The various make commands ensure that the appropriate submodules are retrieved
git:
  submodules: false

jobs:
  include:

    - stage: Test Xcode 11.4
      gemfile: .ci/gemfiles/Gemfile.travis
      osx_image: xcode11.4
      env:
        - TID=RxGRDB [SPM] (macOS)
      script: make test_SPM
    
    - stage: Test Xcode 11.4
      gemfile: .ci/gemfiles/Gemfile.travis
      osx_image: xcode11.4
      env:
        - TID=CocoaPods Lint
      script: make test_CocoaPodsLint


================================================
FILE: CHANGELOG.md
================================================
Release Notes
=============

## 4.0.1

Released September 28, 2025

- Replace deprecated API methods by [@1Consumption](https://github.com/1Consumption)

## 4.0.0

Released March 15, 2025 &bull; [diff](https://github.com/RxSwiftCommunity/RxGRDB/compare/v3.0.0...v4.0.0)

- **New**: Support for GRDB 7
- **Breaking Change**: Swift 6+ and Xcode 16+ are required.
- **Breaking Change**: iOS 13.0+ / macOS 10.15+ / tvOS 13.0+ / watchOS 7.0+
- **Breaking Change**: This version is not available on CocoaPods.

## 3.0.0

Released September 9, 2022 &bull; [diff](https://github.com/RxSwiftCommunity/RxGRDB/compare/v2.1.0...v3.0.0)

- **New**: Support for GRDB 6
- **Breaking Change**: Swift 5.7+ and Xcode 14+ are required.
- **Breaking Change**: iOS 11.0+ / macOS 10.13+ / tvOS 11.0+ / watchOS 4.0+

## 2.1.0

Released October 17, 2021 &bull; [diff](https://github.com/RxSwiftCommunity/RxGRDB/compare/v2.0.0...v2.1.0)

- **Breaking Change**: Minimum iOS version is now iOS 11.0, and 32-bits devices are no longer supported. This brings compatibility with GRDB v5.12.0 and Xcode 13.
- **Breaking Change**: Minimum Swift version is now Swift 5.3.

## 2.0.0

Released January 3, 2021 &bull; [diff](https://github.com/RxSwiftCommunity/RxGRDB/compare/v1.0.0...v2.0.0)

- **New**: Support for RxSwift 6 

## 1.0.0

Released September 20, 2020 &bull; [diff](https://github.com/RxSwiftCommunity/RxGRDB/compare/v1.0.0-beta.3...v1.0.0)


## 1.0.0-beta.3

Released June 7, 2020 &bull; [diff](https://github.com/RxSwiftCommunity/RxGRDB/compare/v1.0.0-beta.2...v1.0.0-beta.3)

- Fixed CocoaPods integration (fix [#65](https://github.com/RxSwiftCommunity/RxGRDB/issues/65))


## 1.0.0-beta.2

Released June 6, 2020 &bull; [diff](https://github.com/RxSwiftCommunity/RxGRDB/compare/v1.0.0-beta...v1.0.0-beta.2)

- **Breaking**: The ValueObservation scheduler is now an argument of the `rx.observe(in:scheduler:)` method, which returns a regular Observable.


## 1.0.0-beta

Released May 3, 2020 &bull; [diff](https://github.com/RxSwiftCommunity/RxGRDB/compare/v0.18.0...v1.0.0-beta)

Check out the [Migration Guide](Documentation/RxGRDB1MigrationGuide.md).

- [#63](https://github.com/RxSwiftCommunity/RxGRDB/pull/63): RxGRDB 1.0


## 0.18.0

Released December 11, 2019 &bull; [diff](https://github.com/RxSwiftCommunity/RxGRDB/compare/v0.17.0...v0.18.0)

- [#60](https://github.com/RxSwiftCommunity/RxGRDB/pull/60) by [@sammygutierrez](https://github.com/sammygutierrez): Add SwiftPM support
- [#61](https://github.com/RxSwiftCommunity/RxGRDB/pull/61): Fix error handling of asynchronous writes
- [#62](https://github.com/RxSwiftCommunity/RxGRDB/pull/62): Test Xcode 11.2 and SPM on Travis


## 0.17.0

Released June 28, 2019 &bull; [diff](https://github.com/RxSwiftCommunity/RxGRDB/compare/v0.16.0...v0.17.0)

- [#58](https://github.com/RxSwiftCommunity/RxGRDB/pull/58): Expose `rx` joiner on database existentials


## 0.16.0

Released June 27, 2019 &bull; [diff](https://github.com/RxSwiftCommunity/RxGRDB/compare/v0.15.0...v0.16.0)

- [#57](https://github.com/RxSwiftCommunity/RxGRDB/pull/57): Deprecate PrimaryKeyScanner

The [demo app](Documentation/RxGRDBDemo/README.md) has been refactored with the latest GRDB good practices, MVVM architecture, and some tests of the database layer


## 0.15.0

Released June 20, 2019 &bull; [diff](https://github.com/RxSwiftCommunity/RxGRDB/compare/v0.14.0...v0.15.0)

- [#55](https://github.com/RxSwiftCommunity/RxGRDB/pull/55): Asynchronous database access
    
    ```swift
    let dbQueue: DatabaseQueue = ...
    
    // Async write
    let write: Completable = dbQueue.rx.write { db in
        try Player(...).insert(db)
    }
    
    let newPlayerCount: Single<Int> = dbQueue.rx.writeAndReturn { db in
        try Player(...).insert(db)
        return try Player.fetchCount(db)
    }
    
    // Async read
    let players: Single<[Player]> = dbQueue.rx.read { db in
        try Player.fetchAll(db)
    }
    ```

### Breaking Changes

- Observation methods have been renamed from `fetch...` to `observe...`:
    
    ```diff
    -Player.all().rx.fetchOne(in: dbQueue)
    -Player.all().rx.fetchAll(in: dbQueue)
    -Player.all().rx.fetchCount(in: dbQueue)
    +Player.all().rx.observeFirst(in: dbQueue)
    +Player.all().rx.observeAll(in: dbQueue)
    +Player.all().rx.observeCount(in: dbQueue)
    ```

- The way to provide a specific scheduler to a value observable has changed:
    
    ```diff
    -Player.all().rx.fetchAll(in: dbQueue, scheduler: MainScheduler.asyncInstance)
    +Player.all().rx.observeAll(in: dbQueue, observeOn: MainScheduler.asyncInstance)
    ```


## 0.14.0

Released May 24, 2019 &bull; [diff](https://github.com/RxSwiftCommunity/RxGRDB/compare/v0.13.0...v0.14.0)

### New

- Support for Swift 5, GRDB 4.0, and RxSwift 5.0.
- Reactive extension on DatabaseRegionObservation:
    
    ```swift
    let players = Player.all()
    let teams = Team.all()
    let observation = DatabaseRegionObservation(tracking: players, teams)
    observation.rx.changes(in: dbQueue)
        .subscribe(onNext: { db: Database in
            print("Players or teams have changed.")
        })
    ```

### Breaking Changes

- Swift 4.0 and Swift 4.1 are no longer supported.
- GRDB 3 and RxSwift 4 are no longer supported.
- iOS 8 is no longer supported. Minimum deployment target is now iOS 9.0.
- Deprecated APIs are no longer available.
- `DatabaseWriter.rx.changes` is removed, replaced with `DatabaseRegionObservation.rx.changes`.
- SQLCipher support is now available under the CocoaPods `RxGRDB/SQLCipher` name.


## 0.13.0

Released November 2, 2018 &bull; [diff](https://github.com/RxSwiftCommunity/RxGRDB/compare/v0.12.1...v0.13.0)

- [#46](https://github.com/RxSwiftCommunity/RxGRDB/pull/46): Implement RxGRDB on top GRDB.ValueObservation


### Breaking Changes

- The `DatabaseWriter.rx.fetch` method has been removed. Instead, use [`ValueObservation.rx.fetch`](README.md#valueobservationrxfetchinstartimmediatelyscheduler).
- The `distinctUntilChanged` parameter is no longer available when one creates an RxGRDB observable. Filtering of consecutive identical database values is now the default behavior.


### New

- One can now create a [values observable](README.md#values-observables) from a [DatabaseReader](https://groue.github.io/GRDB.swift/docs/3.5/Protocols/DatabaseReader.html).


## 0.12.1

Released October 25, 2018 &bull; [diff](https://github.com/RxSwiftCommunity/RxGRDB/compare/v0.12.0...v0.12.1)

- Fixed GRDB Cocoapods dependency.


## 0.12.0

Released Septembre 17, 2018 &bull; [diff](https://github.com/RxSwiftCommunity/RxGRDB/compare/v0.11.0...v0.12.0)

- [#39](https://github.com/RxSwiftCommunity/RxGRDB/pull/39): Xcode 10 & GRDB 3.3.0


## 0.11.0

Released June 7, 2018 &bull; [diff](https://github.com/RxSwiftCommunity/RxGRDB/compare/v0.10.0...v0.11.0)

### New

- Support for GRDB 3.0
- The new DatabaseRegionConvertible protocol allows better request encapsulation ([documentation](README.md#databaseregionconvertible-protocol))

### Breaking Changes

- "Fetch tokens" and the `mapFetch` operator were ill-advised, and have been removed. Now please use the new `DatabaseWriter.fetch(from:startImmediately:scheduler:values:)` method instead, which produces exactly the same observable:
    
    ```diff
     // Old way
    -let values = dbQueue.rx
    -    .fetchTokens(in: [request, ...])
    -    .mapFetch { db in
    -        try fetchResults(db)
    -    }
     
     // New way
    +let values = dbQueue.rx.fetch(from: [request, ...]) { db in
    +    try fetchResults(db)
    +}
    ```


## 0.10.0

Released March 26, 2018 &bull; [diff](https://github.com/RxSwiftCommunity/RxGRDB/compare/v0.9.0...v0.10.0)


### New

- Unless they are provided an explicit scheduler, [values observables](https://github.com/RxSwiftCommunity/RxGRDB/blob/master/README.md#values-observables) subscribed from the main queue are now guaranteed a synchronous emission of their initial value ([#28](https://github.com/RxSwiftCommunity/RxGRDB/pull/28)).

- The RxGRDB repository now uses CocoaPods for its inner dependencies GRBD and RxSwift. After you have downloaded the RxGRDB repository, run `pod repo update; pod install` in order to download all dependencies, build targets, or run tests ([#29](https://github.com/RxSwiftCommunity/RxGRDB/pull/29)).


### Documentation Diff

- The [Values Observables](https://github.com/RxSwiftCommunity/RxGRDB/blob/master/README.md#values-observables) chapter now describes the scheduling of fetched values.


## 0.9.0

Released February 25, 2018 &bull; [diff](https://github.com/RxSwiftCommunity/RxGRDB/compare/v0.8.1...v0.9.0)


### New

- [Values observables](https://github.com/RxSwiftCommunity/RxGRDB/blob/master/README.md#values-observables) can now be scheduled on any RxSwift scheduler (fixes [#22](https://github.com/RxSwiftCommunity/RxGRDB/issues/22)).


### Breaking Changees

- "Change Tokens" have been renamed "Fetch Tokens" in order to better reflect their purpose, and to enhance the distinction between observables that emit values on any schedulers ("values observables") and have "fetch" in their definition, from observables that emit database connections on a GRDB dispatch queue ("changes observables") and have "changes" in their definition).


### Documentation Diff

- The [Scheduling Guide](https://github.com/RxSwiftCommunity/RxGRDB/blob/master/README.md#scheduling) has been augmented with a chapter on data consistency.


### API diff

```diff
+struct FetchToken { }
-struct ChangeToken { }

 extension Reactive where Base: DatabaseWriter {
+    func fetchTokens(in requests: [Request], startImmediately: Bool = true, scheduler: ImmediateSchedulerType = MainScheduler.instance) -> Observable<FetchToken>
-    func changeTokens(in requests: [Request], startImmediately: Bool = true, scheduler: ImmediateSchedulerType = MainScheduler.instance) -> Observable<ChangeToken>
 }
```


## 0.8.1

Released February 20, 2018 &bull; [diff](https://github.com/RxSwiftCommunity/RxGRDB/compare/v0.8.0...v0.8.1)

- Fixes a bug that would have [values observables](https://github.com/RxSwiftCommunity/RxGRDB/blob/master/README.md#values-observables) fail to observe some database changes.


## 0.8.0

Released January 18, 2018 &bull; [diff](https://github.com/RxSwiftCommunity/RxGRDB/compare/v0.7.0...v0.8.0)

This version enhances the scheduling of database notifications, and the tracking of specific database rows.


## New

- The tracking of requests that target specific rows, identified by their row ids, has been enhanced:
    
    In previous version of RxGRDB, tracking `Player.filter(key: 1)` would trigger change notifications for all changes to the players table.
    
    Now RxGRDB is able to precisely track the player of ID 1, and won't emit any notification for changes performed on other players.


## Fixed

- RxGRDB observables used to require subscription and observation to happen on the same dispatch queue. It was easy to fail this precondition, and misuse the library. This has been fixed.
- The [demo application](https://github.com/RxSwiftCommunity/RxGRDB/tree/master/Documentation/RxGRDBDemo) used to misuse MKMapView by converting database changes into annotation coordinate updates on the wrong dispatch queue. This has been fixed.


### Breaking Changes

- GRDB dependency has been bumped to v2.6.
- Database observation scheduling used to be managed through raw dispatch queues. One now uses regular [RxSwift schedulers](https://github.com/ReactiveX/RxSwift/blob/master/Documentation/Schedulers.md). The `synchronizedStart` parameter has been renamed to `startImmediately` in order to reflect the fact that not all schedulers can start synchronously. See the updated [documentation](https://github.com/RxSwiftCommunity/RxGRDB/blob/master/README.md#documentation) of RxGRDB reactive methods.
- The `Diffable` protocol was ill-advised, and has been removed.
- The `primaryKeySortedDiff` operator has been replaced by `PrimaryKeyDiffScanner` ([documentation](https://github.com/RxSwiftCommunity/RxGRDB/blob/master/README.md#primarykeydiffscanner))


### API diff

```diff
 extension Reactive where Base: Request {
-    func changes(
-        in writer: DatabaseWriter,
-        synchronizedStart: Bool = true)
-        -> Observable<Database>
+    func changes(
+        in writer: DatabaseWriter,
+        startImmediately: Bool = true)
+        -> Observable<Database>
 
-    func fetchCount(
-        in writer: DatabaseWriter,
-        synchronizedStart: Bool = true,
-        resultQueue: DispatchQueue = DispatchQueue.main)
-        -> Observable<Int>
+    func fetchCount(
+        in writer: DatabaseWriter,
+        startImmediately: Bool = true,
+        scheduler: SerialDispatchQueueScheduler = MainScheduler.instance)
+        -> Observable<Int>
 }

 extension Reactive where Base: TypedRequest {
-    func fetchAll(
-        in writer: DatabaseWriter,
-        synchronizedStart: Bool = true,
-        resultQueue: DispatchQueue = DispatchQueue.main,
-        distinctUntilChanged: Bool = false)
-        -> Observable<[Base.RowDecoder]>
+    func fetchAll(
+        in writer: DatabaseWriter,
+        startImmediately: Bool = true,
+        scheduler: SerialDispatchQueueScheduler = MainScheduler.instance,
+        distinctUntilChanged: Bool = false)
+        -> Observable<[Base.RowDecoder]>
 
-    func fetchOne(
-        in writer: DatabaseWriter,
-        synchronizedStart: Bool = true,
-        resultQueue: DispatchQueue = DispatchQueue.main,
-        distinctUntilChanged: Bool = false)
-        -> Observable<Base.RowDecoder?>
+    func fetchOne(
+        in writer: DatabaseWriter,
+        startImmediately: Bool = true,
+        scheduler: SerialDispatchQueueScheduler = MainScheduler.instance,
+        distinctUntilChanged: Bool = false)
+        -> Observable<Base.RowDecoder?>
 }

 extension ObservableType where E == ChangeToken {
-    func mapFetch<R>(
-        resultQueue: DispatchQueue = DispatchQueue.main,
-        _ fetch: @escaping (Database) throws -> R)
-        -> Observable<R>
+    func mapFetch<R>(_ fetch: @escaping (Database) throws -> R) -> Observable<R>
 }

 extension Reactive where Base: DatabaseWriter {
-    public func changes(
-        in requests: [Request],
-        synchronizedStart: Bool = true)
-        -> Observable<Database>
+    public func changes(
+        in requests: [Request],
+        startImmediately: Bool = true)
+        -> Observable<Database>
 
-    func changeTokens(
-        in requests: [Request],
-        synchronizedStart: Bool = true)
-        -> Observable<ChangeToken>
+    func changeTokens(
+        in requests: [Request],
+        startImmediately: Bool = true,
+        scheduler: SerialDispatchQueueScheduler = MainScheduler.instance)
+        -> Observable<ChangeToken>
 }

-protocol Diffable {
-    func updated(with row: Row) -> Self
-}
-extension Reactive where Base: TypedRequest, Base.RowDecoder: RowConvertible & MutablePersistable & Diffable {
-    func primaryKeySortedDiff(
-        in writer: DatabaseWriter,
-        initialElements: [Base.RowDecoder] = [])
-        -> Observable<PrimaryKeySortedDiff<Base.RowDecoder>>
-}
-struct PrimaryKeySortedDiff<Element> { ... }
+struct PrimaryKeyDiffScanner<Record: RowConvertible & MutablePersistable> {
+    let diff: PrimaryKeyDiff<Record>
+    init<Request>(
+            database: Database,
+            request: Request,
+            initialRecords: [Record],
+            updateRecord: ((Record, Row) -> Record)? = nil)
+            throws
+            where Request: TypedRequest, Request.RowDecoder == Record
+    func diffed(from rows: [Row]) -> PrimaryKeyDiffScanner
+}
+struct PrimaryKeyDiff<Record> {
+    let inserted: [Record]
+    let updated: [Record]
+    let deleted: [Record]
+    var isEmpty: Bool
+}
```

## 0.7.0

Released October 18, 2017 &bull; [diff](https://github.com/RxSwiftCommunity/RxGRDB/compare/v0.6.0...v0.7.0)

### New

- Support for Swift 4
- Support for various diff algorithms ([Documentation](https://github.com/RxSwiftCommunity/RxGRDB/blob/master/README.md#diffs))
- New [demo application](https://github.com/RxSwiftCommunity/RxGRDB/tree/master/Documentation/RxGRDBDemo) for various diff algorithms.

### Fixed

- Observables that emit fetched values used to emit their first element on the wrong dispatch queue when their `synchronizedStart` option is true. That first element is now correctly emitted on the subscription dispatch queue.

### Breaking Changes

- Requirements have changed: Xcode 9+, Swift 4, GRDB 2.0


## 0.6.0

Released July 13, 2017

- **Fixed**: Support for macOS, broken in v0.5.0
- **New**: GRDB dependency bumped to v1.2


## 0.5.0

Released July 8, 2017

### New

RxGRDB has learned how to observe multiple requests and fetch from other requests. [Documentation](https://github.com/RxSwiftCommunity/RxGRDB/blob/master/README.md#observing-multiple-requests)

To get a single notification when a transaction has modified several requests, use `DatabaseWriter.rx.changes`:

```swift
// Observable<Database>
dbQueue.rx.changes(in: [request, ...])
```

To turn a change notification into consistent results fetched from multiple requests, use `DatabaseWriter.rx.changeTokens` and the `mapFetch` operator:

```swift
dbQueue.rx
    .changeTokens(in: [request, ...])
    .mapFetch { (db: Database) in
        return ...
    }
```

### API diff

```diff
+extension Reactive where Base: DatabaseWriter {
+    func changes(in requests: [Request], synchronizedStart: Bool = true) -> Observable<Database>
+    func changeTokens(in requests: [Request], synchronizedStart: Bool = true) -> Observable<ChangeToken>
+}

+struct ChangeToken {
+    var database: Database { get }
+}

+extension ObservableType where E == ChangeToken {
+    func func mapFetch<R>(resultQueue: DispatchQueue = DispatchQueue.main, _ fetch: @escaping (Database) throws -> R) -> Observable<R>
+}
```


## 0.4.1

Released June 20, 2017

### Fixed

- Podspec requirement for RxSwift changed to `~> 3.3`
- Added missing support for new AdaptedRequest and AdaptedTypedRequest of GRDB 1.0


## 0.4.0

Released June 20, 2017

### Breaking Changes

- RxGRDB now requires GRDB v1.0


## 0.3.0

Released May 22, 2017

### New

- The new `distinctUntilChanged` parameter has RxGRDB avoid notifying consecutive identical values.

    ```swift
    request.rx.fetchAll(in: dbQueue, distinctUntilChanged: true)...
    ```

- Tracking of requests that fetch an array of optional values:
    
    ```swift
    // Email column may be NULL:
    let request = Person.select(email).bound(to: Optional<String>.self)
    request.rx.fetchAll(in: dbQueue)
        .subscribe(onNext: { emails: [String?] in
            ...
        })
    ```


## 0.2.0

Released May 17, 2017

### New

- Support for SQLCipher.


## 0.1.2

Released April 6, 2017

### Fixed

- RxGRDB observables now support the `retry` operator, and no longer crash when disposed on a database queue.


## 0.1.1

Released April 5, 2017

### New

- `synchronizedStart` option
- `Request.rx.fetchCount(in:synchronizedStart)`


## 0.1.0

Released April 5, 2017

Initial release


================================================
FILE: Documentation/RxGRDBDemo/README.md
================================================
RxGRDBDemo
==========

<img align="right" src="https://raw.githubusercontent.com/RxSwiftCommunity/RxGRDB/master/Documentation/RxGRDBDemo/Documentation/Demo1.png" width="50%">

This demo application uses [Action], [RxDataSources], [RxGRDB], and [RxSwift] to synchronize its view with the content of the database.

To play with it:

1. Download the RxGRDB repository
2. Run `pod install`
3. Open `RxGRDB.xcworkspace` at the root of the repository
4. Run the RxGRDBDemo application.

The rows of the players table view animate as you change the players ordering, delete all players, or refresh them (refreshing applies random transformations to the database)

## Models

- [AppDatabase.swift](RxGRDBDemo/AppDatabase.swift)
    
    AppDatabase defines the database for the whole application. It uses [DatabaseMigrator](https://github.com/groue/GRDB.swift/blob/master/README.md#migrations) in order to setup the database schema.

- [Player.swift](RxGRDBDemo/Models/Player.swift)
    
    Player is a [Record](https://github.com/groue/GRDB.swift/blob/master/README.md#records) type, able to read and write in the database. It conforms to the standard Codable protocol in order to gain all advantages of [Codable Records](https://github.com/groue/GRDB.swift/blob/master/README.md#codable-records).
    
    ```swift
    struct Player: Codable, Equatable {
        var id: Int64?
        var name: String
        var score: Int
    }
    ```

- [Players.swift](RxGRDBDemo/Models/Players.swift)
    
    Players defines read and write operations on the players database.


## User Interface

- [PlayersViewModel.swift](RxGRDBDemo/UI/PlayersViewModel.swift)
    
    PlayersViewModel defines the content displayed on screen, and a bunch of available actions of players.

- [PlayersViewController.swift](RxGRDBDemo/UI/PlayersViewController.swift)
    
    PlayersViewController feeds from PlayersViewModel and displays it on screen.


[Action]: https://github.com/RxSwiftCommunity/Action
[RxDataSources]: https://github.com/RxSwiftCommunity/RxDataSources
[RxGRDB]: http://github.com/RxSwiftCommunity/RxGRDB
[RxSwift]: https://github.com/ReactiveX/RxSwift


================================================
FILE: Documentation/RxGRDBDemo/RxGRDBDemo/AppDatabase.swift
================================================
import GRDB

/// A type responsible for initializing an application database.
struct AppDatabase {
    
    /// Prepares a fully initialized database at path
    func setup(_ database: DatabaseWriter) throws {
        // Use DatabaseMigrator to define the database schema
        // See https://github.com/groue/GRDB.swift/#migrations
        try migrator.migrate(database)
        
        // Other possible setup include: custom functions, collations,
        // full-text tokenizers, etc.
    }
    
    /// The DatabaseMigrator that defines the database schema.
    // See https://github.com/groue/GRDB.swift/#migrations
    private var migrator: DatabaseMigrator {
        var migrator = DatabaseMigrator()
        
        #if DEBUG
        // Speed up development by nuking the database when migrations change
        migrator.eraseDatabaseOnSchemaChange = true
        #endif
        
        migrator.registerMigration("v1.0") { db in
            try db.create(table: "player") { t in
                t.autoIncrementedPrimaryKey("id")
                t.column("name", .text).notNull().collate(.localizedCaseInsensitiveCompare)
                t.column("score", .integer).notNull()
            }
            
            try db.create(table: "place") { t in
                t.autoIncrementedPrimaryKey("id")
                t.column("latitude", .double).notNull()
                t.column("longitude", .double).notNull()
            }
        }
        
        return migrator
    }
}


================================================
FILE: Documentation/RxGRDBDemo/RxGRDBDemo/AppDelegate.swift
================================================
import UIKit
import GRDB

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
        // Setup the Current World
        let dbPool = try! setupDatabase(application)
        Current = World(database: { dbPool })
        
        // Application is nicer looking if it starts populated
        try! Current.players().populateIfEmpty()
        
        return true
    }
    
    private func setupDatabase(_ application: UIApplication) throws -> DatabasePool {
        // Create a DatabasePool for efficient multi-threading
        let databaseURL = try FileManager.default
            .url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
            .appendingPathComponent("db.sqlite")
        let dbPool = try DatabasePool(path: databaseURL.path)
        
        // Setup the database
        try AppDatabase().setup(dbPool)
        
        return dbPool
    }
}


================================================
FILE: Documentation/RxGRDBDemo/RxGRDBDemo/Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDevelopmentRegion</key>
	<string>$(DEVELOPMENT_LANGUAGE)</string>
	<key>CFBundleExecutable</key>
	<string>$(EXECUTABLE_NAME)</string>
	<key>CFBundleIdentifier</key>
	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>$(PRODUCT_NAME)</string>
	<key>CFBundlePackageType</key>
	<string>APPL</string>
	<key>CFBundleShortVersionString</key>
	<string>1.0</string>
	<key>CFBundleVersion</key>
	<string>1</string>
	<key>LSRequiresIPhoneOS</key>
	<true/>
	<key>UILaunchStoryboardName</key>
	<string>LaunchScreen</string>
	<key>UIMainStoryboardFile</key>
	<string>Main</string>
	<key>UIRequiredDeviceCapabilities</key>
	<array>
		<string>armv7</string>
	</array>
	<key>UISupportedInterfaceOrientations</key>
	<array>
		<string>UIInterfaceOrientationPortrait</string>
		<string>UIInterfaceOrientationLandscapeLeft</string>
		<string>UIInterfaceOrientationLandscapeRight</string>
	</array>
	<key>UISupportedInterfaceOrientations~ipad</key>
	<array>
		<string>UIInterfaceOrientationPortrait</string>
		<string>UIInterfaceOrientationPortraitUpsideDown</string>
		<string>UIInterfaceOrientationLandscapeLeft</string>
		<string>UIInterfaceOrientationLandscapeRight</string>
	</array>
</dict>
</plist>


================================================
FILE: Documentation/RxGRDBDemo/RxGRDBDemo/Models/Player.swift
================================================
import GRDB

// A player
struct Player: Codable, Equatable {
    var id: Int64?
    var name: String
    var score: Int
}

// Adopt FetchableRecord so that we can fetch players from the database.
// Implementation is automatically derived from Codable.
extension Player: FetchableRecord { }

// Adopt MutablePersistable so that we can create/update/delete players in the
// database. Implementation is partially derived from Codable.
extension Player: MutablePersistableRecord {
    // Update auto-incremented id upon successful insertion
    mutating func didInsert(with rowID: Int64, for column: String?) {
        id = rowID
    }
}

// Define columns that we can use for our database requests.
// They are derived from the CodingKeys enum for extra safety.
extension Player {
    fileprivate enum Columns {
        static let id = Column(CodingKeys.id)
        static let name = Column(CodingKeys.name)
        static let score = Column(CodingKeys.score)
    }
}

// Define requests of players in a constrained extension to the
// DerivableRequest protocol.
extension DerivableRequest where RowDecoder == Player {
    func orderByScore() -> Self {
        return order(Player.Columns.score.desc, Player.Columns.name)
    }
    
    func orderByName() -> Self {
        return order(Player.Columns.name, Player.Columns.score.desc)
    }
}

// Player randomization
extension Player {
    private static let names = [
        "Arthur", "Anita", "Barbara", "Bernard", "Clément", "Chiara", "David",
        "Dean", "Éric", "Elena", "Fatima", "Frederik", "Gilbert", "Georgette",
        "Henriette", "Hassan", "Ignacio", "Irene", "Julie", "Jack", "Karl",
        "Kristel", "Louis", "Liz", "Masashi", "Mary", "Noam", "Nolwenn",
        "Ophelie", "Oleg", "Pascal", "Patricia", "Quentin", "Quinn", "Raoul",
        "Rachel", "Stephan", "Susie", "Tristan", "Tatiana", "Ursule", "Urbain",
        "Victor", "Violette", "Wilfried", "Wilhelmina", "Yvon", "Yann",
        "Zazie", "Zoé"]
    
    static func randomName() -> String {
        return names.randomElement()!
    }
    
    static func randomScore() -> Int {
        return 10 * Int.random(in: 0...100)
    }
}


================================================
FILE: Documentation/RxGRDBDemo/RxGRDBDemo/Models/Players.swift
================================================
import GRDB
import RxGRDB
import RxSwift

/// Players is responsible for high-level operations on the players database.
struct Players {
    private let database: DatabaseWriter
    
    init(database: DatabaseWriter) {
        self.database = database
    }
    
    // MARK: - Modify Players
    
    /// Creates random players if needed, and returns whether the database
    /// was empty.
    @discardableResult
    func populateIfEmpty() throws -> Bool {
        try database.write(_populateIfEmpty)
    }
    
    func deleteAll() -> Single<Void> {
        database.rx.write(updates: _deleteAll)
    }
    
    func deleteOne(_ player: Player) -> Single<Void> {
        database.rx.write(updates: { db in try self._deleteOne(db, player: player) })
    }
    
    func refresh() -> Single<Void> {
        database.rx.write(updates: _refresh)
    }
    
    func stressTest() -> Single<Void> {
        Single.zip(repeatElement(refresh(), count: 50)).map { _ in }
    }
    
    // MARK: - Access Players
    
    /// An observable that tracks changes in the players
    func playersOrderedByScore() -> Observable<[Player]> {
        ValueObservation
            .tracking(Player.all().orderByScore().fetchAll)
            .rx.observe(in: database)
    }
    
    /// An observable that tracks changes in the players
    func playersOrderedByName() -> Observable<[Player]> {
        ValueObservation
            .tracking(Player.all().orderByName().fetchAll)
            .rx.observe(in: database)
    }
    
    // MARK: - Implementation
    //
    // ⭐️ Good practice: when we want to update the database, we define methods
    // that accept a Database connection, because they can easily be composed.
    
    /// Creates random players if needed, and returns whether the database
    /// was empty.
    private func _populateIfEmpty(_ db: Database) throws -> Bool {
        if try Player.fetchCount(db) > 0 {
            return false
        }
        
        // Insert new random players
        for _ in 0..<8 {
            var player = Player(id: nil, name: Player.randomName(), score: Player.randomScore())
            try player.insert(db)
        }
        return true
    }
    
    private func _deleteAll(_ db: Database) throws {
        try Player.deleteAll(db)
    }
    
    private func _deleteOne(_ db: Database, player: Player) throws {
        try player.delete(db)
    }
    
    private func _refresh(_ db: Database) throws {
        if try _populateIfEmpty(db) {
            return
        }
        
        // Insert a player
        if Bool.random() {
            var player = Player(id: nil, name: Player.randomName(), score: Player.randomScore())
            try player.insert(db)
        }
        // Delete a random player
        if Bool.random() {
            try Player.order(sql: "RANDOM()").limit(1).deleteAll(db)
        }
        // Update some players
        for var player in try Player.fetchAll(db) where Bool.random() {
            try player.updateChanges(db) {
                $0.score = Player.randomScore()
            }
        }
    }
}


================================================
FILE: Documentation/RxGRDBDemo/RxGRDBDemo/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json
================================================
{
  "images" : [
    {
      "idiom" : "iphone",
      "size" : "20x20",
      "scale" : "2x"
    },
    {
      "idiom" : "iphone",
      "size" : "20x20",
      "scale" : "3x"
    },
    {
      "idiom" : "iphone",
      "size" : "29x29",
      "scale" : "2x"
    },
    {
      "idiom" : "iphone",
      "size" : "29x29",
      "scale" : "3x"
    },
    {
      "idiom" : "iphone",
      "size" : "40x40",
      "scale" : "2x"
    },
    {
      "idiom" : "iphone",
      "size" : "40x40",
      "scale" : "3x"
    },
    {
      "idiom" : "iphone",
      "size" : "60x60",
      "scale" : "2x"
    },
    {
      "idiom" : "iphone",
      "size" : "60x60",
      "scale" : "3x"
    },
    {
      "idiom" : "ipad",
      "size" : "20x20",
      "scale" : "1x"
    },
    {
      "idiom" : "ipad",
      "size" : "20x20",
      "scale" : "2x"
    },
    {
      "idiom" : "ipad",
      "size" : "29x29",
      "scale" : "1x"
    },
    {
      "idiom" : "ipad",
      "size" : "29x29",
      "scale" : "2x"
    },
    {
      "idiom" : "ipad",
      "size" : "40x40",
      "scale" : "1x"
    },
    {
      "idiom" : "ipad",
      "size" : "40x40",
      "scale" : "2x"
    },
    {
      "idiom" : "ipad",
      "size" : "76x76",
      "scale" : "1x"
    },
    {
      "idiom" : "ipad",
      "size" : "76x76",
      "scale" : "2x"
    },
    {
      "idiom" : "ipad",
      "size" : "83.5x83.5",
      "scale" : "2x"
    },
    {
      "idiom" : "ios-marketing",
      "size" : "1024x1024",
      "scale" : "1x"
    }
  ],
  "info" : {
    "version" : 1,
    "author" : "xcode"
  }
}

================================================
FILE: Documentation/RxGRDBDemo/RxGRDBDemo/Resources/Base.lproj/LaunchScreen.storyboard
================================================
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" systemVersion="17A277" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
    <dependencies>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <!--View Controller-->
        <scene sceneID="EHf-IW-A2E">
            <objects>
                <viewController id="01J-lp-oVM" sceneMemberID="viewController">
                    <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                        <viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
                    </view>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="53" y="375"/>
        </scene>
    </scenes>
</document>


================================================
FILE: Documentation/RxGRDBDemo/RxGRDBDemo/Resources/Base.lproj/Main.storyboard
================================================
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="xTj-0J-jag">
    <device id="retina4_7" orientation="portrait">
        <adaptation id="fullscreen"/>
    </device>
    <dependencies>
        <deployment identifier="iOS"/>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <!--Hall of Fame-->
        <scene sceneID="dn1-UJ-DYy">
            <objects>
                <viewController id="GLL-Xk-ypD" customClass="PlayersViewController" customModule="RxGRDBDemo" customModuleProvider="target" sceneMemberID="viewController">
                    <view key="view" contentMode="scaleToFill" id="t9e-5y-1YT">
                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <subviews>
                            <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="bXR-pQ-fFV">
                                <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                                <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                                <prototypes>
                                    <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" reuseIdentifier="Player" textLabel="q0A-CX-hct" detailTextLabel="ZU9-MH-ADf" style="IBUITableViewCellStyleValue1" id="O5b-qw-AuB">
                                        <rect key="frame" x="0.0" y="28" width="375" height="44"/>
                                        <autoresizingMask key="autoresizingMask"/>
                                        <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="O5b-qw-AuB" id="GIv-EG-2oT">
                                            <rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
                                            <autoresizingMask key="autoresizingMask"/>
                                            <subviews>
                                                <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="q0A-CX-hct">
                                                    <rect key="frame" x="15" y="12" width="33.5" height="20.5"/>
                                                    <autoresizingMask key="autoresizingMask"/>
                                                    <fontDescription key="fontDescription" type="system" pointSize="17"/>
                                                    <nil key="textColor"/>
                                                    <nil key="highlightedColor"/>
                                                </label>
                                                <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Detail" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="ZU9-MH-ADf">
                                                    <rect key="frame" x="316" y="12" width="44" height="20.5"/>
                                                    <autoresizingMask key="autoresizingMask"/>
                                                    <fontDescription key="fontDescription" type="system" pointSize="17"/>
                                                    <nil key="textColor"/>
                                                    <nil key="highlightedColor"/>
                                                </label>
                                            </subviews>
                                        </tableViewCellContentView>
                                    </tableViewCell>
                                </prototypes>
                            </tableView>
                            <visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="d2C-E5-vHW">
                                <rect key="frame" x="0.0" y="64" width="375" height="559"/>
                                <view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="jHw-6h-iUF">
                                    <rect key="frame" x="0.0" y="0.0" width="375" height="559"/>
                                    <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                                    <subviews>
                                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="No Player" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rZc-LE-xF6">
                                            <rect key="frame" x="151" y="269" width="73.5" height="21"/>
                                            <fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
                                            <color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                                            <nil key="highlightedColor"/>
                                        </label>
                                    </subviews>
                                    <constraints>
                                        <constraint firstItem="rZc-LE-xF6" firstAttribute="centerY" secondItem="jHw-6h-iUF" secondAttribute="centerY" id="QUf-R7-eRK"/>
                                        <constraint firstItem="rZc-LE-xF6" firstAttribute="centerX" secondItem="jHw-6h-iUF" secondAttribute="centerX" id="Vxw-R3-mxL"/>
                                    </constraints>
                                </view>
                                <blurEffect style="prominent"/>
                            </visualEffectView>
                        </subviews>
                        <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                        <constraints>
                            <constraint firstItem="J24-oS-fQn" firstAttribute="bottom" secondItem="d2C-E5-vHW" secondAttribute="bottom" id="3aY-JG-086"/>
                            <constraint firstItem="d2C-E5-vHW" firstAttribute="leading" secondItem="t9e-5y-1YT" secondAttribute="leading" id="3lh-10-DaJ"/>
                            <constraint firstItem="bXR-pQ-fFV" firstAttribute="top" secondItem="t9e-5y-1YT" secondAttribute="top" id="BrP-Nz-4ZZ"/>
                            <constraint firstAttribute="trailing" secondItem="d2C-E5-vHW" secondAttribute="trailing" id="D7P-Zp-MgG"/>
                            <constraint firstItem="bXR-pQ-fFV" firstAttribute="leading" secondItem="t9e-5y-1YT" secondAttribute="leading" id="DqX-wI-Qjp"/>
                            <constraint firstItem="bXR-pQ-fFV" firstAttribute="trailing" secondItem="t9e-5y-1YT" secondAttribute="trailing" id="Q9v-wC-nwK"/>
                            <constraint firstItem="d2C-E5-vHW" firstAttribute="top" secondItem="J24-oS-fQn" secondAttribute="top" id="Umo-im-bz7"/>
                            <constraint firstAttribute="bottom" secondItem="bXR-pQ-fFV" secondAttribute="bottom" id="dit-bQ-w2u"/>
                        </constraints>
                        <viewLayoutGuide key="safeArea" id="J24-oS-fQn"/>
                    </view>
                    <navigationItem key="navigationItem" title="Hall of Fame" id="XOX-J3-Q9U"/>
                    <connections>
                        <outlet property="emptyView" destination="d2C-E5-vHW" id="3xT-Hj-O9G"/>
                        <outlet property="tableView" destination="bXR-pQ-fFV" id="JFe-wC-Zyg"/>
                    </connections>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="v2h-i8-h1r" userLabel="First Responder" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="-783.20000000000005" y="-153.37331334332833"/>
        </scene>
        <!--Navigation Controller-->
        <scene sceneID="K9r-My-Kvk">
            <objects>
                <navigationController toolbarHidden="NO" id="xTj-0J-jag" sceneMemberID="viewController">
                    <navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="7ys-OS-Gzd">
                        <rect key="frame" x="0.0" y="20" width="375" height="44"/>
                        <autoresizingMask key="autoresizingMask"/>
                    </navigationBar>
                    <toolbar key="toolbar" opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="R8l-dN-zJb">
                        <rect key="frame" x="0.0" y="623" width="375" height="44"/>
                        <autoresizingMask key="autoresizingMask"/>
                    </toolbar>
                    <connections>
                        <segue destination="GLL-Xk-ypD" kind="relationship" relationship="rootViewController" id="N8i-92-38f"/>
                    </connections>
                </navigationController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="KA4-WL-wIo" userLabel="First Responder" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="-1636" y="-153"/>
        </scene>
    </scenes>
</document>


================================================
FILE: Documentation/RxGRDBDemo/RxGRDBDemo/UI/PlayersViewController.swift
================================================
import UIKit
import RxDataSources
import RxSwift
import RxCocoa

/// An MVVM ViewController that displays PlayersViewModel
class PlayersViewController: UIViewController {
    @IBOutlet private weak var tableView: UITableView!
    @IBOutlet private weak var emptyView: UIView!
    private let viewModel = PlayersViewModel()
    private let disposeBag = DisposeBag()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupNavigationItem()
        setupToolbar()
        setupTableView()
        setupEmptyView()
    }
    
    private func setupNavigationItem() {
        viewModel
            .orderingButtonTitle
            .subscribe(onNext: updateRightBarButtonItem)
            .disposed(by: disposeBag)
    }
    
    private func updateRightBarButtonItem(title: String?) {
        guard let title = title else {
            navigationItem.rightBarButtonItem = nil
            return
        }
        
        var barButtonItem = UIBarButtonItem(title: title, style: .plain, target: nil, action: nil)
        barButtonItem.rx.action = viewModel.toggleOrdering
        navigationItem.rightBarButtonItem = barButtonItem
    }
    
    private func setupToolbar() {
        var deleteAllButtonItem = UIBarButtonItem(barButtonSystemItem: .trash, target: nil, action: nil)
        deleteAllButtonItem.rx.action = viewModel.deleteAll
        
        var refreshButtonItem = UIBarButtonItem(barButtonSystemItem: .refresh, target: nil, action: nil)
        refreshButtonItem.rx.action = viewModel.refresh
        
        var stressTestButtonItem = UIBarButtonItem(title: "💣", style: .plain, target: nil, action: nil)
        stressTestButtonItem.rx.action = viewModel.stressTest
        
        toolbarItems = [
            deleteAllButtonItem,
            UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil),
            refreshButtonItem,
            UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil),
            stressTestButtonItem,
        ]
    }
    
    private func setupTableView() {
        let dataSource = RxTableViewSectionedAnimatedDataSource<Section>(configureCell: { (dataSource, tableView, indexPath, _) in
            let section = dataSource.sectionModels[indexPath.section]
            let player = section.items[indexPath.row]
            let cell = tableView.dequeueReusableCell(withIdentifier: "Player", for: indexPath)
            cell.textLabel?.text = player.name
            cell.detailTextLabel?.text = "\(player.score)"
            return cell
        })
        
        dataSource.animationConfiguration = AnimationConfiguration(
            insertAnimation: .fade,
            reloadAnimation: .fade,
            deleteAnimation: .fade)
        
        dataSource.canEditRowAtIndexPath = { _, _ in true }
        
        viewModel
            .players
            .asDriver(onErrorJustReturn: [])
            .map { [Section(items: $0)] }
            .drive(tableView.rx.items(dataSource: dataSource))
            .disposed(by: disposeBag)
        
        tableView.rx
            .itemDeleted
            .subscribe(onNext: { indexPath in
                let player = dataSource[indexPath]
                self.viewModel.deleteOne.execute(player)
            })
            .disposed(by: disposeBag)
    }
    
    private func setupEmptyView() {
        viewModel
            .players
            .map { !$0.isEmpty }
            .asDriver(onErrorJustReturn: false)
            .drive(emptyView.rx.isHidden)
            .disposed(by: disposeBag)
    }
}

private struct Section {
    var items: [Player]
}

extension Section: AnimatableSectionModelType {
    var identity: Int { return 1 }
    init(original: Section, items: [Player]) {
        self.items = items
    }
}

extension Player: IdentifiableType {
    var identity: Int64 { return id! }
}


================================================
FILE: Documentation/RxGRDBDemo/RxGRDBDemo/UI/PlayersViewModel.swift
================================================
import Action
import Foundation
import GRDB
import RxCocoa
import RxGRDB
import RxSwift

/// An MVVM ViewModel for PlayersViewController
class PlayersViewModel {
    // MARK: - Values Displayed on Screen
    
    var orderingButtonTitle: Observable<String?>
    var players: Observable<[Player]>
    
    // MARK: - Actions
    
    var toggleOrdering: CocoaAction
    var deleteAll: CocoaAction
    var deleteOne: CompletableAction<Player>
    var refresh: CocoaAction
    var stressTest: CocoaAction
    
    // MARK: - Implementation
    
    private enum Ordering: Equatable {
        case byScore
        case byName
    }
    private var ordering = BehaviorRelay<Ordering>(value: .byScore)
    
    init() {
        // The root of everything
        let ordering = BehaviorRelay<Ordering>(value: .byScore)
        
        // Values Displayed on Screen
        players = ordering
            .distinctUntilChanged()
            .flatMapLatest { ordering -> Observable<[Player]> in
                switch ordering {
                case .byScore:
                    return Current.players().playersOrderedByScore()
                case .byName:
                    return Current.players().playersOrderedByName()
                }
            }
            .share(replay: 1)
        
        orderingButtonTitle = Observable
            .combineLatest(players, ordering)
            .map { players, ordering -> String? in
                if players.isEmpty {
                    return nil
                }
                switch ordering {
                case .byScore:
                    return NSLocalizedString("Score ⬇︎", comment: "")
                case .byName:
                    return NSLocalizedString("Name ⬆︎", comment: "")
                }
        }
        
        // Actions
        deleteAll = CocoaAction {
            Current.players().deleteAll()
        }
        
        deleteOne = CompletableAction { player in
            Current.players().deleteOne(player).asCompletable()
        }
        
        refresh = CocoaAction {
            Current.players().refresh()
        }
        
        stressTest = CocoaAction {
            Current.players().stressTest()
        }
        
        toggleOrdering = CocoaAction {
            switch ordering.value {
            case .byName:
                ordering.accept(.byScore)
            case .byScore:
                ordering.accept(.byName)
            }
            return Observable.just(())
        }
    }
}


================================================
FILE: Documentation/RxGRDBDemo/RxGRDBDemo/World.swift
================================================
import GRDB

/// Dependency Injection based on the "How to Control the World" article:
/// https://www.pointfree.co/blog/posts/21-how-to-control-the-world
struct World {
    /// Access to the players database
    func players() -> Players { return Players(database: database()) }
    
    /// The database, private so that only high-level operations exposed by
    /// `players` are available to the rest of the application.
    private var database: () -> DatabaseWriter
    
    /// Creates a World with a database
    init(database: @escaping () -> DatabaseWriter) {
        self.database = database
    }
}

var Current = World(database: { fatalError("Database is uninitialized") })


================================================
FILE: Documentation/RxGRDBDemo/RxGRDBDemo.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
	archiveVersion = 1;
	classes = {
	};
	objectVersion = 52;
	objects = {

/* Begin PBXBuildFile section */
		565BD7C522C3F8D600BB9B5A /* AppDatabaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565BD7C422C3F8D600BB9B5A /* AppDatabaseTests.swift */; };
		565BD7C622C3F92F00BB9B5A /* AppDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56E1F9931F81021300793BFA /* AppDatabase.swift */; };
		565BD7C722C3F97900BB9B5A /* Player.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56E1F9951F81033200793BFA /* Player.swift */; };
		565BD7C922C3FA8B00BB9B5A /* PlayersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565BD7C822C3FA8B00BB9B5A /* PlayersTests.swift */; };
		565BD7CA22C3FAA700BB9B5A /* Players.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567515A322C3A9BC00A6FF66 /* Players.swift */; };
		565BD7D022C49D2100BB9B5A /* PlayersViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565BD7CF22C49D2100BB9B5A /* PlayersViewModelTests.swift */; };
		565BD7D122C49D9800BB9B5A /* PlayersViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567515A722C3B1F100A6FF66 /* PlayersViewModel.swift */; };
		565BD7D222C49E6600BB9B5A /* World.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567515A122C3A7DC00A6FF66 /* World.swift */; };
		565BD7D922C5DEE800BB9B5A /* PlayerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565BD7D822C5DEE800BB9B5A /* PlayerTests.swift */; };
		567515A222C3A7DC00A6FF66 /* World.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567515A122C3A7DC00A6FF66 /* World.swift */; };
		567515A422C3A9BC00A6FF66 /* Players.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567515A322C3A9BC00A6FF66 /* Players.swift */; };
		567515A822C3B1F100A6FF66 /* PlayersViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567515A722C3B1F100A6FF66 /* PlayersViewModel.swift */; };
		567CE94228CB747200A95C34 /* RxCocoa in Frameworks */ = {isa = PBXBuildFile; productRef = 567CE94128CB747200A95C34 /* RxCocoa */; };
		567E66D02438A6F80091B5D8 /* RxGRDB in Frameworks */ = {isa = PBXBuildFile; productRef = 567E66CF2438A6F80091B5D8 /* RxGRDB */; };
		56D6EA2F2438AB77000D55EF /* RxSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 56D6EA2E2438AB77000D55EF /* RxSwift */; };
		56D6EA322438ABAE000D55EF /* RxDataSources in Frameworks */ = {isa = PBXBuildFile; productRef = 56D6EA312438ABAE000D55EF /* RxDataSources */; };
		56D6EA352438ABD7000D55EF /* Action in Frameworks */ = {isa = PBXBuildFile; productRef = 56D6EA342438ABD7000D55EF /* Action */; };
		56D6EA442438AF90000D55EF /* GRDB in Frameworks */ = {isa = PBXBuildFile; productRef = 56D6EA432438AF90000D55EF /* GRDB */; };
		56E1F97D1F8101EE00793BFA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56E1F97C1F8101EE00793BFA /* AppDelegate.swift */; };
		56E1F97F1F8101EE00793BFA /* PlayersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56E1F97E1F8101EE00793BFA /* PlayersViewController.swift */; };
		56E1F9821F8101EE00793BFA /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 56E1F9801F8101EE00793BFA /* Main.storyboard */; };
		56E1F9841F8101EE00793BFA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 56E1F9831F8101EE00793BFA /* Assets.xcassets */; };
		56E1F9871F8101EE00793BFA /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 56E1F9851F8101EE00793BFA /* LaunchScreen.storyboard */; };
		56E1F9941F81021300793BFA /* AppDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56E1F9931F81021300793BFA /* AppDatabase.swift */; };
		56E1F9961F81033200793BFA /* Player.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56E1F9951F81033200793BFA /* Player.swift */; };
		56E549F72438B4770060D2DC /* GRDB in Frameworks */ = {isa = PBXBuildFile; productRef = 56E549F62438B4770060D2DC /* GRDB */; };
		56E549F92438B49A0060D2DC /* RxSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 56E549F82438B49A0060D2DC /* RxSwift */; };
		56E549FB2438B4A40060D2DC /* RxBlocking in Frameworks */ = {isa = PBXBuildFile; productRef = 56E549FA2438B4A40060D2DC /* RxBlocking */; };
		56E549FD2438B5510060D2DC /* RxGRDB in Frameworks */ = {isa = PBXBuildFile; productRef = 56E549FC2438B5510060D2DC /* RxGRDB */; };
		56E549FF2438B5670060D2DC /* Action in Frameworks */ = {isa = PBXBuildFile; productRef = 56E549FE2438B5670060D2DC /* Action */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
		565BD7BB22C3F84A00BB9B5A /* PBXContainerItemProxy */ = {
			isa = PBXContainerItemProxy;
			containerPortal = 56E1F9711F8101EE00793BFA /* Project object */;
			proxyType = 1;
			remoteGlobalIDString = 56E1F9781F8101EE00793BFA;
			remoteInfo = RxGRDBDemo;
		};
/* End PBXContainerItemProxy section */

/* Begin PBXCopyFilesBuildPhase section */
		569E8501200A5C720028D1EC /* Embed Frameworks */ = {
			isa = PBXCopyFilesBuildPhase;
			buildActionMask = 2147483647;
			dstPath = "";
			dstSubfolderSpec = 10;
			files = (
			);
			name = "Embed Frameworks";
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
		565BD7B622C3F84A00BB9B5A /* RxGRDBDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RxGRDBDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
		565BD7BA22C3F84A00BB9B5A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
		565BD7C422C3F8D600BB9B5A /* AppDatabaseTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDatabaseTests.swift; sourceTree = "<group>"; };
		565BD7C822C3FA8B00BB9B5A /* PlayersTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayersTests.swift; sourceTree = "<group>"; };
		565BD7CF22C49D2100BB9B5A /* PlayersViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayersViewModelTests.swift; sourceTree = "<group>"; };
		565BD7D822C5DEE800BB9B5A /* PlayerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerTests.swift; sourceTree = "<group>"; };
		567515A122C3A7DC00A6FF66 /* World.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = World.swift; sourceTree = "<group>"; };
		567515A322C3A9BC00A6FF66 /* Players.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Players.swift; sourceTree = "<group>"; };
		567515A722C3B1F100A6FF66 /* PlayersViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayersViewModel.swift; sourceTree = "<group>"; };
		567E66CD2438A6730091B5D8 /* RxGRDB */ = {isa = PBXFileReference; lastKnownFileType = folder; name = RxGRDB; path = ../..; sourceTree = "<group>"; };
		56E1F9791F8101EE00793BFA /* RxGRDBDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RxGRDBDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
		56E1F97C1F8101EE00793BFA /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
		56E1F97E1F8101EE00793BFA /* PlayersViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayersViewController.swift; sourceTree = "<group>"; };
		56E1F9811F8101EE00793BFA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
		56E1F9831F8101EE00793BFA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
		56E1F9861F8101EE00793BFA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
		56E1F9881F8101EE00793BFA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
		56E1F9931F81021300793BFA /* AppDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDatabase.swift; sourceTree = "<group>"; };
		56E1F9951F81033200793BFA /* Player.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Player.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
		565BD7B322C3F84A00BB9B5A /* Frameworks */ = {
			isa = PBXFrameworksBuildPhase;
			buildActionMask = 2147483647;
			files = (
				56E549FF2438B5670060D2DC /* Action in Frameworks */,
				56E549F92438B49A0060D2DC /* RxSwift in Frameworks */,
				56E549FB2438B4A40060D2DC /* RxBlocking in Frameworks */,
				56E549F72438B4770060D2DC /* GRDB in Frameworks */,
				56E549FD2438B5510060D2DC /* RxGRDB in Frameworks */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
		56E1F9761F8101EE00793BFA /* Frameworks */ = {
			isa = PBXFrameworksBuildPhase;
			buildActionMask = 2147483647;
			files = (
				567CE94228CB747200A95C34 /* RxCocoa in Frameworks */,
				56D6EA352438ABD7000D55EF /* Action in Frameworks */,
				56D6EA442438AF90000D55EF /* GRDB in Frameworks */,
				56D6EA322438ABAE000D55EF /* RxDataSources in Frameworks */,
				56D6EA2F2438AB77000D55EF /* RxSwift in Frameworks */,
				567E66D02438A6F80091B5D8 /* RxGRDB in Frameworks */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
		565BD7B722C3F84A00BB9B5A /* RxGRDBDemoTests */ = {
			isa = PBXGroup;
			children = (
				565BD7BA22C3F84A00BB9B5A /* Info.plist */,
				565BD7C422C3F8D600BB9B5A /* AppDatabaseTests.swift */,
				565BD7C822C3FA8B00BB9B5A /* PlayersTests.swift */,
				565BD7CF22C49D2100BB9B5A /* PlayersViewModelTests.swift */,
				565BD7D822C5DEE800BB9B5A /* PlayerTests.swift */,
			);
			path = RxGRDBDemoTests;
			sourceTree = "<group>";
		};
		5675159E22C3A77800A6FF66 /* Models */ = {
			isa = PBXGroup;
			children = (
				56E1F9951F81033200793BFA /* Player.swift */,
				567515A322C3A9BC00A6FF66 /* Players.swift */,
			);
			path = Models;
			sourceTree = "<group>";
		};
		5675159F22C3A7AE00A6FF66 /* UI */ = {
			isa = PBXGroup;
			children = (
				56E1F97E1F8101EE00793BFA /* PlayersViewController.swift */,
				567515A722C3B1F100A6FF66 /* PlayersViewModel.swift */,
			);
			path = UI;
			sourceTree = "<group>";
		};
		567515A022C3A7BA00A6FF66 /* Resources */ = {
			isa = PBXGroup;
			children = (
				56E1F9831F8101EE00793BFA /* Assets.xcassets */,
				56E1F9851F8101EE00793BFA /* LaunchScreen.storyboard */,
				56E1F9801F8101EE00793BFA /* Main.storyboard */,
			);
			path = Resources;
			sourceTree = "<group>";
		};
		567E66CE2438A6F80091B5D8 /* Frameworks */ = {
			isa = PBXGroup;
			children = (
			);
			name = Frameworks;
			sourceTree = "<group>";
		};
		56E1F9701F8101EE00793BFA = {
			isa = PBXGroup;
			children = (
				56E1F97B1F8101EE00793BFA /* RxGRDBDemo */,
				565BD7B722C3F84A00BB9B5A /* RxGRDBDemoTests */,
				56E1F97A1F8101EE00793BFA /* Products */,
				567E66CD2438A6730091B5D8 /* RxGRDB */,
				567E66CE2438A6F80091B5D8 /* Frameworks */,
			);
			sourceTree = "<group>";
		};
		56E1F97A1F8101EE00793BFA /* Products */ = {
			isa = PBXGroup;
			children = (
				56E1F9791F8101EE00793BFA /* RxGRDBDemo.app */,
				565BD7B622C3F84A00BB9B5A /* RxGRDBDemoTests.xctest */,
			);
			name = Products;
			sourceTree = "<group>";
		};
		56E1F97B1F8101EE00793BFA /* RxGRDBDemo */ = {
			isa = PBXGroup;
			children = (
				56E1F9881F8101EE00793BFA /* Info.plist */,
				56E1F9931F81021300793BFA /* AppDatabase.swift */,
				56E1F97C1F8101EE00793BFA /* AppDelegate.swift */,
				567515A122C3A7DC00A6FF66 /* World.swift */,
				5675159E22C3A77800A6FF66 /* Models */,
				5675159F22C3A7AE00A6FF66 /* UI */,
				567515A022C3A7BA00A6FF66 /* Resources */,
			);
			path = RxGRDBDemo;
			sourceTree = "<group>";
		};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
		565BD7B522C3F84A00BB9B5A /* RxGRDBDemoTests */ = {
			isa = PBXNativeTarget;
			buildConfigurationList = 565BD7BD22C3F84A00BB9B5A /* Build configuration list for PBXNativeTarget "RxGRDBDemoTests" */;
			buildPhases = (
				565BD7B222C3F84A00BB9B5A /* Sources */,
				565BD7B322C3F84A00BB9B5A /* Frameworks */,
				565BD7B422C3F84A00BB9B5A /* Resources */,
			);
			buildRules = (
			);
			dependencies = (
				565BD7BC22C3F84A00BB9B5A /* PBXTargetDependency */,
			);
			name = RxGRDBDemoTests;
			packageProductDependencies = (
				56E549F62438B4770060D2DC /* GRDB */,
				56E549F82438B49A0060D2DC /* RxSwift */,
				56E549FA2438B4A40060D2DC /* RxBlocking */,
				56E549FC2438B5510060D2DC /* RxGRDB */,
				56E549FE2438B5670060D2DC /* Action */,
			);
			productName = RxGRDBDemoTests;
			productReference = 565BD7B622C3F84A00BB9B5A /* RxGRDBDemoTests.xctest */;
			productType = "com.apple.product-type.bundle.unit-test";
		};
		56E1F9781F8101EE00793BFA /* RxGRDBDemo */ = {
			isa = PBXNativeTarget;
			buildConfigurationList = 56E1F98B1F8101EE00793BFA /* Build configuration list for PBXNativeTarget "RxGRDBDemo" */;
			buildPhases = (
				56E1F9751F8101EE00793BFA /* Sources */,
				56E1F9761F8101EE00793BFA /* Frameworks */,
				56E1F9771F8101EE00793BFA /* Resources */,
				569E8501200A5C720028D1EC /* Embed Frameworks */,
			);
			buildRules = (
			);
			dependencies = (
			);
			name = RxGRDBDemo;
			packageProductDependencies = (
				567E66CF2438A6F80091B5D8 /* RxGRDB */,
				56D6EA2E2438AB77000D55EF /* RxSwift */,
				56D6EA312438ABAE000D55EF /* RxDataSources */,
				56D6EA342438ABD7000D55EF /* Action */,
				56D6EA432438AF90000D55EF /* GRDB */,
				567CE94128CB747200A95C34 /* RxCocoa */,
			);
			productName = RxGRDBDemo;
			productReference = 56E1F9791F8101EE00793BFA /* RxGRDBDemo.app */;
			productType = "com.apple.product-type.application";
		};
/* End PBXNativeTarget section */

/* Begin PBXProject section */
		56E1F9711F8101EE00793BFA /* Project object */ = {
			isa = PBXProject;
			attributes = {
				LastSwiftUpdateCheck = 1020;
				LastUpgradeCheck = 1230;
				ORGANIZATIONNAME = "Gwendal Roué";
				TargetAttributes = {
					565BD7B522C3F84A00BB9B5A = {
						CreatedOnToolsVersion = 10.2.1;
						ProvisioningStyle = Automatic;
						TestTargetID = 56E1F9781F8101EE00793BFA;
					};
					56E1F9781F8101EE00793BFA = {
						CreatedOnToolsVersion = 9.0;
						ProvisioningStyle = Automatic;
					};
				};
			};
			buildConfigurationList = 56E1F9741F8101EE00793BFA /* Build configuration list for PBXProject "RxGRDBDemo" */;
			compatibilityVersion = "Xcode 8.0";
			developmentRegion = en;
			hasScannedForEncodings = 0;
			knownRegions = (
				en,
				Base,
			);
			mainGroup = 56E1F9701F8101EE00793BFA;
			packageReferences = (
				56D6EA2D2438AB77000D55EF /* XCRemoteSwiftPackageReference "RxSwift" */,
				56D6EA302438ABAE000D55EF /* XCRemoteSwiftPackageReference "RxDataSources" */,
				56D6EA332438ABD7000D55EF /* XCRemoteSwiftPackageReference "Action" */,
				56D6EA422438AF90000D55EF /* XCRemoteSwiftPackageReference "GRDB.swift" */,
			);
			productRefGroup = 56E1F97A1F8101EE00793BFA /* Products */;
			projectDirPath = "";
			projectRoot = "";
			targets = (
				56E1F9781F8101EE00793BFA /* RxGRDBDemo */,
				565BD7B522C3F84A00BB9B5A /* RxGRDBDemoTests */,
			);
		};
/* End PBXProject section */

/* Begin PBXResourcesBuildPhase section */
		565BD7B422C3F84A00BB9B5A /* Resources */ = {
			isa = PBXResourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
		56E1F9771F8101EE00793BFA /* Resources */ = {
			isa = PBXResourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				56E1F9871F8101EE00793BFA /* LaunchScreen.storyboard in Resources */,
				56E1F9841F8101EE00793BFA /* Assets.xcassets in Resources */,
				56E1F9821F8101EE00793BFA /* Main.storyboard in Resources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXResourcesBuildPhase section */

/* Begin PBXSourcesBuildPhase section */
		565BD7B222C3F84A00BB9B5A /* Sources */ = {
			isa = PBXSourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				565BD7D922C5DEE800BB9B5A /* PlayerTests.swift in Sources */,
				565BD7D122C49D9800BB9B5A /* PlayersViewModel.swift in Sources */,
				565BD7C922C3FA8B00BB9B5A /* PlayersTests.swift in Sources */,
				565BD7CA22C3FAA700BB9B5A /* Players.swift in Sources */,
				565BD7C622C3F92F00BB9B5A /* AppDatabase.swift in Sources */,
				565BD7D022C49D2100BB9B5A /* PlayersViewModelTests.swift in Sources */,
				565BD7C722C3F97900BB9B5A /* Player.swift in Sources */,
				565BD7D222C49E6600BB9B5A /* World.swift in Sources */,
				565BD7C522C3F8D600BB9B5A /* AppDatabaseTests.swift in Sources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
		56E1F9751F8101EE00793BFA /* Sources */ = {
			isa = PBXSourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				567515A422C3A9BC00A6FF66 /* Players.swift in Sources */,
				56E1F9961F81033200793BFA /* Player.swift in Sources */,
				56E1F9941F81021300793BFA /* AppDatabase.swift in Sources */,
				567515A822C3B1F100A6FF66 /* PlayersViewModel.swift in Sources */,
				56E1F97F1F8101EE00793BFA /* PlayersViewController.swift in Sources */,
				56E1F97D1F8101EE00793BFA /* AppDelegate.swift in Sources */,
				567515A222C3A7DC00A6FF66 /* World.swift in Sources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXSourcesBuildPhase section */

/* Begin PBXTargetDependency section */
		565BD7BC22C3F84A00BB9B5A /* PBXTargetDependency */ = {
			isa = PBXTargetDependency;
			target = 56E1F9781F8101EE00793BFA /* RxGRDBDemo */;
			targetProxy = 565BD7BB22C3F84A00BB9B5A /* PBXContainerItemProxy */;
		};
/* End PBXTargetDependency section */

/* Begin PBXVariantGroup section */
		56E1F9801F8101EE00793BFA /* Main.storyboard */ = {
			isa = PBXVariantGroup;
			children = (
				56E1F9811F8101EE00793BFA /* Base */,
			);
			name = Main.storyboard;
			sourceTree = "<group>";
		};
		56E1F9851F8101EE00793BFA /* LaunchScreen.storyboard */ = {
			isa = PBXVariantGroup;
			children = (
				56E1F9861F8101EE00793BFA /* Base */,
			);
			name = LaunchScreen.storyboard;
			sourceTree = "<group>";
		};
/* End PBXVariantGroup section */

/* Begin XCBuildConfiguration section */
		565BD7BE22C3F84A00BB9B5A /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				CLANG_ENABLE_OBJC_WEAK = YES;
				CODE_SIGN_STYLE = Automatic;
				DEVELOPMENT_TEAM = AMD8W895CT;
				INFOPLIST_FILE = RxGRDBDemoTests/Info.plist;
				IPHONEOS_DEPLOYMENT_TARGET = 12.2;
				LD_RUNPATH_SEARCH_PATHS = (
					"$(inherited)",
					"@executable_path/Frameworks",
					"@loader_path/Frameworks",
				);
				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
				MTL_FAST_MATH = YES;
				OTHER_SWIFT_FLAGS = "-Xcc -fmodule-map-file=$(PROJECT_TEMP_ROOT)/GeneratedModuleMaps-$(PLATFORM_NAME)/RxCocoaRuntime.modulemap";
				PRODUCT_BUNDLE_IDENTIFIER = com.github.groue.RxGRDBDemoTests;
				PRODUCT_NAME = "$(TARGET_NAME)";
				SWIFT_VERSION = 5.0;
				TARGETED_DEVICE_FAMILY = "1,2";
				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RxGRDBDemo.app/RxGRDBDemo";
			};
			name = Debug;
		};
		565BD7BF22C3F84A00BB9B5A /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				CLANG_ENABLE_OBJC_WEAK = YES;
				CODE_SIGN_STYLE = Automatic;
				DEVELOPMENT_TEAM = AMD8W895CT;
				INFOPLIST_FILE = RxGRDBDemoTests/Info.plist;
				IPHONEOS_DEPLOYMENT_TARGET = 12.2;
				LD_RUNPATH_SEARCH_PATHS = (
					"$(inherited)",
					"@executable_path/Frameworks",
					"@loader_path/Frameworks",
				);
				MTL_FAST_MATH = YES;
				OTHER_SWIFT_FLAGS = "-Xcc -fmodule-map-file=$(PROJECT_TEMP_ROOT)/GeneratedModuleMaps-$(PLATFORM_NAME)/RxCocoaRuntime.modulemap";
				PRODUCT_BUNDLE_IDENTIFIER = com.github.groue.RxGRDBDemoTests;
				PRODUCT_NAME = "$(TARGET_NAME)";
				SWIFT_VERSION = 5.0;
				TARGETED_DEVICE_FAMILY = "1,2";
				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RxGRDBDemo.app/RxGRDBDemo";
			};
			name = Release;
		};
		56E1F9891F8101EE00793BFA /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_SEARCH_USER_PATHS = NO;
				CLANG_ANALYZER_NONNULL = YES;
				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
				CLANG_CXX_LIBRARY = "libc++";
				CLANG_ENABLE_MODULES = YES;
				CLANG_ENABLE_OBJC_ARC = YES;
				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
				CLANG_WARN_BOOL_CONVERSION = YES;
				CLANG_WARN_COMMA = YES;
				CLANG_WARN_CONSTANT_CONVERSION = YES;
				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
				CLANG_WARN_EMPTY_BODY = YES;
				CLANG_WARN_ENUM_CONVERSION = YES;
				CLANG_WARN_INFINITE_RECURSION = YES;
				CLANG_WARN_INT_CONVERSION = YES;
				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
				CLANG_WARN_STRICT_PROTOTYPES = YES;
				CLANG_WARN_SUSPICIOUS_MOVE = YES;
				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
				CLANG_WARN_UNREACHABLE_CODE = YES;
				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
				CODE_SIGN_IDENTITY = "iPhone Developer";
				COPY_PHASE_STRIP = NO;
				DEBUG_INFORMATION_FORMAT = dwarf;
				ENABLE_STRICT_OBJC_MSGSEND = YES;
				ENABLE_TESTABILITY = YES;
				GCC_C_LANGUAGE_STANDARD = gnu11;
				GCC_DYNAMIC_NO_PIC = NO;
				GCC_NO_COMMON_BLOCKS = YES;
				GCC_OPTIMIZATION_LEVEL = 0;
				GCC_PREPROCESSOR_DEFINITIONS = (
					"DEBUG=1",
					"$(inherited)",
				);
				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
				GCC_WARN_UNDECLARED_SELECTOR = YES;
				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
				GCC_WARN_UNUSED_FUNCTION = YES;
				GCC_WARN_UNUSED_VARIABLE = YES;
				MTL_ENABLE_DEBUG_INFO = YES;
				ONLY_ACTIVE_ARCH = YES;
				SDKROOT = iphoneos;
				SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
				SWIFT_VERSION = 5.0;
			};
			name = Debug;
		};
		56E1F98A1F8101EE00793BFA /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_SEARCH_USER_PATHS = NO;
				CLANG_ANALYZER_NONNULL = YES;
				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
				CLANG_CXX_LIBRARY = "libc++";
				CLANG_ENABLE_MODULES = YES;
				CLANG_ENABLE_OBJC_ARC = YES;
				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
				CLANG_WARN_BOOL_CONVERSION = YES;
				CLANG_WARN_COMMA = YES;
				CLANG_WARN_CONSTANT_CONVERSION = YES;
				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
				CLANG_WARN_EMPTY_BODY = YES;
				CLANG_WARN_ENUM_CONVERSION = YES;
				CLANG_WARN_INFINITE_RECURSION = YES;
				CLANG_WARN_INT_CONVERSION = YES;
				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
				CLANG_WARN_STRICT_PROTOTYPES = YES;
				CLANG_WARN_SUSPICIOUS_MOVE = YES;
				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
				CLANG_WARN_UNREACHABLE_CODE = YES;
				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
				CODE_SIGN_IDENTITY = "iPhone Developer";
				COPY_PHASE_STRIP = NO;
				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
				ENABLE_NS_ASSERTIONS = NO;
				ENABLE_STRICT_OBJC_MSGSEND = YES;
				GCC_C_LANGUAGE_STANDARD = gnu11;
				GCC_NO_COMMON_BLOCKS = YES;
				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
				GCC_WARN_UNDECLARED_SELECTOR = YES;
				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
				GCC_WARN_UNUSED_FUNCTION = YES;
				GCC_WARN_UNUSED_VARIABLE = YES;
				MTL_ENABLE_DEBUG_INFO = NO;
				SDKROOT = iphoneos;
				SWIFT_COMPILATION_MODE = wholemodule;
				SWIFT_OPTIMIZATION_LEVEL = "-O";
				SWIFT_VERSION = 5.0;
				VALIDATE_PRODUCT = YES;
			};
			name = Release;
		};
		56E1F98C1F8101EE00793BFA /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
				CODE_SIGN_STYLE = Automatic;
				DEVELOPMENT_TEAM = AMD8W895CT;
				INFOPLIST_FILE = RxGRDBDemo/Info.plist;
				LD_RUNPATH_SEARCH_PATHS = (
					"$(inherited)",
					"@executable_path/Frameworks",
				);
				PRODUCT_BUNDLE_IDENTIFIER = com.github.groue.RxGRDBDemo;
				PRODUCT_NAME = "$(TARGET_NAME)";
				TARGETED_DEVICE_FAMILY = "1,2";
			};
			name = Debug;
		};
		56E1F98D1F8101EE00793BFA /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
				CODE_SIGN_STYLE = Automatic;
				DEVELOPMENT_TEAM = AMD8W895CT;
				INFOPLIST_FILE = RxGRDBDemo/Info.plist;
				LD_RUNPATH_SEARCH_PATHS = (
					"$(inherited)",
					"@executable_path/Frameworks",
				);
				PRODUCT_BUNDLE_IDENTIFIER = com.github.groue.RxGRDBDemo;
				PRODUCT_NAME = "$(TARGET_NAME)";
				TARGETED_DEVICE_FAMILY = "1,2";
			};
			name = Release;
		};
/* End XCBuildConfiguration section */

/* Begin XCConfigurationList section */
		565BD7BD22C3F84A00BB9B5A /* Build configuration list for PBXNativeTarget "RxGRDBDemoTests" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				565BD7BE22C3F84A00BB9B5A /* Debug */,
				565BD7BF22C3F84A00BB9B5A /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
		56E1F9741F8101EE00793BFA /* Build configuration list for PBXProject "RxGRDBDemo" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				56E1F9891F8101EE00793BFA /* Debug */,
				56E1F98A1F8101EE00793BFA /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
		56E1F98B1F8101EE00793BFA /* Build configuration list for PBXNativeTarget "RxGRDBDemo" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				56E1F98C1F8101EE00793BFA /* Debug */,
				56E1F98D1F8101EE00793BFA /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
/* End XCConfigurationList section */

/* Begin XCRemoteSwiftPackageReference section */
		56D6EA2D2438AB77000D55EF /* XCRemoteSwiftPackageReference "RxSwift" */ = {
			isa = XCRemoteSwiftPackageReference;
			repositoryURL = "https://github.com/ReactiveX/RxSwift.git";
			requirement = {
				kind = upToNextMajorVersion;
				minimumVersion = 6.0.0;
			};
		};
		56D6EA302438ABAE000D55EF /* XCRemoteSwiftPackageReference "RxDataSources" */ = {
			isa = XCRemoteSwiftPackageReference;
			repositoryURL = "https://github.com/RxSwiftCommunity/RxDataSources.git";
			requirement = {
				kind = upToNextMajorVersion;
				minimumVersion = 5.0.0;
			};
		};
		56D6EA332438ABD7000D55EF /* XCRemoteSwiftPackageReference "Action" */ = {
			isa = XCRemoteSwiftPackageReference;
			repositoryURL = "https://github.com/RxSwiftCommunity/Action.git";
			requirement = {
				kind = upToNextMajorVersion;
				minimumVersion = 4.0.1;
			};
		};
		56D6EA422438AF90000D55EF /* XCRemoteSwiftPackageReference "GRDB.swift" */ = {
			isa = XCRemoteSwiftPackageReference;
			repositoryURL = "https://github.com/groue/GRDB.swift.git";
			requirement = {
				kind = upToNextMajorVersion;
				minimumVersion = 6.0.0;
			};
		};
/* End XCRemoteSwiftPackageReference section */

/* Begin XCSwiftPackageProductDependency section */
		567CE94128CB747200A95C34 /* RxCocoa */ = {
			isa = XCSwiftPackageProductDependency;
			package = 56D6EA2D2438AB77000D55EF /* XCRemoteSwiftPackageReference "RxSwift" */;
			productName = RxCocoa;
		};
		567E66CF2438A6F80091B5D8 /* RxGRDB */ = {
			isa = XCSwiftPackageProductDependency;
			productName = RxGRDB;
		};
		56D6EA2E2438AB77000D55EF /* RxSwift */ = {
			isa = XCSwiftPackageProductDependency;
			package = 56D6EA2D2438AB77000D55EF /* XCRemoteSwiftPackageReference "RxSwift" */;
			productName = RxSwift;
		};
		56D6EA312438ABAE000D55EF /* RxDataSources */ = {
			isa = XCSwiftPackageProductDependency;
			package = 56D6EA302438ABAE000D55EF /* XCRemoteSwiftPackageReference "RxDataSources" */;
			productName = RxDataSources;
		};
		56D6EA342438ABD7000D55EF /* Action */ = {
			isa = XCSwiftPackageProductDependency;
			package = 56D6EA332438ABD7000D55EF /* XCRemoteSwiftPackageReference "Action" */;
			productName = Action;
		};
		56D6EA432438AF90000D55EF /* GRDB */ = {
			isa = XCSwiftPackageProductDependency;
			package = 56D6EA422438AF90000D55EF /* XCRemoteSwiftPackageReference "GRDB.swift" */;
			productName = GRDB;
		};
		56E549F62438B4770060D2DC /* GRDB */ = {
			isa = XCSwiftPackageProductDependency;
			package = 56D6EA422438AF90000D55EF /* XCRemoteSwiftPackageReference "GRDB.swift" */;
			productName = GRDB;
		};
		56E549F82438B49A0060D2DC /* RxSwift */ = {
			isa = XCSwiftPackageProductDependency;
			package = 56D6EA2D2438AB77000D55EF /* XCRemoteSwiftPackageReference "RxSwift" */;
			productName = RxSwift;
		};
		56E549FA2438B4A40060D2DC /* RxBlocking */ = {
			isa = XCSwiftPackageProductDependency;
			package = 56D6EA2D2438AB77000D55EF /* XCRemoteSwiftPackageReference "RxSwift" */;
			productName = RxBlocking;
		};
		56E549FC2438B5510060D2DC /* RxGRDB */ = {
			isa = XCSwiftPackageProductDependency;
			productName = RxGRDB;
		};
		56E549FE2438B5670060D2DC /* Action */ = {
			isa = XCSwiftPackageProductDependency;
			package = 56D6EA332438ABD7000D55EF /* XCRemoteSwiftPackageReference "Action" */;
			productName = Action;
		};
/* End XCSwiftPackageProductDependency section */
	};
	rootObject = 56E1F9711F8101EE00793BFA /* Project object */;
}


================================================
FILE: Documentation/RxGRDBDemo/RxGRDBDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
   version = "1.0">
   <FileRef
      location = "self:">
   </FileRef>
</Workspace>


================================================
FILE: Documentation/RxGRDBDemo/RxGRDBDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>IDEDidComputeMac32BitWarning</key>
	<true/>
</dict>
</plist>


================================================
FILE: Documentation/RxGRDBDemo/RxGRDBDemo.xcodeproj/xcshareddata/xcschemes/RxGRDBDemo.xcscheme
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
   LastUpgradeVersion = "1230"
   version = "1.3">
   <BuildAction
      parallelizeBuildables = "YES"
      buildImplicitDependencies = "YES">
      <BuildActionEntries>
         <BuildActionEntry
            buildForTesting = "YES"
            buildForRunning = "YES"
            buildForProfiling = "YES"
            buildForArchiving = "YES"
            buildForAnalyzing = "YES">
            <BuildableReference
               BuildableIdentifier = "primary"
               BlueprintIdentifier = "56E1F9781F8101EE00793BFA"
               BuildableName = "RxGRDBDemo.app"
               BlueprintName = "RxGRDBDemo"
               ReferencedContainer = "container:RxGRDBDemo.xcodeproj">
            </BuildableReference>
         </BuildActionEntry>
         <BuildActionEntry
            buildForTesting = "YES"
            buildForRunning = "YES"
            buildForProfiling = "NO"
            buildForArchiving = "NO"
            buildForAnalyzing = "NO">
            <BuildableReference
               BuildableIdentifier = "primary"
               BlueprintIdentifier = "565BD7B522C3F84A00BB9B5A"
               BuildableName = "RxGRDBDemoTests.xctest"
               BlueprintName = "RxGRDBDemoTests"
               ReferencedContainer = "container:RxGRDBDemo.xcodeproj">
            </BuildableReference>
         </BuildActionEntry>
      </BuildActionEntries>
   </BuildAction>
   <TestAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      shouldUseLaunchSchemeArgsEnv = "YES">
      <MacroExpansion>
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "56E1F9781F8101EE00793BFA"
            BuildableName = "RxGRDBDemo.app"
            BlueprintName = "RxGRDBDemo"
            ReferencedContainer = "container:RxGRDBDemo.xcodeproj">
         </BuildableReference>
      </MacroExpansion>
      <Testables>
         <TestableReference
            skipped = "NO">
            <BuildableReference
               BuildableIdentifier = "primary"
               BlueprintIdentifier = "565BD7B522C3F84A00BB9B5A"
               BuildableName = "RxGRDBDemoTests.xctest"
               BlueprintName = "RxGRDBDemoTests"
               ReferencedContainer = "container:RxGRDBDemo.xcodeproj">
            </BuildableReference>
         </TestableReference>
      </Testables>
   </TestAction>
   <LaunchAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      launchStyle = "0"
      useCustomWorkingDirectory = "NO"
      ignoresPersistentStateOnLaunch = "NO"
      debugDocumentVersioning = "YES"
      debugServiceExtension = "internal"
      allowLocationSimulation = "YES">
      <BuildableProductRunnable
         runnableDebuggingMode = "0">
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "56E1F9781F8101EE00793BFA"
            BuildableName = "RxGRDBDemo.app"
            BlueprintName = "RxGRDBDemo"
            ReferencedContainer = "container:RxGRDBDemo.xcodeproj">
         </BuildableReference>
      </BuildableProductRunnable>
   </LaunchAction>
   <ProfileAction
      buildConfiguration = "Release"
      shouldUseLaunchSchemeArgsEnv = "YES"
      savedToolIdentifier = ""
      useCustomWorkingDirectory = "NO"
      debugDocumentVersioning = "YES">
      <BuildableProductRunnable
         runnableDebuggingMode = "0">
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "56E1F9781F8101EE00793BFA"
            BuildableName = "RxGRDBDemo.app"
            BlueprintName = "RxGRDBDemo"
            ReferencedContainer = "container:RxGRDBDemo.xcodeproj">
         </BuildableReference>
      </BuildableProductRunnable>
   </ProfileAction>
   <AnalyzeAction
      buildConfiguration = "Debug">
   </AnalyzeAction>
   <ArchiveAction
      buildConfiguration = "Release"
      revealArchiveInOrganizer = "YES">
   </ArchiveAction>
</Scheme>


================================================
FILE: Documentation/RxGRDBDemo/RxGRDBDemoTests/AppDatabaseTests.swift
================================================
import XCTest
import GRDB

class AppDatabaseTests: XCTestCase {
    
    func testDatabaseSchemaContainsPlayerTable() throws {
        let dbQueue = try DatabaseQueue()
        try AppDatabase().setup(dbQueue)
        try dbQueue.read { db in
            try XCTAssert(db.tableExists("player"))
            let columns = try db.columns(in: "player")
            let columnNames = Set(columns.map { $0.name })
            XCTAssertEqual(columnNames, ["id", "name", "score"])
        }
    }
}


================================================
FILE: Documentation/RxGRDBDemo/RxGRDBDemoTests/Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDevelopmentRegion</key>
	<string>$(DEVELOPMENT_LANGUAGE)</string>
	<key>CFBundleExecutable</key>
	<string>$(EXECUTABLE_NAME)</string>
	<key>CFBundleIdentifier</key>
	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>$(PRODUCT_NAME)</string>
	<key>CFBundlePackageType</key>
	<string>BNDL</string>
	<key>CFBundleShortVersionString</key>
	<string>1.0</string>
	<key>CFBundleVersion</key>
	<string>1</string>
</dict>
</plist>


================================================
FILE: Documentation/RxGRDBDemo/RxGRDBDemoTests/PlayerTests.swift
================================================
import XCTest
import GRDB

class PlayerTests: XCTestCase {
    
    func testInsert() throws {
        let dbQueue = try DatabaseQueue()
        try AppDatabase().setup(dbQueue)
        try dbQueue.write { db in
            var player = Player(id: nil, name: "Arthur", score: 100)
            try player.insert(db)
            XCTAssertNotNil(player.id)
        }
    }
    
    func testRoundtrip() throws {
        let dbQueue = try DatabaseQueue()
        try AppDatabase().setup(dbQueue)
        try dbQueue.write { db in
            var insertedPlayer = Player(id: 1, name: "Arthur", score: 100)
            try insertedPlayer.insert(db)
            let fetchedPlayer = try Player.fetchOne(db, key: 1)
            XCTAssertEqual(insertedPlayer, fetchedPlayer)
        }
    }
    
    func testOrderByScore() throws {
        let dbQueue = try DatabaseQueue()
        try AppDatabase().setup(dbQueue)
        var player1 = Player(id: 1, name: "Arthur", score: 100)
        var player2 = Player(id: 2, name: "Barbara", score: 200)
        var player3 = Player(id: 3, name: "Craig", score: 150)
        var player4 = Player(id: 4, name: "David", score: 150)
        try dbQueue.write { db in
            try player1.insert(db)
            try player2.insert(db)
            try player3.insert(db)
            try player4.insert(db)
        }
        
        let request = Player.all().orderByScore()
        
        try XCTAssertEqual(
            dbQueue.read(request.fetchAll),
            [player2, player3, player4, player1])
        
        try dbQueue.write { db in
            _ = try player1.updateChanges(db) { $0.score = 300 }
        }
        
        try XCTAssertEqual(
            dbQueue.read(request.fetchAll),
            [player1, player2, player3, player4])
    }
    
    func testOrderByName() throws {
        let dbQueue = try DatabaseQueue()
        try AppDatabase().setup(dbQueue)
        var player1 = Player(id: 1, name: "Arthur", score: 100)
        var player2 = Player(id: 2, name: "Barbara", score: 200)
        var player3 = Player(id: 3, name: "Craig", score: 150)
        var player4 = Player(id: 4, name: "David", score: 150)
        try dbQueue.write { db in
            try player1.insert(db)
            try player2.insert(db)
            try player3.insert(db)
            try player4.insert(db)
        }
        
        let request = Player.all().orderByName()
        
        try XCTAssertEqual(
            dbQueue.read(request.fetchAll),
            [player1, player2, player3, player4])
        
        try dbQueue.write { db in
            _ = try player1.updateChanges(db) { $0.name = "Craig" }
        }
        
        try XCTAssertEqual(
            dbQueue.read(request.fetchAll),
            [player2, player3, player1, player4])
    }
}


================================================
FILE: Documentation/RxGRDBDemo/RxGRDBDemoTests/PlayersTests.swift
================================================
import GRDB
import RxBlocking
import RxSwift
import XCTest

class PlayersTests: XCTestCase {
    
    private func makeDatabase() throws -> DatabaseQueue {
        // Players needs a database.
        // Setup an in-memory database, for fast access.
        let database = try DatabaseQueue()
        try AppDatabase().setup(database)
        return database
    }
    
    func testPopulateIfEmptyFromEmptyDatabase() throws {
        let database = try makeDatabase()
        let players = Players(database: database)
        
        try XCTAssertEqual(database.read(Player.fetchCount), 0)
        try XCTAssertTrue(players.populateIfEmpty())
        try XCTAssertGreaterThan(database.read(Player.fetchCount), 0)
    }
    
    func testPopulateIfEmptyFromNonEmptyDatabase() throws {
        let database = try makeDatabase()
        let players = Players(database: database)
        
        var player = Player(id: 1, name: "Arthur", score: 100)
        try database.write { db in
            try player.insert(db)
        }
        
        try XCTAssertFalse(players.populateIfEmpty())
        try XCTAssertEqual(database.read(Player.fetchAll), [player])
    }
    
    func testDeleteAll() throws {
        let database = try makeDatabase()
        let players = Players(database: database)
        
        try database.write { db in
            var player = Player(id: 1, name: "Arthur", score: 100)
            try player.insert(db)
        }
        
        _ = players.deleteAll().toBlocking().materialize()
        try XCTAssertEqual(database.read(Player.fetchCount), 0)
    }
    
    func testDeleteOne() throws {
        let database = try makeDatabase()
        let players = Players(database: database)
        
        var player1 = Player(id: 1, name: "Arthur", score: 100)
        var player2 = Player(id: 2, name: "Barbara", score: 200)
        try database.write { db in
            try player1.insert(db)
            try player2.insert(db)
        }
        
        _ = players.deleteOne(player1).toBlocking().materialize()
        try XCTAssertEqual(database.read(Player.fetchAll), [player2])
    }
    
    func testRefreshPopulatesEmptyDatabase() throws {
        let database = try makeDatabase()
        let players = Players(database: database)
        
        try XCTAssertEqual(database.read(Player.fetchCount), 0)
        _ = players.refresh().toBlocking().materialize()
        try XCTAssertGreaterThan(database.read(Player.fetchCount), 0)
    }
    
    func testPlayersOrderedByName() throws {
        let database = try makeDatabase()
        let players = Players(database: database)
        
        let disposeBag = DisposeBag()
        let testSubject = ReplaySubject<[Player]>.createUnbounded()
        players
            .playersOrderedByName()
            .subscribe(testSubject)
            .disposed(by: disposeBag)
        
        var player1 = Player(id: 1, name: "Barbara", score: 100)
        var player2 = Player(id: 2, name: "Arthur", score: 300)
        var player3 = Player(id: 3, name: "Craig", score: 200)
        try database.write { db in
            try player1.insert(db)
            try player2.insert(db)
        }
        try database.write { db in
            try player2.delete(db)
            try player3.insert(db)
        }
        
        let expectedElements: [[Player]] = [
            [],
            [player2, player1],
            [player1, player3],
        ]
        try XCTAssertEqual(
            testSubject
                .take(expectedElements.count)
                .toBlocking()
                .toArray(),
            expectedElements)
    }
    
    func testPlayersOrderedByScore() throws {
        let database = try makeDatabase()
        let players = Players(database: database)
        
        let disposeBag = DisposeBag()
        let testSubject = ReplaySubject<[Player]>.createUnbounded()
        players
            .playersOrderedByScore()
            .subscribe(testSubject)
            .disposed(by: disposeBag)
        
        var player1 = Player(id: 1, name: "Barbara", score: 100)
        var player2 = Player(id: 2, name: "Arthur", score: 300)
        var player3 = Player(id: 3, name: "Craig", score: 200)
        try database.write { db in
            try player1.insert(db)
            try player2.insert(db)
        }
        try database.write { db in
            try player2.delete(db)
            try player3.insert(db)
        }
        
        let expectedElements: [[Player]] = [
            [],
            [player2, player1],
            [player3, player1],
        ]
        try XCTAssertEqual(
            testSubject
                .take(expectedElements.count)
                .toBlocking()
                .toArray(),
            expectedElements)
    }
}


================================================
FILE: Documentation/RxGRDBDemo/RxGRDBDemoTests/PlayersViewModelTests.swift
================================================
import Action
import GRDB
import RxBlocking
import RxSwift
import XCTest

class PlayersViewModelTests: XCTestCase {
    override func setUp() {
        // PlayerViewModel needs a Current World.
        // Setup one with an in-memory database, for fast access.
        let dbQueue = try! DatabaseQueue()
        try! AppDatabase().setup(dbQueue)
        Current = World(database: { dbQueue })
    }
    
    func testInitialStateFromEmptyDatabase() throws {
        let viewModel = PlayersViewModel()
        let orderingButtonTitle = try viewModel.orderingButtonTitle.take(1).toBlocking().single()
        let players = try viewModel.players.take(1).toBlocking().single()
        XCTAssertNil(orderingButtonTitle)
        XCTAssert(players.isEmpty)
    }
    
    func testInitialStateFromNonEmptyDatabase() throws {
        try Current.players().populateIfEmpty()
        let viewModel = PlayersViewModel()
        let orderingButtonTitle = try viewModel.orderingButtonTitle.take(1).toBlocking().single()
        let players = try viewModel.players.take(1).toBlocking().single()
        XCTAssertEqual(orderingButtonTitle, "Score ⬇︎")
        XCTAssert(!players.isEmpty)
    }
    
    func testToggleOrdering() throws {
        try Current.players().populateIfEmpty()
        let viewModel = PlayersViewModel()
        _ = viewModel.toggleOrdering.execute().toBlocking().materialize()
        let orderingButtonTitle = try viewModel.orderingButtonTitle.take(1).toBlocking().single()
        XCTAssertEqual(orderingButtonTitle, "Name ⬆︎")
    }
}


================================================
FILE: LICENSE
================================================
Copyright (C) 2018 RxSwiftCommunity

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


================================================
FILE: Makefile
================================================
# Rules
# =====
#
# make test - Run all tests but performance tests
# make distclean - Restore repository to a pristine state

default: test


# Configuration
# =============

GIT := $(shell command -v git)
POD := $(shell command -v pod)
XCRUN := $(shell command -v xcrun)
SWIFT = $(shell $(XCRUN) --find swift 2> /dev/null)

# Used to determine if xcpretty is available
XCPRETTY_PATH := $(shell command -v xcpretty 2> /dev/null)


# Tests
# =====

# If xcpretty is available, use it for xcodebuild output
XCPRETTY = 
ifdef XCPRETTY_PATH
  XCPRETTY = | xcpretty -c
  
  # On Travis-CI, use xcpretty-travis-formatter
  ifeq ($(TRAVIS),true)
    XCPRETTY += -f `xcpretty-travis-formatter`
  endif
endif

test: test_SPM

test_SPM:
	$(SWIFT) package clean
	$(SWIFT) build
	$(SWIFT) build -c release
	set -o pipefail && $(SWIFT) test $(XCPRETTY)

# Cleanup
# =======

distclean:
	$(GIT) reset --hard
	$(GIT) clean -dffx .

.PHONY: distclean test


================================================
FILE: Package.swift
================================================
// swift-tools-version:6.0

import PackageDescription

let package = Package(
    name: "RxGRDB",
    platforms: [
        .iOS(.v13),
        .macOS(.v10_15),
        .tvOS(.v13),
        .watchOS(.v7),
    ],
    products: [
        .library(name: "RxGRDB", targets: ["RxGRDB"]),
    ],
    dependencies: [
        .package(url: "https://github.com/groue/GRDB.swift.git", .upToNextMajor(from: "7.1.0")),
        .package(url: "https://github.com/ReactiveX/RxSwift.git", .upToNextMajor(from: "6.0.0"))
    ],
    targets: [
        .target(
            name: "RxGRDB",
            dependencies: [
                .product(name: "GRDB", package: "GRDB.swift"),
                .product(name: "RxSwift", package: "RxSwift"),
            ]),
        .testTarget(
            name: "RxGRDBTests",
            dependencies: [
                "RxGRDB",
                .product(name: "GRDB", package: "GRDB.swift"),
                .product(name: "RxBlocking", package: "RxSwift"),
            ])
    ],
    swiftLanguageModes: [.v5]
)


================================================
FILE: README.md
================================================
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)
======

### A set of extensions for [SQLite], [GRDB.swift], and [RxSwift]

**Latest release**: September 28, 2025 • [version 4.0.1](https://github.com/RxSwiftCommunity/RxGRDB/tree/v4.0.1) • [Release Notes]

**Requirements**: iOS 11.0+ / macOS 10.13+ / tvOS 11.0+ / watchOS 4.0+ • Swift 6+ / Xcode 16+

---

## Usage

To connect to the database, please refer to [GRDB](https://github.com/groue/GRDB.swift), the database library that supports RxGRDB.

<details>
  <summary><strong>Asynchronously read from the database</strong></summary>

This observable reads a single value and delivers it.

```swift
// Single<[Player]>
let players = dbQueue.rx.read { db in
    try Player.fetchAll(db)
}

players.subscribe(
    onSuccess: { (players: [Player]) in
        print("Players: \(players)")
    },
    onError: { error in ... })
```

</details>

<details>
  <summary><strong>Asynchronously write in the database</strong></summary>

This observable completes after the database has been updated.

```swift
// Single<Void>
let write = dbQueue.rx.write { db in 
    try Player(...).insert(db)
}

write.subscribe(
    onSuccess: { _ in
        print("Updates completed")
    },
    onError: { error in ... })

// Single<Int>
let newPlayerCount = dbQueue.rx.write { db -> Int in
    try Player(...).insert(db)
    return try Player.fetchCount(db)
}

newPlayerCount.subscribe(
    onSuccess: { (playerCount: Int) in
        print("New players count: \(playerCount)")
    },
    onError: { error in ... })
```

</details>

<details>
  <summary><strong>Observe changes in database values</strong></summary>

This observable delivers fresh values whenever the database changes:

```swift
// Observable<[Player]>
let observable = ValueObservation
    .tracking { db in try Player.fetchAll(db) }
    .rx.observe(in: dbQueue)

observable.subscribe(
    onNext: { (players: [Player]) in
        print("Fresh players: \(players)")
    },
    onError: { error in ... })

// Observable<Int?>
let observable = ValueObservation
    .tracking { db in try Int.fetchOne(db, sql: "SELECT MAX(score) FROM player") }
    .rx.observe(in: dbQueue)

observable.subscribe(
    onNext: { (maxScore: Int?) in
        print("Fresh maximum score: \(maxScore)")
    },
    onError: { error in ... })
```

</details>

<details>
  <summary><strong>Observe database transactions</strong></summary>

This observable delivers database connections whenever a database transaction has impacted an observed region:

```swift
// Observable<Database>
let observable = DatabaseRegionObservation
    .tracking(Player.all())
    .rx.changes(in: dbQueue)

observable.subscribe(
    onNext: { (db: Database) in
        print("Exclusive write access to the database after players have been impacted")
    },
    onError: { error in ... })

// Observable<Database>
let observable = DatabaseRegionObservation
    .tracking(SQLRequest<Int>(sql: "SELECT MAX(score) FROM player"))
    .rx.changes(in: dbQueue)

observable.subscribe(
    onNext: { (db: Database) in
        print("Exclusive write access to the database after maximum score has been impacted")
    },
    onError: { error in ... })
```

</details>

Documentation
=============

- [Installation]
- [Demo Application]
- [Asynchronous Database Access]
- [Database Observation]

## Installation

To use RxGRDB with the [Swift Package Manager], add a dependency to your `Package.swift` file:

```swift
let package = Package(
    dependencies: [
        .package(url: "https://github.com/RxSwiftCommunity/RxGRDB.git", ...)
    ]
)
```

To use RxGRDB with [CocoaPods](http://cocoapods.org/), specify in your `Podfile`:

```ruby
# Pick only one
pod 'RxGRDB'
pod 'RxGRDB/SQLCipher'
```


# Asynchronous Database Access

RxGRDB provide observables that perform asynchronous database accesses.

- [`rx.read(observeOn:value:)`]
- [`rx.write(observeOn:updates:)`]
- [`rx.write(observeOn:updates:thenRead:)`]


#### `DatabaseReader.rx.read(observeOn:value:)`

This methods returns a [Single] that completes after database values have been asynchronously fetched.

```swift
// Single<[Player]>
let players = dbQueue.rx.read { db in
    try Player.fetchAll(db)
}
```

Any attempt at modifying the database completes subscriptions with an error.

When 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.

When 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].

This observable can be subscribed from any thread. A new database access starts on every subscription.

The fetched value is published on the main queue, unless you provide a specific scheduler to the `observeOn` argument.


#### `DatabaseWriter.rx.write(observeOn:updates:)`

This method returns a [Single] that completes after database updates have been successfully executed inside a database transaction.

```swift
// Single<Void>
let write = dbQueue.rx.write { db in
    try Player(...).insert(db)
}

// Single<Int>
let newPlayerCount = dbQueue.rx.write { db -> Int in
    try Player(...).insert(db)
    return try Player.fetchCount(db)
}
```

This observable can be subscribed from any thread. A new database access starts on every subscription.

It completes on the main queue, unless you provide a specific [scheduler] to the `observeOn` argument.

You can ignore its value and turn it into a [Completable] with the `asCompletable` operator:

```swift
// Completable
let write = dbQueue.rx
    .write { db in try Player(...).insert(db) }
    .asCompletable()
```

When 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.


#### `DatabaseWriter.rx.write(observeOn:updates:thenRead:)`

This method returns a [Single] that completes after database updates have been successfully executed inside a database transaction, and values have been subsequently fetched:

```swift
// Single<Int>
let newPlayerCount = dbQueue.rx.write(
    updates: { db in try Player(...).insert(db) }
    thenRead: { db, _ in try Player.fetchCount(db) })
}
```

It publishes exactly the same values as [`rx.write(observeOn:updates:)`]:

```swift
// Single<Int>
let newPlayerCount = dbQueue.rx.write { db -> Int in
    try Player(...).insert(db)
    return try Player.fetchCount(db)
}
```

The 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).

When 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.

When you use a [database queue], the results are guaranteed to be identical, but no scheduling optimization is applied.

This observable can be subscribed from any thread. A new database access starts on every subscription.

It completes on the main queue, unless you provide a specific [scheduler] to the `observeOn` argument.


# Database Observation

Database 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.

- [`ValueObservation.rx.observe(in:scheduling:)`]
- [`DatabaseRegionObservation.rx.changes(in:)`]


#### `ValueObservation.rx.observe(in:scheduling:)`

GRDB's [ValueObservation] tracks changes in database values. You can turn it into an RxSwift observable:

```swift
let observation = ValueObservation.tracking { db in
    try Player.fetchAll(db)
}

// Observable<[Player]>
let observable = observation.rx.observe(in: dbQueue)
```

This observable has the same behavior as ValueObservation:

- It notifies an initial value before the eventual changes.
- It may coalesce subsequent changes into a single notification.
- 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.
- It stops emitting any value after the database connection is closed. But it never completes.
- By default, it notifies the initial value, as well as eventual changes and errors, on the main thread, asynchronously.
    
    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).
    
    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:
    
    ```swift
    // Immediate notification of the initial value
    let disposable = observation.rx
        .observe(
            in: dbQueue,
            scheduling: .immediate) // <-
        .subscribe(
            onNext: { players: [Player] in print("fresh players: \(players)") },
            onError: { error in ... })
    // <- here "fresh players" is already printed.
    ```
    
    Note that the `.immediate` scheduler requires that the observable is subscribed from the main thread. It raises a fatal error otherwise.

See [ValueObservation Scheduling](https://github.com/groue/GRDB.swift/blob/master/README.md#valueobservation-scheduling) for more information.

:warning: **ValueObservation and Data Consistency**

When 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)).

Instead, compose requests together into **one single** ValueObservation, as below:

```swift
// DATA CONSISTENCY GUARANTEED
let hallOfFameObservable = ValueObservation
    .tracking { db -> HallOfFame in
        let playerCount = try Player.fetchCount(db)
        let bestPlayers = try Player.limit(10).orderedByScore().fetchAll(db)
        return HallOfFame(playerCount:playerCount, bestPlayers:bestPlayers)
    }
    .rx.observe(in: dbQueue)
```

See [ValueObservation] for more information.


#### `DatabaseRegionObservation.rx.changes(in:)`

GRDB's [DatabaseRegionObservation] notifies all transactions that impact a tracked database region. You can turn it into an RxSwift observable:

```swift
let request = Player.all()
let observation = DatabaseRegionObservation.tracking(request)

// Observable<Database>
let observable = observation.rx.changes(in: dbQueue)
```

This 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.

```swift
let request = Player.all()
let disposable = DatabaseRegionObservation
    .tracking(request)
    .rx.changes(in: dbQueue)
    .subscribe(
        onNext: { (db: Database) in
            print("Players have changed.")
        },
        onError: { error in ... })

try dbQueue.write { db in
    try Player(name: "Arthur").insert(db)
    try Player(name: "Barbara").insert(db)
} 
// Prints "Players have changed."

try dbQueue.write { db in
    try Player.deleteAll(db)
}
// Prints "Players have changed."
```

See [DatabaseRegionObservation] for more information.


[Asynchronous Database Access]: #asynchronous-database-access
[RxSwift]: https://github.com/ReactiveX/RxSwift
[Database Changes Observation]: https://github.com/groue/GRDB.swift/blob/master/README.md#database-changes-observation
[Database Observation]: #database-observation
[DatabaseRegionObservation]: https://github.com/groue/GRDB.swift/blob/master/README.md#databaseregionobservation
[Demo Application]: Documentation/RxGRDBDemo/README.md
[GRDB.swift]: https://github.com/groue/GRDB.swift
[Installation]: #installation
[Release Notes]: CHANGELOG.md
[SQLite]: http://sqlite.org
[Swift Package Manager]: https://swift.org/package-manager/
[ValueObservation]: https://github.com/groue/GRDB.swift/blob/master/README.md#valueobservation
[`DatabaseRegionObservation.rx.changes(in:)`]: #databaseregionobservationrxchangesin
[`ValueObservation.rx.observe(in:scheduling:)`]: #valueobservationrxobserveinscheduling
[`rx.read(observeOn:value:)`]: #databasereaderrxreadobserveonvalue
[`rx.write(observeOn:updates:)`]: #databasewriterrxwriteobserveonupdates
[`rx.write(observeOn:updates:thenRead:)`]: #databasewriterrxwriteobserveonupdatesthenread
[configured]: https://github.com/groue/GRDB.swift/blob/master/README.md#databasepool-configuration
[database pool]: https://github.com/groue/GRDB.swift/blob/master/README.md#database-pools
[database queue]: https://github.com/groue/GRDB.swift/blob/master/README.md#database-queues
[database snapshot]: https://github.com/groue/GRDB.swift/blob/master/README.md#database-snapshots
[scheduler]: https://github.com/ReactiveX/RxSwift/blob/master/Documentation/Schedulers.md
[Single]: https://github.com/ReactiveX/RxSwift/blob/master/Documentation/Traits.md#single
[Completable]: https://github.com/ReactiveX/RxSwift/blob/master/Documentation/Traits.md#completable


================================================
FILE: Sources/RxGRDB/DatabaseReader+Rx.swift
================================================
import GRDB
import RxSwift

/// We want the `rx` joiner on DatabaseReader.
/// Normally we'd use ReactiveCompatible. But ReactiveCompatible is unable to
/// define `rx` on existentials as well:
///
///     let reader: DatabaseReader
///     reader.rx...
///
/// :nodoc:
extension DatabaseReader {
    /// Reactive extensions.
    public var rx: Reactive<AnyDatabaseReader> { Reactive(AnyDatabaseReader(self)) }
}

extension Reactive where Base: DatabaseReader {
    /// Returns a Single that asynchronously emits the fetched value.
    ///
    ///     let dbQueue = try DatabaseQueue()
    ///     let players: Single<[Player]> = dbQueue.rx.read { db in
    ///         try Player.fetchAll(db)
    ///     }
    ///
    /// By default, returned values are emitted on the main dispatch queue. If
    /// you give a *scheduler*, values are emitted on that scheduler.
    ///
    /// - parameter value: A closure which accesses the database.
    /// - parameter scheduler: The scheduler on which the single completes.
    ///   Defaults to MainScheduler.instance.
    public func read<T>(
        observeOn scheduler: ImmediateSchedulerType = MainScheduler.instance,
        value: @escaping (Database) throws -> T)
    -> Single<T>
    {
        Single
            .create(subscribe: { observer in
                self.base.asyncRead { db in
                    do {
                        try observer(.success(value(db.get())))
                    } catch {
                        observer(.failure(error))
                    }
                }
                return Disposables.create { }
            })
            .observe(on: scheduler)
    }
}


================================================
FILE: Sources/RxGRDB/DatabaseRegionObservation+Rx.swift
================================================
import GRDB
import RxSwift

extension DatabaseRegionObservation {
    /// Reactive extensions.
    public var rx: GRDBReactive<Self> { GRDBReactive(self) }
}

extension GRDBReactive where Base == DatabaseRegionObservation {
    /// Returns an Observable that emits the same elements as
    /// a DatabaseRegionObservation.
    ///
    /// All elements are emitted in a protected database dispatch queue,
    /// serialized with all database updates. If you set *startImmediately* to
    /// true (the default value), the first element is emitted synchronously
    /// upon subscription. See [GRDB Concurrency Guide](https://github.com/groue/GRDB.swift/blob/master/README.md#concurrency)
    /// for more information.
    ///
    ///     let dbQueue = try DatabaseQueue()
    ///     try dbQueue.write { db in
    ///         try db.create(table: "player") { t in
    ///             t.column("id", .integer).primaryKey()
    ///             t.column("name", .text)
    ///         }
    ///     }
    ///
    ///     struct Player: Encodable, PersistableRecord {
    ///         var id: Int64
    ///         var name: String
    ///     }
    ///
    ///     let request = Player.all()
    ///     let observation = DatabaseRegionObservation(tracking: request)
    ///     observation.rx
    ///         .changes(in: dbQueue)
    ///         .subscribe(onNext: { db in
    ///             let count = try! Player.fetchCount(db)
    ///             print("Number of players: \(count)")
    ///         })
    ///     // Prints "Number of players: 0"
    ///
    ///     try dbQueue.write { db in
    ///         try Player(id: 1, name: "Arthur").insert(db)
    ///         try Player(id: 2, name: "Barbara").insert(db)
    ///     }
    ///     // Prints "Number of players: 2"
    ///
    ///     try dbQueue.inDatabase { db in
    ///         try Player(id: 3, name: "Craig").insert(db)
    ///         // Prints "Number of players: 3"
    ///         try Player(id: 4, name: "David").insert(db)
    ///         // Prints "Number of players: 4"
    ///     }
    ///
    /// - parameter writer: A DatabaseWriter (DatabaseQueue or DatabasePool).
    public func changes(in writer: DatabaseWriter) -> Observable<Database> {
        Observable.create { observer in
            let cancellable = self.base.start(
                in: writer,
                onError: observer.onError,
                onChange: observer.onNext)
            return Disposables.create(with: cancellable.cancel)
        }
    }
}


================================================
FILE: Sources/RxGRDB/DatabaseWriter+Rx.swift
================================================
import GRDB
import RxSwift

/// We want the `rx` joiner on DatabaseWriter.
/// Normally we'd use ReactiveCompatible. But ReactiveCompatible is unable to
/// define `rx` on existentials as well:
///
///     let writer: DatabaseWriter
///     writer.rx...
///
/// :nodoc:
extension DatabaseWriter {
    /// Reactive extensions.
    public var rx: Reactive<AnyDatabaseWriter> {
        Reactive(AnyDatabaseWriter(self))
    }
}

extension Reactive where Base: DatabaseWriter {
    /// Returns a Single that asynchronously writes into the database.
    ///
    ///     let newPlayerCount: Single<Int> = dbQueue.rx.write { db in
    ///         try Player(...).insert(db)
    ///         return try Player.fetchCount(db)
    ///     }
    ///
    /// By default, the single completes on the main dispatch queue. If
    /// you give a *scheduler*, is completes on that scheduler.
    ///
    /// - parameter scheduler: The scheduler on which the observable completes.
    ///   Defaults to MainScheduler.instance.
    /// - parameter updates: A closure which writes in the database.
    public func write<T>(
        observeOn scheduler: ImmediateSchedulerType = MainScheduler.instance,
        updates: @escaping @Sendable (Database) throws -> T)
    -> Single<T>
    {
        Single
            .create(subscribe: { observer in
                self.base.asyncWrite(updates, completion: { _, result in
                    switch result {
                    case let .success(value):
                        observer(.success(value))
                    case let .failure(error):
                        observer(.failure(error))
                    }
                })
                return Disposables.create()
            })
            // We don't want users to process emitted values on a
            // database dispatch queue.
            .observe(on: scheduler)
    }
    
    /// Returns a Single that asynchronously writes into the database.
    ///
    ///     let newPlayerCount: Single<Int> = dbQueue.rx.write(
    ///         updates: { db in try Player(...).insert(db) },
    ///         thenRead: { db, _ in try Player.fetchCount(db) })
    ///
    /// By default, the single completes on the main dispatch queue. If
    /// you give a *scheduler*, is completes on that scheduler.
    ///
    /// - parameter scheduler: The scheduler on which the observable completes.
    ///   Defaults to MainScheduler.instance.
    /// - parameter updates: A closure which writes in the database.
    /// - parameter value: A closure which reads from the database.
    public func write<T, U>(
        observeOn scheduler: ImmediateSchedulerType = MainScheduler.instance,
        updates: @escaping @Sendable (Database) throws -> T,
        thenRead value: @escaping @Sendable (Database, T) throws -> U)
    -> Single<U>
    {
        Single
            .create(subscribe: { observer in
                self.base.asyncWriteWithoutTransaction { db in
                    var updatesValue: T?
                    do {
                        try db.inTransaction {
                            updatesValue = try updates(db)
                            return .commit
                        }
                    } catch {
                        observer(.failure(error))
                        return
                    }
                    
                    // Non-mutable copy so that compiler does not raise any warning.
                    let updatesValue2 = updatesValue
                    self.base.spawnConcurrentRead { dbResult in
                        do {
                            try observer(.success(value(dbResult.get(), updatesValue2!)))
                        } catch {
                            observer(.failure(error))
                        }
                    }
                }
                return Disposables.create()
            })
            // We don't want users to process emitted values on a
            // database dispatch queue.
            .observe(on: scheduler)
    }
}


================================================
FILE: Sources/RxGRDB/GRDBReactive.swift
================================================
// Workaround https://github.com/ReactiveX/RxSwift/issues/2270
/// :nodoc:
public struct GRDBReactive<Base> {
    /// Base object to extend.
    let base: Base
    
    /// Creates extensions with base object.
    ///
    /// - parameter base: Base object.
    init(_ base: Base) {
        self.base = base
    }
}


================================================
FILE: Sources/RxGRDB/ValueObservation+Rx.swift
================================================
import Dispatch
import GRDB
import RxSwift

extension ValueObservation {
    /// Reactive extensions.
    public var rx: GRDBReactive<Self> { GRDBReactive(self) }
}

/// :nodoc:
public protocol _ValueObservationProtocol {
    associatedtype Reducer: _ValueReducer
    func _start(
        in reader: GRDB.DatabaseReader,
        scheduling scheduler: GRDB.ValueObservationScheduler,
        onError: @escaping (Error) -> Void,
        onChange: @escaping (Reducer.Value) -> Void)
    -> GRDB.DatabaseCancellable
}

/// :nodoc:
extension ValueObservation: _ValueObservationProtocol where Reducer: ValueReducer {
    public func _start(in reader: GRDB.DatabaseReader, scheduling scheduler: GRDB.ValueObservationScheduler, onError: @escaping (Error) -> Void, onChange: @escaping (Reducer.Value) -> Void) -> GRDB.DatabaseCancellable {
        start(in: reader, scheduling: scheduler, onError: onError, onChange: onChange)
    }
}

extension GRDBReactive where Base: _ValueObservationProtocol {
    /// Creates an Observable which tracks changes in database values.
    ///
    /// For example:
    ///
    ///     let observation = ValueObservation.tracking { db in
    ///         try Player.fetchAll(db)
    ///     }
    ///     let disposable = observation.rx
    ///         .observe(in: dbQueue)
    ///         .subscribe(
    ///             onNext: { players: [Player] in
    ///                 print("fresh players: \(players)")
    ///             },
    ///             onError: { error in ... })
    ///
    /// By default, fresh values are dispatched asynchronously on the
    /// main queue. You can change this behavior by by providing a scheduler.
    ///
    /// For example, `.immediate` notifies all values on the main queue as well,
    /// and the first one is immediately notified when the observable
    /// is subscribed:
    ///
    ///     // on the main queue
    ///     observation.rx
    ///         .observe(
    ///             in: dbQueue,
    ///             scheduling: .immediate) // <-
    ///         .subscribe(onNext: { (players: [Player]) in
    ///             // on the main queue
    ///             print("Fresh players: \(players)")
    ///         })
    ///     // <- here "Fresh players" has been printed
    ///
    /// Note that the `.immediate` scheduler requires that the observable is
    /// subscribed from the main thread. It raises a fatal error otherwise.
    ///
    /// - parameter reader: A DatabaseReader (DatabaseQueue or DatabasePool).
    ///   are emitted.
    /// - parameter scheduler: A Scheduler. By default, fresh values are
    ///   dispatched asynchronously on the main queue.
    /// - returns: An Observable of fresh values.
    public func observe(
        in reader: DatabaseReader,
        scheduling scheduler: ValueObservationScheduler = .async(onQueue: .main))
    -> Observable<Base.Reducer.Value>
    {
        Observable.create { observer in
            let cancellable = self.base._start(
                in: reader,
                scheduling: scheduler,
                onError: observer.onError,
                onChange: observer.onNext)
            return Disposables.create(with: cancellable.cancel)
        }
    }
}


================================================
FILE: TODO.md
================================================



================================================
FILE: Tests/RxGRDBTests/DatabaseReaderReadTests.swift
================================================
import GRDB
import RxBlocking
import RxGRDB
import RxSwift
import XCTest

private struct Player: Codable, FetchableRecord, PersistableRecord {
    var id: Int64
    var name: String
    var score: Int?
    
    static func createTable(_ db: Database) throws {
        try db.create(table: "player") { t in
            t.autoIncrementedPrimaryKey("id")
            t.column("name", .text).notNull()
            t.column("score", .integer)
        }
    }
}

class DatabaseReaderReadTests : XCTestCase {
    func testRxJoiner() {
        // Make sure `rx` joiner is available in various contexts
        func f1(_ reader: DatabasePool) {
            _ = reader.rx.read(value: { db in })
        }
        func f2(_ reader: DatabaseQueue) {
            _ = reader.rx.read(value: { db in })
        }
        func f3(_ reader: DatabaseSnapshot) {
            _ = reader.rx.read(value: { db in })
        }
        func f4<Reader: DatabaseReader>(_ reader: Reader) {
            _ = reader.rx.read(value: { db in })
        }
        func f5(_ reader: DatabaseReader) {
            _ = reader.rx.read(value: { db in })
        }
    }
    
    // MARK: -
    
    func testReadObservable() throws {
        func setUp<Writer: DatabaseWriter>(_ writer: Writer) throws -> Writer {
            try writer.write(Player.createTable)
            return writer
        }
        
        func test(reader: DatabaseReader) throws {
            let single = reader.rx.read(value: { db in
                try Player.fetchCount(db)
            })
            let value = try single.toBlocking(timeout: 1).single()
            XCTAssertEqual(value, 0)
        }
        
        try Test(test)
            .run { try setUp(DatabaseQueue()) }
            .runAtTemporaryDatabasePath { try setUp(DatabaseQueue(path: $0)) }
            .runAtTemporaryDatabasePath { try setUp(DatabasePool(path: $0)) }
            .runAtTemporaryDatabasePath { try setUp(DatabasePool(path: $0)).makeSnapshot() }
    }
    
    // MARK: -
    
    func testReadObservableError() throws {
        func test(reader: DatabaseReader) throws {
            let single = reader.rx.read(value: { db in
                try Row.fetchAll(db, sql: "THIS IS NOT SQL")
            })
            do {
                _ = try single.toBlocking(timeout: 1).single()
                XCTFail("Expected error")
            } catch let error as DatabaseError {
                XCTAssertEqual(error.resultCode, .SQLITE_ERROR)
                XCTAssertEqual(error.sql, "THIS IS NOT SQL")
            }
        }
        
        try Test(test)
            .run { try DatabaseQueue() }
            .runAtTemporaryDatabasePath { try DatabaseQueue(path: $0) }
            .runAtTemporaryDatabasePath { try DatabasePool(path: $0) }
            .runAtTemporaryDatabasePath { try DatabasePool(path: $0).makeSnapshot() }
    }
    
    // MARK: -
    
    func testReadObservableIsAsynchronous() throws {
        func setUp<Writer: DatabaseWriter>(_ writer: Writer) throws -> Writer {
            try writer.write(Player.createTable)
            return writer
        }
        
        func test(reader: DatabaseReader) throws {
            let disposeBag = DisposeBag()
            withExtendedLifetime(disposeBag) {
                let expectation = self.expectation(description: "")
                let semaphore = DispatchSemaphore(value: 0)
                reader.rx
                    .read(value: { db in
                        try Player.fetchCount(db)
                    })
                    .subscribe(
                        onSuccess: { _ in
                            semaphore.wait()
                            expectation.fulfill()
                        },
                        onFailure: { error in XCTFail("Unexpected error \(error)") })
                    .disposed(by: disposeBag)
                
                semaphore.signal()
                waitForExpectations(timeout: 1, handler: nil)
            }
        }
        
        try Test(test)
            .run { try setUp(DatabaseQueue()) }
            .runAtTemporaryDatabasePath { try setUp(DatabaseQueue(path: $0)) }
            .runAtTemporaryDatabasePath { try setUp(DatabasePool(path: $0)) }
            .runAtTemporaryDatabasePath { try setUp(DatabasePool(path: $0)).makeSnapshot() }
    }
    
    // MARK: -
    
    func testReadObservableDefaultScheduler() throws {
        if #available(OSX 10.12, iOS 10.0, watchOS 3.0, *) {
            func setUp<Writer: DatabaseWriter>(_ writer: Writer) throws -> Writer {
                try writer.write(Player.createTable)
                return writer
            }
            
            func test(reader: DatabaseReader) {
                let disposeBag = DisposeBag()
                withExtendedLifetime(disposeBag) {
                    let expectation = self.expectation(description: "")
                    reader.rx
                        .read(value: { db in
                            try Player.fetchCount(db)
                        })
                        .subscribe(
                            onSuccess: { _ in
                                dispatchPrecondition(condition: .onQueue(.main))
                                expectation.fulfill()
                            },
                            onFailure: { error in XCTFail("Unexpected error \(error)") })
                        .disposed(by: disposeBag)
                    
                    waitForExpectations(timeout: 1, handler: nil)
                }
            }
            
            try Test(test)
                .run { try setUp(DatabaseQueue()) }
                .runAtTemporaryDatabasePath { try setUp(DatabaseQueue(path: $0)) }
                .runAtTemporaryDatabasePath { try setUp(DatabasePool(path: $0)) }
                .runAtTemporaryDatabasePath { try setUp(DatabasePool(path: $0)).makeSnapshot() }
        }
    }
    
    // MARK: -
    
    func testReadObservableCustomScheduler() throws {
        if #available(OSX 10.12, iOS 10.0, watchOS 3.0, *) {
            func setUp<Writer: DatabaseWriter>(_ writer: Writer) throws -> Writer {
                try writer.write(Player.createTable)
                return writer
            }
            
            func test(reader: DatabaseReader) {
                let disposeBag = DisposeBag()
                withExtendedLifetime(disposeBag) {
                    let queue = DispatchQueue(label: "test")
                    let expectation = self.expectation(description: "")
                    reader.rx
                        .read(
                            observeOn: SerialDispatchQueueScheduler(queue: queue, internalSerialQueueName: "test"),
                            value: { db in
                                try Player.fetchCount(db)
                            })
                        .subscribe(
                            onSuccess: { _ in
                                dispatchPrecondition(condition: .onQueue(queue))
                                expectation.fulfill()
                            },
                            onFailure: { error in XCTFail("Unexpected error \(error)") })
                        .disposed(by: disposeBag)
                    
                    waitForExpectations(timeout: 1, handler: nil)
                }
            }
            
            try Test(test)
                .run { try setUp(DatabaseQueue()) }
                .runAtTemporaryDatabasePath { try setUp(DatabaseQueue(path: $0)) }
                .runAtTemporaryDatabasePath { try setUp(DatabasePool(path: $0)) }
                .runAtTemporaryDatabasePath { try setUp(DatabasePool(path: $0)).makeSnapshot() }
        }
    }
    
    // MARK: -
    
    func testReadObservableIsReadonly() throws {
        func test(reader: DatabaseReader) throws {
            let single = reader.rx.read(value: { db in
                try Player.createTable(db)
            })
            do {
                _ = try single.toBlocking(timeout: 1).single()
                XCTFail("Expected error")
            } catch let error as DatabaseError {
                XCTAssertEqual(error.resultCode, .SQLITE_READONLY)
            }
        }
        
        try Test(test)
            .run { try DatabaseQueue() }
            .runAtTemporaryDatabasePath { try DatabaseQueue(path: $0) }
            .runAtTemporaryDatabasePath { try DatabasePool(path: $0) }
            .runAtTemporaryDatabasePath { try DatabasePool(path: $0).makeSnapshot() }
    }
}


================================================
FILE: Tests/RxGRDBTests/DatabaseRegionObservationTests.swift
================================================
import XCTest
import GRDB
import RxSwift
import RxGRDB

private struct Player: Codable, FetchableRecord, PersistableRecord {
    var id: Int64
    var name: String
    var score: Int?
    
    static func createTable(_ db: Database) throws {
        try db.create(table: "player") { t in
            t.autoIncrementedPrimaryKey("id")
            t.column("name", .text).notNull()
            t.column("score", .integer)
        }
    }
}

class DatabaseRegionObservationTests : XCTestCase {
    
    func testChangesNotifications() throws {
        func setUp<Writer: DatabaseWriter>(_ writer: Writer) throws -> Writer {
            try writer.write(Player.createTable)
            return writer
        }
        
        func test(writer: DatabaseWriter) throws {
            let disposeBag = DisposeBag()
            try withExtendedLifetime(disposeBag) {
                let testSubject = ReplaySubject<Int>.createUnbounded()
                DatabaseRegionObservation(tracking: Player.all())
                    .rx.changes(in: writer)
                    .map(Player.fetchCount)
                    .subscribe(testSubject)
                    .disposed(by: disposeBag)
                
                try writer.writeWithoutTransaction { db in
                    try Player(id: 1, name: "Arthur", score: 1000).insert(db)
                    
                    try db.inTransaction {
                        try Player(id: 2, name: "Barbara", score: 750).insert(db)
                        try Player(id: 3, name: "Craig", score: 500).insert(db)
                        return .commit
                    }
                }
                
                let expectedElements = [1, 3]
                let elements = try testSubject
                    .take(expectedElements.count)
                    .toBlocking(timeout: 1)
                    .toArray()
                XCTAssertEqual(elements, expectedElements)
            }
        }
        
        try Test(test)
            .run { try setUp(DatabaseQueue()) }
            .runAtTemporaryDatabasePath { try setUp(DatabaseQueue(path: $0)) }
            .runAtTemporaryDatabasePath { try setUp(DatabasePool(path: $0)) }
    }
    
    // This is an usage test. Do the available APIs allow to prepend a
    // database connection synchronously, with the guarantee that no race can
    // have the subscriber miss an impactful change?
    //
    // TODO: do the same, but asynchronously. If this is too hard, update the
    // public API so that users can easily do it.
    func testPrependInitialDatabaseSync() throws {
        func setUp<Writer: DatabaseWriter>(_ writer: Writer) throws -> Writer {
            try writer.write(Player.createTable)
            return writer
        }
        
        func test(writer: DatabaseWriter) throws {
            let disposeBag = DisposeBag()
            try withExtendedLifetime(disposeBag) {
                let expectation = self.expectation(description: "")
                let testSubject = ReplaySubject<Database>.createUnbounded()
                testSubject
                    .map(Player.fetchCount)
                    .take(3)
                    .toArray()
                    .subscribe(
                        onSuccess: { value in
                            XCTAssertEqual(value, [0, 1, 3])
                            expectation.fulfill()
                        },
                        onFailure: { error in XCTFail("Unexpected error \(error)") })
                    .disposed(by: disposeBag)
                
                try writer
                    .write({ db in
                        DatabaseRegionObservation(tracking: Player.all())
                            .rx.changes(in: writer)
                            .startWith(db)
                            .subscribe(testSubject)
                    })
                    .disposed(by: disposeBag)
                
                try writer.writeWithoutTransaction { db in
                    try Player(id: 1, name: "Arthur", score: 1000).insert(db)
                    
                    try db.inTransaction {
                        try Player(id: 2, name: "Barbara", score: 750).insert(db)
                        try Player(id: 3, name: "Craig", score: 500).insert(db)
                        return .commit
                    }
                }
                
                waitForExpectations(timeout: 1, handler: nil)
            }
        }
        
        try Test(test)
            .run { try setUp(DatabaseQueue()) }
            .runAtTemporaryDatabasePath { try setUp(DatabaseQueue(path: $0)) }
            .runAtTemporaryDatabasePath { try setUp(DatabasePool(path: $0)) }
    }
}


================================================
FILE: Tests/RxGRDBTests/DatabaseWriterWriteTests.swift
================================================
import GRDB
import RxBlocking
import RxGRDB
import RxSwift
import XCTest

private struct Player: Codable, FetchableRecord, PersistableRecord {
    var id: Int64
    var name: String
    var score: Int?
    
    static func createTable(_ db: Database) throws {
        try db.create(table: "player") { t in
            t.autoIncrementedPrimaryKey("id")
            t.column("name", .text).notNull()
            t.column("score", .integer)
        }
    }
}

class DatabaseWriterWriteTests : XCTestCase {
    func testRxJoiner() {
        // Make sure `rx` joiner is available in various contexts
        func f1(_ writer: DatabasePool) {
            _ = writer.rx.write(updates: { db in })
        }
        func f2(_ writer: DatabaseQueue) {
            _ = writer.rx.write(updates: { db in })
        }
        func f4<Writer: DatabaseWriter>(_ writer: Writer) {
            _ = writer.rx.write(updates: { db in })
        }
        func f5(_ writer: DatabaseWriter) {
            _ = writer.rx.write(updates: { db in })
        }
    }
    
    // MARK: - Write
    
    func testWriteObservable() throws {
        func setUp<Writer: DatabaseWriter>(_ writer: Writer) throws -> Writer {
            try writer.write(Player.createTable)
            return writer
        }
        
        func test(writer: DatabaseWriter) throws {
            let single = writer.rx.write(updates: { db -> Int in
                try Player(id: 1, name: "Arthur", score: 1000).insert(db)
                return try Player.fetchCount(db)
            })
            let count = try single.toBlocking(timeout: 1).single()
            XCTAssertEqual(count, 1)
        }
        
        try Test(test)
            .run { try setUp(DatabaseQueue()) }
            .runAtTemporaryDatabasePath { try setUp(DatabaseQueue(path: $0)) }
            .runAtTemporaryDatabasePath { try setUp(DatabasePool(path: $0)) }
    }
    
    // MARK: -
    
    func testWriteObservableAsCompletable() throws {
        func setUp<Writer: DatabaseWriter>(_ writer: Writer) throws -> Writer {
            try writer.write(Player.createTable)
            return writer
        }
        
        func test(writer: DatabaseWriter) throws {
            let completable = writer.rx
                .write(updates: { db -> Int in
                    try Player(id: 1, name: "Arthur", score: 1000).insert(db)
                    return try Player.fetchCount(db)
                })
                .asCompletable()
            _ = try completable.toBlocking(timeout: 1).last()
        }
        
        try Test(test)
            .run { try setUp(DatabaseQueue()) }
            .runAtTemporaryDatabasePath { try setUp(DatabaseQueue(path: $0)) }
            .runAtTemporaryDatabasePath { try setUp(DatabasePool(path: $0)) }
    }
    
    // MARK: -
    
    func testWriteObservableError() throws {
        func test(writer: DatabaseWriter) throws {
            let single = writer.rx.write(updates: { db in
                try db.execute(sql: "THIS IS NOT SQL")
            })
            do {
                _ = try single.toBlocking(timeout: 1).single()
                XCTFail("Expected error")
            } catch let error as DatabaseError {
                XCTAssertEqual(error.resultCode, .SQLITE_ERROR)
                XCTAssertEqual(error.sql, "THIS IS NOT SQL")
            }
        }
        
        try Test(test)
            .run { try DatabaseQueue() }
            .runAtTemporaryDatabasePath { try DatabaseQueue(path: $0) }
            .runAtTemporaryDatabasePath { try DatabasePool(path: $0) }
    }
    
    func testWriteObservableErrorRollbacksTransaction() throws {
        func setUp<Writer: DatabaseWriter>(_ writer: Writer) throws -> Writer {
            try writer.write(Player.createTable)
            return writer
        }
        
        func test(writer: DatabaseWriter) throws {
            let single = writer.rx.write(updates: { db in
                try Player(id: 1, name: "Arthur", score: 1000).insert(db)
                try db.execute(sql: "THIS IS NOT SQL")
            })
            do {
                _ = try single.toBlocking(timeout: 1).single()
                XCTFail("Expected error")
            } catch let error as DatabaseError {
                XCTAssertEqual(error.resultCode, .SQLITE_ERROR)
                XCTAssertEqual(error.sql, "THIS IS NOT SQL")
            }
            let count = try writer.read(Player.fetchCount)
            XCTAssertEqual(count, 0)
        }
        
        try Test(test)
            .run { try setUp(DatabaseQueue()) }
            .runAtTemporaryDatabasePath { try setUp(DatabaseQueue(path: $0)) }
            .runAtTemporaryDatabasePath { try setUp(DatabasePool(path: $0)) }
    }
    
    // MARK: -
    
    func testWriteObservableIsAsynchronous() throws {
        func setUp<Writer: DatabaseWriter>(_ writer: Writer) throws -> Writer {
            try writer.write(Player.createTable)
            return writer
        }
        
        func test(writer: DatabaseWriter) throws {
            let disposeBag = DisposeBag()
            withExtendedLifetime(disposeBag) {
                let expectation = self.expectation(description: "")
                let semaphore = DispatchSemaphore(value: 0)
                writer.rx
                    .write(updates: { db in
                        try Player(id: 1, name: "Arthur", score: 1000).insert(db)
                    })
                    .subscribe(
                        onSuccess: {
                            semaphore.wait()
                            expectation.fulfill()
                        },
                        onFailure: { error in XCTFail("Unexpected error \(error)") })
                    .disposed(by: disposeBag)
                
                semaphore.signal()
                waitForExpectations(timeout: 1, handler: nil)
            }
        }
        
        try Test(test)
            .run { try setUp(DatabaseQueue()) }
            .runAtTemporaryDatabasePath { try setUp(DatabaseQueue(path: $0)) }
            .runAtTemporaryDatabasePath { try setUp(DatabasePool(path: $0)) }
    }
    
    func testWriteObservableDefaultScheduler() throws {
        if #available(OSX 10.12, iOS 10.0, watchOS 3.0, *) {
            func setUp<Writer: DatabaseWriter>(_ writer: Writer) throws -> Writer {
                try writer.write(Player.createTable)
                return writer
            }
            
            func test(writer: DatabaseWriter) {
                let disposeBag = DisposeBag()
                withExtendedLifetime(disposeBag) {
                    let expectation = self.expectation(description: "")
                    writer.rx
                        .write(updates: { db in
                            try Player(id: 1, name: "Arthur", score: 1000).insert(db)
                        })
                        .subscribe(
                            onSuccess: { _ in
                                dispatchPrecondition(condition: .onQueue(.main))
                                expectation.fulfill()
                            },
                            onFailure: { error in XCTFail("Unexpected error \(error)") })
                        .disposed(by: disposeBag)
                    
                    waitForExpectations(timeout: 1, handler: nil)
                }
            }
            
            try Test(test)
                .run { try setUp(DatabaseQueue()) }
                .runAtTemporaryDatabasePath { try setUp(DatabaseQueue(path: $0)) }
                .runAtTemporaryDatabasePath { try setUp(DatabasePool(path: $0)) }
        }
    }
    
    // MARK: -
    
    func testWriteObservableCustomScheduler() throws {
        if #available(OSX 10.12, iOS 10.0, watchOS 3.0, *) {
            func setUp<Writer: DatabaseWriter>(_ writer: Writer) throws -> Writer {
                try writer.write(Player.createTable)
                return writer
            }
            
            func test(writer: DatabaseWriter) {
                let disposeBag = DisposeBag()
                withExtendedLifetime(disposeBag) {
                    let queue = DispatchQueue(label: "test")
                    let expectation = self.expectation(description: "")
                    writer.rx
                        .write(
                            observeOn: SerialDispatchQueueScheduler(queue: queue, internalSerialQueueName: "test"),
                            updates: { db in
                                try Player(id: 1, name: "Arthur", score: 1000).insert(db)
                            })
                        .subscribe(
                            onSuccess: { _ in
                                dispatchPrecondition(condition: .onQueue(queue))
                                expectation.fulfill()
                            },
                            onFailure: { error in XCTFail("Unexpected error \(error)") })
                        .disposed(by: disposeBag)
                    
                    waitForExpectations(timeout: 1, handler: nil)
                }
            }
            
            try Test(test)
                .run { try setUp(DatabaseQueue()) }
                .runAtTemporaryDatabasePath { try setUp(DatabaseQueue(path: $0)) }
                .runAtTemporaryDatabasePath { try setUp(DatabasePool(path: $0)) }
        }
    }
    
    // MARK: - WriteThenRead
    
    func testWriteThenReadObservable() throws {
        func setUp<Writer: DatabaseWriter>(_ writer: Writer) throws -> Writer {
            try writer.write(Player.createTable)
            return writer
        }
        
        func test(writer: DatabaseWriter) throws {
            let single = writer.rx.write(
                updates: { db in try Player(id: 1, name: "Arthur", score: 1000).insert(db) },
                thenRead: { db, _ in try Player.fetchCount(db) })
            let count = try single.toBlocking(timeout: 1).single()
            XCTAssertEqual(count, 1)
        }
        
        try Test(test)
            .run { try setUp(DatabaseQueue()) }
            .runAtTemporaryDatabasePath { try setUp(DatabaseQueue(path: $0)) }
            .runAtTemporaryDatabasePath { try setUp(DatabasePool(path: $0)) }
    }
    
    // MARK: -
    
    func testWriteThenReadObservableIsReadonly() throws {
        func test(writer: DatabaseWriter) throws {
            let single = writer.rx.write(
                updates: { _ in },
                thenRead: { db, _ in try Player.createTable(db) })
            do {
                _ = try single.toBlocking(timeout: 1).single()
                XCTFail("Expected error")
            } catch let error as DatabaseError {
                XCTAssertEqual(error.resultCode, .SQLITE_READONLY)
            }
        }
        
        try Test(test)
            .run { try DatabaseQueue() }
            .runAtTemporaryDatabasePath { try DatabaseQueue(path: $0) }
            .runAtTemporaryDatabasePath { try DatabasePool(path: $0) }
    }
    
    // MARK: -
    
    func testWriteThenReadObservableWriteError() throws {
        func test(writer: DatabaseWriter) throws {
            let single = writer.rx.write(
                updates: { db in try db.execute(sql: "THIS IS NOT SQL") },
                thenRead: { _, _ in })
            do {
                _ = try single.toBlocking(timeout: 1).single()
                XCTFail("Expected error")
            } catch let error as DatabaseError {
                XCTAssertEqual(error.resultCode, .SQLITE_ERROR)
                XCTAssertEqual(error.sql, "THIS IS NOT SQL")
            }
        }
        
        try Test(test)
            .run { try DatabaseQueue() }
            .runAtTemporaryDatabasePath { try DatabaseQueue(path: $0) }
            .runAtTemporaryDatabasePath { try DatabasePool(path: $0) }
    }
    
    func testWriteThenReadObservableWriteErrorRollbacksTransaction() throws {
        func setUp<Writer: DatabaseWriter>(_ writer: Writer) throws -> Writer {
            try writer.write(Player.createTable)
            return writer
        }
        
        func test(writer: DatabaseWriter) throws {
            let single = writer.rx.write(
                updates: { db in
                    try Player(id: 1, name: "Arthur", score: 1000).insert(db)
                    try db.execute(sql: "THIS IS NOT SQL")
                },
                thenRead: { _, _ in })
            do {
                _ = try single.toBlocking(timeout: 1).single()
                XCTFail("Expected error")
            } catch let error as DatabaseError {
                XCTAssertEqual(error.resultCode, .SQLITE_ERROR)
                XCTAssertEqual(error.sql, "THIS IS NOT SQL")
            }
            let count = try writer.read(Player.fetchCount)
            XCTAssertEqual(count, 0)
        }
        
        try Test(test)
            .run { try setUp(DatabaseQueue()) }
            .runAtTemporaryDatabasePath { try setUp(DatabaseQueue(path: $0)) }
            .runAtTemporaryDatabasePath { try setUp(DatabasePool(path: $0)) }
    }
    
    // MARK: -
    
    func testWriteThenReadObservableReadError() throws {
        func test(writer: DatabaseWriter) throws {
            let single = writer.rx.write(
                updates: { _ in },
                thenRead: { db, _ in try Row.fetchAll(db, sql: "THIS IS NOT SQL") })
            do {
                _ = try single.toBlocking(timeout: 1).single()
                XCTFail("Expected error")
            } catch let error as DatabaseError {
                XCTAssertEqual(error.resultCode, .SQLITE_ERROR)
                XCTAssertEqual(error.sql, "THIS IS NOT SQL")
            }
        }
        
        try Test(test)
            .run { try DatabaseQueue() }
            .runAtTemporaryDatabasePath { try DatabaseQueue(path: $0) }
            .runAtTemporaryDatabasePath { try DatabasePool(path: $0) }
    }
    
    // MARK: -
    
    func testWriteThenReadObservableDefaultScheduler() throws {
        if #available(OSX 10.12, iOS 10.0, watchOS 3.0, *) {
            func setUp<Writer: DatabaseWriter>(_ writer: Writer) throws -> Writer {
                try writer.write(Player.createTable)
                return writer
            }
            
            func test(writer: DatabaseWriter) {
                let disposeBag = DisposeBag()
                withExtendedLifetime(disposeBag) {
                    let expectation = self.expectation(description: "")
                    writer.rx
                        .write(
                            updates: { _ in },
                            thenRead: { _, _ in })
                        .subscribe(
                            onSuccess: { _ in
                                dispatchPrecondition(condition: .onQueue(.main))
                                expectation.fulfill()
                            },
                            onFailure: { error in XCTFail("Unexpected error \(error)") })
                        .disposed(by: disposeBag)
                    
                    waitForExpectations(timeout: 1, handler: nil)
                }
            }
            
            try Test(test)
                .run { try setUp(DatabaseQueue()) }
                .runAtTemporaryDatabasePath { try setUp(DatabaseQueue(path: $0)) }
                .runAtTemporaryDatabasePath { try setUp(DatabasePool(path: $0)) }
        }
    }
    
    // MARK: -
    
    func testWriteThenReadObservableCustomScheduler() throws {
        if #available(OSX 10.12, iOS 10.0, watchOS 3.0, *) {
            func setUp<Writer: DatabaseWriter>(_ writer: Writer) throws -> Writer {
                try writer.write(Player.createTable)
                return writer
            }
            
            func test(writer: DatabaseWriter) {
                let disposeBag = DisposeBag()
                withExtendedLifetime(disposeBag) {
                    let queue = DispatchQueue(label: "test")
                    let expectation = self.expectation(description: "")
                    writer.rx
                        .write(
                            observeOn: SerialDispatchQueueScheduler(queue: queue, internalSerialQueueName: "test"),
                            updates: { _ in },
                            thenRead: { _, _ in })
                        .subscribe(
                            onSuccess: { _ in
                                dispatchPrecondition(condition: .onQueue(queue))
                                expectation.fulfill()
                            },
                            onFailure: { error in XCTFail("Unexpected error \(error)") })
                        .disposed(by: disposeBag)
                    
                    waitForExpectations(timeout: 1, handler: nil)
                }
            }
            
            try Test(test)
                .run { try setUp(DatabaseQueue()) }
                .runAtTemporaryDatabasePath { try setUp(DatabaseQueue(path: $0)) }
                .runAtTemporaryDatabasePath { try setUp(DatabasePool(path: $0)) }
        }
    }
}


================================================
FILE: Tests/RxGRDBTests/Support.swift
================================================
import XCTest
import RxSwift
import GRDB

final class Test<Context> {
    // Raise the repeatCount in order to help spotting flaky tests.
    private let repeatCount = 1
    private let test: (Context) throws -> ()
    
    init(_ test: @escaping (Context) throws -> ()) {
        self.test = test
    }
    
    @discardableResult
    func run(context: () throws -> Context) throws -> Self {
        for _ in 1...repeatCount {
            try test(context())
        }
        return self
    }
    
    @discardableResult
    func runInTemporaryDirectory(context: (_ directoryURL: URL) throws -> Context) throws -> Self {
        for _ in 1...repeatCount {
            let directoryURL = URL(fileURLWithPath: NSTemporaryDirectory())
                .appendingPathComponent("GRDBCombine", isDirectory: true)
                .appendingPathComponent(ProcessInfo.processInfo.globallyUniqueString, isDirectory: true)
            
            try FileManager.default.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil)
            defer {
                try! FileManager.default.removeItem(at: directoryURL)
            }
            
            try test(context(directoryURL))
        }
        return self
    }
    
    @discardableResult
    func runAtTemporaryDatabasePath(context: (_ path: String) throws -> Context) throws -> Self {
        try runInTemporaryDirectory { url in
            try context(url.appendingPathComponent("db.sqlite").path)
        }
    }
}


================================================
FILE: Tests/RxGRDBTests/ValueObservationTests.swift
================================================
import XCTest
import GRDB
import RxSwift
import RxGRDB

private struct Player: Codable, FetchableRecord, PersistableRecord {
    var id: Int64
    var name: String
    var score: Int?
    
    static func createTable(_ db: Database) throws {
        try db.create(table: "player") { t in
            t.autoIncrementedPrimaryKey("id")
            t.column("name", .text).notNull()
            t.column("score", .integer)
        }
    }
}

class ValueObservationTests : XCTestCase {
    
    // MARK: - Default Scheduler
    
    func testDefaultSchedulerChangesNotifications() throws {
        func setUp<Writer: DatabaseWriter>(_ writer: Writer) throws -> Writer {
            try writer.write(Player.createTable)
            return writer
        }
        
        func test(writer: DatabaseWriter) throws {
            let disposeBag = DisposeBag()
            try withExtendedLifetime(disposeBag) {
                let testSubject = ReplaySubject<Int>.createUnbounded()
                ValueObservation
                    .tracking(Player.fetchCount)
                    .rx.observe(in: writer)
                    .subscribe(testSubject)
                    .disposed(by: disposeBag)
                
                try writer.writeWithoutTransaction { db in
                    try Player(id: 1, name: "Arthur", score: 1000).insert(db)
                    
                    try db.inTransaction {
                        try Player(id: 2, name: "Barbara", score: 750).insert(db)
                        try Player(id: 3, name: "Craig", score: 500).insert(db)
                        return .commit
                    }
                }
                
                let expectedElements = [0, 1, 3]
                if writer is DatabaseQueue {
                    let elements = try testSubject
                        .take(expectedElements.count)
                        .toBlocking(timeout: 1).toArray()
                    XCTAssertEqual(elements, expectedElements)
                } else {
                    let elements = try testSubject
                        .take(until: { $0 == expectedElements.last }, behavior: .inclusive)
                        .toBlocking(timeout: 1).toArray()
                    assertValueObservationRecordingMatch(recorded: elements, expected: expectedElements)
                }
            }
        }
        
        try Test(test)
            .run { try setUp(DatabaseQueue()) }
            .runAtTemporaryDatabasePath { try setUp(DatabaseQueue(path: $0)) }
            .runAtTemporaryDatabasePath { try setUp(DatabasePool(path: $0)) }
    }
    
    func testDefaultSchedulerFirstValueIsEmittedAsynchronously() throws {
        func setUp<Writer: DatabaseWriter>(_ writer: Writer) throws -> Writer {
            try writer.write(Player.createTable)
            return writer
        }
        
        func test(writer: DatabaseWriter) throws {
            let disposeBag = DisposeBag()
            withExtendedLifetime(disposeBag) {
                let expectation = self.expectation(description: "")
                let semaphore = DispatchSemaphore(value: 0)
                ValueObservation
                    .tracking(Player.fetchCount)
                    .rx.observe(in: writer)
                    .subscribe(onNext: { _ in
                        semaphore.wait()
                        expectation.fulfill()
                    })
                    .disposed(by: disposeBag)
                
                semaphore.signal()
                waitForExpectations(timeout: 1, handler: nil)
            }
        }
        
        try Test(test)
            .run { try setUp(DatabaseQueue()) }
            .runAtTemporaryDatabasePath { try setUp(DatabaseQueue(path: $0)) }
            .runAtTemporaryDatabasePath { try setUp(DatabasePool(path: $0)) }
    }
    
    func testDefaultSchedulerError() throws {
        func test(writer: DatabaseWriter) throws {
            let observable = ValueObservation
                .tracking { try $0.execute(sql: "THIS IS NOT SQL") }
                .rx.observe(in: writer)
            let result = observable.toBlocking().materialize()
            switch result {
            case .completed:
                XCTFail("Expected error")
            case let .failed(elements: _, error: error):
                XCTAssertNotNil(error as? DatabaseError)
            }
        }
        
        try Test(test)
            .run { try DatabaseQueue() }
            .runAtTemporaryDatabasePath { try DatabaseQueue(path: $0) }
            .runAtTemporaryDatabasePath { try DatabasePool(path: $0) }
    }
    
    // MARK: - Immediate Scheduler
    
    func testImmediateSchedulerChangesNotifications() throws {
        func setUp<Writer: DatabaseWriter>(_ writer: Writer) throws -> Writer {
            try writer.write(Player.createTable)
            return writer
        }
        
        func test(writer: DatabaseWriter) throws {
            let disposeBag = DisposeBag()
            try withExtendedLifetime(disposeBag) {
                let testSubject = ReplaySubject<Int>.createUnbounded()
                ValueObservation
                    .tracking(Player.fetchCount)
                    .rx.observe(in: writer, scheduling: .immediate)
                    .subscribe(testSubject)
                    .disposed(by: disposeBag)
                
                try writer.writeWithoutTransaction { db in
                    try Player(id: 1, name: "Arthur", score: 1000).insert(db)
                    
                    try db.inTransaction {
                        try Player(id: 2, name: "Barbara", score: 750).insert(db)
                        try Player(id: 3, name: "Craig", score: 500).insert(db)
                        return .commit
                    }
                }
                
                let expectedElements = [0, 1, 3]
                if writer is DatabaseQueue {
                    let elements = try testSubject
                        .take(expectedElements.count)
                        .toBlocking(timeout: 1).toArray()
                    XCTAssertEqual(elements, expectedElements)
                } else {
                    let elements = try testSubject
                        .take(until: { $0 == expectedElements.last }, behavior: .inclusive)
                        .toBlocking(timeout: 1).toArray()
                    assertValueObservationRecordingMatch(recorded: elements, expected: expectedElements)
                }
            }
        }
        
        try Test(test)
            .run { try setUp(DatabaseQueue()) }
            .runAtTemporaryDatabasePath { try setUp(DatabaseQueue(path: $0)) }
            .runAtTemporaryDatabasePath { try setUp(DatabasePool(path: $0)) }
    }
    
    func testImmediateSchedulerEmitsFirstValueSynchronously() throws {
        func setUp<Writer: DatabaseWriter>(_ writer: Writer) throws -> Writer {
            try writer.write(Player.createTable)
            return writer
        }
        
        func test(writer: DatabaseWriter) throws {
            let disposeBag = DisposeBag()
            withExtendedLifetime(disposeBag) {
                let semaphore = DispatchSemaphore(value: 0)
                ValueObservation
                    .tracking(Player.fetchCount)
                    .rx.observe(in: writer, scheduling: .immediate)
                    .subscribe(onNext: { _ in
                        semaphore.signal()
                    })
                    .disposed(by: disposeBag)
                
                semaphore.wait()
            }
        }
        
        try Test(test)
            .run { try setUp(DatabaseQueue()) }
            .runAtTemporaryDatabasePath { try setUp(DatabaseQueue(path: $0)) }
            .runAtTemporaryDatabasePath { try setUp(DatabasePool(path: $0)) }
    }
    
    func testImmediateSchedulerError() throws {
        func test(writer: DatabaseWriter) throws {
            let observable = ValueObservation
                .tracking { try $0.execute(sql: "THIS IS NOT SQL") }
                .rx.observe(in: writer, scheduling: .immediate)
            let result = observable.toBlocking().materialize()
            switch result {
            case .completed:
                XCTFail("Expected error")
            case let .failed(elements: _, error: error):
                XCTAssertNotNil(error as? DatabaseError)
            }
        }
        
        try Test(test)
            .run { try DatabaseQueue() }
            .runAtTemporaryDatabasePath { try DatabaseQueue(path: $0) }
            .runAtTemporaryDatabasePath { try DatabasePool(path: $0) }
    }
    
    func testIssue780() throws {
        func test(dbPool: DatabasePool) throws {
            struct Entity: Codable, FetchableRecord, PersistableRecord, Equatable {
                var id: Int64
                var name: String
            }
            try dbPool.write { db in
                try db.create(table: "entity") { t in
                    t.autoIncrementedPrimaryKey("id")
                    t.column("name", .text)
                }
            }
            let observation = ValueObservation.tracking(Entity.fetchAll)
            let entities = try dbPool.rx
                .write { db in try Entity(id: 1, name: "foo").insert(db) }
                .asCompletable()
                .andThen(observation.rx.observe(in: dbPool, scheduling: .immediate))
                .take(1)
                .toBlocking(timeout: 1)
                .single()
            XCTAssertEqual(entities, [Entity(id: 1, name: "foo")])
        }
        try Test(test).runAtTemporaryDatabasePath { try DatabasePool(path: $0) }
    }
    
    // MARK: - Utils
    
    /// This test checks the fundamental promise of ValueObservation by
    /// comparing recorded values with expected values.
    ///
    /// Recorded values match the expected values if and only if:
    ///
    /// - The last recorded value is the last expected value
    /// - Recorded values are in the same order as expected values
    ///
    /// However, both missing and repeated values are allowed - with the only
    /// exception of the last expected value which can not be missed.
    ///
    /// For example, if the expected values are [0, 1], then the following
    /// recorded values match:
    ///
    /// - `[0, 1]` (identical values)
    /// - `[1]` (missing value but the last one)
    /// - `[0, 0, 1, 1]` (repeated value)
    ///
    /// However the following recorded values don't match, and fail the test:
    ///
    /// - `[1, 0]` (wrong order)
    /// - `[0]` (missing last value)
    /// - `[]` (missing last value)
    /// - `[0, 1, 2]` (unexpected value)
    /// - `[1, 0, 1]` (unexpected value)
    func assertValueObservationRecordingMatch<Value>(
        recorded recordedValues: [Value],
        expected expectedValues: [Value],
        _ message: @autoclosure () -> String = "",
        file: StaticString = #file,
        line: UInt = #line)
    where Value: Equatable
    {
        _assertValueObservationRecordingMatch(
            recorded: recordedValues,
            expected: expectedValues,
            // Last value can't be missed
            allowMissingLastValue: false,
            message(), file: file, line: line)
    }
    
    private func _assertValueObservationRecordingMatch<R, E>(
        recorded recordedValues: R,
        expected expectedValues: E,
        allowMissingLastValue: Bool,
        _ message: @autoclosure () -> String = "",
        file: StaticString = #file,
        line: UInt = #line)
    where
        R: BidirectionalCollection,
        E: BidirectionalCollection,
        R.Element == E.Element,
        R.Element: Equatable
    {
        guard let value = expectedValues.last else {
            if !recordedValues.isEmpty {
                XCTFail("unexpected recorded prefix \(Array(recordedValues)) - \(message())", file: file, line: line)
            }
            return
        }
        
        let recordedSuffix = recordedValues.reversed().prefix(while: { $0 == value })
        let expectedSuffix = expectedValues.reversed().prefix(while: { $0 == value })
        if !allowMissingLastValue {
            // Both missing and repeated values are allowed in the recorded values.
            // This is because of asynchronous DatabasePool observations.
            if recordedSuffix.isEmpty {
                XCTFail("missing expected value \(value) - \(message())", file: file, line: line)
            }
        }
        
        let remainingRecordedValues = recordedValues.prefix(recordedValues.count - recordedSuffix.count)
        let remainingExpectedValues = expectedValues.prefix(expectedValues.count - expectedSuffix.count)
        _assertValueObservationRecordingMatch(
            recorded: remainingRecordedValues,
            expected: remainingExpectedValues,
            // Other values can be missed
            allowMissingLastValue: true,
            message(), file: file, line: line)
    }
}
Download .txt
gitextract_sqrfypq0/

├── .ci/
│   └── gemfiles/
│       └── Gemfile.travis
├── .gitignore
├── .swiftpm/
│   └── xcode/
│       ├── package.xcworkspace/
│       │   └── contents.xcworkspacedata
│       └── xcshareddata/
│           └── xcschemes/
│               ├── RxGRDB.xcscheme
│               └── RxGRDBTests.xcscheme
├── .travis.yml
├── CHANGELOG.md
├── Documentation/
│   └── RxGRDBDemo/
│       ├── README.md
│       ├── RxGRDBDemo/
│       │   ├── AppDatabase.swift
│       │   ├── AppDelegate.swift
│       │   ├── Info.plist
│       │   ├── Models/
│       │   │   ├── Player.swift
│       │   │   └── Players.swift
│       │   ├── Resources/
│       │   │   ├── Assets.xcassets/
│       │   │   │   └── AppIcon.appiconset/
│       │   │   │       └── Contents.json
│       │   │   └── Base.lproj/
│       │   │       ├── LaunchScreen.storyboard
│       │   │       └── Main.storyboard
│       │   ├── UI/
│       │   │   ├── PlayersViewController.swift
│       │   │   └── PlayersViewModel.swift
│       │   └── World.swift
│       ├── RxGRDBDemo.xcodeproj/
│       │   ├── project.pbxproj
│       │   ├── project.xcworkspace/
│       │   │   ├── contents.xcworkspacedata
│       │   │   └── xcshareddata/
│       │   │       └── IDEWorkspaceChecks.plist
│       │   └── xcshareddata/
│       │       └── xcschemes/
│       │           └── RxGRDBDemo.xcscheme
│       └── RxGRDBDemoTests/
│           ├── AppDatabaseTests.swift
│           ├── Info.plist
│           ├── PlayerTests.swift
│           ├── PlayersTests.swift
│           └── PlayersViewModelTests.swift
├── LICENSE
├── Makefile
├── Package.swift
├── README.md
├── Sources/
│   └── RxGRDB/
│       ├── DatabaseReader+Rx.swift
│       ├── DatabaseRegionObservation+Rx.swift
│       ├── DatabaseWriter+Rx.swift
│       ├── GRDBReactive.swift
│       └── ValueObservation+Rx.swift
├── TODO.md
└── Tests/
    └── RxGRDBTests/
        ├── DatabaseReaderReadTests.swift
        ├── DatabaseRegionObservationTests.swift
        ├── DatabaseWriterWriteTests.swift
        ├── Support.swift
        └── ValueObservationTests.swift
Condensed preview — 43 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (190K chars).
[
  {
    "path": ".ci/gemfiles/Gemfile.travis",
    "chars": 104,
    "preview": "source 'https://rubygems.org'\n\ngem 'xcpretty'\ngem 'xcpretty-travis-formatter'\ngem 'cocoapods', '~> 1.7'\n"
  },
  {
    "path": ".gitignore",
    "chars": 1208,
    "preview": "## https://github.com/github/gitignore/blob/master/Global/macOS.gitignore\n\n*.DS_Store\n.AppleDouble\n.LSOverride\n\n# Icon m"
  },
  {
    "path": ".swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata",
    "chars": 135,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:\">\n   </FileRef"
  },
  {
    "path": ".swiftpm/xcode/xcshareddata/xcschemes/RxGRDB.xcscheme",
    "chars": 3258,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1230\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": ".swiftpm/xcode/xcshareddata/xcschemes/RxGRDBTests.xcscheme",
    "chars": 1774,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1230\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": ".travis.yml",
    "chars": 822,
    "preview": "# The OS X Build Environment\n# https://docs.travis-ci.com/user/reference/osx/#Xcode-version\n\nlanguage: objective-c\nxcode"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 19133,
    "preview": "Release Notes\n=============\n\n## 4.0.1\n\nReleased September 28, 2025\n\n- Replace deprecated API methods by [@1Consumption]("
  },
  {
    "path": "Documentation/RxGRDBDemo/README.md",
    "chars": 2145,
    "preview": "RxGRDBDemo\n==========\n\n<img align=\"right\" src=\"https://raw.githubusercontent.com/RxSwiftCommunity/RxGRDB/master/Document"
  },
  {
    "path": "Documentation/RxGRDBDemo/RxGRDBDemo/AppDatabase.swift",
    "chars": 1494,
    "preview": "import GRDB\n\n/// A type responsible for initializing an application database.\nstruct AppDatabase {\n    \n    /// Prepares"
  },
  {
    "path": "Documentation/RxGRDBDemo/RxGRDBDemo/AppDelegate.swift",
    "chars": 1118,
    "preview": "import UIKit\nimport GRDB\n\n@UIApplicationMain\nclass AppDelegate: UIResponder, UIApplicationDelegate {\n    var window: UIW"
  },
  {
    "path": "Documentation/RxGRDBDemo/RxGRDBDemo/Info.plist",
    "chars": 1463,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Documentation/RxGRDBDemo/RxGRDBDemo/Models/Player.swift",
    "chars": 2166,
    "preview": "import GRDB\n\n// A player\nstruct Player: Codable, Equatable {\n    var id: Int64?\n    var name: String\n    var score: Int\n"
  },
  {
    "path": "Documentation/RxGRDBDemo/RxGRDBDemo/Models/Players.swift",
    "chars": 3089,
    "preview": "import GRDB\nimport RxGRDB\nimport RxSwift\n\n/// Players is responsible for high-level operations on the players database.\n"
  },
  {
    "path": "Documentation/RxGRDBDemo/RxGRDBDemo/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 1590,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"iphone\",\n      \"size\" : \"20x20\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\""
  },
  {
    "path": "Documentation/RxGRDBDemo/RxGRDBDemo/Resources/Base.lproj/LaunchScreen.storyboard",
    "chars": 1681,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
  },
  {
    "path": "Documentation/RxGRDBDemo/RxGRDBDemo/Resources/Base.lproj/Main.storyboard",
    "chars": 10701,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3"
  },
  {
    "path": "Documentation/RxGRDBDemo/RxGRDBDemo/UI/PlayersViewController.swift",
    "chars": 3872,
    "preview": "import UIKit\nimport RxDataSources\nimport RxSwift\nimport RxCocoa\n\n/// An MVVM ViewController that displays PlayersViewMod"
  },
  {
    "path": "Documentation/RxGRDBDemo/RxGRDBDemo/UI/PlayersViewModel.swift",
    "chars": 2502,
    "preview": "import Action\nimport Foundation\nimport GRDB\nimport RxCocoa\nimport RxGRDB\nimport RxSwift\n\n/// An MVVM ViewModel for Playe"
  },
  {
    "path": "Documentation/RxGRDBDemo/RxGRDBDemo/World.swift",
    "chars": 687,
    "preview": "import GRDB\n\n/// Dependency Injection based on the \"How to Control the World\" article:\n/// https://www.pointfree.co/blog"
  },
  {
    "path": "Documentation/RxGRDBDemo/RxGRDBDemo.xcodeproj/project.pbxproj",
    "chars": 29978,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 52;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "Documentation/RxGRDBDemo/RxGRDBDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "chars": 135,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:\">\n   </FileRef"
  },
  {
    "path": "Documentation/RxGRDBDemo/RxGRDBDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "chars": 238,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Documentation/RxGRDBDemo/RxGRDBDemo.xcodeproj/xcshareddata/xcschemes/RxGRDBDemo.xcscheme",
    "chars": 4265,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1230\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "Documentation/RxGRDBDemo/RxGRDBDemoTests/AppDatabaseTests.swift",
    "chars": 492,
    "preview": "import XCTest\nimport GRDB\n\nclass AppDatabaseTests: XCTestCase {\n    \n    func testDatabaseSchemaContainsPlayerTable() th"
  },
  {
    "path": "Documentation/RxGRDBDemo/RxGRDBDemoTests/Info.plist",
    "chars": 701,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Documentation/RxGRDBDemo/RxGRDBDemoTests/PlayerTests.swift",
    "chars": 2802,
    "preview": "import XCTest\nimport GRDB\n\nclass PlayerTests: XCTestCase {\n    \n    func testInsert() throws {\n        let dbQueue = try"
  },
  {
    "path": "Documentation/RxGRDBDemo/RxGRDBDemoTests/PlayersTests.swift",
    "chars": 4787,
    "preview": "import GRDB\nimport RxBlocking\nimport RxSwift\nimport XCTest\n\nclass PlayersTests: XCTestCase {\n    \n    private func makeD"
  },
  {
    "path": "Documentation/RxGRDBDemo/RxGRDBDemoTests/PlayersViewModelTests.swift",
    "chars": 1548,
    "preview": "import Action\nimport GRDB\nimport RxBlocking\nimport RxSwift\nimport XCTest\n\nclass PlayersViewModelTests: XCTestCase {\n    "
  },
  {
    "path": "LICENSE",
    "chars": 1060,
    "preview": "Copyright (C) 2018 RxSwiftCommunity\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of thi"
  },
  {
    "path": "Makefile",
    "chars": 941,
    "preview": "# Rules\n# =====\n#\n# make test - Run all tests but performance tests\n# make distclean - Restore repository to a pristine "
  },
  {
    "path": "Package.swift",
    "chars": 1031,
    "preview": "// swift-tools-version:6.0\n\nimport PackageDescription\n\nlet package = Package(\n    name: \"RxGRDB\",\n    platforms: [\n     "
  },
  {
    "path": "README.md",
    "chars": 14197,
    "preview": "RxGRDB [![Swift 6](https://img.shields.io/badge/swift-6-orange.svg?style=flat)](https://developer.apple.com/swift/)  [!["
  },
  {
    "path": "Sources/RxGRDB/DatabaseReader+Rx.swift",
    "chars": 1654,
    "preview": "import GRDB\nimport RxSwift\n\n/// We want the `rx` joiner on DatabaseReader.\n/// Normally we'd use ReactiveCompatible. But"
  },
  {
    "path": "Sources/RxGRDB/DatabaseRegionObservation+Rx.swift",
    "chars": 2507,
    "preview": "import GRDB\nimport RxSwift\n\nextension DatabaseRegionObservation {\n    /// Reactive extensions.\n    public var rx: GRDBRe"
  },
  {
    "path": "Sources/RxGRDB/DatabaseWriter+Rx.swift",
    "chars": 4030,
    "preview": "import GRDB\nimport RxSwift\n\n/// We want the `rx` joiner on DatabaseWriter.\n/// Normally we'd use ReactiveCompatible. But"
  },
  {
    "path": "Sources/RxGRDB/GRDBReactive.swift",
    "chars": 315,
    "preview": "// Workaround https://github.com/ReactiveX/RxSwift/issues/2270\n/// :nodoc:\npublic struct GRDBReactive<Base> {\n    /// Ba"
  },
  {
    "path": "Sources/RxGRDB/ValueObservation+Rx.swift",
    "chars": 3209,
    "preview": "import Dispatch\nimport GRDB\nimport RxSwift\n\nextension ValueObservation {\n    /// Reactive extensions.\n    public var rx:"
  },
  {
    "path": "TODO.md",
    "chars": 1,
    "preview": "\n"
  },
  {
    "path": "Tests/RxGRDBTests/DatabaseReaderReadTests.swift",
    "chars": 8515,
    "preview": "import GRDB\nimport RxBlocking\nimport RxGRDB\nimport RxSwift\nimport XCTest\n\nprivate struct Player: Codable, FetchableRecor"
  },
  {
    "path": "Tests/RxGRDBTests/DatabaseRegionObservationTests.swift",
    "chars": 4705,
    "preview": "import XCTest\nimport GRDB\nimport RxSwift\nimport RxGRDB\n\nprivate struct Player: Codable, FetchableRecord, PersistableReco"
  },
  {
    "path": "Tests/RxGRDBTests/DatabaseWriterWriteTests.swift",
    "chars": 17089,
    "preview": "import GRDB\nimport RxBlocking\nimport RxGRDB\nimport RxSwift\nimport XCTest\n\nprivate struct Player: Codable, FetchableRecor"
  },
  {
    "path": "Tests/RxGRDBTests/Support.swift",
    "chars": 1505,
    "preview": "import XCTest\nimport RxSwift\nimport GRDB\n\nfinal class Test<Context> {\n    // Raise the repeatCount in order to help spot"
  },
  {
    "path": "Tests/RxGRDBTests/ValueObservationTests.swift",
    "chars": 13006,
    "preview": "import XCTest\nimport GRDB\nimport RxSwift\nimport RxGRDB\n\nprivate struct Player: Codable, FetchableRecord, PersistableReco"
  }
]

About this extraction

This page contains the full source code of the RxSwiftCommunity/RxGRDB GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 43 files (173.5 KB), approximately 45.9k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!