Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

doc: fix broken doc links; add policies page #452

Merged
merged 7 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 88 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ An intuitive way to work with persistent data in Dart.

## [Full documentation](https://getdutchie.github.io/brick/)

- [GraphQL](https://getdutchie.github.io/brick/#/offline_first/offline_first_with_graphql_repository)
- [REST](https://getdutchie.github.io/brick/#/offline_first/offline_first_with_rest_repository)
- [Supabase](https://getdutchie.github.io/brick/#/offline_first/offline_first_with_supabase_repository?id=repository-configuration)

## Why Brick?

- Out-of-the-box [offline access](packages/brick_offline_first) to data
- [Handle and hide](packages/brick_build) complex serialization/deserialization logic
- Single [access point](docs/data/repositories.md) and opinionated DSL
- Automatic, [intelligently-generated migrations](docs/sqlite.md#intelligent-migrations)
- Legible [querying interface](docs/data/query.md)
- Single [access point](https://getdutchie.github.io/brick/#/data/repositories) and opinionated DSL
- Automatic, [intelligently-generated migrations](https://getdutchie.github.io/brick/#/sqlite)
- Legible [querying interface](https://getdutchie.github.io/brick/#/data/query)

## What is Brick?

Expand All @@ -37,7 +41,7 @@ Brick is an extensible query interface for Dart applications. It's an [all-in-on
```
1. Add [models](docs/data/models.md) that contain your app logic. Models **must be** saved with the `.model.dart` suffix (i.e. `lib/brick/models/person.model.dart`).
1. Run `dart run build_runner build` to generate your models and [sometimes migrations](docs/sqlite.md#intelligent-migrations). Rerun after every new model change or `dart run build_runner watch` for automatic generations. You'll need to run this again after your first migration.
1. Extend [an existing repository](docs/data/repositories.md) or create your own:
1. Extend [an existing repository](docs/data/repositories.md) or create your own (Supabase has [some exceptions](https://getdutchie.github.io/brick/#/offline_first/offline_first_with_supabase_repository)):

```dart
// lib/brick/repository.dart
Expand Down Expand Up @@ -71,3 +75,83 @@ Brick is an extensible query interface for Dart applications. It's an [all-in-on
```

1. Profit.

## Usage

Create a model as the app's business logic:

```dart
// brick/models/user.dart
@ConnectOfflineFirstWithRest()
class User extends OfflineFirstWithRestModel {}
```

And generate (de)serializing code to fetch to and from multiple providers:

```bash
$ (flutter) pub run build_runner build
```

### Fetching Data

A repository fetches and returns data across multiple providers. It's the single access point for data in your app:

```dart
class MyRepository extends OfflineFirstWithRestRepository {
MyRepository();
}

final repository = MyRepository();

// Now the models can be queried:
final users = await repository.get<User>();
```

Behind the scenes, this repository could poll a memory cache, then SQLite, then a REST API. The repository intelligently determines how and when to use each of the providers to return the fastest, most reliable data.

```dart
// Queries can be general:
final query = Query(where: [Where('lastName').contains('Muster')]);
final users = await repository.get<User>(query: query);

// Or singular:
final query = Query.where('email', 'user@example.com', limit1: true);
final user = await repository.get<User>(query: query);
```

Queries can also receive **reactive updates**. The subscribed stream receives all models from its query whenever the local copy is updated (e.g. when the data is hydrated in another part of the app):

```dart
final users = repository.subscribe<User>().listen((users) {})
```

### Mutating Data

Once a model has been created, it's sent to the repository and back out to _each_ provider:

```dart
final user = User();
await repository.upsert<User>(user);
```

### Associating Data

Repositories can support associations and automatic (de)serialization of child models.

```dart
class Hat extends OfflineFirstWithRestModel {
final String color;
Hat({this.color});
}
class User extends OfflineFirstWithRestModel {
// user has many hats
final List<Hat> hats;
}

final query = Query.where('hats', Where('color').isExactly('brown'));
final usersWithBrownHats = repository.get<User>(query: query);
```

Brick natively [serializes primitives, associations, and more](packages/brick_offline_first/example/lib/brick/models/kitchen_sink.model.dart).

If it's still murky, [check out Learn](https://getdutchie.github.io/brick/#/README?id=learn) for videos, tutorials, and examples that break down Brick.
3 changes: 2 additions & 1 deletion docs/_sidebar.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
- [Fetching data](data/query.md)
- [Providers](data/providers.md)
- [Repositories](data/repositories.md)
- [Supabase](supabase/repository.md)
- [Supabase](supabase/fields.md)
- [Model Config](supabase/models.md)
- [Field Config](supabase/fields.md)
- [Querying](supabase/query.md)
Expand All @@ -29,6 +29,7 @@
- [Offline First](offline_first/fields.md)
- [Model Config](offline_first/models.md)
- [Field Config](offline_first/fields.md)
- [Policies](offline_first/policies.md)
- [Testing](offline_first/testing.md)
- [With Supabase](offline_first/offline_first_with_supabase_repository.md)
- [With GraphQL](offline_first/offline_first_with_graphql_repository.md)
Expand Down
46 changes: 23 additions & 23 deletions docs/data/query.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,19 @@ Query.where('lastName', 'Mustermann') // note this is lastName and not name or l

Querying can be done with `Where` or `WherePhrase`:

1) `WherePhrase` is a collection of `Where` statements.
2) `WherePhrase` can't contain mixed `required:` because this will output invalid SQL. For example, when it's mixed: `WHERE id = 2 AND name = 'Thomas' OR name = 'Guy'`. The OR needs to be its own phrase: `WHERE (id = 2 AND name = 'Thomas') OR (name = 'Guy')`.
3) `WherePhrase` can be intermixed with `Where`.
```dart
[
Where('id').isExactly(2),
WherePhrase([
Or('name').isExactly('Guy'),
Or('name').isExactly('Thomas')
], required: false)
]
// => (id == 2) || (name == 'Thomas' || name == 'Guy')
```
1. `WherePhrase` is a collection of `Where` statements.
2. `WherePhrase` can't contain mixed `required:` because this will output invalid SQL. For example, when it's mixed: `WHERE id = 2 AND name = 'Thomas' OR name = 'Guy'`. The OR needs to be its own phrase: `WHERE (id = 2 AND name = 'Thomas') OR (name = 'Guy')`.
3. `WherePhrase` can be intermixed with `Where`.
```dart
[
Where('id').isExactly(2),
WherePhrase([
Or('name').isExactly('Guy'),
Or('name').isExactly('Thomas')
], required: false)
]
// => (id == 2) || (name == 'Thomas' || name == 'Guy')
```

!> Queried enum values should map to a primitive. Plainly, **always include `.index`**: `Where('type').isExactly(MyEnumType.value.index)`.

Expand All @@ -57,15 +57,15 @@ Fields can be compared to their values beyond an exact match (the default).
Where('name', value: 'Thomas', compare: Compare.contains);
```

* `between`
* `contains`
* `doesNotContain`
* `exact`
* `greaterThan`
* `greaterThanOrEqualTo`
* `lessThan`
* `lessThanOrEqualTo`
* `notEqual`
- `between`
- `contains`
- `doesNotContain`
- `exact`
- `greaterThan`
- `greaterThanOrEqualTo`
- `lessThan`
- `lessThanOrEqualTo`
- `notEqual`

Please note that the provider is ultimately responsible for supporting `Where` queries.

Expand Down Expand Up @@ -97,7 +97,7 @@ Query(where: [
// => (name == 'Thomas' || age != 42) && (height > 182 && height < 186 && country == 'France')
```

?> If expanded `WherePhrase`s become unlegible, helpers `And` and `Or` can be used:
?> If expanded `WherePhrase`s become illegible, helpers `And` and `Or` can be used:

```dart
Query(where: [
Expand Down
8 changes: 3 additions & 5 deletions docs/graphql/fields.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
?> The GraphQL domain is currently in Alpha. APIs are subject to change.

# Field Configuration

## Annotations
Expand All @@ -11,7 +9,7 @@ Brick by default assumes enums from a GraphQL API will be delivered as integers
Given the API:

```json
{ "user": { "hats": [ "bowler", "birthday" ] } }
{ "user": { "hats": ["bowler", "birthday"] } }
```

Simply convert `hats` into a Dart enum:
Expand Down Expand Up @@ -80,5 +78,5 @@ query {

The following are not serialized to GraphQL. However, unsupported types can still be accessed in the model as non-final fields.

* Nested `List<>` e.g. `<List<List<int>>>`
* Many-to-many associations
- Nested `List<>` e.g. `<List<List<int>>>`
- Many-to-many associations
13 changes: 6 additions & 7 deletions docs/graphql/query.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
?> The GraphQL domain is currently in Alpha. APIs are subject to change.

# `Query` Configuration

## `providerArgs:`

| Name | Type | Description |
|---|---|---|
| `'operation'` | `GraphqlOperation` | apply this operation instead of one of the defaults from `graphqlOperationTransformer`. The document subfields **will not** be populated by the model. |
| `'context'` | `Map<String, ContextEntry>` | apply this as the context to the request instead of an empty object. Useful for subsequent consumers/`Link`s of the request. The key should be the runtime type of the `ContextEntry`. |
| Name | Type | Description |
| ------------- | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `'operation'` | `GraphqlOperation` | apply this operation instead of one of the defaults from `graphqlOperationTransformer`. The document subfields **will not** be populated by the model. |
| `'context'` | `Map<String, ContextEntry>` | apply this as the context to the request instead of an empty object. Useful for subsequent consumers/`Link`s of the request. The key should be the runtime type of the `ContextEntry`. |

#### `variablesNamespace`

Expand Down Expand Up @@ -47,4 +45,5 @@ final variables = {
!> Association values within `Where` **are not** converted to variables

!> Multiple `where` keys (`OfflineFirst(where: {'id': 'data["id"]', 'otherVar': 'data["otherVar"]'})`) or nested properties (`OfflineFirst(where: {'id': 'data["subfield"]["id"]})`) will not generate.
* `@OfflineFirst(where:` only supports extremely simple renames. Multiple `where` keys (`OfflineFirst(where: {'id': 'data["id"]', 'otherVar': 'data["otherVar"]'})`) or nested properties (`OfflineFirst(where: {'id': 'data["subfield"]["id"]})`) will be ignored. Be sure to use `@Graphql(name:)` to rename the generated document field.

- `@OfflineFirst(where:` only supports extremely simple renames. Multiple `where` keys (`OfflineFirst(where: {'id': 'data["id"]', 'otherVar': 'data["otherVar"]'})`) or nested properties (`OfflineFirst(where: {'id': 'data["subfield"]["id"]})`) will be ignored. Be sure to use `@Graphql(name:)` to rename the generated document field.
3 changes: 2 additions & 1 deletion docs/home.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,11 @@

## Learn

- Video: [Brick Architecture](https://www.youtube.com/watch?v=2noLcro9iIw). An explanation of Brick parlance with a supplemental analogy.
- Video: [Brick Architecture](https://www.youtube.com/watch?v=2noLcro9iIw). An explanation of Brick parlance with a [supplemental analogy](https://medium.com/flutter-community/brick-your-app-five-compelling-reasons-and-a-pizza-analogy-to-make-your-data-accessible-8d802e1e526e).
- Video: [Brick Basics](https://www.youtube.com/watch?v=jm5i7e_BQq0). An overview of essential Brick mechanics.
- Example: [Simple Associations using the OfflineFirstWithGraphql domain](https://github.com/GetDutchie/brick/blob/main/example_graphql)
- Example: [Simple Associations using the OfflineFirstWithRest domain](https://github.com/GetDutchie/brick/blob/main/example)
- Example: [Simple Associations using the OfflineFirstWithSupabase domain](https://github.com/GetDutchie/brick/blob/main/example_supabase)
- Tutorial: [Setting up a simple app with Brick](http://www.flutterbyexample.com/#/posts/2_adding_a_repository)

## Glossary
Expand Down
5 changes: 2 additions & 3 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
<link href="https://fonts.googleapis.com" rel="preconnect" />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400..900&display=swap" rel="stylesheet" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=10.0, user-scalable=1" />
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify/themes/vue.css" />
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify-themeable@0/dist/css/theme-simple.css">

<style type="text/css">
body {
:root {
--theme-color: #9A3536;
}

Expand Down Expand Up @@ -59,7 +59,6 @@
code .token {
color: #3c7cc2;
}

</style>
</head>

Expand Down
6 changes: 2 additions & 4 deletions docs/introduction/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,10 @@ final query = Query.where('email', 'user@example.com', limit1: true);
final user = await repository.get<User>(query: query);
```

For continuous updates, queries can also be streams. The stream receives all models from its query whenever the local copy is updated:
Queries can also receive **reactive updates**. The subscribed stream receives all models from its query whenever the local copy is updated (e.g. when the data is hydrated in another part of the app):

```dart
final subscription repository.subscribe<User>().listen((users) {

})
final users = repository.subscribe<User>().listen((users) {})
```

## Mutating Data
Expand Down
2 changes: 0 additions & 2 deletions docs/offline_first/offline_first_with_graphql_repository.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
?> The GraphQL domain is currently in Alpha. APIs are subject to change.

# Offline First With GraphQL Repository

`OfflineFirstWithGraphqlRepository` streamlines the GraphQL integration with an `OfflineFirstRepository`. A serial queue is included to track GraphQL mutations in a separate SQLite database, only removing requests when a response is returned from the host (i.e. the device has lost internet connectivity).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ When using [supabase_flutter](https://pub.dev/packages/supabase_flutter), create

```dart
final (client, queue) = OfflineFirstWithSupabaseRepository.clientQueue(databaseFactory: databaseFactory);
await Supabase.initialize(httpClient: client)
await Supabase.initialize(url: supabaseUrl, anonKey: supabaseAnonKey, httpClient: client)
final supabaseProvider = SupabaseProvider(Supabase.instance.client, modelDictionary: ...)
```

Expand Down
41 changes: 41 additions & 0 deletions docs/offline_first/policies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Offline First Policies

Repository methods can be invoked with policies to prioritize data sources. For example, a request may need to skip the offline queue or the response must come from a remote source. It is strongly encouraged to use a policy (e.g. `Repository().get<User>(policy: .requireRemote))`) instead of directly accessing a provider (e.g. `Repository().restPovider.get<User>()`)

## OfflineFirstDeletePolicy

### `optimisticLocal`

Delete local results before waiting for the remote provider to respond

### `requireRemote`

Delete local results after remote responds; local results are not deleted if remote responds with any exception

## OfflineFirstGetPolicy

### `alwaysHydrate`

Ensures data is fetched from the remote provider(s) at each invocation. This hydration is unawaited and is not guaranteed to complete before results are returned. This can be expensive to perform for some queries; see [`awaitRemoteWhenNoneExist`](#awaitremotewhennoneexist) for a more performant option or [`awaitRemote`](#awaitremote) to await the hydration before returning results.

### `awaitRemote`

Ensures results must be updated from the remote proivder(s) before returning if the app is online. An empty array will be returned if the app is offline.

### `awaitRemoteWhenNoneExist`

Retrieves from the remote provider(s) if the query returns no results from the local provider(s).

### `localOnly`

Do not request from the remote provider(s)

## OfflineFirstUpsertPolicy

### `optimisticLocal`

Save results to local before waiting for the remote provider to respond

### `requireRemote`

Save results to local after remote responds; local results are not saved if remote responds with any exception
5 changes: 1 addition & 4 deletions example_supabase/lib/brick/models/customer.model.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import 'package:brick_offline_first_with_supabase/brick_offline_first_with_supabase.dart';
import 'package:brick_sqlite/brick_sqlite.dart';
import 'package:brick_supabase/brick_supabase.dart';

@ConnectOfflineFirstWithSupabase(
supabaseConfig: SupabaseSerializable(),
)
@ConnectOfflineFirstWithSupabase()
class Customer extends OfflineFirstWithSupabaseModel {
@Sqlite(unique: true)
final String id;
Expand Down
2 changes: 1 addition & 1 deletion packages/brick_offline_first_with_supabase/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ When using [supabase_flutter](https://pub.dev/packages/supabase_flutter), create

```dart
final (client, queue) = OfflineFirstWithSupabaseRepository.clientQueue(databaseFactory: databaseFactory);
await Supabase.initialize(httpClient: client)
await Supabase.initialize(url: supabaseUrl, anonKey: supabaseAnonKey, httpClient: client)
final supabaseProvider = SupabaseProvider(Supabase.instance.client, modelDictionary: ...)
```

Expand Down