# Refine Apito Data Provider

[![npm version](https://badge.fury.io/js/refine-apito.svg)](https://badge.fury.io/js/refine-apito)
[![npm](https://img.shields.io/npm/dt/refine-apito.svg)](https://www.npmjs.com/package/refine-apito)

A data provider for [Refine v5](https://refine.dev/) that connects to [Apito](https://apito.io/) - An API builder with Serverless Functions

## Features

- 🚀 Full support for all Refine data provider methods
- 🔄 Automatic GraphQL query generation for Apito
- 🔒 Authentication support
- 🛠️ Comprehensive error handling for GraphQL and network errors
- 📝 Support for custom GraphQL queries and mutations
- 🐞 Debug mode for easier troubleshooting
- 🧩 Well-organized code with separate type definitions for better debugging

## GraphQL naming and `resource`

Refine **`resource`** values must match the **stored Apito model id** (canonical **snake_case** such as `food_order`, or legacy camelCase where applicable). Helpers in [`src/apitoGraphqlNames.ts`](src/apitoGraphqlNames.ts) mirror the engine’s naming helpers.

For the full field/type/mutation matrix (including list vs connection filter type strings), see **[GRAPHQL_NAMES.md](./GRAPHQL_NAMES.md)** and the engine doc it links to.

## Installation

```bash
npm install refine-apito
# or
yarn add refine-apito
# or
pnpm add refine-apito
```

## Usage

### Basic Setup

```tsx
import { Refine } from '@refinedev/core';
import { apitoDataProvider } from 'refine-apito';

const App = () => {
  return (
    <Refine
      dataProvider={apitoDataProvider(
        'https://api.apito.io/secured/graphql', // Your Apito GraphQL endpoint
        'YOUR_API_TOKEN' // Your Apito API token
      )}
      // ... other Refine configurations
    >
      {/* ... */}
    </Refine>
  );
};
```

### Debug Mode

For easier debugging, you can use the debug version of the data provider which logs all method calls and their parameters:

```tsx
import { Refine } from '@refinedev/core';
import { debugApitoDataProvider } from 'refine-apito';

const App = () => {
  return (
    <Refine
      dataProvider={debugApitoDataProvider(
        'https://api.apito.io/secured/graphql',
        'YOUR_API_TOKEN'
      )}
      // ... other Refine configurations
    >
      {/* ... */}
    </Refine>
  );
};
```

This will log detailed information about each data provider method call to the console, making it easier to debug issues.

### Tenant-Based Authentication

If you're using tenant-based authentication, set the third parameter to `true`:

```tsx
dataProvider={apitoDataProvider(
  "https://api.apito.io/secured/graphql",
  "INITIAL_TOKEN" // Your Apito API token
)}
```

## API Reference

### `apitoDataProvider(apiUrl, token)`

Creates a data provider for Refine that connects to Apito.

#### Parameters

- `apiUrl` (string): The URL of your Apito GraphQL API endpoint.
- `token` (string): Your Apito API token for authentication.

#### Returns

A Refine data provider object with the following methods:

- `getList`: Fetches a list of resources with pagination, sorting, and filtering.
- `getOne`: Fetches a single resource by ID.
- `create`: Creates a new resource.
- `createMany`: Creates multiple resources at once.
- `update`: Updates an existing resource.
- `deleteOne`: Deletes a resource by ID.
- `custom`: Executes a custom GraphQL query.

### Additional Methods

The data provider also includes these utility methods:

- `getApiClient()`: Returns the GraphQL client instance.
- `getToken()`: Returns the current API token.
- `getApiUrl()`: Returns the API URL.

## Advanced Usage

### Custom Fields

You can specify which fields to include in the response by providing a `fields` array in the `meta` parameter:

```tsx
const { data } = useList({
  resource: 'products',
  meta: {
    fields: ['name', 'price', 'description', 'category'],
  },
});
```

### Connection Fields

For related data, you can specify connection fields:

```tsx
const { data } = useList({
  resource: 'products',
  meta: {
    fields: ['name', 'price'],
    connectionFields: {
      category: 'id name',
      tags: 'id name slug',
    },
  },
});
```

### Custom GraphQL Queries

For more complex queries, you can provide your own GraphQL query:

```tsx
import { gql } from '@urql/core';

const { data } = useList({
  resource: 'products',
  meta: {
    gqlQuery: gql`
      query GetProducts($where: PRODUCTS_INPUT_WHERE_PAYLOAD) {
        productsList(where: $where) {
          id
          data {
            name
            price
            category
          }
          meta {
            created_at
          }
        }
        productsListCount {
          total
        }
      }
    `,
    variables: {
      where: { category: { eq: 'electronics' } },
    },
    queryKey: 'productsList',
  },
});
```

### Filtering

Apito supports various filter operators:

```tsx
const { data } = useList({
  resource: 'products',
  filters: [
    {
      field: 'name',
      operator: 'contains',
      value: 'phone',
    },
    {
      field: 'price',
      operator: 'gt',
      value: 100,
    },
  ],
});
```

### Sorting

```tsx
const { data } = useList({
  resource: 'products',
  sorters: [
    {
      field: 'price',
      order: 'desc',
    },
  ],
});
```

### Pagination

```tsx
const { data } = useList({
  resource: 'products',
  pagination: {
    currentPage: 1,
    pageSize: 10,
  },
});
```

### Alias Fields

You can also use GraphQL aliases for connection fields by providing `aliasFields` alongside `connectionFields`. This is useful when you want to query the same field with different names or create aliases that point to other fields:

```tsx
const { data } = useList({
  resource: 'orders',
  meta: {
    fields: ['id', 'total', 'status'],
    aliasFields: {
      waiter: 'employee',
    },
    connectionFields: {
      foodList: 'id data { name price }',
      customer: 'id data { name }',
      employee: 'id data { full_name }',
      waiter: 'id data { full_name }',
    },
  },
});
```

This will generate a GraphQL query like:

```graphql
{
  orderList {
    id
    data {
      id
      total
      status
    }
    foodList {
      id
      data {
        name
        price
      }
    }
    customer {
      id
      data {
        name
      }
    }
    employee {
      id
      data {
        full_name
      }
    }
    waiter: employee {
      id
      data {
        full_name
      }
    }
  }
}
```

In this example:

- `waiter` is defined as an alias that points to the `employee` field
- The `waiter` connection field specification from `connectionFields` is used for the alias
- This allows you to get the same employee data under two different names in your response

### GraphQL names (1:1 with Apito public schema builder)

The provider and `refine-apito/graphql-names` mirror the same helpers the engine uses when registering queries and mutations:

- `utility.SingularResourceName` / `MultipleResourceName` — Apito engine `open-core/utility/name_extractor.go` (`strcase.ToLowerCamel` + `inflection.Singular`, with `List` / `ListCount` suffix handling)
- `utility.GraphQLTypeName` — `open-core/utility/graphql_typename.go`
- Filter input names — `open-core/schemas/objects/search_filter_arg.go` (`strings.ToUpper(name + "_…")` on the same `name` values the builder passes)

Example: Refine resource `foodOrders` → model name `foodOrder` → GraphQL type fragment **`Foodorder`** → **`Foodorder_Create_Payload`**, **`createFoodorder`**. List root field is **`foodOrderList`**; `$connection` uses **`FOODORDER_CONNECTION_FILTER_CONDITION`** (uppercase of `foodOrder + "_Connection_Filter_Condition"`). Writing Refine-style **`FoodOrder_*`** / **`createFoodOrder`** will not match the schema.

**Fix:** use the exported helpers (or default provider methods) instead of hand-typed strings:

```ts
import {
  apitoSingularGraphQLTypeName,
  apitoMultipleResourceName,
  buildApitoCreateMutation,
} from 'refine-apito/graphql-names';
import { gql } from '@urql/core';

const typeFragment = apitoSingularGraphQLTypeName('foodOrders'); // "Foodorder"
const listField = apitoMultipleResourceName('foodOrders'); // "foodOrderList"
const doc = gql(buildApitoCreateMutation('foodOrders', ['order_no', 'date']));
```

Parity with Go is enforced in tests via [`src/fixtures/goVectors.json`](src/fixtures/goVectors.json) (vectors produced from the engine’s `utility` package).

**Refine `resource` casing:** Use camelCase plurals aligned with Apito (`foodCategories`, `bankAccounts`), or snake/kebab plurals (`food_categories`, `food-categories`), so `strcase.ToLowerCamel` matches the engine. An all-lowercase run-on plural (`foodcategories`) normalizes to `foodcategory` in Go as well—it is not the same as model id `foodCategory`, so list fields will not match the explorer.

Prefer the default `create()` / `getList()` without `meta.gqlMutation` / `meta.gqlQuery` when possible so generated documents always track the engine.

### Error Handling

The data provider includes comprehensive error handling for both GraphQL and network errors. All errors are converted to Refine's `HttpError` format for consistent error handling throughout your application.

### Code Organization for Debugging

The library is organized to make debugging easier:

- **types.ts**: Contains all TypeScript type definitions
- **provider.ts**: Contains the main data provider implementation
- **debug-provider.ts**: Contains a debug version with console logs
- **index.tsx**: Exports everything for external use

This separation makes it easier to navigate the code and set breakpoints when debugging.

## Contributing & Development

### Local Development

To develop this package locally:

```bash
# Install dependencies
pnpm install

# Start development mode
pnpm start

# Test
pnpm test

# Build the package
pnpm build

# Check bundle size
pnpm size
```

### Releasing New Versions

This package uses GitHub Actions for automated releases. To release a new version:

1. Update the version in `package.json`
2. Commit your changes
3. Create and push a new tag:

```bash
git tag v0.1.2  # Use appropriate version
git push origin v0.1.2
```

The GitHub workflow will automatically:

- Build the package
- Publish to npm
- Create a GitHub release

Alternatively, you can use the release script:

```bash
./release.sh 0.1.2  # Replace with your desired version
# Or with a custom commit message:
./release.sh 0.1.2 "feat: add improved error handling"
```

## License

MIT
