diff --git a/packages/firebase_data_connect/firebase_data_connect/example/android/app/src/main/AndroidManifest.xml b/packages/firebase_data_connect/firebase_data_connect/example/android/app/src/main/AndroidManifest.xml index 843b956968dd..6a3a3ac5ac03 100644 --- a/packages/firebase_data_connect/firebase_data_connect/example/android/app/src/main/AndroidManifest.xml +++ b/packages/firebase_data_connect/firebase_data_connect/example/android/app/src/main/AndroidManifest.xml @@ -43,4 +43,4 @@ - \ No newline at end of file + diff --git a/packages/firebase_data_connect/firebase_data_connect/example/dataconnect/.dataconnect/schema/prelude.gql b/packages/firebase_data_connect/firebase_data_connect/example/dataconnect/.dataconnect/schema/prelude.gql index c75ac521add7..54eee276ad8a 100755 --- a/packages/firebase_data_connect/firebase_data_connect/example/dataconnect/.dataconnect/schema/prelude.gql +++ b/packages/firebase_data_connect/firebase_data_connect/example/dataconnect/.dataconnect/schema/prelude.gql @@ -46,6 +46,17 @@ directive @auth( """ expr: Boolean_Expr @fdc_oneOf(required: true) ) on QUERY | MUTATION + +""" +Marks an element of a GraphQL operation as no longer supported on the client. +The Firebase Data Connect backend will still support this element, but it will +no longer appear in generated SDKs. +""" +directive @retired( + "Explains why this element was retired." + reason: String +) on QUERY | MUTATION | FIELD | VARIABLE_DEFINITION + "Query filter criteria for `String` scalar fields." input String_Filter { "When true, match if field `IS NULL`. When false, match if field is `NOT NULL`." @@ -304,6 +315,7 @@ input Any_ListFilter { "Match if list field does not contain any of the provided values as members." excludesAll: [Any!] } + """ (Internal) A string that uniquely identifies a type, field, and so on. @@ -358,53 +370,7 @@ directive @fdc_generated( | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION -""" -Defines a database index to optimize query performance. - -Given `type TableName @table @index(fields: [“fieldName”, “secondFieldName”])`, -`table_name_field_name_second_field_name_aa_idx` is the SQL index id. -`table_name_field_name_second_field_name_ad_idx` if `order: [ASC DESC]`. -`table_name_field_name_second_field_name_dd_idx` if `order: [DESC DESC]`. - -Given `type TableName @table { fieldName: Int @index } ` -`table_name_field_name_idx` is the SQL index id. -`order` matters less for single field indexes because they can be scanned in both ways. - -Override with `@index(name)` in case of index name conflicts. -""" -directive @index( - "The SQL database index id. Defaults to __idx." - name: String - """ - Only allowed and required when used on OBJECT. - The fields to create an index on. - """ - fields: [String!] - """ - Only allowed when used on OBJECT and BTREE index. - Index order of each column. Default to all ASC. - """ - order: [IndexFieldOrder!] - """ - For array field, default to `GIN`. - For Vector field, default to `HNSW`. - """ - type: IndexType - """ - Only allowed when used on vector field. - The vector similarity method. Default to `INNER_PRODUCT`. - """ - vector_method: VectorSimilarityMethod -) repeatable on FIELD_DEFINITION | OBJECT -enum IndexFieldOrder { ASC DESC } - -enum IndexType { - BTREE - GIN - HNSW - IVFFLAT -} type Query { _service: _Service! } @@ -415,7 +381,10 @@ type Mutation { } type _Service { + "Full Service Definition Language of the Frebase Data Connect Schema, including normalized schema, predefined and generated types." sdl: String! + "Orignal Schema Sources in the service." + schema: String! } "(Internal) Added to things that may be removed from FDC and will soon be no longer usable in schema or operations." @@ -465,6 +434,7 @@ directive @fdc_oneOf( "If true, exactly one field / argument in the group must be specified." required: Boolean! = false ) repeatable on FIELD_DEFINITION | ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION + """ UUID is a string with hex digits representing an RFC4122 value. @@ -490,16 +460,7 @@ scalar True @fdc_forbiddenAsFieldType @fdc_forbiddenAsVariableType @fdc_example(value: true, description: "The only allowed value.") -"Define the intervals used in timestamps and dates (subset)" -enum TimestampInterval @fdc_deprecated { - second - minute - hour - day - week - month - year -} + """ A Common Expression Language (CEL) expression that returns a boolean at runtime. @@ -566,6 +527,7 @@ scalar Any_SQL @fdc_sqlExpression @fdc_forbiddenAsVariableType @fdc_forbiddenAsFieldType + """ Defines a relational database table. @@ -718,24 +680,6 @@ enum OrderDirection { DESC } -enum ColDefault @fdc_deprecated { - """ - Generates a random UUID (v4) as the default column value. - Compatible with String or UUID typed fields. - """ - UUID - """ - Generates an auto-incrementing sequence as the default column value. - Compatible with Int and Int64 typed fields. - """ - SEQUENCE - """ - Populates the default column value with the current date or timestamp. - Compatible with Date and Timestamp typed fields. - """ - NOW -} - """ Specify the default column value. @@ -744,8 +688,6 @@ The supported arguments vary based on the field type. directive @default( "A constant value. Validated against the field GraphQL type at compile-time." value: Any @fdc_oneOf(required: true) - "(Deprecated) Built-in common ways to generate initial value." - generate: ColDefault @fdc_oneOf(required: true) @deprecated "A CEL expression, whose return value must match the field data type." expr: Any_Expr @fdc_oneOf(required: true) """ @@ -757,6 +699,75 @@ directive @default( """ sql: Any_SQL @fdc_oneOf(required: true) ) on FIELD_DEFINITION + +""" +Defines a database index to optimize query performance. + +Given `type TableName @table @index(fields: [“fieldName”, “secondFieldName”])`, +`table_name_field_name_second_field_name_aa_idx` is the SQL index id. +`table_name_field_name_second_field_name_ad_idx` if `order: [ASC DESC]`. +`table_name_field_name_second_field_name_dd_idx` if `order: [DESC DESC]`. + +Given `type TableName @table { fieldName: Int @index } ` +`table_name_field_name_idx` is the SQL index id. +`order` matters less for single field indexes because they can be scanned in both ways. + +Override with `@index(name)` in case of index name conflicts. +""" +directive @index( + "The SQL database index id. Defaults to __idx." + name: String + """ + Only allowed and required when used on OBJECT. + The fields to create an index on. + """ + fields: [String!] + """ + Only allowed when used on OBJECT and BTREE index. + Index order of each column. Default to all ASC. + """ + order: [IndexFieldOrder!] + """ + For array field, default to `GIN`. + For Vector field, default to `HNSW`. + """ + type: IndexType + """ + Only allowed when used on vector field. + The vector similarity method. Default to `INNER_PRODUCT`. + """ + vector_method: VectorSimilarityMethod +) repeatable on FIELD_DEFINITION | OBJECT + +enum IndexFieldOrder { ASC DESC } + +enum IndexType { + BTREE + GIN + HNSW + IVFFLAT +} + +""" +Defines a unique constraint. + +Given `type TableName @table @unique(fields: [“fieldName”, “secondFieldName”])`, +`table_name_field_name_second_field_name_uidx` is the SQL unique index id. +Given `type TableName @table { fieldName: Int @unique } ` +`table_name_field_name_uidx` is the SQL unique index id. + +Override with `@unique(indexName)` in case of index name conflicts. +""" +directive @unique( + "The SQL database unique index name. Defaults to __uidx." + indexName: String + """ + Only allowed and required when used on OBJECT. + The fields to create a unique constraint on. + """ + fields: [String!] +) repeatable on FIELD_DEFINITION | OBJECT + """ Date is a string in the YYYY-MM-DD format representing a local-only date. @@ -980,25 +991,7 @@ enum Date_Interval @fdc_forbiddenAsFieldType { MONTH YEAR } -""" -Defines a unique constraint. - -Given `type TableName @table @unique(fields: [“fieldName”, “secondFieldName”])`, -`table_name_field_name_second_field_name_uidx` is the SQL unique index id. -Given `type TableName @table { fieldName: Int @unique } ` -`table_name_field_name_uidx` is the SQL unique index id. -Override with `@unique(indexName)` in case of index name conflicts. -""" -directive @unique( - "The SQL database unique index name. Defaults to __uidx." - indexName: String - """ - Only allowed and required when used on OBJECT. - The fields to create a unique constraint on. - """ - fields: [String!] -) repeatable on FIELD_DEFINITION | OBJECT "Update input of a String value" input String_Update { set: String @fdc_oneOf(group: "set") @@ -1090,6 +1083,7 @@ input Any_ListUpdate { append: [Any!] prepend: [Any!] } + """ Vector is an array of single-precision floating-point numbers, serialized as a JSON array. All elements must be finite (no NaN, Infinity or -Infinity). @@ -1168,3 +1162,4 @@ scalar Vector_Embed_Model @fdc_forbiddenAsVariableType @fdc_forbiddenAsFieldType @fdc_example(value: "textembedding-gecko@003", description: "A stable version of the textembedding-gecko model") + diff --git a/packages/firebase_data_connect/firebase_data_connect/example/dataconnect/connector/mutations.gql b/packages/firebase_data_connect/firebase_data_connect/example/dataconnect/connector/mutations.gql index a869f6fea236..d7cc7276a6e5 100644 --- a/packages/firebase_data_connect/firebase_data_connect/example/dataconnect/connector/mutations.gql +++ b/packages/firebase_data_connect/firebase_data_connect/example/dataconnect/connector/mutations.gql @@ -6,6 +6,17 @@ mutation addDirectorToMovie($personId: Person_Key, $movieId: UUID) @auth(level: PUBLIC) { directedBy_insert(data: { directedby: $personId, movieId: $movieId }) } +mutation seedData @auth(level: PUBLIC) { + movies1: movie_insert( + data: { + title: "The Matrix" + releaseYear: 1999 + genre: "Action" + rating: 5.0 + description: "When a beautiful stranger leads computer hacker Neo to a forbidding underworld, he discovers the shocking truth--the life he knows is the elaborate deception of an evil cyber-intelligence." + } + ) +} mutation createMovie( $title: String! $releaseYear: Int! diff --git a/packages/firebase_data_connect/firebase_data_connect/example/firebase.json b/packages/firebase_data_connect/firebase_data_connect/example/firebase.json index 2d00ab15a830..f8e4ba878186 100644 --- a/packages/firebase_data_connect/firebase_data_connect/example/firebase.json +++ b/packages/firebase_data_connect/firebase_data_connect/example/firebase.json @@ -7,4 +7,4 @@ "port": 9099 } } -} \ No newline at end of file +} diff --git a/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/add_director_to_movie.dart b/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/add_director_to_movie.dart index 0929d0a602b9..a64ab25e9d9e 100644 --- a/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/add_director_to_movie.dart +++ b/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/add_director_to_movie.dart @@ -4,20 +4,19 @@ class AddDirectorToMovie { String name = "addDirectorToMovie"; AddDirectorToMovie({required this.dataConnect}); - Deserializer dataDeserializer = (String json) => - AddDirectorToMovieResponse.fromJson( - jsonDecode(json) as Map); + Deserializer dataDeserializer = (String json) => + AddDirectorToMovieData.fromJson(jsonDecode(json) as Map); Serializer varsSerializer = (AddDirectorToMovieVariables vars) => jsonEncode(vars.toJson()); - MutationRef ref( - {AddDirectorToMovieVariablesPersonId? personId, - String? movieId, - AddDirectorToMovieVariables? addDirectorToMovieVariables}) { - AddDirectorToMovieVariables vars1 = AddDirectorToMovieVariables( + MutationRef ref({ + AddDirectorToMovieVariablesPersonId? personId, + String? movieId, + }) { + AddDirectorToMovieVariables vars = AddDirectorToMovieVariables( personId: personId, movieId: movieId, ); - AddDirectorToMovieVariables vars = addDirectorToMovieVariables ?? vars1; + return dataConnect.mutation( this.name, dataDeserializer, varsSerializer, vars); } @@ -53,10 +52,10 @@ class AddDirectorToMovieDirectedByInsert { } } -class AddDirectorToMovieResponse { +class AddDirectorToMovieData { late AddDirectorToMovieDirectedByInsert directedBy_insert; - AddDirectorToMovieResponse.fromJson(Map json) + AddDirectorToMovieData.fromJson(Map json) : directedBy_insert = AddDirectorToMovieDirectedByInsert.fromJson( json['directedBy_insert']) {} @@ -69,7 +68,7 @@ class AddDirectorToMovieResponse { return json; } - AddDirectorToMovieResponse({ + AddDirectorToMovieData({ required this.directedBy_insert, }) { // TODO(mtewani): Only show this if there are optional fields. @@ -124,8 +123,8 @@ class AddDirectorToMovieVariables { } AddDirectorToMovieVariables({ - AddDirectorToMovieVariablesPersonId? this.personId, - String? this.movieId, + this.personId, + this.movieId, }) { // TODO(mtewani): Only show this if there are optional fields. } diff --git a/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/add_person.dart b/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/add_person.dart index 9d418952fcd1..22f681927a96 100644 --- a/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/add_person.dart +++ b/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/add_person.dart @@ -4,16 +4,17 @@ class AddPerson { String name = "addPerson"; AddPerson({required this.dataConnect}); - Deserializer dataDeserializer = (String json) => - AddPersonResponse.fromJson(jsonDecode(json) as Map); + Deserializer dataDeserializer = (String json) => + AddPersonData.fromJson(jsonDecode(json) as Map); Serializer varsSerializer = (AddPersonVariables vars) => jsonEncode(vars.toJson()); - MutationRef ref( - {String? name, AddPersonVariables? addPersonVariables}) { - AddPersonVariables vars1 = AddPersonVariables( + MutationRef ref({ + String? name, + }) { + AddPersonVariables vars = AddPersonVariables( name: name, ); - AddPersonVariables vars = addPersonVariables ?? vars1; + return dataConnect.mutation( this.name, dataDeserializer, varsSerializer, vars); } @@ -42,10 +43,10 @@ class AddPersonPersonInsert { } } -class AddPersonResponse { +class AddPersonData { late AddPersonPersonInsert person_insert; - AddPersonResponse.fromJson(Map json) + AddPersonData.fromJson(Map json) : person_insert = AddPersonPersonInsert.fromJson(json['person_insert']) {} // TODO(mtewani): Fix up to create a map on the fly @@ -57,7 +58,7 @@ class AddPersonResponse { return json; } - AddPersonResponse({ + AddPersonData({ required this.person_insert, }) { // TODO(mtewani): Only show this if there are optional fields. @@ -82,7 +83,7 @@ class AddPersonVariables { } AddPersonVariables({ - String? this.name, + this.name, }) { // TODO(mtewani): Only show this if there are optional fields. } diff --git a/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/create_movie.dart b/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/create_movie.dart index 6d1e020cce3b..e070dd38a185 100644 --- a/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/create_movie.dart +++ b/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/create_movie.dart @@ -4,25 +4,25 @@ class CreateMovie { String name = "createMovie"; CreateMovie({required this.dataConnect}); - Deserializer dataDeserializer = (String json) => - CreateMovieResponse.fromJson(jsonDecode(json) as Map); + Deserializer dataDeserializer = (String json) => + CreateMovieData.fromJson(jsonDecode(json) as Map); Serializer varsSerializer = (CreateMovieVariables vars) => jsonEncode(vars.toJson()); - MutationRef ref( - {required String title, - required int releaseYear, - required String genre, - double? rating, - String? description, - CreateMovieVariables? createMovieVariables}) { - CreateMovieVariables vars1 = CreateMovieVariables( + MutationRef ref({ + required String title, + required int releaseYear, + required String genre, + double? rating, + String? description, + }) { + CreateMovieVariables vars = CreateMovieVariables( title: title, releaseYear: releaseYear, genre: genre, rating: rating, description: description, ); - CreateMovieVariables vars = createMovieVariables ?? vars1; + return dataConnect.mutation( this.name, dataDeserializer, varsSerializer, vars); } @@ -52,10 +52,10 @@ class CreateMovieMovieInsert { } } -class CreateMovieResponse { +class CreateMovieData { late CreateMovieMovieInsert movie_insert; - CreateMovieResponse.fromJson(Map json) + CreateMovieData.fromJson(Map json) : movie_insert = CreateMovieMovieInsert.fromJson(json['movie_insert']) {} // TODO(mtewani): Fix up to create a map on the fly @@ -67,7 +67,7 @@ class CreateMovieResponse { return json; } - CreateMovieResponse({ + CreateMovieData({ required this.movie_insert, }) { // TODO(mtewani): Only show this if there are optional fields. @@ -117,8 +117,8 @@ class CreateMovieVariables { required this.title, required this.releaseYear, required this.genre, - double? this.rating, - String? this.description, + this.rating, + this.description, }) { // TODO(mtewani): Only show this if there are optional fields. } diff --git a/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/delete_movie.dart b/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/delete_movie.dart index 5aeb6cdd1938..970e97c44b3f 100644 --- a/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/delete_movie.dart +++ b/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/delete_movie.dart @@ -4,16 +4,17 @@ class DeleteMovie { String name = "deleteMovie"; DeleteMovie({required this.dataConnect}); - Deserializer dataDeserializer = (String json) => - DeleteMovieResponse.fromJson(jsonDecode(json) as Map); + Deserializer dataDeserializer = (String json) => + DeleteMovieData.fromJson(jsonDecode(json) as Map); Serializer varsSerializer = (DeleteMovieVariables vars) => jsonEncode(vars.toJson()); - MutationRef ref( - {required String id, DeleteMovieVariables? deleteMovieVariables}) { - DeleteMovieVariables vars1 = DeleteMovieVariables( + MutationRef ref({ + required String id, + }) { + DeleteMovieVariables vars = DeleteMovieVariables( id: id, ); - DeleteMovieVariables vars = deleteMovieVariables ?? vars1; + return dataConnect.mutation( this.name, dataDeserializer, varsSerializer, vars); } @@ -43,10 +44,10 @@ class DeleteMovieMovieDelete { } } -class DeleteMovieResponse { +class DeleteMovieData { late DeleteMovieMovieDelete? movie_delete; - DeleteMovieResponse.fromJson(Map json) + DeleteMovieData.fromJson(Map json) : movie_delete = DeleteMovieMovieDelete.fromJson(json['movie_delete']) {} // TODO(mtewani): Fix up to create a map on the fly @@ -60,8 +61,8 @@ class DeleteMovieResponse { return json; } - DeleteMovieResponse({ - DeleteMovieMovieDelete? movie_delete, + DeleteMovieData({ + this.movie_delete, }) { // TODO(mtewani): Only show this if there are optional fields. } diff --git a/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/list_movies.dart b/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/list_movies.dart index 58e13493a23e..aacfbc7b0aea 100644 --- a/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/list_movies.dart +++ b/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/list_movies.dart @@ -4,11 +4,12 @@ class ListMovies { String name = "ListMovies"; ListMovies({required this.dataConnect}); - Deserializer dataDeserializer = (String json) => - ListMoviesResponse.fromJson(jsonDecode(json) as Map); + Deserializer dataDeserializer = (String json) => + ListMoviesData.fromJson(jsonDecode(json) as Map); - QueryRef ref() { - return dataConnect.query(this.name, dataDeserializer, null, null); + QueryRef ref() { + return dataConnect.query( + this.name, dataDeserializer, emptySerializer, null); } FirebaseDataConnect dataConnect; @@ -72,10 +73,10 @@ class ListMoviesMoviesDirectedBy { } } -class ListMoviesResponse { +class ListMoviesData { late List movies; - ListMoviesResponse.fromJson(Map json) + ListMoviesData.fromJson(Map json) : movies = (json['movies'] as List) .map((e) => ListMoviesMovies.fromJson(e)) .toList() {} @@ -89,7 +90,7 @@ class ListMoviesResponse { return json; } - ListMoviesResponse({ + ListMoviesData({ required this.movies, }) { // TODO(mtewani): Only show this if there are optional fields. diff --git a/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/list_movies_by_partial_title.dart b/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/list_movies_by_partial_title.dart index f06a7f68d304..991a71bbc461 100644 --- a/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/list_movies_by_partial_title.dart +++ b/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/list_movies_by_partial_title.dart @@ -4,21 +4,19 @@ class ListMoviesByPartialTitle { String name = "ListMoviesByPartialTitle"; ListMoviesByPartialTitle({required this.dataConnect}); - Deserializer dataDeserializer = - (String json) => ListMoviesByPartialTitleResponse.fromJson( + Deserializer dataDeserializer = (String json) => + ListMoviesByPartialTitleData.fromJson( jsonDecode(json) as Map); Serializer varsSerializer = (ListMoviesByPartialTitleVariables vars) => jsonEncode(vars.toJson()); - QueryRef - ref( - {required String input, - ListMoviesByPartialTitleVariables? - listMoviesByPartialTitleVariables}) { - ListMoviesByPartialTitleVariables vars1 = ListMoviesByPartialTitleVariables( + QueryRef + ref({ + required String input, + }) { + ListMoviesByPartialTitleVariables vars = ListMoviesByPartialTitleVariables( input: input, ); - ListMoviesByPartialTitleVariables vars = - listMoviesByPartialTitleVariables ?? vars1; + return dataConnect.query(this.name, dataDeserializer, varsSerializer, vars); } @@ -61,16 +59,16 @@ class ListMoviesByPartialTitleMovies { required this.id, required this.title, required this.genre, - double? rating, + this.rating, }) { // TODO(mtewani): Only show this if there are optional fields. } } -class ListMoviesByPartialTitleResponse { +class ListMoviesByPartialTitleData { late List movies; - ListMoviesByPartialTitleResponse.fromJson(Map json) + ListMoviesByPartialTitleData.fromJson(Map json) : movies = (json['movies'] as List) .map((e) => ListMoviesByPartialTitleMovies.fromJson(e)) .toList() {} @@ -84,7 +82,7 @@ class ListMoviesByPartialTitleResponse { return json; } - ListMoviesByPartialTitleResponse({ + ListMoviesByPartialTitleData({ required this.movies, }) { // TODO(mtewani): Only show this if there are optional fields. diff --git a/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/list_persons.dart b/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/list_persons.dart index 8a4d7e4cb0a6..5fbb80c56558 100644 --- a/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/list_persons.dart +++ b/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/list_persons.dart @@ -4,11 +4,12 @@ class ListPersons { String name = "ListPersons"; ListPersons({required this.dataConnect}); - Deserializer dataDeserializer = (String json) => - ListPersonsResponse.fromJson(jsonDecode(json) as Map); + Deserializer dataDeserializer = (String json) => + ListPersonsData.fromJson(jsonDecode(json) as Map); - QueryRef ref() { - return dataConnect.query(this.name, dataDeserializer, null, null); + QueryRef ref() { + return dataConnect.query( + this.name, dataDeserializer, emptySerializer, null); } FirebaseDataConnect dataConnect; @@ -42,10 +43,10 @@ class ListPersonsPeople { } } -class ListPersonsResponse { +class ListPersonsData { late List people; - ListPersonsResponse.fromJson(Map json) + ListPersonsData.fromJson(Map json) : people = (json['people'] as List) .map((e) => ListPersonsPeople.fromJson(e)) .toList() {} @@ -59,7 +60,7 @@ class ListPersonsResponse { return json; } - ListPersonsResponse({ + ListPersonsData({ required this.people, }) { // TODO(mtewani): Only show this if there are optional fields. diff --git a/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/movies.dart b/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/movies.dart index 9c0f15b0ac39..cc45dd04c9dd 100644 --- a/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/movies.dart +++ b/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/movies.dart @@ -7,6 +7,8 @@ part 'add_person.dart'; part 'add_director_to_movie.dart'; +part 'seed_data.dart'; + part 'create_movie.dart'; part 'delete_movie.dart'; @@ -26,6 +28,10 @@ class MoviesConnector { return AddDirectorToMovie(dataConnect: dataConnect); } + SeedData get seedData { + return SeedData(dataConnect: dataConnect); + } + CreateMovie get createMovie { return CreateMovie(dataConnect: dataConnect); } diff --git a/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/seed_data.dart b/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/seed_data.dart new file mode 100644 index 000000000000..26dac17efe67 --- /dev/null +++ b/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/seed_data.dart @@ -0,0 +1,59 @@ +part of movies; + +class SeedData { + String name = "seedData"; + SeedData({required this.dataConnect}); + + Deserializer dataDeserializer = (String json) => + SeedDataData.fromJson(jsonDecode(json) as Map); + + MutationRef ref() { + return dataConnect.mutation( + this.name, dataDeserializer, emptySerializer, null); + } + + FirebaseDataConnect dataConnect; +} + +class SeedDataMovies1 { + late String id; + + SeedDataMovies1.fromJson(Map json) : id = json['id'] {} + + // TODO(mtewani): Fix up to create a map on the fly + Map toJson() { + Map json = {}; + + json['id'] = id; + + return json; + } + + SeedDataMovies1({ + required this.id, + }) { + // TODO(mtewani): Only show this if there are optional fields. + } +} + +class SeedDataData { + late SeedDataMovies1 movies1; + + SeedDataData.fromJson(Map json) + : movies1 = SeedDataMovies1.fromJson(json['movies1']) {} + + // TODO(mtewani): Fix up to create a map on the fly + Map toJson() { + Map json = {}; + + json['movies1'] = movies1.toJson(); + + return json; + } + + SeedDataData({ + required this.movies1, + }) { + // TODO(mtewani): Only show this if there are optional fields. + } +} diff --git a/packages/firebase_data_connect/firebase_data_connect/example/lib/main.dart b/packages/firebase_data_connect/firebase_data_connect/example/lib/main.dart index 10da1ce05f60..1faa49a538cf 100644 --- a/packages/firebase_data_connect/firebase_data_connect/example/lib/main.dart +++ b/packages/firebase_data_connect/firebase_data_connect/example/lib/main.dart @@ -109,7 +109,7 @@ class _DataConnectWidgetState extends State { void initState() { super.initState(); - QueryRef ref = + QueryRef ref = MoviesConnector.instance.listMovies.ref(); ref.subscribe().listen((event) { diff --git a/packages/firebase_data_connect/firebase_data_connect/example/test/widget_test.dart b/packages/firebase_data_connect/firebase_data_connect/example/test/widget_test.dart new file mode 100644 index 000000000000..5ef64fc01675 --- /dev/null +++ b/packages/firebase_data_connect/firebase_data_connect/example/test/widget_test.dart @@ -0,0 +1,34 @@ +// Copyright 2024, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:firebase_data_connect_example/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/common/common_library.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/common/common_library.dart index c8edc7320221..7c25d77c15a6 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/common/common_library.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/common/common_library.dart @@ -30,7 +30,7 @@ class TransportOptions { /// Interface for transports connecting to the DataConnect backend. abstract class DataConnectTransport { /// Constructor. - DataConnectTransport(this.transportOptions, this.options); + DataConnectTransport(this.transportOptions, this.options, this.appId); /// Transport options. TransportOptions transportOptions; @@ -44,17 +44,20 @@ abstract class DataConnectTransport { /// FirebaseAppCheck to use to get app check token. FirebaseAppCheck? appCheck; + /// Application ID + String appId; + /// Invokes corresponding query endpoint. Future invokeQuery( String queryName, Deserializer deserializer, - Serializer? serializer, + Serializer serializer, Variables? vars); /// Invokes corresponding mutation endpoint. Future invokeMutation( String queryName, Deserializer deserializer, - Serializer? serializer, + Serializer serializer, Variables? vars); } diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/common/dataconnect_options.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/common/dataconnect_options.dart index 4f55b88cc784..3d23ef63432c 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/common/dataconnect_options.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/common/dataconnect_options.dart @@ -21,9 +21,9 @@ class ConnectorConfig { /// String representation of connectorConfig String toJson() { return jsonEncode({ - 'location': location, - 'connector': connector, - 'serviceId': serviceId, + location: location, + connector: connector, + serviceId: serviceId, }); } } diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/firebase_data_connect.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/firebase_data_connect.dart index b68d2069298e..cd693460cfd9 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/firebase_data_connect.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/firebase_data_connect.dart @@ -54,24 +54,19 @@ class FirebaseDataConnect extends FirebasePluginPlatform { void checkTransport() { transportOptions ??= TransportOptions('firebasedataconnect.googleapis.com', null, true); - transport = getTransport(transportOptions!, options, auth, appCheck); + transport = getTransport( + transportOptions!, options, app.options.appId, auth, appCheck); } /// Returns a [QueryRef] object. QueryRef query( String operationName, Deserializer dataDeserializer, - Serializer? varsSerializer, + Serializer varsSerializer, Variables? vars) { checkTransport(); - return QueryRef( - this, - operationName, - transport, - dataDeserializer, - _queryManager, - varsSerializer ?? emptySerializer, - vars); + return QueryRef(this, operationName, transport, + dataDeserializer, _queryManager, varsSerializer, vars); } /// Returns a [MutationRef] object. diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/network/grpc_transport.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/network/grpc_transport.dart index 7b9cf4919232..6fcc25df53c4 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/network/grpc_transport.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/network/grpc_transport.dart @@ -7,7 +7,13 @@ part of firebase_data_connect_grpc; /// Transport used for Android/iOS. Uses a GRPC transport instead of REST. class GRPCTransport implements DataConnectTransport { /// GRPCTransport creates a new channel - GRPCTransport(this.transportOptions, this.options, this.auth, this.appCheck) { + GRPCTransport( + this.transportOptions, + this.options, + this.appId, + this.auth, + this.appCheck, + ) { bool isSecure = transportOptions.isSecure == null || transportOptions.isSecure == true; channel = ClientChannel(transportOptions.host, @@ -46,6 +52,10 @@ class GRPCTransport implements DataConnectTransport { @override DataConnectOptions options; + /// Application ID + @override + String appId; + Future> getMetadata() async { String? authToken; try { @@ -70,6 +80,7 @@ class GRPCTransport implements DataConnectTransport { if (appCheckToken != null) { metadata['X-Firebase-AppCheck'] = appCheckToken; } + metadata['x-firebase-gmpid'] = appId; return metadata; } @@ -134,6 +145,7 @@ class GRPCTransport implements DataConnectTransport { DataConnectTransport getTransport( TransportOptions transportOptions, DataConnectOptions options, + String appId, FirebaseAuth? auth, FirebaseAppCheck? appCheck) => - GRPCTransport(transportOptions, options, auth, appCheck); + GRPCTransport(transportOptions, options, appId, auth, appCheck); diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/network/rest_transport.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/network/rest_transport.dart index 1d1bffcf7446..2dee084bbbb4 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/network/rest_transport.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/network/rest_transport.dart @@ -7,7 +7,8 @@ part of firebase_data_connect_rest; /// RestTransport makes requests out to the REST endpoints of the configured backend. class RestTransport implements DataConnectTransport { /// Initializes necessary protocol and port. - RestTransport(this.transportOptions, this.options, this.auth, this.appCheck) { + RestTransport(this.transportOptions, this.options, this.appId, this.auth, + this.appCheck) { String protocol = 'http'; if (transportOptions.isSecure == null || transportOptions.isSecure == true) { @@ -48,6 +49,10 @@ class RestTransport implements DataConnectTransport { @override DataConnectOptions options; + /// Firebase application ID. + @override + String appId; + /// Invokes the current operation, whether its a query or mutation. Future invokeOperation( String queryName, @@ -82,6 +87,7 @@ class RestTransport implements DataConnectTransport { if (appCheckToken != null) { headers['X-Firebase-AppCheck'] = appCheckToken; } + headers['x-firebase-gmpid'] = appId; Map body = { 'name': @@ -148,6 +154,7 @@ class RestTransport implements DataConnectTransport { DataConnectTransport getTransport( TransportOptions transportOptions, DataConnectOptions options, + String appId, FirebaseAuth? auth, FirebaseAppCheck? appCheck) => - RestTransport(transportOptions, options, auth, appCheck); + RestTransport(transportOptions, options, appId, auth, appCheck); diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/network/transport_stub.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/network/transport_stub.dart index 2a5fb717a199..07bc379ff160 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/network/transport_stub.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/network/transport_stub.dart @@ -7,7 +7,13 @@ part of firebase_data_connect_transport; /// Default TransportStub to satisfy compilation of the library. class TransportStub implements DataConnectTransport { /// Constructor. - TransportStub(this.transportOptions, this.options, this.auth, this.appCheck); + TransportStub( + this.transportOptions, + this.options, + this.appId, + this.auth, + this.appCheck, + ); /// FirebaseAuth @override @@ -25,6 +31,9 @@ class TransportStub implements DataConnectTransport { @override TransportOptions transportOptions; + @override + String appId; + /// Stub for invoking a mutation. @override Future invokeMutation( @@ -49,8 +58,10 @@ class TransportStub implements DataConnectTransport { } DataConnectTransport getTransport( - TransportOptions transportOptions, - DataConnectOptions options, - FirebaseAuth? auth, - FirebaseAppCheck? appCheck) => - TransportStub(transportOptions, options, auth, appCheck); + TransportOptions transportOptions, + DataConnectOptions options, + String appId, + FirebaseAuth? auth, + FirebaseAppCheck? appCheck, +) => + TransportStub(transportOptions, options, appId, auth, appCheck); diff --git a/packages/firebase_data_connect/firebase_data_connect/test/src/common/common_library_test.dart b/packages/firebase_data_connect/firebase_data_connect/test/src/common/common_library_test.dart index 785d438a9b5f..ece83d750da7 100644 --- a/packages/firebase_data_connect/firebase_data_connect/test/src/common/common_library_test.dart +++ b/packages/firebase_data_connect/firebase_data_connect/test/src/common/common_library_test.dart @@ -65,6 +65,7 @@ void main() { transport = TestDataConnectTransport( transportOptions, dataConnectOptions, + 'testAppId', auth: mockFirebaseAuth, appCheck: mockFirebaseAppCheck, ); @@ -79,8 +80,8 @@ void main() { test('should handle invokeQuery with proper deserializer', () async { final queryName = 'testQuery'; final deserializer = (json) => json; - final result = - await transport.invokeQuery(queryName, deserializer, null, null); + final result = await transport.invokeQuery( + queryName, deserializer, emptySerializer, null); expect(result, isNotNull); }); @@ -88,8 +89,8 @@ void main() { test('should handle invokeMutation with proper deserializer', () async { final queryName = 'testMutation'; final deserializer = (json) => json; - final result = - await transport.invokeMutation(queryName, deserializer, null, null); + final result = await transport.invokeMutation( + queryName, deserializer, emptySerializer, null); expect(result, isNotNull); }); @@ -98,10 +99,10 @@ void main() { // Test class extending DataConnectTransport for testing purposes class TestDataConnectTransport extends DataConnectTransport { - TestDataConnectTransport( - TransportOptions transportOptions, DataConnectOptions options, + TestDataConnectTransport(TransportOptions transportOptions, + DataConnectOptions options, String appId, {FirebaseAuth? auth, FirebaseAppCheck? appCheck}) - : super(transportOptions, options) { + : super(transportOptions, options, appId) { this.auth = auth; this.appCheck = appCheck; } diff --git a/packages/firebase_data_connect/firebase_data_connect/test/src/network/rest_transport_test.dart b/packages/firebase_data_connect/firebase_data_connect/test/src/network/rest_transport_test.dart index 76aaf7aba059..5d3f8c3579ae 100644 --- a/packages/firebase_data_connect/firebase_data_connect/test/src/network/rest_transport_test.dart +++ b/packages/firebase_data_connect/firebase_data_connect/test/src/network/rest_transport_test.dart @@ -40,6 +40,7 @@ void main() { 'testConnector', 'testService', ), + 'testAppId', mockAuth, mockAppCheck, ); @@ -64,6 +65,7 @@ void main() { 'testConnector', 'testService', ), + 'testAppId', mockAuth, mockAppCheck, ); diff --git a/packages/firebase_data_connect/firebase_data_connect/test/src/network/transport_stub_test.dart b/packages/firebase_data_connect/firebase_data_connect/test/src/network/transport_stub_test.dart index eb2484625cac..939e45257461 100644 --- a/packages/firebase_data_connect/firebase_data_connect/test/src/network/transport_stub_test.dart +++ b/packages/firebase_data_connect/firebase_data_connect/test/src/network/transport_stub_test.dart @@ -36,6 +36,7 @@ void main() { final transportStub = TransportStub( mockTransportOptions, mockDataConnectOptions, + 'mockAppId', mockAuth, mockAppCheck, ); @@ -50,6 +51,7 @@ void main() { final transportStub = TransportStub( mockTransportOptions, mockDataConnectOptions, + 'mockAppId', mockAuth, mockAppCheck, ); @@ -69,6 +71,7 @@ void main() { final transportStub = TransportStub( mockTransportOptions, mockDataConnectOptions, + 'mockAppId', mockAuth, mockAppCheck, );