Skip to content

Commit

Permalink
eng(supabase): add abstract packages (#402)
Browse files Browse the repository at this point in the history
  • Loading branch information
tshedor authored Aug 19, 2024
1 parent 278aa99 commit 5461db3
Show file tree
Hide file tree
Showing 18 changed files with 644 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
!example/**/*.g.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## Unreleased

### 0.0.1

Initial
21 changes: 21 additions & 0 deletions packages/brick_offline_first_with_supabase_abstract/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) Green Bits, Inc. and its affiliates.

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.
147 changes: 147 additions & 0 deletions packages/brick_offline_first_with_supabase_abstract/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
![brick_offline_first_with_rest workflow](https://github.com/GetDutchie/brick/actions/workflows/brick_offline_first_with_rest.yaml/badge.svg)

`OfflineFirstWithRestRepository` streamlines the REST integration with an `OfflineFirstRepository`. A serial queue is included to track REST requests in a separate SQLite database, only removing requests when a response is returned from the host (i.e. the device has lost internet connectivity). See `OfflineFirstWithRest#reattemptForStatusCodes`.

The `OfflineFirstWithRest` domain uses all the same configurations and annotations as `OfflineFirst`.

## Models

### ConnectOfflineFirstWithRest

`@ConnectOfflineFirstWithRest` decorates the model that can be serialized by one or more providers. Offline First does not have configuration at the class level and only extends configuration held by its providers:

```dart
@ConnectOfflineFirstWithRest(
restConfig: RestSerializable(),
sqliteConfig: SqliteSerializable(),
)
class MyModel extends OfflineFirstModel {}
```

## Generating Models from a REST Endpoint

A utility class is provided to make model generation from a JSON API a snap. Given an endpoint, the converter will infer the type of a field and scaffold a class. For example, the following would be saved to the `lib` directory of your project and run `$ dart lib/converter_script.dart`:

```dart
// lib/converter_script.dart
import 'package:brick_offline_first/rest_to_offline_first_converter.dart';
const BASE = "http://0.0.0.0:3000";
const endpoint = "$BASE/users";
final converter = RestToOfflineFirstConverter(endpoint: endpoint);
void main() {
converter.saveToFile();
}
// => dart lib/converter_script.dart
```

After the model is generated, double check for `List<dynamic>` and `null` types. While the converter is smart, it's not smarter than you.

## Testing

Responses can be stubbed to and from an `OfflineFirstWithRest` repository. For convenience, file data can be used to stub JSON responses from an API:

```dart
// test/models/api/user.json
{
"user": { "name" : "Thomas" }
}
// test/models/user_test.dart
import 'package:brick_sqlite/testing.dart';
import 'package:my_app/brick/repository.dart';
void main() {
group("MySqliteProvider", () {
late MyRepository repository;
setUpAll(() async {
repository = MyRepository(
restProvider: RestProvider(
client: StubOfflineFirstWithRest.fromFiles('http://0.0.0.0:3000', {
'users': 'api/user.json'
}).client,
)
);
await repository.initialize()
});
});
}
```

By default, the same response is returned for both `upsert` and `get` methods, with the only variation being in status code. However, responses can be configured for different methods:

```dart
StubOfflineFirstWithRest(
baseEndpoint: 'http://0.0.0.0:3000',
responses: [
StubOfflineFirstRestResponse.fromFile('users', 'api/user.json', StubHttpMethod.get),
StubOfflineFirstRestResponse.fromFile('users', 'api/user-post.json', StubHttpMethod.post),
],
)
```

### Stubbing Without Files

While storing the responses in a file can be convenient and reduce code clutter, responses can be defined inline:

```dart
StubOfflineFirstWithRest(
baseEndpoint: 'http://0.0.0.0:3000',
responses: [
StubOfflineFirstRestResponse('users', '{"name":"Bob"'),
StubOfflineFirstRestResponse('users', '{"name":"Alice"'),
],
)
```

### Handling Endpoint Variations

Variants in the endpoint must be explicitly declared. For example, `/user`, `/users`, `/users?by_first_name=Guy` are all different. When instantiating, specify any expected variants:

```dart
StubOfflineFirstRestResponse<User>(
endpoints: ["user", "users", "users?by_first_name=Guy"]
)
```

### Stubbing Multiple Models

Rarely will only one model need to be stubbed. All classes in an app can be stubbed efficiently using `StubOfflineFirstWithRest`:

```dart
setUpAll() async {
final config = {
User: ['user', 'users'],
// Even individual member endpoints must be declared for association fetching
// REST endpoints are manually configured, so the content may vary
Hat: ['hat/1', 'hat/2', 'hats'],
}
final responses = config.entries.map((modelConfig) {
return modelConfig.value.map((endpoint) {
return StubOfflineFirstRestResponse.fromFile(
'api/${modelConfig.key.toString().toLowerCase()}.json',
endpoint: endpoint,
);
});
}).expand((e) => e);
final client = StubOfflineFirstWithRest(
baseEndpoint: 'http://0.0.0.0:3000',
responses: responses,
).client;
}
```

### FAQ

#### Why can't I declare a model argument?

Due to [an open analyzer bug](https://github.com/dart-lang/sdk/issues/38309), a custom model cannot be passed to the repository as a type argument.

## Unsupported Field Types

* Any unsupported field types from `RestProvider`, or `SqliteProvider`
* Future iterables of future models (i.e. `Future<List<Future<Model>>>`.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include: ../../analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export 'package:brick_offline_first_with_supabase_abstract/src/connect_offline_first_with_supabase.dart';
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import 'package:brick_sqlite/brick_sqlite.dart';
import 'package:brick_supabase_abstract/brick_supabase_abstract.dart';

/// An annotation used to specify a class to generate code for.
///
/// Clones the annotated class to two files for processing by their respective builders
class ConnectOfflineFirstWithSupabase {
/// Creates a new [ConnectOfflineFirstWithSupabase] instance.
const ConnectOfflineFirstWithSupabase({
this.sqliteConfig,
this.supabaseConfig,
});

/// Configuration for the [SqliteSerializable] annotation
final SqliteSerializable? sqliteConfig;

/// Configuration for the [SupabaseSerializable] annotation
final SupabaseSerializable? supabaseConfig;

/// An instance of [ConnectOfflineFirstWithSupabase] with all fields set to their default
/// values.
static const defaults = ConnectOfflineFirstWithSupabase(
sqliteConfig: SqliteSerializable.defaults,
supabaseConfig: SupabaseSerializable.defaults,
);

/// Returns a new [ConnectOfflineFirstWithSupabase] instance with fields equal to the
/// corresponding values in `this`, if not `null`.
///
/// Otherwise, the returned value has the default value as defined in
/// [defaults].
ConnectOfflineFirstWithSupabase withDefaults() => ConnectOfflineFirstWithSupabase(
sqliteConfig: sqliteConfig ?? defaults.sqliteConfig,
supabaseConfig: supabaseConfig ?? defaults.supabaseConfig,
);
}
19 changes: 19 additions & 0 deletions packages/brick_offline_first_with_supabase_abstract/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: brick_offline_first_with_supabase_abstract
description: A Brick domain that routes data fetching through local providers
before a Supabase provider.
homepage: https://github.com/GetDutchie/brick/tree/main/packages/brick_offline_first_with_supabase_abstract
issue_tracker: https://github.com/GetDutchie/brick/issues
repository: https://github.com/GetDutchie/brick

version: 0.0.1

environment:
sdk: ">=2.18.0 <4.0.0"

dependencies:
brick_supabase_abstract: ">=0.0.1 <1.0.0"
brick_sqlite: ">=3.0.0 <4.0.0"

dev_dependencies:
lints: ^2.0.1
test: ^1.16.5
5 changes: 5 additions & 0 deletions packages/brick_supabase_abstract/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## Unreleased

### 0.0.1

Initial
21 changes: 21 additions & 0 deletions packages/brick_supabase_abstract/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) Green Bits, Inc. and its affiliates.

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 supabaseriction, 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.
Loading

0 comments on commit 5461db3

Please sign in to comment.