GetDutchie / brick

An intuitive way to work with persistent data in Dart

Home Page:https://getdutchie.github.io/brick/#/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

**Migrating to Brick 3 from Brick 2**

tshedor opened this issue · comments

Brick v3 has arrived. This release focuses on bringing GraphQL parity to REST. It also consolidates the usage of providerArgs and expands the latest features in Dart, like enhanced enums and the latest analyzer.

BrickOfflineFirstWithRest now has a subscribe method. Invoking this method will return a stream populated as events hit the local SQLite database. For example, when a user is upserted, all repository.subscribe<User>() listeners will receive the copy of that user.

Additionally, the RestSerializable configuration has been overhauled. endpoint has been replaced by requestTransformer. This will permit even more granular control over individual requests and code analysis to catch errors ahead of runtime.

Abstract packages have been removed since Sqflite has abstracted its Flutter dependency to a "common" API. brick_offline_first_with_graphql_abstract, brick_offline_first_with_rest_abstract, brick_sqlite_abstract, and brick_offline_first_abstract will remain on pub.dev since publishing is forever. While this change is internal, parent packages no longer export the contents of child packages. Some adjustments may need to be made.

And much less interesting but extremely helpful for local dev: melos has been added to simplify local package development. All commits following v3 will use conventional commits to simplify release and changelog management.

As always, the migration guide and scripts are below:

Breaking Changes

  • Primary package files are renamed in line with pub.dev standards.
    for FILE in $(find "lib" -type f -name "*.dart"); do
      sed -i '' 's/package:brick_offline_first\/offline_first.dart/package:brick_offline_first\/brick_offline_first.dart/g' $FILE
      sed -i '' 's/package:brick_offline_first_with_rest\/offline_first_with_rest.dart/package:brick_offline_first_with_rest\/brick_offline_first_with_rest.dart/g' $FILE
      sed -i '' 's/package:brick_offline_first_with_graphql\/offline_first_with_graphql.dart/package:brick_offline_first_with_graphql\/brick_offline_first_with_graphql.dart/g' $FILE
      sed -i '' 's/package:brick_rest\/rest.dart/package:brick_rest\/brick_rest.dart/g' $FILE
      sed -i '' 's/package:brick_sqlite\/sqlite.dart/package:brick_sqlite\/brick_sqlite.dart/g' $FILE
      sed -i '' 's/package:brick_graphql\/graphql.dart/package:brick_graphql\/brick_graphql.dart/g' $FILE
    done
    • brick_offline_first/offline_first.dart is now brick_offline_first/brick_offline_first.dart
    • brick_offline_first_with_rest/offline_first_with_rest.dart is now brick_offline_first_with_rest/brick_offline_first_with_rest.dart
    • brick_offline_first_with_graphql/brick_offline_first_with_graphql.dart is now brick_offline_first_with_graphql/brick_offline_first_with_graphql.dart
    • brick_graphql/graphql.dart is now brick_rest/brick_graphql.dart
    • brick_rest/rest.dart is now brick_rest/brick_rest.dart
    • brick_sqlite/sqlite.dart is now brick_sqlite/brick_sqlite.dart
  • brick_sqlite_abstract/db.dart is now brick_sqlite/db.dart. brick_sqlite_abstract/sqlite_model.dart and brick_sqlite_abstract/annotations.dart are now exported by brick_sqlite/brick_sqlite.dart
    for FILE in $(find "lib" -type f -name "*.dart"); do
      sed -i '' 's/package:brick_sqlite_abstract\/annotations.dart/package:brick_sqlite\/brick_sqlite.dart/g' $FILE
      sed -i '' 's/package:brick_sqlite_abstract\/sqlite_model.dart/package:brick_sqlite\/brick_sqlite.dart/g' $FILE
      sed -i '' 's/package:brick_sqlite_abstract\/db.dart/package:brick_sqlite\/db.dart/g' $FILE
    done
  • The minimum Dart version has been increased to 2.18
  • providerArgs in Brick Rest have changed: 'topLevelKey' and 'headers' and 'supplementalTopLevelData' have been removed (use 'request') and 'request' now accepts a RestRequest instead of the HTTP method string.
  • providerArgs in Brick Graphql have changed: 'document' and 'variables' have been removed. Instead, use 'operation'.
  • analyzer is now >= 5

Brick Offline First with Graphql

  • FieldRename, Graphql GraphqlProvider, and GraphqlSerializable are no longer exported by offline_first_with_graphql.dart. Instead, import these file from package:brick_graphql/brick_graphql.dart

Brick Offline First with Rest

  • FieldRename, Rest, RestProvider, and RestSerializable are no longer exported by offline_first_with_rest.dart. Instead, import these file from package:brick_rest/brick_rest.dart
  • OfflineFirstWithRestRepository#reattemptForStatusCodes has been removed from instance-level access. The constructor argument forwards to the RestOfflineQueueClient, where it can be accessed if needed.
  • OfflineFirstWithRestRepository#throwTunnerNotFoundExceptions has been removed. This value was duplicated from offlineQueueManager; the queue manager is where the property exclusively lives now.

Improvements

  • Listen for SQLite changes via OfflineFirstWithRestRepository#subscribe

Brick Graphql

This breaking change migration is less automatable. The script below is a best attempt and should be manually confirmed after running.

for FILE in $(find "lib" -type f -name "*.dart"); do
  # `sed` regex capture may work for you; it didn't on Mac for me
  # sed -i '' "s/\'document\': (.*)/\'operation\': GraphqlOperation\(document: \1\) \/\/ TODO verify migration to GraphqlOperation /g" $FILE
  perl -0777 -i -pe "s/'document': (.*)/'operation': GraphqlOperation\(document: \1\) \/\/ TODO verify migration to GraphqlOperation /igs" $FILE
  # sed -i '' "s/\'variables\': (.*)/\'operation\': GraphqlOperation\(variables: \1\) \/\/ TODO verify migration to GraphqlOperation /g" $FILE
  perl -0777 -i -pe "s/'variables': (.*)/'operation': GraphqlOperation\(variables: \1\) \/\/ TODO verify migration to GraphqlOperation /igs" $FILE
done

providerArgs['document']

This has been consolidated to 'operation'. For example: providerArgs: { 'operation': GraphqlOperation(document: r'''mutation UpdateUser(id: ....)''')}.

providerArgs['variables']

This has been consolidated to 'operation'. For example: providerArgs: { 'operation': GraphqlOperation(variables: {'id': '1'}) }.

Brick Rest

This breaking change migration is less automatable. The script below is a best attempt and should be manually confirmed after running.

for FILE in $(find "lib" -type f -name "*.dart"); do
  # `sed` regex capture may work for you; it didn't on Mac for me
  # sed -i '' "s/\'request\': (.*)/\'request\': RestRequest\(method: \1\) \/\/ TODO verify migration to RestRequest /g" $FILE
  perl -0777 -i -pe "s/'request': (.*)/'request': RestRequest\(method: \1\) \/\/ TODO verify migration to RestRequest /igs" $FILE
  # sed -i '' "s/\'headers\': (.*)/\'request\': RestRequest\(headers: \1\) \/\/ TODO verify migration to RestRequest /g" $FILE
  perl -0777 -i -pe "s/'headers': (.*)/'request': RestRequest\(headers: \1\) \/\/ TODO verify migration to RestRequest /igs" $FILE
  # sed -i '' "s/\'topLevelKey\': (.*)/\'request\': RestRequest\(topLevelKey: \1\) \/\/ TODO verify migration to RestRequest /g" $FILE
  perl -0777 -i -pe "s/'topLevelKey': (.*)/'request': RestRequest\(topLevelKey: \1\) \/\/ TODO verify migration to RestRequest /igs" $FILE
  # sed -i '' "s/\'supplementalTopLevelData\': (.*)/\'request\': RestRequest\(supplementalTopLevelData: \1\) \/\/ TODO verify migration to RestRequest /g" $FILE
  perl -0777 -i -pe "s/'supplementalTopLevelData': (.*)/'request': RestRequest\(supplementalTopLevelData: \1\) \/\/ TODO verify migration to RestRequest /igs" $FILE
done

providerArgs['request']

This key now accepts a RestRequest class instead of an HTTP method name.

providerArgs['headers']

This has been consolidated to 'request'. For example: providerArgs: { 'request': RestRequest(headers: {'Authorization': 'Bearer'})}.

providerArgs['topLevelKey']

This has been consolidated to 'request'. For example: providerArgs: { 'request': RestRequest(topLevelKey: 'myKey' )}.

providerArgs['supplementalTopLevelData']

This has been consolidated to 'request'. For example: providerArgs: { 'request': RestRequest(supplementalTopLevelData: {'myKey': {'myData': 1}}) }.

RestSerializable(requestTransformer:)

  • RestSerializable's fromKey and toKey have been consolidated to RestRequest(topLevelKey:)
  • RestSerializable(endpoint:) has been replaced in this release by RestSerializable(requestTransformer:). It will be painful to upgrade though with good reason.
  1. Strongly-typed classes. endpoint was a string, which removed analysis in IDEs, permitting errors to escape during runtime. With endpoints as classes, Query and instance objects will receive type hinting.
  2. Fine control over REST requests. Define on a request-level basis what key to pull from or push to. Declare specific HTTP methods like PATCH in a class that manages request instead of in distributed providerArgs.
  3. Future-proof development. Enhancing REST's configuration will be on a class object instead of in untyped string keys on providerArgs. The REST interface is consolidated to this subclass.

Since all APIs are different, and endpoint used stringified code, the migration cannot be scripted for all users. Instead, examples are provided below to illustrate how to refactor from Brick 2's endpoint to Brick 3's requestTransformer. Some examples:

// BEFORE
@ConnectOfflineFirstWithRest(
  restConfig: RestSerializable(
    endpoint: '"/users";'
    fromKey: 'users',
  )
)

// AFTER
class UserRequestTransformer extends RestRequestTransformer {
  final get = const RestRequest(url: '/users', topLevelKey: 'users');
  const UserRequestTransformer(Query? query, RestModel? instance) : super(query, instance);
}
@ConnectOfflineFirstWithRest(
  restConfig: RestSerializable(
    requestTransformer: UserRequestTransformer.new,
  )
)

Some cases are more complex:

// BEFORE
@ConnectOfflineFirstWithRest(
  restConfig: RestSerializable(
    endpoint: r'''{
      if (query?.action == QueryAction.delete) return "/users/${instance.id}";

      if (query?.action == QueryAction.get &&
          query?.providerArgs.isNotEmpty &&
          query?.providerArgs['limit'] != null) {
            return "/users?limit=${query.providerArgs['limit']}";
      }

      return "/users";
    }''';
  )
)

// AFTER
class UserRequestTransformer extends RestRequestTransformer {
  RestRequest? get get {
    if (query?.providerArgs.isNotEmpty && query.providerArgs['limit'] != null) {
      return RestRequest(url: "/users?limit=${query.providerArgs['limit']}");
    }
    const RestRequest(url: '/users');
  }

  final delete = RestRequest(url: '/users/${instance.id}');

  const UserRequestTransformer(Query? query, RestModel? instance) : super(query, instance);
}

@ConnectOfflineFirstWithRest(
  restConfig: RestSerializable(
    requestTransformer: UserRequestTransformer.new,
  )
)

💡 For ease of illustration, the code is provided as if the transformer and model logic live in the same file. It's strongly recommended to include the request transformer logic in its own, colocated file (such as user.model.request.dart).