refine-apito
Version:
A data provider for Refine that connects to Apito - a headless CMS and backend builder.
407 lines (315 loc) โข 10.5 kB
Markdown
# Refine Apito Data Provider
[](https://badge.fury.io/js/refine-apito)
[](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