brick_supabase 2.0.0 copy "brick_supabase: ^2.0.0" to clipboard
brick_supabase: ^2.0.0 copied to clipboard

Supabase connector for Brick, a data persistence library. Includes annotations, adapter, model, and provider.

brick_supabase workflow

Brick Supabase #

Connecting Brick with Supabase.

Supported Query Configuration #

where: #

Brick currently does not support all of Supabase's filtering methods. Consider the associated Compare enum value to Supabase's method when building a Brick query:

Brick Supabase
Compare.exact .eq
Compare.notEqual .neq
Compare.contains .like
Compare.doesNotContain .not.like
Compare.greaterThan .gt
Compare.greaterThanOrEqualTo .gte
Compare.lessThen .lt
Compare.lessThenOrEqualTo .lte
Compare.between .adj

Models #

@SupabaseSerializable(tableName:) #

The Supabase table name must be specified to connect from, upsert and delete invocations:

@SupabaseSerializable(tableName: 'users')
class User
copied to clipboard

@SupabaseSerializable(fieldRename:) #

By default, Brick assumes the Dart field name is the camelized version of the Supabase column name (i.e. final String lastName => 'last_name'). However, this can be changed to rename all fields.

@SupabaseSerializable(fieldRename: FieldRename.pascal)
class User
copied to clipboard

fieldRename is only the default transformation. Naming can be overriden on a field-by-field basis with @Supabase(name:).

@SupabaseSerializable(defaultToNull:) #

Forwards to Supabase's defaultToNull during upsert operations.

@SupabaseSerializable(ignoreDuplicates:) #

Forwards to Supabase's ignoreDuplicates during upsert operations.

@SupabaseSerializable(onConflict:) #

Forwards to Supabase's onConflict during upsert operations.

Fields #

@Supabase(unique:) #

Connect Supabase's primary key (or any other index) to your application code. This is useful for upsert and delete logic when mutating instances.

@Supabase(unique: true, name: 'uuid')
final String supabaseUuid;
copied to clipboard

@Supabase(enumAsString:) #

Brick by default assumes enums from a Supabase API will be delivered as integers matching the index in the Flutter app. However, if your API delivers strings instead, the field can be easily annotated without writing a custom generator.

Given the API:

{ "user": { "hats": ["bowler", "birthday"] } }
copied to clipboard

Simply convert hats into a Dart enum:

enum Hat { baseball, bowler, birthday }

...

@Supabase(enumAsString: true)
final List<Hat> hats;
copied to clipboard

@Supabase(name:) #

Supabase keys can be renamed per field. This will override the default set by SupabaseSerializable#fieldRename.

// "full_name" is used in from and to requests to Supabase instead of "last_name"
@Supabase(name: "full_name")
final String lastName;
copied to clipboard

Do not use name when annotating an association. Instead, use foreignKey.

💡 By default, Brick renames fields to be snake case when translating to Supabase, but you can change this default in the @SupabaseSerializable(fieldRename:) annotation that decorates models.

@Supabase(foreignKey:) #

When the annotated field type extends the model's type, the Supabase column should be a foreign key.

class User extends OfflineFirstWithSupabaseModel{
  // The foreign key is a relation to the `id` column of the Address table
  @Supabase(foreignKey: 'address_id')
  final Address address;
}

class Address extends OfflineFirstWithSupabaseModel{
  final String id;
}
copied to clipboard

💡 The remote column type can be different than the local Dart type for associations. For example, @Supabase(name: 'user_id') that annotates final User user can be a Postgres string type.

@Supabase(ignoreFrom:) and @Supabase(ignoreTo:) #

When true, the field will be ignored by the (de)serializing function in the adapter.

Testing #

Mocking a Supabase Instance #

Quickly create a convenient mock server within test groups. The server should be configured to reset after every test block. Strongly-typed Dart models can be used to protect against code drift.

import 'package:brick_supabase/testing.dart';
import 'package:test/test.dart'

void main() {
  // Pass an instance of your model dictionary to the mock server.
  // This permits quick generation of fields and generated responses
  final mock = SupabaseMockServer(modelDictionary: supabaseModelDictionary);

  group('MyClass', () {
    setUp(mock.setUp);

    tearDown(mock.tearDown);

    test('#myMethod', () async {
      // If your request won't exactly match the columns of MyModel, provide
      // the query list to the `fields:` parameter
      final req = SupabaseRequest<MyModel>();
      final resp = SupabaseResponse([
        // mock.serialize converts models to expected Supabase payloads
        // but you don't need to use it - any jsonEncode-able object
        // can be passed to SupabaseRepsonse
        await mock.serialize(MyModel(name: 'Demo 1', id: '1')),
        await mock.serialize(MyModel(name: 'Demo 2', id: '2')),
      ]);
      // This method stubs the server based on the described requests
      // and their matched responses
      mock.handle({req: resp});
      final provider = SupabaseProvider(mock.client, modelDictionary: supabaseModelDictionary);
      final retrieved = await provider.get<MyModel>();
      expect(retrieved, hasLength(2));
    });
  });
}
copied to clipboard

SupabaseRequest #

The request object can be much more detailed. A type argument (e.g. <MyModel>) is not necessary if fields: are passed as a parameter.

It's important to specify the filter parameter for more complex queries or nested association upserts:

final upsertReq = SupabaseRequest<MyModel>(
  requestMethod: 'POST',
  // Filter will specify to only return the response if the filter also matches
  // This is an important parameter when querying for a specific property
  // or using multiple requests/responses
  filter: 'id=eq.2',
  limit: 1,
);
final associationUpsertReq = SupabaseRequest<AssociationModel>(
  requestMethod: 'POST',
  filter: 'id=eq.1',
  limit: 1,
);
final baseResp = SupabaseResponse(await mock.serialize(MyModel(age: 1, name: 'Demo 1', id: '1')));
final associationResp = SupabaseResponse(
  await mock.serialize(AssociationModel(
    assoc: MyModel(age: 1, name: 'Nested', id: '2'),
    name: 'Demo 1',
    id: '1',
  )),
);
mock.handle({upsertReq: baseResp, associationUpsertReq: associationResp});
copied to clipboard

?> See supabase_provider_test.dart for more practial examples that use all SupabaseProvider methods, or offline_first_with_supabase_repository.dart for mocking with a repository.

Unsupported Field Types #

The following are not serialized to Supabase. 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
10
likes
150
points
2.16k
downloads

Publisher

unverified uploader

Weekly Downloads

2024.09.05 - 2025.03.20

Supabase connector for Brick, a data persistence library. Includes annotations, adapter, model, and provider.

Homepage
Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

brick_core, collection, logging, meta, supabase

More

Packages that depend on brick_supabase