diff --git a/docs/_sidebar.md b/docs/_sidebar.md index ab4bf387..58328069 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -30,6 +30,7 @@ - [Model Config](offline_first/models.md) - [Field Config](offline_first/fields.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) - [With Rest](offline_first/offline_first_with_rest_repository.md) - [Offline Queue](offline_first/offline_queue.md) diff --git a/docs/offline_first/offline_first_with_supabase_repository.md b/docs/offline_first/offline_first_with_supabase_repository.md index 7a5d7f87..bcb1399e 100644 --- a/docs/offline_first/offline_first_with_supabase_repository.md +++ b/docs/offline_first/offline_first_with_supabase_repository.md @@ -138,10 +138,14 @@ Field types of classes that `extends OfflineFirstWithSupabaseModel` will automat ```dart class User extends OfflineFirstWithSupabaseModel { // The foreign key is a relation to the `id` column of the Address table - @Supabase(name: 'address_id') - // Help the SQLite provider connect the association locally to the one provided from remote - @OfflineFirst(where: {'id': "data['address']['id']"}) + @Supabase(foreignKey: 'address_id') final Address address; + + // If the association will be created by the app, specify + // a field that maps directly to the foreign key column + // so that Brick can notify Supabase of the association. + @Sqlite(ignore: true) + String get addressId => address.id; } class Address extends OfflineFirstWithSupabaseModel{ diff --git a/docs/offline_first/testing.md b/docs/offline_first/testing.md index 0469f6a4..34088b4a 100644 --- a/docs/offline_first/testing.md +++ b/docs/offline_first/testing.md @@ -89,4 +89,4 @@ setUpAll() async { ## OfflineFirstWithSupabase -See [Supabase Testing](../supabase/testing.md) +See [Supabase Testing](https://getdutchie.github.io/brick/#/supabase/testing) diff --git a/docs/supabase/fields.md b/docs/supabase/fields.md index 169339a5..343028f6 100644 --- a/docs/supabase/fields.md +++ b/docs/supabase/fields.md @@ -31,12 +31,15 @@ final String lastName; ?> 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](models.md). -When the annotated field type extends the model's type, the Supabase column should be a foreign key. +!> **Do not use** `name` when annotating an association. Instead, use `foreignKey`. + +### `@Supabase(foreignKey:)` + +When the annotated field references a `OfflineFirstWithSupabaseModel`, a foreign key can be specified. Supabase's PostgREST API can usually determine the association without specifying the foreign key. However, [when multiple foreign keys exist](https://supabase.com/docs/guides/database/joins-and-nesting?queryGroups=language&language=dart#specifying-the-on-clause-for-joins-with-multiple-foreign-keys) to the same table, guiding Brick to use the right foreign key is required. ```dart class User extends OfflineFirstWithSupabaseModel{ - // The foreign key is a relation to the `id` column of the Address table - @Supabase(name: 'address_id') + @Supabase(foreignKey: 'address_id') final Address address; } diff --git a/example_supabase/.gitignore b/example_supabase/.gitignore new file mode 100644 index 00000000..43b6bf66 --- /dev/null +++ b/example_supabase/.gitignore @@ -0,0 +1,49 @@ +# 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/ +.flutterplugins +.flutterpluginsdependencies +.pubcache/ +.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 +windows/* +web/* +macos/* +linux/* +android/* +ios/* diff --git a/example_supabase/.metadata b/example_supabase/.metadata new file mode 100644 index 00000000..ac711aae --- /dev/null +++ b/example_supabase/.metadata @@ -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: "2663184aa79047d0a33a14a3b607954f8fdd8730" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + - platform: android + create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + - platform: ios + create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + - platform: linux + create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + - platform: macos + create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + - platform: web + create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + - platform: windows + create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + + # 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" diff --git a/example_supabase/README.md b/example_supabase/README.md index dd82d02a..e9617d70 100644 --- a/example_supabase/README.md +++ b/example_supabase/README.md @@ -1,35 +1,39 @@ # Brick with Supabase Example -This minimal example demonstrates how to use Brick with [Supabase](https://supabase.com/). Follow the instructions below to get started. - -## Setting up the Supabase project - -1. **Create the table**: Run the following SQL command in your Supabase SQL editor to create the customers table: +1. **Create the table**: Run the following SQL command in your Supabase SQL editor to create the customers and pizzas table: ```sql CREATE TABLE customers ( - id UUID PRIMARY KEY, - first_name text NOT NULL, - last_name text NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP + id UUID PRIMARY KEY, + first_name text NOT NULL, + last_name text NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE pizzas ( + id UUID PRIMARY KEY, + frozen boolean NOT NULL DEFAULT false, + customer_id UUID NOT NULL ); + +ALTER TABLE pizzas ADD CONSTRAINT fk_customer FOREIGN KEY (customer_id) REFERENCES customers (id); ``` -2. **Insert Dummy Data**: Insert some dummy data into the customers table by running the following SQL command +2. **Insert Dummy Data**: Insert some dummy data: ```sql INSERT INTO customers (id, first_name, last_name, created_at) VALUES - ('a8098c1a-f86e-11da-bd1a-00112444be1e', 'Bruce', 'Fortner', NOW()), - ('b8098c1a-f86e-11da-bd1a-00112444be1e', 'Jane', 'Smith', NOW()), - ('c8098c1a-f86e-11da-bd1a-00112444be1e', 'Alice', 'Johnson', NOW()); + ('a8098c1a-f86e-11da-bd1a-00112444be1e', 'Bruce', 'Fortner', NOW()), + ('b8098c1a-f86e-11da-bd1a-00112444be1e', 'Jane', 'Smith', NOW()), + ('c8098c1a-f86e-11da-bd1a-00112444be1e', 'Alice', 'Johnson', NOW()); +INSERT INTO pizzas (id, frozen, customer_id) VALUES + ('d8098c1a-f86e-11da-bd1a-00112444be1e', TRUE, 'a8098c1a-f86e-11da-bd1a-00112444be1e'), + ('e8098c1a-f86e-11da-bd1a-00112444be1e', FALSE, 'b8098c1a-f86e-11da-bd1a-00112444be1e'), + ('f8098c1a-f86e-11da-bd1a-00112444be1e', TRUE, 'c8098c1a-f86e-11da-bd1a-00112444be1e'); ``` 3. **Enable Anonymous Sign-Ins**: Go to your Supabase dashboard, navigate to Settings > Authentication > User Signups, and enable anonymous sign-ins. -## Setting up the Flutter example project - 4. **Update Variables**: Update `main.dart` with your Supabase project URL and anonymous key. You can find these values in the Supabase dashboard under Settings > API. -## Running the Flutter app - 5. **Run the Flutter Project**: This example supports iOS and Android. Make sure run `flutter create .` first. diff --git a/example_supabase/lib/brick/adapters/customer_adapter.g.dart b/example_supabase/lib/brick/adapters/customer_adapter.g.dart index d5141cd0..04dbecd0 100644 --- a/example_supabase/lib/brick/adapters/customer_adapter.g.dart +++ b/example_supabase/lib/brick/adapters/customer_adapter.g.dart @@ -4,47 +4,22 @@ part of '../brick.g.dart'; Future _$CustomerFromSupabase(Map data, {required SupabaseProvider provider, OfflineFirstWithSupabaseRepository? repository}) async { return Customer( - id: data['id'] as int?, + id: data['id'] as String, firstName: data['first_name'] as String?, - lastName: data['last_name'] as String?, - pizzas: await Future.wait(data['pizzas'] - ?.map( - (d) => PizzaAdapter().fromSupabase(d, provider: provider, repository: repository)) - .toList() - .cast>() ?? - [])); + lastName: data['last_name'] as String?); } Future> _$CustomerToSupabase(Customer instance, {required SupabaseProvider provider, OfflineFirstWithSupabaseRepository? repository}) async { - return { - 'id': instance.id, - 'first_name': instance.firstName, - 'last_name': instance.lastName, - 'pizzas': await Future.wait>(instance.pizzas - .map((s) => PizzaAdapter().toSupabase(s, provider: provider, repository: repository)) - .toList()) - }; + return {'id': instance.id, 'first_name': instance.firstName, 'last_name': instance.lastName}; } Future _$CustomerFromSqlite(Map data, {required SqliteProvider provider, OfflineFirstWithSupabaseRepository? repository}) async { return Customer( - id: data['id'] == null ? null : data['id'] as int?, + id: data['id'] as String, firstName: data['first_name'] == null ? null : data['first_name'] as String?, - lastName: data['last_name'] == null ? null : data['last_name'] as String?, - pizzas: (await provider.rawQuery( - 'SELECT DISTINCT `f_Pizza_brick_id` FROM `_brick_Customer_pizzas` WHERE l_Customer_brick_id = ?', - [data['_brick_id'] as int]).then((results) { - final ids = results.map((r) => r['f_Pizza_brick_id']); - return Future.wait(ids.map((primaryKey) => repository! - .getAssociation( - Query.where('primaryKey', primaryKey, limit1: true), - ) - .then((r) => r!.first))); - })) - .toList() - .cast()) + lastName: data['last_name'] == null ? null : data['last_name'] as String?) ..primaryKey = data['_brick_id'] as int; } @@ -74,12 +49,6 @@ class CustomerAdapter extends OfflineFirstWithSupabaseAdapter { 'lastName': const RuntimeSupabaseColumnDefinition( association: false, columnName: 'last_name', - ), - 'pizzas': const RuntimeSupabaseColumnDefinition( - association: true, - columnName: 'pizzas', - associationType: Pizza, - associationIsNullable: false, ) }; @override @@ -98,7 +67,7 @@ class CustomerAdapter extends OfflineFirstWithSupabaseAdapter { association: false, columnName: 'id', iterable: false, - type: int, + type: String, ), 'firstName': const RuntimeSqliteColumnDefinition( association: false, @@ -111,12 +80,6 @@ class CustomerAdapter extends OfflineFirstWithSupabaseAdapter { columnName: 'last_name', iterable: false, type: String, - ), - 'pizzas': const RuntimeSqliteColumnDefinition( - association: true, - columnName: 'pizzas', - iterable: true, - type: Pizza, ) }; @override @@ -134,30 +97,6 @@ class CustomerAdapter extends OfflineFirstWithSupabaseAdapter { @override final String tableName = 'Customer'; - @override - Future afterSave(instance, {required provider, repository}) async { - if (instance.primaryKey != null) { - final pizzasOldColumns = await provider.rawQuery( - 'SELECT `f_Pizza_brick_id` FROM `_brick_Customer_pizzas` WHERE `l_Customer_brick_id` = ?', - [instance.primaryKey]); - final pizzasOldIds = pizzasOldColumns.map((a) => a['f_Pizza_brick_id']); - final pizzasNewIds = instance.pizzas.map((s) => s.primaryKey).whereType(); - final pizzasIdsToDelete = pizzasOldIds.where((id) => !pizzasNewIds.contains(id)); - - await Future.wait(pizzasIdsToDelete.map((id) async { - return await provider.rawExecute( - 'DELETE FROM `_brick_Customer_pizzas` WHERE `l_Customer_brick_id` = ? AND `f_Pizza_brick_id` = ?', - [instance.primaryKey, id]).catchError((e) => null); - })); - - await Future.wait(instance.pizzas.map((s) async { - final id = s.primaryKey ?? await provider.upsert(s, repository: repository); - return await provider.rawInsert( - 'INSERT OR IGNORE INTO `_brick_Customer_pizzas` (`l_Customer_brick_id`, `f_Pizza_brick_id`) VALUES (?, ?)', - [instance.primaryKey, id]); - })); - } - } @override Future fromSupabase(Map input, diff --git a/example_supabase/lib/brick/adapters/pizza_adapter.g.dart b/example_supabase/lib/brick/adapters/pizza_adapter.g.dart index 6746a7d3..9f10071e 100644 --- a/example_supabase/lib/brick/adapters/pizza_adapter.g.dart +++ b/example_supabase/lib/brick/adapters/pizza_adapter.g.dart @@ -4,31 +4,32 @@ part of '../brick.g.dart'; Future _$PizzaFromSupabase(Map data, {required SupabaseProvider provider, OfflineFirstWithSupabaseRepository? repository}) async { return Pizza( - id: data['id'] as int, - toppings: - data['toppings'].whereType().map(Topping.values.byName).toList().cast(), - frozen: data['frozen'] as bool); + id: data['id'] as String, + frozen: data['frozen'] as bool, + customer: await CustomerAdapter() + .fromSupabase(data['customer'], provider: provider, repository: repository)); } Future> _$PizzaToSupabase(Pizza instance, {required SupabaseProvider provider, OfflineFirstWithSupabaseRepository? repository}) async { return { 'id': instance.id, - 'toppings': instance.toppings.map((e) => e.name).toList(), - 'frozen': instance.frozen + 'frozen': instance.frozen, + 'customer': await CustomerAdapter() + .toSupabase(instance.customer, provider: provider, repository: repository), + 'customer_id': instance.customerId }; } Future _$PizzaFromSqlite(Map data, {required SqliteProvider provider, OfflineFirstWithSupabaseRepository? repository}) async { return Pizza( - id: data['id'] as int, - toppings: jsonDecode(data['toppings']) - .map((d) => d as int > -1 ? Topping.values[d] : null) - .whereType() - .toList() - .cast(), - frozen: data['frozen'] == 1) + id: data['id'] as String, + frozen: data['frozen'] == 1, + customer: (await repository!.getAssociation( + Query.where('primaryKey', data['customer_Customer_brick_id'] as int, limit1: true), + ))! + .first) ..primaryKey = data['_brick_id'] as int; } @@ -36,8 +37,9 @@ Future> _$PizzaToSqlite(Pizza instance, {required SqliteProvider provider, OfflineFirstWithSupabaseRepository? repository}) async { return { 'id': instance.id, - 'toppings': jsonEncode(instance.toppings.map((s) => Topping.values.indexOf(s)).toList()), - 'frozen': instance.frozen ? 1 : 0 + 'frozen': instance.frozen ? 1 : 0, + 'customer_Customer_brick_id': instance.customer.primaryKey ?? + await provider.upsert(instance.customer, repository: repository) }; } @@ -55,13 +57,20 @@ class PizzaAdapter extends OfflineFirstWithSupabaseAdapter { association: false, columnName: 'id', ), - 'toppings': const RuntimeSupabaseColumnDefinition( - association: false, - columnName: 'toppings', - ), 'frozen': const RuntimeSupabaseColumnDefinition( association: false, columnName: 'frozen', + ), + 'customer': const RuntimeSupabaseColumnDefinition( + association: true, + columnName: 'customer', + associationType: Customer, + associationIsNullable: false, + foreignKey: 'customer_id', + ), + 'customerId': const RuntimeSupabaseColumnDefinition( + association: false, + columnName: 'customer_id', ) }; @override @@ -80,19 +89,19 @@ class PizzaAdapter extends OfflineFirstWithSupabaseAdapter { association: false, columnName: 'id', iterable: false, - type: int, - ), - 'toppings': const RuntimeSqliteColumnDefinition( - association: false, - columnName: 'toppings', - iterable: true, - type: Topping, + type: String, ), 'frozen': const RuntimeSqliteColumnDefinition( association: false, columnName: 'frozen', iterable: false, type: bool, + ), + 'customer': const RuntimeSqliteColumnDefinition( + association: true, + columnName: 'customer_Customer_brick_id', + iterable: false, + type: Customer, ) }; @override diff --git a/example_supabase/lib/brick/brick.g.dart b/example_supabase/lib/brick/brick.g.dart index 6d5e2496..ea3f9f6b 100644 --- a/example_supabase/lib/brick/brick.g.dart +++ b/example_supabase/lib/brick/brick.g.dart @@ -9,7 +9,7 @@ import 'package:brick_sqlite/brick_sqlite.dart'; // ignore: unused_import, unused_shown_name, unnecessary_import import 'package:brick_supabase/brick_supabase.dart'; // ignore: unused_import, unused_shown_name, unnecessary_import -import 'package:pizza_shoppe/brick/models/pizza.model.dart'; // GENERATED CODE DO NOT EDIT +import 'package:pizza_shoppe/brick/models/customer.model.dart'; // GENERATED CODE DO NOT EDIT // ignore: unused_import import 'dart:convert'; import 'package:brick_sqlite/brick_sqlite.dart' diff --git a/example_supabase/lib/brick/db/20240906052847.migration.dart b/example_supabase/lib/brick/db/20240906052847.migration.dart deleted file mode 100644 index 0e8a9c0c..00000000 --- a/example_supabase/lib/brick/db/20240906052847.migration.dart +++ /dev/null @@ -1,74 +0,0 @@ -// GENERATED CODE EDIT WITH CAUTION -// THIS FILE **WILL NOT** BE REGENERATED -// This file should be version controlled and can be manually edited. -part of 'schema.g.dart'; - -// While migrations are intelligently created, the difference between some commands, such as -// DropTable vs. RenameTable, cannot be determined. For this reason, please review migrations after -// they are created to ensure the correct inference was made. - -// The migration version must **always** mirror the file name - -const List _migration_20240906052847_up = [ - InsertTable('_brick_Customer_pizzas'), - InsertTable('Customer'), - InsertTable('Pizza'), - InsertForeignKey( - '_brick_Customer_pizzas', - 'Customer', - foreignKeyColumn: 'l_Customer_brick_id', - onDeleteCascade: true, - onDeleteSetDefault: false, - ), - InsertForeignKey( - '_brick_Customer_pizzas', - 'Pizza', - foreignKeyColumn: 'f_Pizza_brick_id', - onDeleteCascade: true, - onDeleteSetDefault: false, - ), - InsertColumn('id', Column.integer, onTable: 'Customer', unique: true), - InsertColumn('first_name', Column.varchar, onTable: 'Customer'), - InsertColumn('last_name', Column.varchar, onTable: 'Customer'), - InsertColumn('id', Column.integer, onTable: 'Pizza', unique: true), - InsertColumn('toppings', Column.varchar, onTable: 'Pizza'), - InsertColumn('frozen', Column.boolean, onTable: 'Pizza'), - CreateIndex( - columns: ['l_Customer_brick_id', 'f_Pizza_brick_id'], - onTable: '_brick_Customer_pizzas', - unique: true, - ), -]; - -const List _migration_20240906052847_down = [ - DropTable('_brick_Customer_pizzas'), - DropTable('Customer'), - DropTable('Pizza'), - DropColumn('l_Customer_brick_id', onTable: '_brick_Customer_pizzas'), - DropColumn('f_Pizza_brick_id', onTable: '_brick_Customer_pizzas'), - DropColumn('id', onTable: 'Customer'), - DropColumn('first_name', onTable: 'Customer'), - DropColumn('last_name', onTable: 'Customer'), - DropColumn('id', onTable: 'Pizza'), - DropColumn('toppings', onTable: 'Pizza'), - DropColumn('frozen', onTable: 'Pizza'), - DropIndex('index__brick_Customer_pizzas_on_l_Customer_brick_id_f_Pizza_brick_id'), -]; - -// -// DO NOT EDIT BELOW THIS LINE -// - -@Migratable( - version: '20240906052847', - up: _migration_20240906052847_up, - down: _migration_20240906052847_down, -) -class Migration20240906052847 extends Migration { - const Migration20240906052847() - : super( - version: 20240906052847, - up: _migration_20240906052847_up, - down: _migration_20240906052847_down, - ); -} diff --git a/example_supabase/lib/brick/db/20240920063917.migration.dart b/example_supabase/lib/brick/db/20240920063917.migration.dart new file mode 100644 index 00000000..6e2aef80 --- /dev/null +++ b/example_supabase/lib/brick/db/20240920063917.migration.dart @@ -0,0 +1,56 @@ +// GENERATED CODE EDIT WITH CAUTION +// THIS FILE **WILL NOT** BE REGENERATED +// This file should be version controlled and can be manually edited. +part of 'schema.g.dart'; + +// While migrations are intelligently created, the difference between some commands, such as +// DropTable vs. RenameTable, cannot be determined. For this reason, please review migrations after +// they are created to ensure the correct inference was made. + +// The migration version must **always** mirror the file name + +const List _migration_20240920063917_up = [ + InsertTable('Customer'), + InsertTable('Pizza'), + InsertColumn('id', Column.varchar, onTable: 'Customer', unique: true), + InsertColumn('first_name', Column.varchar, onTable: 'Customer'), + InsertColumn('last_name', Column.varchar, onTable: 'Customer'), + InsertColumn('id', Column.varchar, onTable: 'Pizza', unique: true), + InsertColumn('frozen', Column.boolean, onTable: 'Pizza'), + InsertForeignKey( + 'Pizza', + 'Customer', + foreignKeyColumn: 'customer_Customer_brick_id', + onDeleteCascade: false, + onDeleteSetDefault: false, + ), +]; + +const List _migration_20240920063917_down = [ + DropTable('Customer'), + DropTable('Pizza'), + DropColumn('id', onTable: 'Customer'), + DropColumn('first_name', onTable: 'Customer'), + DropColumn('last_name', onTable: 'Customer'), + DropColumn('id', onTable: 'Pizza'), + DropColumn('frozen', onTable: 'Pizza'), + DropColumn('customer_Customer_brick_id', onTable: 'Pizza'), +]; + +// +// DO NOT EDIT BELOW THIS LINE +// + +@Migratable( + version: '20240920063917', + up: _migration_20240920063917_up, + down: _migration_20240920063917_down, +) +class Migration20240920063917 extends Migration { + const Migration20240920063917() + : super( + version: 20240920063917, + up: _migration_20240920063917_up, + down: _migration_20240920063917_down, + ); +} diff --git a/example_supabase/lib/brick/db/schema.g.dart b/example_supabase/lib/brick/db/schema.g.dart index 0509e747..4e51a98f 100644 --- a/example_supabase/lib/brick/db/schema.g.dart +++ b/example_supabase/lib/brick/db/schema.g.dart @@ -1,43 +1,29 @@ // GENERATED CODE DO NOT EDIT // This file should be version controlled import 'package:brick_sqlite/db.dart'; -part '20240906052847.migration.dart'; +part '20240920063917.migration.dart'; /// All intelligently-generated migrations from all `@Migratable` classes on disk -final migrations = { - const Migration20240906052847(), -}; +final migrations = {const Migration20240920063917()}; /// A consumable database structure including the latest generated migration. -final schema = Schema(20240906052847, generatorVersion: 1, tables: { - SchemaTable('_brick_Customer_pizzas', columns: { - SchemaColumn('_brick_id', Column.integer, - autoincrement: true, nullable: false, isPrimaryKey: true), - SchemaColumn('l_Customer_brick_id', Column.integer, - isForeignKey: true, - foreignTableName: 'Customer', - onDeleteCascade: true, - onDeleteSetDefault: false), - SchemaColumn('f_Pizza_brick_id', Column.integer, - isForeignKey: true, - foreignTableName: 'Pizza', - onDeleteCascade: true, - onDeleteSetDefault: false) - }, indices: { - SchemaIndex(columns: ['l_Customer_brick_id', 'f_Pizza_brick_id'], unique: true) - }), +final schema = Schema(20240920063917, generatorVersion: 1, tables: { SchemaTable('Customer', columns: { SchemaColumn('_brick_id', Column.integer, autoincrement: true, nullable: false, isPrimaryKey: true), - SchemaColumn('id', Column.integer, unique: true), + SchemaColumn('id', Column.varchar, unique: true), SchemaColumn('first_name', Column.varchar), SchemaColumn('last_name', Column.varchar) }, indices: {}), SchemaTable('Pizza', columns: { SchemaColumn('_brick_id', Column.integer, autoincrement: true, nullable: false, isPrimaryKey: true), - SchemaColumn('id', Column.integer, unique: true), - SchemaColumn('toppings', Column.varchar), - SchemaColumn('frozen', Column.boolean) + SchemaColumn('id', Column.varchar, unique: true), + SchemaColumn('frozen', Column.boolean), + SchemaColumn('customer_Customer_brick_id', Column.integer, + isForeignKey: true, + foreignTableName: 'Customer', + onDeleteCascade: false, + onDeleteSetDefault: false) }, indices: {}) }); diff --git a/example_supabase/lib/brick/models/customer.model.dart b/example_supabase/lib/brick/models/customer.model.dart index 1e6ad2db..aa659e7d 100644 --- a/example_supabase/lib/brick/models/customer.model.dart +++ b/example_supabase/lib/brick/models/customer.model.dart @@ -1,25 +1,21 @@ 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'; -import 'package:pizza_shoppe/brick/models/pizza.model.dart'; @ConnectOfflineFirstWithSupabase( supabaseConfig: SupabaseSerializable(), ) class Customer extends OfflineFirstWithSupabaseModel { @Sqlite(unique: true) - final int? id; + final String id; final String? firstName; final String? lastName; - final List pizzas; - Customer({ - this.id, + required this.id, this.firstName, this.lastName, - required this.pizzas, }); } diff --git a/example_supabase/lib/brick/models/pizza.model.dart b/example_supabase/lib/brick/models/pizza.model.dart index 7c9b86e6..4bae5382 100644 --- a/example_supabase/lib/brick/models/pizza.model.dart +++ b/example_supabase/lib/brick/models/pizza.model.dart @@ -1,6 +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'; +import 'package:pizza_shoppe/brick/models/customer.model.dart'; @ConnectOfflineFirstWithSupabase( supabaseConfig: SupabaseSerializable(), @@ -8,18 +9,23 @@ import 'package:brick_supabase/brick_supabase.dart'; class Pizza extends OfflineFirstWithSupabaseModel { /// Read more about `@Sqlite`: https://github.com/GetDutchie/brick/tree/main/packages/brick_sqlite#fields @Sqlite(unique: true) - final int id; - - /// Read more about `@Supabase`: https://github.com/GetDutchie/brick/tree/main/packages/brick_supabase#fields - @Supabase(enumAsString: true) - final List toppings; + final String id; final bool frozen; + @Supabase(foreignKey: 'customer_id') + final Customer customer; + + // If the association will be created by the app, specify + // a field that maps directly to the foreign key column + // so that Brick can notify Supabase of the association. + @Sqlite(ignore: true) + String get customerId => customer.id; + Pizza({ required this.id, - required this.toppings, required this.frozen, + required this.customer, }); } diff --git a/example_supabase/lib/main.dart b/example_supabase/lib/main.dart index 684051db..5083a00a 100644 --- a/example_supabase/lib/main.dart +++ b/example_supabase/lib/main.dart @@ -1,11 +1,18 @@ import 'package:flutter/material.dart'; -import 'package:pizza_shoppe/brick/models/customer.model.dart'; +import 'package:pizza_shoppe/brick/models/pizza.model.dart'; import 'package:pizza_shoppe/brick/repository.dart'; const supabaseUrl = 'YOUR_SUPABASE_URL'; const supabaseAnonKey = 'YOUR_SUPABASE_ANON_KEY'; -void main() => runApp(MyApp()); +Future main() async { + await Repository.initializeSupabaseAndConfigure( + supabaseUrl: supabaseUrl, + supabaseAnonKey: supabaseAnonKey, + ); + await Repository().initialize(); + runApp(MyApp()); +} class MyApp extends StatelessWidget { @override @@ -23,82 +30,49 @@ class MyApp extends StatelessWidget { } } -class MyHomePage extends StatefulWidget { - MyHomePage({super.key, required this.title}); - +class MyHomePage extends StatelessWidget { final String title; - @override - _MyHomePageState createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - var migrated = false; - @override - Future initState() async { - await Repository.initializeSupabaseAndConfigure( - supabaseUrl: supabaseUrl, - supabaseAnonKey: supabaseAnonKey, - ); - await Repository().initialize(); - // Note that subsequent boots of the app will use cached data - // To clear this, wipe data on android or tap-press on iOS and delete the app - setState(() => migrated = true); - - super.initState(); - } + MyHomePage({super.key, required this.title}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text(widget.title), + title: Text(title), ), - body: migrated - ? Container( - padding: const EdgeInsets.all(20.0), - child: FutureBuilder( - future: Repository().get(), - builder: (context, AsyncSnapshot> customerList) { - final customers = customerList.data; + body: Container( + padding: const EdgeInsets.all(20.0), + child: FutureBuilder( + future: Repository().get(), + builder: (context, AsyncSnapshot> pizzaList) { + final pizzas = pizzaList.data; - return ListView.builder( - itemCount: customers?.length ?? 0, - itemBuilder: (ctx, index) => - customers?[index] == null ? Container() : CustomerTile(customers![index]), - ); - }, - ), - ) - : Text('Migrating database...'), + return ListView.builder( + itemCount: pizzas?.length ?? 0, + itemBuilder: (ctx, index) => + pizzas?[index] == null ? Container() : PizzaTile(pizzas![index]), + ); + }, + ), + ), ); } } -class CustomerTile extends StatelessWidget { - final Customer customer; +class PizzaTile extends StatelessWidget { + final Pizza pizza; - CustomerTile(this.customer); + PizzaTile(this.pizza); @override Widget build(BuildContext context) { return Column( mainAxisAlignment: MainAxisAlignment.start, children: [ - Text('id: ${customer.id}'), - Text('name: ${customer.firstName} ${customer.lastName}'), - Text('pizzas:'), - if (customer.pizzas != null) - Padding( - padding: const EdgeInsets.only(left: 20.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - for (var pizza in customer.pizzas!) - Text('id: ${pizza.id}\nfrozen: ${pizza.frozen}'), - ], - ), - ), + Text('id: ${pizza.id}'), + Text('frozen: ${pizza.frozen}'), + Text('name: ${pizza.customer.firstName} ${pizza.customer.lastName}'), ], ); } diff --git a/packages/brick_build/CHANGELOG.md b/packages/brick_build/CHANGELOG.md index 2ddbe282..1a5f11b0 100644 --- a/packages/brick_build/CHANGELOG.md +++ b/packages/brick_build/CHANGELOG.md @@ -1,5 +1,7 @@ ## Unreleased +- Add documentation to increase pub.dev score + ## 3.2.1 - Revert `.getDisplayString()` change due to Flutter 3.22 being restricted to analyzer <6.4.1. `meta` is pinned to `1.12` in this version of Flutter, and `analyzer >=6.5.0`, where the change was made, requires `meta >= 1.15`. This change will eventually be re-reverted. diff --git a/packages/brick_build/lib/src/serdes_generator.dart b/packages/brick_build/lib/src/serdes_generator.dart index 9db1cef2..02c96c83 100644 --- a/packages/brick_build/lib/src/serdes_generator.dart +++ b/packages/brick_build/lib/src/serdes_generator.dart @@ -177,6 +177,7 @@ abstract class SerdesGenerator address.id; } class Address extends OfflineFirstWithSupabaseModel{ diff --git a/packages/brick_offline_first_with_supabase/pubspec.yaml b/packages/brick_offline_first_with_supabase/pubspec.yaml index a92d9e05..9910f673 100644 --- a/packages/brick_offline_first_with_supabase/pubspec.yaml +++ b/packages/brick_offline_first_with_supabase/pubspec.yaml @@ -11,19 +11,19 @@ environment: sdk: ">=3.0.0 <4.0.0" dependencies: - brick_core: ^1.1.1 + brick_core: ">=1.2.0 <2.0.0" brick_offline_first: ">=3.0.0 <4.0.0" brick_sqlite: ">=3.0.0 <4.0.0" - brick_supabase: ">=0.0.1 <2.0.0" - brick_offline_first_with_rest: ">=3.0.2 <4.0.0" + brick_supabase: ">=1.0.0 <2.0.0" + brick_offline_first_with_rest: ">=3.1.0 <4.0.0" http: ">=1.0.0 <2.0.0" logging: ">=1.0.0 <2.0.0" meta: ">=1.3.0 <2.0.0" sqflite_common: ">=2.0.0 <3.0.0" + supabase: ">=2.3.0 <3.0.0" dev_dependencies: lints: ^2.0.1 mockito: ^5.0.0 sqflite_common_ffi: ^2.0.0 - supabase: ">=2.3.0 <3.0.0" test: ^1.16.5 diff --git a/packages/brick_offline_first_with_supabase_build/CHANGELOG.md b/packages/brick_offline_first_with_supabase_build/CHANGELOG.md index ba71affd..80618179 100644 --- a/packages/brick_offline_first_with_supabase_build/CHANGELOG.md +++ b/packages/brick_offline_first_with_supabase_build/CHANGELOG.md @@ -1,5 +1,7 @@ ## Unreleased +- Add documentation to increase pub.dev score + ## 1.0.0 - Stable release diff --git a/packages/brick_offline_first_with_supabase_build/LICENSE b/packages/brick_offline_first_with_supabase_build/LICENSE index 3b662c6b..06795618 100644 --- a/packages/brick_offline_first_with_supabase_build/LICENSE +++ b/packages/brick_offline_first_with_supabase_build/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) Green Bits, Inc. and its affiliates. +Copyright (c) Tim Shedor. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/brick_offline_first_with_supabase_build/lib/brick_offline_first_with_supabase_build.dart b/packages/brick_offline_first_with_supabase_build/lib/brick_offline_first_with_supabase_build.dart index 1a97f011..554c763b 100644 --- a/packages/brick_offline_first_with_supabase_build/lib/brick_offline_first_with_supabase_build.dart +++ b/packages/brick_offline_first_with_supabase_build/lib/brick_offline_first_with_supabase_build.dart @@ -7,11 +7,13 @@ import 'package:build/build.dart'; final _schemaGenerator = OfflineFirstSchemaGenerator(); +/// Generates migrations based off the [schemaGenerator] class OfflineFirstMigrationBuilder extends NewMigrationBuilder { @override final schemaGenerator = _schemaGenerator; } +/// Generates a schema using the [schemaGenerator] class OfflineFirstSchemaBuilder extends SchemaBuilder { @override final schemaGenerator = _schemaGenerator; diff --git a/packages/brick_offline_first_with_supabase_build/pubspec.yaml b/packages/brick_offline_first_with_supabase_build/pubspec.yaml index dbd4410b..024a91bb 100644 --- a/packages/brick_offline_first_with_supabase_build/pubspec.yaml +++ b/packages/brick_offline_first_with_supabase_build/pubspec.yaml @@ -14,9 +14,9 @@ dependencies: brick_build: ">=3.1.0 <4.0.0" brick_offline_first: ">=3.0.0 <4.0.0" brick_offline_first_build: ">=3.2.0 <4.0.0" - brick_offline_first_with_supabase: ">=0.0.1 <2.0.0" - brick_supabase: ">=0.0.1 <2.0.0" - brick_supabase_generators: ">=0.0.1 <2.0.0" + brick_offline_first_with_supabase: ">=1.0.0 <2.0.0" + brick_supabase: ">=1.0.0 <2.0.0" + brick_supabase_generators: ">=1.0.0 <2.0.0" brick_sqlite: ">=3.0.0 <4.0.0" brick_sqlite_generators: ">=3.0.0 <4.0.0" build: ">=2.0.0 <3.0.0" diff --git a/packages/brick_supabase/CHANGELOG.md b/packages/brick_supabase/CHANGELOG.md index 3012d9ba..441bb886 100644 --- a/packages/brick_supabase/CHANGELOG.md +++ b/packages/brick_supabase/CHANGELOG.md @@ -1,5 +1,14 @@ ## Unreleased +## 1.0.2 + +- Only specify key lookup in query transformer if `RuntimeSupabaseColumnDefinition#foreignKey` is specified + +## 1.0.1 + +- Add `@Supabase(foreignKey:)` to specify association querying +- Add `RuntimeSupabaseColumnDefinition#foreignKey` to track `@Supabase(foreignKey:)` values + ## 1.0.0 - Stable release diff --git a/packages/brick_supabase/LICENSE b/packages/brick_supabase/LICENSE index 3b662c6b..06795618 100644 --- a/packages/brick_supabase/LICENSE +++ b/packages/brick_supabase/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) Green Bits, Inc. and its affiliates. +Copyright (c) Tim Shedor. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/brick_supabase/README.md b/packages/brick_supabase/README.md index c61a8445..c04e009b 100644 --- a/packages/brick_supabase/README.md +++ b/packages/brick_supabase/README.md @@ -110,14 +110,18 @@ Supabase keys can be renamed per field. This will override the default set by `S final String lastName; ``` +**Do not use** `name` when annotating an association. Instead, use `foreignKey`. + :bulb: 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](models.md). +### `@Supabase(foreignKey:)` + When the annotated field type extends the model's type, the Supabase column should be a foreign key. ```dart class User extends OfflineFirstWithSupabaseModel{ // The foreign key is a relation to the `id` column of the Address table - @Supabase(name: 'address_id') + @Supabase(foreignKey: 'address_id') final Address address; } diff --git a/packages/brick_supabase/lib/src/annotations/supabase.dart b/packages/brick_supabase/lib/src/annotations/supabase.dart index 057e5ce1..63788c53 100644 --- a/packages/brick_supabase/lib/src/annotations/supabase.dart +++ b/packages/brick_supabase/lib/src/annotations/supabase.dart @@ -2,13 +2,10 @@ import 'package:brick_core/field_serializable.dart'; /// An annotation used to specify how a field is serialized for a [SupabaseAdapter]. /// -/// If this annotates a field type that extends this model's type, the Supabase column -/// should be a foreign key. -/// /// ```dart /// class User extends OfflineFirstWithSupabaseModel{ /// // The foreign key is a relation to the `id` column of the Address table -/// @Supabase(name: 'address_id') +/// @Supabase(foreignKey: 'address_id') /// final Address address; /// } /// @@ -33,6 +30,18 @@ class Supabase implements FieldSerializable { @override final bool enumAsString; + /// Specify a column for the ON clause in association queries. + /// For example, `'customer_id'` in `customer:customers!customer_id(...)`. + /// This is the model's column, not the association's column. + /// + /// This field must be specified if the current model uses + /// [multiple foreign keys](https://supabase.com/docs/guides/database/joins-and-nesting?queryGroups=language&language=dart#specifying-the-on-clause-for-joins-with-multiple-foreign-keys). + /// + /// The remote column type can be different than the local Dart type. For example, + /// `@Supabase(foreignKey: 'user_id')` that annotates `final User user` can be + /// a Postgres string type. + final String? foreignKey; + @override final String? fromGenerator; @@ -54,11 +63,10 @@ class Supabase implements FieldSerializable { /// The key name to use when reading and writing values corresponding /// to the annotated field. /// - /// 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. + /// **Do not use** `name` when annotating an association. + /// Instead, use [foreignKey]. /// - /// If `null`, the snake case value of the field is used. + /// If `null`, the renamed value of the field is used. @override final String? name; @@ -78,6 +86,7 @@ class Supabase implements FieldSerializable { this.defaultValue, bool? enumAsString, this.fromGenerator, + this.foreignKey, bool? ignore, bool? ignoreFrom, bool? ignoreTo, diff --git a/packages/brick_supabase/lib/src/query_supabase_transformer.dart b/packages/brick_supabase/lib/src/query_supabase_transformer.dart index f42c5477..5654b230 100644 --- a/packages/brick_supabase/lib/src/query_supabase_transformer.dart +++ b/packages/brick_supabase/lib/src/query_supabase_transformer.dart @@ -65,7 +65,10 @@ class QuerySupabaseTransformer<_Model extends SupabaseModel> { final field = entry.value; if (field.association && field.associationType != null) { var associationOutput = - '${entry.key}:${modelDictionary.adapterFor[field.associationType!]?.supabaseTableName}!${field.columnName}'; + '${entry.key}:${modelDictionary.adapterFor[field.associationType!]?.supabaseTableName}'; + if (field.foreignKey != null) { + associationOutput += '!${field.foreignKey}'; + } associationOutput += '('; final fields = destructureAssociationProperties( modelDictionary.adapterFor[field.associationType!]?.fieldsToSupabaseColumns, diff --git a/packages/brick_supabase/lib/src/runtime_supabase_column_definition.dart b/packages/brick_supabase/lib/src/runtime_supabase_column_definition.dart index bb9ffc29..68bced2f 100644 --- a/packages/brick_supabase/lib/src/runtime_supabase_column_definition.dart +++ b/packages/brick_supabase/lib/src/runtime_supabase_column_definition.dart @@ -16,10 +16,18 @@ class RuntimeSupabaseColumnDefinition { /// Then the [columnName] should reflect the Dart field name for deserialization. final String columnName; + /// When specified, this value will be used in the ON clause of the association query. + /// For example, `'customer_id'` in `customer:customers!customer_id(...)`. + /// + /// When left unspecified, the query will not use a foreign key. + /// (e.g. `customer:customers(...)`) + final String? foreignKey; + const RuntimeSupabaseColumnDefinition({ this.association = false, this.associationIsNullable = false, this.associationType, required this.columnName, + this.foreignKey, }); } diff --git a/packages/brick_supabase/pubspec.yaml b/packages/brick_supabase/pubspec.yaml index 84c2f648..c4dcdbfe 100644 --- a/packages/brick_supabase/pubspec.yaml +++ b/packages/brick_supabase/pubspec.yaml @@ -4,18 +4,18 @@ homepage: https://github.com/GetDutchie/brick/tree/main/packages/brick_supabase issue_tracker: https://github.com/GetDutchie/brick/issues repository: https://github.com/GetDutchie/brick -version: 1.0.0 +version: 1.0.2+1 environment: sdk: ">=3.0.0 <4.0.0" dependencies: - brick_core: ^1.1.1 - supabase: ">=2.3.0 <3.0.0" + brick_core: ">=1.2.1 <2.0.0" + collection: ">=1.15.0 <2.0.0" logging: ">=1.0.0 <2.0.0" meta: ">=1.3.0 <2.0.0" + supabase: ">=2.3.0 <3.0.0" dev_dependencies: lints: ^2.0.1 test: ^1.16.5 - collection: ">=1.15.0 <2.0.0" diff --git a/packages/brick_supabase/test/__mocks__.dart b/packages/brick_supabase/test/__mocks__.dart index 1901a396..d0b0529c 100644 --- a/packages/brick_supabase/test/__mocks__.dart +++ b/packages/brick_supabase/test/__mocks__.dart @@ -222,6 +222,7 @@ class DemoAssociationModelAdapter extends SupabaseAdapter association: true, associationIsNullable: false, columnName: 'assoc_id', + foreignKey: 'assoc_id', associationType: DemoModel, ), 'assocs': const RuntimeSupabaseColumnDefinition( diff --git a/packages/brick_supabase/test/query_supabase_transformer_test.dart b/packages/brick_supabase/test/query_supabase_transformer_test.dart index f1a92d2b..ba670748 100644 --- a/packages/brick_supabase/test/query_supabase_transformer_test.dart +++ b/packages/brick_supabase/test/query_supabase_transformer_test.dart @@ -52,7 +52,7 @@ void main() { final transformer = _buildTransformer(); expect( transformer.selectFields, - 'id,name,assoc:demos!assoc_id(id,name,age),assocs:demos!assocs_id(id,name,age)', + 'id,name,assoc:demos!assoc_id(id,name,age),assocs:demos(id,name,age)', ); }); @@ -60,7 +60,7 @@ void main() { final transformer = _buildTransformer(); expect( transformer.selectFields, - 'id,name,nested:demo_associations!nested_id(id,name,assoc:demos!assoc_id(id,name,age),assocs:demos!assocs_id(id,name,age))', + 'id,name,nested:demo_associations(id,name,assoc:demos!assoc_id(id,name,age),assocs:demos(id,name,age))', ); }); }); @@ -90,7 +90,7 @@ void main() { expect( select.query, - 'select=id,name,assoc:demos!assoc_id(id,name,age),assocs:demos!assocs_id(id,name,age)&demos.name=eq.Thomas&assoc=not.is.null', + 'select=id,name,assoc:demos!assoc_id(id,name,age),assocs:demos(id,name,age)&demos.name=eq.Thomas&assoc=not.is.null', ); }); }); @@ -256,7 +256,7 @@ void main() { containsAll([ 'id', 'name', - 'nested:demo_associations!nested_id(id,name,assoc:demos!assoc_id(id,name,age),assocs:demos!assocs_id(id,name,age))', + 'nested:demo_associations(id,name,assoc:demos!assoc_id(id,name,age),assocs:demos(id,name,age))', ]), ); }); diff --git a/packages/brick_supabase_generators/CHANGELOG.md b/packages/brick_supabase_generators/CHANGELOG.md index 68e35227..f6439dcf 100644 --- a/packages/brick_supabase_generators/CHANGELOG.md +++ b/packages/brick_supabase_generators/CHANGELOG.md @@ -1,5 +1,9 @@ ## Unreleased +## 1.0.1 + +- Apply `@Supabase(foreignKey:)` to `RuntimeSupabaseColumnDefinition#foreignKey` + ## 1.0.0 - Stable release diff --git a/packages/brick_supabase_generators/LICENSE b/packages/brick_supabase_generators/LICENSE index 74e31d3c..06795618 100644 --- a/packages/brick_supabase_generators/LICENSE +++ b/packages/brick_supabase_generators/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) Courier Plus, Inc. and its affiliates. +Copyright (c) Tim Shedor. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/brick_supabase_generators/README.md b/packages/brick_supabase_generators/README.md index 630a8c9e..0762a06e 100644 --- a/packages/brick_supabase_generators/README.md +++ b/packages/brick_supabase_generators/README.md @@ -1,6 +1,6 @@ ![brick_supabase_generators workflow](https://github.com/GetDutchie/brick/actions/workflows/brick_supabase_generators.yaml/badge.svg) -# Brick Supabase Build +# Brick Supabase Generators Code generator that provides (de)serializing functions for Brick adapters using SupabaseProvider. This package **does not** produce code. It can be imported into other Brick build domains. diff --git a/packages/brick_supabase_generators/lib/src/supabase_fields.dart b/packages/brick_supabase_generators/lib/src/supabase_fields.dart index 28ca6d5d..5b0ba87b 100644 --- a/packages/brick_supabase_generators/lib/src/supabase_fields.dart +++ b/packages/brick_supabase_generators/lib/src/supabase_fields.dart @@ -7,6 +7,7 @@ import 'package:brick_supabase/brick_supabase.dart'; /// Find `@Supabase` given a field class SupabaseAnnotationFinder extends AnnotationFinder with AnnotationFinderWithFieldRename { + /// Model-level settings final SupabaseSerializable? config; SupabaseAnnotationFinder([this.config]); @@ -33,6 +34,7 @@ class SupabaseAnnotationFinder extends AnnotationFinder return Supabase( defaultValue: obj.getField('defaultValue')!.toStringValue(), enumAsString: obj.getField('enumAsString')!.toBoolValue() ?? Supabase.defaults.enumAsString, + foreignKey: obj.getField('foreignKey')!.toStringValue(), fromGenerator: obj.getField('fromGenerator')!.toStringValue(), ignore: obj.getField('ignore')!.toBoolValue() ?? Supabase.defaults.ignore, ignoreFrom: obj.getField('ignoreFrom')!.toBoolValue() ?? Supabase.defaults.ignoreFrom, diff --git a/packages/brick_supabase_generators/lib/src/supabase_serialize.dart b/packages/brick_supabase_generators/lib/src/supabase_serialize.dart index 9cce99e8..c0d6762d 100644 --- a/packages/brick_supabase_generators/lib/src/supabase_serialize.dart +++ b/packages/brick_supabase_generators/lib/src/supabase_serialize.dart @@ -32,6 +32,7 @@ class SupabaseSerialize extends SupabaseSerdesGenerator '''; if (isAssociation) definition += 'associationType: ${checker.withoutNullResultType},'; if (isAssociation) definition += 'associationIsNullable: ${checker.isNullable},'; + if (annotation.foreignKey != null) definition += "foreignKey: '${annotation.foreignKey}',"; definition += ')'; fieldsToColumns.add(definition); diff --git a/packages/brick_supabase_generators/pubspec.yaml b/packages/brick_supabase_generators/pubspec.yaml index b4007756..c1cbf2a5 100644 --- a/packages/brick_supabase_generators/pubspec.yaml +++ b/packages/brick_supabase_generators/pubspec.yaml @@ -4,7 +4,7 @@ homepage: https://github.com/GetDutchie/brick/tree/main/packages/brick_supabase_ issue_tracker: https://github.com/GetDutchie/brick/issues repository: https://github.com/GetDutchie/brick -version: 1.0.0 +version: 1.0.1+3 environment: sdk: ">=3.0.0 <4.0.0" @@ -14,7 +14,7 @@ dependencies: brick_build: ">=3.2.0 <4.0.0" brick_core: ">=1.2.1 <2.0.0" brick_json_generators: ">=3.1.0 <4.0.0" - brick_supabase: ">=0.1.1+2 <2.0.0" + brick_supabase: ">=1.0.2+1 <2.0.0" build: ">=2.0.0 <3.0.0" dart_style: ">=2.0.0 <3.0.0" source_gen: ">=1.2.2 <2.0.0" diff --git a/packages/brick_supabase_generators/test/supabase_model_serdes_generator/test_runtime_supabase_column_definition.dart b/packages/brick_supabase_generators/test/supabase_model_serdes_generator/test_runtime_supabase_column_definition.dart index a8fa8019..7c2d86ae 100644 --- a/packages/brick_supabase_generators/test/supabase_model_serdes_generator/test_runtime_supabase_column_definition.dart +++ b/packages/brick_supabase_generators/test/supabase_model_serdes_generator/test_runtime_supabase_column_definition.dart @@ -13,7 +13,7 @@ Future _$SupabaseRuntimeFromSupabase(Map data, data['unannotated_assoc'], provider: provider, repository: repository), - annotatedAssoc: await AssocAdapter().fromSupabase(data['assoc_id'], + annotatedAssoc: await AssocAdapter().fromSupabase(data['annotated_assoc'], provider: provider, repository: repository), nullableAssoc: data['assocs_id'] == null ? null @@ -31,7 +31,7 @@ Future> _$SupabaseRuntimeToSupabase( instance.unannotatedAssoc, provider: provider, repository: repository), - 'assoc_id': await AssocAdapter().toSupabase(instance.annotatedAssoc, + 'annotated_assoc': await AssocAdapter().toSupabase(instance.annotatedAssoc, provider: provider, repository: repository), 'assocs_id': instance.nullableAssoc != null ? await AssocAdapter().toSupabase(instance.nullableAssoc!, @@ -60,9 +60,10 @@ class SupabaseRuntimeAdapter extends SupabaseFirstAdapter { ), 'annotatedAssoc': const RuntimeSupabaseColumnDefinition( association: true, - columnName: 'assoc_id', + columnName: 'annotated_assoc', associationType: Assoc, associationIsNullable: false, + foreignKey: 'assocs_id', ), 'nullableAssoc': const RuntimeSupabaseColumnDefinition( association: true, @@ -97,7 +98,7 @@ class SupabaseRuntime extends SupabaseModel { final Assoc unannotatedAssoc; - @Supabase(name: 'assoc_id') + @Supabase(foreignKey: 'assocs_id') final Assoc annotatedAssoc; @Supabase(name: 'assocs_id')