Skip to content

Commit

Permalink
Added the Database Protocol with Drift and RxDB implementations (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
wkok authored Feb 23, 2024
1 parent 53c8961 commit 0c970fc
Show file tree
Hide file tree
Showing 50 changed files with 52,005 additions and 167 deletions.
8 changes: 7 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ jobs:

strategy:
matrix:
app: [coeffects, counter, fetch, flow, local_storage, signals, event_queue]
app: [coeffects, counter, fetch, flow, local_storage, signals, event_queue, drift]

steps:
- name: Checkout
Expand Down Expand Up @@ -148,6 +148,12 @@ jobs:
path: samples/event_queue/build/web
key: samples-event_queue-build-output-${{ github.RUN_NUMBER }}

- name: Restore drift build output
uses: actions/cache@v3
with:
path: samples/drift/build/web
key: samples-drift-build-output-${{ github.RUN_NUMBER }}

- name: Setup Pages
uses: actions/configure-pages@v4

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ In the `samples` folder of this repository:
| [Launch](https://htihospitality.github.io/re-dash/local_storage/build/web/) | `local_storage` | Shows an example of how to initialize shared_preferences and inject values into event handlers using coeffects. |
| [Launch](https://htihospitality.github.io/re-dash/flow/build/web/) | `flow` | (alpha) Shows an example of using [Flows](/doc/03-flows.md) to calculate a derived result of some calculation, in addition to Flow life-cycle controls. |
| [Launch](https://htihospitality.github.io/re-dash/event_queue/build/web/) | `event_queue` | Shows an example of ordered event execution during longer processing events & effects. Logged to console. |
| [Launch](https://htihospitality.github.io/re-dash/drift/build/web/) | `drift` | Shows an example of how to use Drift as the app state back-end database instead of the default path based Map Atom. |
| N/A | `rxdb` | Shows an example of how to use RxDB as the app state back-end database instead of the default path based Map Atom (NOTE - RxDB for Flutter is [not yet production ready](https://github.com/pubkey/rxdb/tree/master/examples/flutter)) and also does not support the Web platform. |


## Quickstart
Expand Down
178 changes: 178 additions & 0 deletions doc/04-databases.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
# Databases

Global state in re-dash is stored in some database.

Usually this database is in-memory only and represented as some data structure, like a map - this is the default behavior.

Other times we might require more capability like persistence, relational queries, replication etc. and want to opt for using a full featured database engine on the client instead, while keeping with the core concepts of the [data loop](https://day8.github.io/re-frame/a-loop/) - events, subscriptions etc.

This is achieved by implementing the `Database` protocol. Out the box, re-dash ships with support for 3 database implementations of this protocol:

- AppDB (the default path based Map Atom)
- [Drift](https://drift.simonbinder.eu/) (a relational persistence library over sqlite3)
- [RxDB](https://rxdb.info/articles/flutter-database.html) (a popular reactive NoSQL database for JavaScript, usable from Flutter)

> Multiple databases may be registered and used in the same app simultaneously. Note however, that subscription signals of different database implementations cannot be used with a layer 3 subscription.
## AppDB

The default path based Map Atom.

This `Database` is registered by default, always available to query via subscriptions or update via events using the built-in `:db` effect.

## Drift

[Drift](https://drift.simonbinder.eu/) is the relational persistence library for your Dart and Flutter apps - it is an abstraction over sqlite3.

### Schema

The simplest way (currently) is to maintain the Drift schema in raw `.dart` files in the `/.lib` folder directly and have the code generator watch it with `dart run build_runner watch`.

For more info see the `samples/drift/lib/database.dart` sample and the [drift documentation](https://drift.simonbinder.eu/docs/getting-started/#database-class).

### Database registration

Register it early on in the app startup (like in `main`)

```clojure
(await
(rd/reg-database
:drift ;; <== Choose any `database-id`
{:impl (drift/Drift.) ;; <== The `Database` protocol implementation
:instance (database/AppDatabase.)})) ;; <== The database instance
```

### Subscription

Use the chosen database-id in `reg-sub` that should target Drift for example

```clojure
(rd/reg-sub
::app-state
:drift ;; <== The registered database-id
(fn [^db/AppDatabase db _]
(.select db (.-appState db)))) ;; <== Query that returns a subsribable (stream)

(rd/reg-sub
::width
:drift
(fn [_]
(rd/subscribe [::app-state])) ;; <== Layer 3 signal(s) supported
(fn [[^db/AppStateData data] _]
(.-width data)))
```

### Events

Use the chosen database-id as the effect-id in events.

Note that the event itself does not mutate the database directly, but instead returns a function being passed the database instance. This is keeping with re-dash (& re-frame) methodology of writing pure event handlers - the mutation is applied later when re-dash execute effects.

There is no need to register the `:drift` (database-id) effect as this is automatically done for you.

```clojure
(rd/reg-event-fx
::increment-width
(fn [_ [_ width]]
{:drift #(let [db ^db/AppDatabase %]
(.write (.update db (.-appState db))
(db/AppStateCompanion
.width (-> width inc int d/Value))))}))
```

### Sample app

There's a working sample app in the `samples/drift` folder using the Drift database as app state


## RxDB

A popular reactive NoSQL database for JavaScript, [somewhat usable from Flutter](https://rxdb.info/articles/flutter-database.html). It has various storage layer plugins including an in-memory only option.

### Schema

Maintain the schema in raw JavaScript, and compile it with node.

For more info see the `samples/rxdb/javascript/src/index.js` sample and the [rxdb documentation](https://github.com/pubkey/rxdb/tree/master/examples/flutter#in-javascript)

### Database registration

Register it early on in the app startup (like in `main`)

```clojure
(await
(rd/reg-database
:rxdb ;; <== Choose any `database-id`
{:impl (rx-db/RxDB.) ;; <== The `Database` protocol implementation
:instance (database/getRxDatabase "javascript/dist/index.js" ;; <== The database instance
"AppDatabase")}))
```

### Subscription

Use the chosen database-id in `reg-sub` that should target RxDB for example

```clojure
(rd/reg-sub
::app-state
:rxdb ;; <== The registered database-id
(fn [db [_ selector]]
(-> (.getCollection db coll-name)
(.find selector)))) ;; <== Query that returns a subsribable (stream)

(rd/reg-sub
::width
:rxdb
(fn [_]
(rd/subscribe [::app-state])) ;; <== Layer 3 signal(s) supported
(fn [[doc] _]
(get (->> (.-data ^rxdb/RxDocument doc)
(into {}))
"width")))
```

### Events

Use the chosen database-id as the effect-id in events.

Note that the event itself does not mutate the database directly, but instead returns a function being passed the database instance. This is keeping with re-dash (& re-frame) methodology of writing pure event handlers - the mutation is applied later when re-dash execute effects.

There is no need to register the `:rxdb` (database-id) effect as this is automatically done for you.

```clojure
(rd/reg-event-fx
::increment-width
(fn [_ _]
{:rxdb #(.insert (.getCollection % coll-name)
{"id" (str (DateTime/now))
"width" ((fnil f 0) val)})}))
```

### Status

RxDB for Flutter is experimental, and many features are missing.

Some of the known limitations include:

- Documents can only be inserted, queried and observed. [Update is not yet supported](https://pub.dev/documentation/rxdb/latest/rxdb/RxCollection-class.html)
- Only collection queries can be observed (subscribed) not documents, or individual fields in a document.
- RxDB for Flutter is not supported in the web platform.


### Sample app

There's a working sample app in the `samples/rxdb` folder using the RxDB database as app state

## Custom implementation

Adding support for another database is a matter of implementing the `Database` protocol and registering it early on in the app startup, like in main:

```clojure
(await
(rd/reg-database
:my-database-id ;; <== Choose any `database-id`
{:impl (my/DatabaseImpl.) ;; <== Instantiate the `Database` protocol implementation
:instance (some/MyDatabase.)})) ;; <== Instantiate the database instance
```

To support subscriptions, the database engine must support some way of watching / observing queries.
44 changes: 44 additions & 0 deletions samples/drift/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/

# IntelliJ related
*.iml
*.ipr
*.iws
.idea/

# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/

# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/

# Symbolication related
app.*.symbols

# Obfuscation related
app.*.map.json

# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
45 changes: 45 additions & 0 deletions samples/drift/.metadata
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.

version:
revision: "d211f42860350d914a5ad8102f9ec32764dc6d06"
channel: "stable"

project_type: app

# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- platform: android
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- platform: ios
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- platform: linux
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- platform: macos
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- platform: web
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- platform: windows
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06

# User provided section

# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'
42 changes: 42 additions & 0 deletions samples/drift/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# flow_drift

> This sample tries to demonstrate the Flow sample using Drift as the back-end for app state as opposed to a Clojure map.
Shows an example of using Flows to calculate a derived result of some calculation, in addition to Flow life-cycle controls.

## Run the sample

### Create the platform folders

```bash
flutter create .
```

### Run it

```bash
clj -M:cljd flutter
```

## Updating the Drift database schema

> Ignore this section if you only intend to run this sample as is. Read on if you made some changes that require a scheme update.
Drift requires a schema to be persisted prior to any rows being inserted, see [Database class](https://drift.simonbinder.eu/docs/getting-started/#database-class)

This sample includes a pre-built schema with source in `lib/database.dart`.

Any changes to the schema will need to be recompiled:


```bash

# Either once off

dart run build_runner build

# Or watched for changes

dart run build_runner watch

```
28 changes: 28 additions & 0 deletions samples/drift/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.

# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml

linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule

# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
9 changes: 9 additions & 0 deletions samples/drift/bb.edn
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{:paths ["script"]
:deps {}
:tasks
{compile {:doc "Compile app"
:task (shell "clojure -M:cljd compile")}

build {:doc "Builds the deployable assets"
:task (shell "flutter build web --base-href=/re-dash/drift/build/web/")
:depends [compile]}}}
Loading

0 comments on commit 0c970fc

Please sign in to comment.