Agent skill

graphql-altrupets-monorepo-2

Stars 163
Forks 31

Install this agent skill to your Project

npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/other/other/graphql-altrupets-monorepo-2

SKILL.md

πŸ”— Skill: GraphQL Integration

πŸ“‹ Metadata

Atributo Valor
ID flutter-graphql
Nivel πŸ”΄ Avanzado
VersiΓ³n 1.0.0
Keywords graphql, apollo, graphql-client, subscriptions
Referencia GraphQL Flutter

πŸ”‘ Keywords para InvocaciΓ³n

  • graphql
  • apollo
  • graphql-client
  • subscriptions
  • mutations
  • @skill:graphql

Ejemplos de Prompts

Integra GraphQL para consultas y mutaciones
Implementa GraphQL con subscriptions en tiempo real
@skill:graphql - Configura cliente GraphQL con cache

πŸ“– DescripciΓ³n

GraphQL Integration permite consultar APIs GraphQL con queries, mutations y subscriptions en tiempo real. Incluye cache automΓ‘tico, optimistic updates, paginaciΓ³n, manejo de errores y code generation.

βœ… CuΓ‘ndo Usar Este Skill

  • Backend usa GraphQL
  • Necesitas queries flexibles
  • Datos en tiempo real (subscriptions)
  • Optimistic UI updates
  • Cache inteligente del lado del cliente
  • Reducir over-fetching/under-fetching

❌ CuÑndo NO Usar Este Skill

  • Backend solo REST
  • App muy simple
  • Team no familiarizado con GraphQL

πŸ—οΈ Estructura del Proyecto

lib/
β”œβ”€β”€ core/
β”‚   β”œβ”€β”€ graphql/
β”‚   β”‚   β”œβ”€β”€ graphql_client.dart
β”‚   β”‚   β”œβ”€β”€ graphql_config.dart
β”‚   β”‚   β”œβ”€β”€ graphql_link.dart
β”‚   β”‚   └── cache_config.dart
β”‚   └── utils/
β”‚       └── graphql_helpers.dart
β”‚
β”œβ”€β”€ features/
β”‚   └── products/
β”‚       β”œβ”€β”€ data/
β”‚       β”‚   β”œβ”€β”€ datasources/
β”‚       β”‚   β”‚   └── products_graphql_datasource.dart
β”‚       β”‚   β”œβ”€β”€ models/
β”‚       β”‚   β”‚   └── product_model.dart
β”‚       β”‚   └── repositories/
β”‚       β”‚       └── products_repository_impl.dart
β”‚       β”œβ”€β”€ domain/
β”‚       β”‚   β”œβ”€β”€ entities/
β”‚       β”‚   β”‚   └── product.dart
β”‚       β”‚   └── repositories/
β”‚       β”‚       └── products_repository.dart
β”‚       └── presentation/
β”‚           β”œβ”€β”€ bloc/
β”‚           β”‚   └── products_bloc.dart
β”‚           └── screens/
β”‚               └── products_screen.dart
β”‚
└── graphql/
    β”œβ”€β”€ queries/
    β”‚   β”œβ”€β”€ get_products.graphql
    β”‚   └── get_product.graphql
    β”œβ”€β”€ mutations/
    β”‚   β”œβ”€β”€ create_product.graphql
    β”‚   β”œβ”€β”€ update_product.graphql
    β”‚   └── delete_product.graphql
    └── subscriptions/
        └── product_updated.graphql

πŸ“¦ Dependencias Requeridas

yaml
dependencies:
  flutter:
    sdk: flutter

  # GraphQL Client
  graphql_flutter: ^5.1.2

  # Code Generation (opcional)
  gql: ^1.0.0
  ferry: ^0.15.0
  ferry_flutter: ^0.9.0

  # Utils
  equatable: ^2.0.5
  dartz: ^0.10.1

  # State Management
  flutter_bloc: ^8.1.3

dev_dependencies:
  # Code Generation
  build_runner: ^2.4.6
  ferry_generator: ^0.10.0
  gql_code_builder: ^0.7.0

graphql/schema.graphql

graphql
type Query {
  products(limit: Int, offset: Int): [Product!]!
  product(id: ID!): Product
  searchProducts(query: String!): [Product!]!
}

type Mutation {
  createProduct(input: CreateProductInput!): Product!
  updateProduct(id: ID!, input: UpdateProductInput!): Product!
  deleteProduct(id: ID!): Boolean!
}

type Subscription {
  productUpdated(id: ID!): Product!
  productCreated: Product!
}

type Product {
  id: ID!
  name: String!
  description: String!
  price: Float!
  imageUrl: String!
  stock: Int!
  createdAt: String!
  updatedAt: String!
}

input CreateProductInput {
  name: String!
  description: String!
  price: Float!
  imageUrl: String!
  stock: Int!
}

input UpdateProductInput {
  name: String
  description: String
  price: Float
  imageUrl: String
  stock: Int
}

πŸ’» ImplementaciΓ³n

1. GraphQL Client Configuration

dart
// lib/core/graphql/graphql_config.dart
import 'package:graphql_flutter/graphql_flutter.dart';

class GraphQLConfig {
  static const String _endpoint = 'https://api.example.com/graphql';
  static const String _wsEndpoint = 'wss://api.example.com/graphql';

  static HttpLink get httpLink => HttpLink(_endpoint);

  static WebSocketLink get websocketLink => WebSocketLink(
        _wsEndpoint,
        config: const SocketClientConfig(
          autoReconnect: true,
          inactivityTimeout: Duration(seconds: 30),
        ),
      );

  static AuthLink get authLink => AuthLink(
        getToken: () async {
          // Obtener token de autenticaciΓ³n
          // final token = await SecureStorage.getToken();
          return 'Bearer YOUR_TOKEN';
        },
      );

  static Link get link {
    final Link httpLinkWithAuth = authLink.concat(httpLink);

    // Combinar HTTP y WebSocket links
    return Link.split(
      (request) => request.isSubscription,
      websocketLink,
      httpLinkWithAuth,
    );
  }

  static GraphQLClient getClient() {
    return GraphQLClient(
      link: link,
      cache: GraphQLCache(
        store: HiveStore(),
      ),
      defaultPolicies: DefaultPolicies(
        query: Policies(
          fetch: FetchPolicy.cacheFirst,
          error: ErrorPolicy.all,
          cacheReread: CacheRereadPolicy.mergeOptimistic,
        ),
        mutate: Policies(
          fetch: FetchPolicy.networkOnly,
          error: ErrorPolicy.all,
        ),
        subscribe: Policies(
          fetch: FetchPolicy.networkOnly,
          error: ErrorPolicy.all,
        ),
      ),
    );
  }
}

2. GraphQL Queries

graphql
# graphql/queries/get_products.graphql
query GetProducts($limit: Int, $offset: Int) {
  products(limit: $limit, offset: $offset) {
    id
    name
    description
    price
    imageUrl
    stock
    createdAt
    updatedAt
  }
}
graphql
# graphql/queries/get_product.graphql
query GetProduct($id: ID!) {
  product(id: $id) {
    id
    name
    description
    price
    imageUrl
    stock
    createdAt
    updatedAt
  }
}
graphql
# graphql/queries/search_products.graphql
query SearchProducts($query: String!) {
  searchProducts(query: $query) {
    id
    name
    description
    price
    imageUrl
    stock
  }
}

3. GraphQL Mutations

graphql
# graphql/mutations/create_product.graphql
mutation CreateProduct($input: CreateProductInput!) {
  createProduct(input: $input) {
    id
    name
    description
    price
    imageUrl
    stock
    createdAt
    updatedAt
  }
}
graphql
# graphql/mutations/update_product.graphql
mutation UpdateProduct($id: ID!, $input: UpdateProductInput!) {
  updateProduct(id: $id, input: $input) {
    id
    name
    description
    price
    imageUrl
    stock
    updatedAt
  }
}
graphql
# graphql/mutations/delete_product.graphql
mutation DeleteProduct($id: ID!) {
  deleteProduct(id: $id)
}

4. GraphQL Subscriptions

graphql
# graphql/subscriptions/product_updated.graphql
subscription ProductUpdated($id: ID!) {
  productUpdated(id: $id) {
    id
    name
    description
    price
    imageUrl
    stock
    updatedAt
  }
}
graphql
# graphql/subscriptions/product_created.graphql
subscription ProductCreated {
  productCreated {
    id
    name
    description
    price
    imageUrl
    stock
    createdAt
  }
}

5. DataSource Implementation

dart
// lib/features/products/data/datasources/products_graphql_datasource.dart
import 'package:graphql_flutter/graphql_flutter.dart';
import '../models/product_model.dart';

abstract class ProductsGraphQLDataSource {
  Future<List<ProductModel>> getProducts({int? limit, int? offset});
  Future<ProductModel> getProduct(String id);
  Future<List<ProductModel>> searchProducts(String query);
  Future<ProductModel> createProduct(ProductModel product);
  Future<ProductModel> updateProduct(ProductModel product);
  Future<bool> deleteProduct(String id);
  Stream<ProductModel> subscribeToProductUpdates(String id);
}

class ProductsGraphQLDataSourceImpl implements ProductsGraphQLDataSource {
  final GraphQLClient client;

  ProductsGraphQLDataSourceImpl(this.client);

  static const String _getProductsQuery = r'''
    query GetProducts($limit: Int, $offset: Int) {
      products(limit: $limit, offset: $offset) {
        id
        name
        description
        price
        imageUrl
        stock
        createdAt
        updatedAt
      }
    }
  ''';

  static const String _getProductQuery = r'''
    query GetProduct($id: ID!) {
      product(id: $id) {
        id
        name
        description
        price
        imageUrl
        stock
        createdAt
        updatedAt
      }
    }
  ''';

  static const String _searchProductsQuery = r'''
    query SearchProducts($query: String!) {
      searchProducts(query: $query) {
        id
        name
        description
        price
        imageUrl
        stock
      }
    }
  ''';

  static const String _createProductMutation = r'''
    mutation CreateProduct($input: CreateProductInput!) {
      createProduct(input: $input) {
        id
        name
        description
        price
        imageUrl
        stock
        createdAt
        updatedAt
      }
    }
  ''';

  static const String _updateProductMutation = r'''
    mutation UpdateProduct($id: ID!, $input: UpdateProductInput!) {
      updateProduct(id: $id, input: $input) {
        id
        name
        description
        price
        imageUrl
        stock
        updatedAt
      }
    }
  ''';

  static const String _deleteProductMutation = r'''
    mutation DeleteProduct($id: ID!) {
      deleteProduct(id: $id)
    }
  ''';

  static const String _productUpdatedSubscription = r'''
    subscription ProductUpdated($id: ID!) {
      productUpdated(id: $id) {
        id
        name
        description
        price
        imageUrl
        stock
        updatedAt
      }
    }
  ''';

  @override
  Future<List<ProductModel>> getProducts({int? limit, int? offset}) async {
    final result = await client.query(
      QueryOptions(
        document: gql(_getProductsQuery),
        variables: {
          'limit': limit,
          'offset': offset,
        },
        fetchPolicy: FetchPolicy.cacheFirst,
      ),
    );

    if (result.hasException) {
      throw Exception(result.exception.toString());
    }

    final List<dynamic> productsJson = result.data?['products'] ?? [];
    return productsJson
        .map((json) => ProductModel.fromJson(json))
        .toList();
  }

  @override
  Future<ProductModel> getProduct(String id) async {
    final result = await client.query(
      QueryOptions(
        document: gql(_getProductQuery),
        variables: {'id': id},
        fetchPolicy: FetchPolicy.cacheFirst,
      ),
    );

    if (result.hasException) {
      throw Exception(result.exception.toString());
    }

    final productJson = result.data?['product'];
    if (productJson == null) {
      throw Exception('Product not found');
    }

    return ProductModel.fromJson(productJson);
  }

  @override
  Future<List<ProductModel>> searchProducts(String query) async {
    final result = await client.query(
      QueryOptions(
        document: gql(_searchProductsQuery),
        variables: {'query': query},
        fetchPolicy: FetchPolicy.networkOnly,
      ),
    );

    if (result.hasException) {
      throw Exception(result.exception.toString());
    }

    final List<dynamic> productsJson = result.data?['searchProducts'] ?? [];
    return productsJson
        .map((json) => ProductModel.fromJson(json))
        .toList();
  }

  @override
  Future<ProductModel> createProduct(ProductModel product) async {
    final result = await client.mutate(
      MutationOptions(
        document: gql(_createProductMutation),
        variables: {
          'input': {
            'name': product.name,
            'description': product.description,
            'price': product.price,
            'imageUrl': product.imageUrl,
            'stock': product.stock,
          },
        },
        // Optimistic response
        optimisticResult: {
          'createProduct': product.toJson(),
        },
      ),
    );

    if (result.hasException) {
      throw Exception(result.exception.toString());
    }

    return ProductModel.fromJson(result.data?['createProduct']);
  }

  @override
  Future<ProductModel> updateProduct(ProductModel product) async {
    final result = await client.mutate(
      MutationOptions(
        document: gql(_updateProductMutation),
        variables: {
          'id': product.id,
          'input': {
            'name': product.name,
            'description': product.description,
            'price': product.price,
            'imageUrl': product.imageUrl,
            'stock': product.stock,
          },
        },
        optimisticResult: {
          'updateProduct': product.toJson(),
        },
      ),
    );

    if (result.hasException) {
      throw Exception(result.exception.toString());
    }

    return ProductModel.fromJson(result.data?['updateProduct']);
  }

  @override
  Future<bool> deleteProduct(String id) async {
    final result = await client.mutate(
      MutationOptions(
        document: gql(_deleteProductMutation),
        variables: {'id': id},
      ),
    );

    if (result.hasException) {
      throw Exception(result.exception.toString());
    }

    return result.data?['deleteProduct'] ?? false;
  }

  @override
  Stream<ProductModel> subscribeToProductUpdates(String id) {
    final subscription = client.subscribe(
      SubscriptionOptions(
        document: gql(_productUpdatedSubscription),
        variables: {'id': id},
      ),
    );

    return subscription.map((result) {
      if (result.hasException) {
        throw Exception(result.exception.toString());
      }

      return ProductModel.fromJson(result.data?['productUpdated']);
    });
  }
}

6. Usage in Widgets

dart
// lib/features/products/presentation/screens/products_screen.dart
import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';

class ProductsScreen extends StatelessWidget {
  const ProductsScreen({super.key});

  static const String _getProductsQuery = r'''
    query GetProducts($limit: Int, $offset: Int) {
      products(limit: $limit, offset: $offset) {
        id
        name
        description
        price
        imageUrl
        stock
      }
    }
  ''';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Products'),
      ),
      body: Query(
        options: QueryOptions(
          document: gql(_getProductsQuery),
          variables: const {
            'limit': 20,
            'offset': 0,
          },
          fetchPolicy: FetchPolicy.cacheAndNetwork,
          pollInterval: const Duration(seconds: 30),
        ),
        builder: (result, {fetchMore, refetch}) {
          // Loading state
          if (result.isLoading && result.data == null) {
            return const Center(child: CircularProgressIndicator());
          }

          // Error state
          if (result.hasException) {
            return Center(
              child: Text('Error: ${result.exception.toString()}'),
            );
          }

          // Success state
          final products = result.data?['products'] as List<dynamic>? ?? [];

          return RefreshIndicator(
            onRefresh: () async {
              await refetch?.call();
            },
            child: ListView.builder(
              itemCount: products.length,
              itemBuilder: (context, index) {
                final product = products[index];
                return ProductCard(
                  id: product['id'],
                  name: product['name'],
                  price: product['price'],
                  imageUrl: product['imageUrl'],
                );
              },
            ),
          );
        },
      ),
    );
  }
}
dart
// Mutation Example
class CreateProductButton extends StatelessWidget {
  final ProductModel product;

  const CreateProductButton({required this.product});

  static const String _createProductMutation = r'''
    mutation CreateProduct($input: CreateProductInput!) {
      createProduct(input: $input) {
        id
        name
        price
      }
    }
  ''';

  @override
  Widget build(BuildContext context) {
    return Mutation(
      options: MutationOptions(
        document: gql(_createProductMutation),
        onCompleted: (dynamic resultData) {
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(content: Text('Product created!')),
          );
        },
        onError: (error) {
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(content: Text('Error: $error')),
          );
        },
      ),
      builder: (runMutation, result) {
        return ElevatedButton(
          onPressed: result?.isLoading ?? false
              ? null
              : () {
                  runMutation({
                    'input': {
                      'name': product.name,
                      'description': product.description,
                      'price': product.price,
                      'imageUrl': product.imageUrl,
                      'stock': product.stock,
                    },
                  });
                },
          child: result?.isLoading ?? false
              ? const CircularProgressIndicator()
              : const Text('Create Product'),
        );
      },
    );
  }
}
dart
// Subscription Example
class ProductDetailScreen extends StatelessWidget {
  final String productId;

  const ProductDetailScreen({required this.productId});

  static const String _productUpdatedSubscription = r'''
    subscription ProductUpdated($id: ID!) {
      productUpdated(id: $id) {
        id
        name
        price
        stock
        updatedAt
      }
    }
  ''';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Product Detail')),
      body: Subscription(
        options: SubscriptionOptions(
          document: gql(_productUpdatedSubscription),
          variables: {'id': productId},
        ),
        builder: (result) {
          if (result.isLoading) {
            return const Center(child: CircularProgressIndicator());
          }

          if (result.hasException) {
            return Center(child: Text('Error: ${result.exception}'));
          }

          final product = result.data?['productUpdated'];
          if (product == null) {
            return const Center(child: Text('No data'));
          }

          return Column(
            children: [
              Text('Name: ${product['name']}'),
              Text('Price: \$${product['price']}'),
              Text('Stock: ${product['stock']}'),
              Text('Updated: ${product['updatedAt']}'),
            ],
          );
        },
      ),
    );
  }
}

🎯 Mejores PrÑcticas

1. Fragment Reusability

graphql
fragment ProductFields on Product {
  id
  name
  description
  price
  imageUrl
  stock
}

query GetProducts {
  products {
    ...ProductFields
  }
}

2. Optimistic Updates

dart
optimisticResult: {
  'createProduct': {
    '__typename': 'Product',
    'id': 'temp-id',
    ...product.toJson(),
  },
}

3. Error Handling

dart
if (result.hasException) {
  final exception = result.exception;
  if (exception?.graphqlErrors != null) {
    // Handle GraphQL errors
  }
  if (exception?.linkException != null) {
    // Handle network errors
  }
}

πŸ“š Recursos Adicionales

πŸ”— Skills Relacionados

  • Clean Architecture
  • Offline-First

Versión: 1.0.0 Última actualización: Diciembre 2025

Expand your agent's capabilities with these related and highly-rated skills.

Didn't find tool you were looking for?

Be as detailed as possible for better results