Data Provider System
The provider system in the @repo/data
package offers a collection of data providers that implement the CrudProvider interface for various data sources. This allows you to interact with different backends using a consistent API.
Available Providers
The package includes providers for various data sources:
- Supabase: For PostgreSQL databases with Supabase
- Firebase: For Firebase Realtime Database and Firestore
- REST API: For RESTful APIs
- GraphQL: For GraphQL APIs
- Mock: For testing and development
Supabase Provider
The Supabase provider allows you to connect to a PostgreSQL database using Supabase.
Configuration
import { providers } from '@repo/data';
const supabaseProvider = providers.supabase.createProvider({
url: process.env.SUPABASE_URL,
key: process.env.SUPABASE_KEY,
schema: 'public', // optional, defaults to 'public'
options: {
// Additional Supabase client options
}
});
Usage
// Get a list of users
const { data, total } = await supabaseProvider.getList({
resource: 'users',
pagination: { page: 1, perPage: 10 },
sort: { field: 'created_at', order: 'desc' },
filter: { role: 'admin' }
});
// Get a single user
const { data: user } = await supabaseProvider.getOne({
resource: 'users',
id: '123'
});
// Create a user
const { data: newUser } = await supabaseProvider.create({
resource: 'users',
data: { name: 'John Doe', email: 'john@example.com' }
});
// Update a user
const { data: updatedUser } = await supabaseProvider.update({
resource: 'users',
id: '123',
data: { name: 'John Smith' }
});
// Delete a user
const { data: deletedUser } = await supabaseProvider.delete({
resource: 'users',
id: '123'
});
Advanced Features
The Supabase provider supports advanced features like:
- Relations: Automatically fetch related data
- RLS Policies: Work with Row Level Security policies
- Full-text Search: Use Supabase’s full-text search capabilities
- Realtime: Subscribe to realtime updates
// Get users with related posts
const { data } = await supabaseProvider.getList({
resource: 'users',
include: ['posts']
});
// Use full-text search
const { data } = await supabaseProvider.getList({
resource: 'posts',
filter: {
_search: 'typescript react'
}
});
Firebase Provider
The Firebase provider allows you to connect to Firebase Realtime Database or Firestore.
Configuration
import { providers } from '@repo/data';
// Firestore provider
const firestoreProvider = providers.firebase.createProvider({
type: 'firestore',
config: {
apiKey: process.env.FIREBASE_API_KEY,
authDomain: process.env.FIREBASE_AUTH_DOMAIN,
projectId: process.env.FIREBASE_PROJECT_ID
}
});
// Realtime Database provider
const realtimeProvider = providers.firebase.createProvider({
type: 'realtime',
config: {
apiKey: process.env.FIREBASE_API_KEY,
authDomain: process.env.FIREBASE_AUTH_DOMAIN,
databaseURL: process.env.FIREBASE_DATABASE_URL
}
});
Usage
// Get a list of users
const { data, total } = await firestoreProvider.getList({
resource: 'users',
pagination: { page: 1, perPage: 10 },
sort: { field: 'createdAt', order: 'desc' },
filter: { role: 'admin' }
});
// Get a single user
const { data: user } = await firestoreProvider.getOne({
resource: 'users',
id: '123'
});
Advanced Features
The Firebase provider supports advanced features like:
- Subcollections: Work with nested collections
- Transactions: Perform atomic operations
- Realtime Updates: Subscribe to realtime updates
// Get posts from a user's subcollection
const { data } = await firestoreProvider.getList({
resource: 'users/123/posts'
});
// Use transactions
await firestoreProvider.transaction(async (tx) => {
const user = await tx.getOne({ resource: 'users', id: '123' });
await tx.update({
resource: 'users',
id: '123',
data: { postCount: user.data.postCount + 1 }
});
await tx.create({
resource: 'posts',
data: { title: 'New Post', userId: '123' }
});
});
REST API Provider
The REST API provider allows you to connect to a RESTful API.
Configuration
import { providers } from '@repo/data';
const restProvider = providers.rest.createProvider({
apiUrl: 'https://api.example.com',
headers: {
Authorization: `Bearer ${process.env.API_TOKEN}`
}
});
Usage
// Get a list of users
const { data, total } = await restProvider.getList({
resource: 'users',
pagination: { page: 1, perPage: 10 },
sort: { field: 'createdAt', order: 'desc' },
filter: { role: 'admin' }
});
// Get a single user
const { data: user } = await restProvider.getOne({
resource: 'users',
id: '123'
});
Custom Endpoints
The REST provider allows you to customize the endpoints for each operation:
const customRestProvider = providers.rest.createProvider({
apiUrl: 'https://api.example.com',
resources: {
users: {
getList: '/users/search',
getOne: '/users/profile/:id',
create: '/users/register',
update: '/users/update/:id',
delete: '/users/remove/:id'
}
}
});
You can customize how requests are transformed:
const transformedRestProvider = providers.rest.createProvider({
apiUrl: 'https://api.example.com',
requestTransforms: {
getList: (params) => ({
url: `/${params.resource}`,
method: 'GET',
params: {
page: params.pagination?.page,
limit: params.pagination?.perPage,
sort: params.sort ? `${params.sort.field}:${params.sort.order}` : undefined,
...params.filter
}
})
}
});
GraphQL Provider
The GraphQL provider allows you to connect to a GraphQL API.
Configuration
import { providers } from '@repo/data';
const graphqlProvider = providers.graphql.createProvider({
clientOptions: {
uri: 'https://api.example.com/graphql',
headers: {
Authorization: `Bearer ${process.env.API_TOKEN}`
}
}
});
Usage
// Get a list of users
const { data, total } = await graphqlProvider.getList({
resource: 'users',
pagination: { page: 1, perPage: 10 },
sort: { field: 'createdAt', order: 'desc' },
filter: { role: 'admin' }
});
// Get a single user
const { data: user } = await graphqlProvider.getOne({
resource: 'users',
id: '123'
});
Custom Queries
The GraphQL provider allows you to customize the queries for each operation:
const customGraphqlProvider = providers.graphql.createProvider({
clientOptions: {
uri: 'https://api.example.com/graphql'
},
queries: {
users: {
getList: `
query GetUsers($page: Int, $perPage: Int, $sort: String, $filter: UserFilter) {
users(page: $page, perPage: $perPage, sort: $sort, filter: $filter) {
data {
id
name
email
role
}
total
}
}
`,
getOne: `
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
role
}
}
`
}
}
});
Mock Provider
The Mock provider is useful for testing and development.
Configuration
import { providers } from '@repo/data';
const mockProvider = providers.mock.createProvider({
data: {
users: [
{ id: '1', name: 'John Doe', email: 'john@example.com', role: 'admin' },
{ id: '2', name: 'Jane Smith', email: 'jane@example.com', role: 'user' }
],
posts: [
{ id: '1', title: 'First Post', content: 'Hello World', userId: '1' },
{ id: '2', title: 'Second Post', content: 'Another post', userId: '2' }
]
},
// Optional: simulate network delay
delay: 500
});
Usage
// Get a list of users
const { data, total } = await mockProvider.getList({
resource: 'users',
pagination: { page: 1, perPage: 10 },
sort: { field: 'name', order: 'asc' },
filter: { role: 'admin' }
});
// Get a single user
const { data: user } = await mockProvider.getOne({
resource: 'users',
id: '1'
});
Customizing Behavior
You can customize the behavior of the mock provider:
const customMockProvider = providers.mock.createProvider({
data: {
users: [/* ... */]
},
handlers: {
getList: async (params, data) => {
console.log('Getting list', params);
// Custom filtering
let filteredData = data[params.resource] || [];
if (params.filter) {
filteredData = filteredData.filter(item => {
return Object.entries(params.filter).every(([key, value]) => {
return item[key] === value;
});
});
}
// Custom sorting
if (params.sort) {
filteredData.sort((a, b) => {
const aValue = a[params.sort.field];
const bValue = b[params.sort.field];
return params.sort.order === 'asc'
? aValue.localeCompare(bValue)
: bValue.localeCompare(aValue);
});
}
// Custom pagination
const page = params.pagination?.page || 1;
const perPage = params.pagination?.perPage || 10;
const start = (page - 1) * perPage;
const end = start + perPage;
const paginatedData = filteredData.slice(start, end);
return {
data: paginatedData,
total: filteredData.length
};
}
}
});
Creating Custom Providers
You can create custom providers by implementing the CrudProvider interface:
import { base } from '@repo/data';
// Create a custom provider
const customProvider = base.createDataProvider({
type: 'custom',
implementation: {
getList: async (params) => {
// Custom implementation
return { data: [], total: 0 };
},
getOne: async (params) => {
// Custom implementation
return { data: {} };
},
create: async (params) => {
// Custom implementation
return { data: {} };
},
update: async (params) => {
// Custom implementation
return { data: {} };
},
delete: async (params) => {
// Custom implementation
return { data: {} };
}
}
});
// Register the custom provider
base.registerProvider('custom', {
createProvider: (config) => {
// Create and return a provider instance
return customProvider;
}
});
Provider Composition
You can compose multiple providers together to add cross-cutting concerns:
import { base } from '@repo/data';
// Create a logging provider wrapper
const withLogging = (provider) => {
return {
getList: async (params) => {
console.log('Getting list', params);
const result = await provider.getList(params);
console.log('Got list', result);
return result;
},
getOne: async (params) => {
console.log('Getting one', params);
const result = await provider.getOne(params);
console.log('Got one', result);
return result;
},
create: async (params) => {
console.log('Creating', params);
const result = await provider.create(params);
console.log('Created', result);
return result;
},
update: async (params) => {
console.log('Updating', params);
const result = await provider.update(params);
console.log('Updated', result);
return result;
},
delete: async (params) => {
console.log('Deleting', params);
const result = await provider.delete(params);
console.log('Deleted', result);
return result;
}
};
};
// Create a caching provider wrapper
const withCaching = (provider) => {
const cache = new Map();
return {
getList: async (params) => {
const cacheKey = `getList:${params.resource}:${JSON.stringify(params)}`;
if (cache.has(cacheKey)) {
return cache.get(cacheKey);
}
const result = await provider.getList(params);
cache.set(cacheKey, result);
return result;
},
getOne: async (params) => {
const cacheKey = `getOne:${params.resource}:${params.id}`;
if (cache.has(cacheKey)) {
return cache.get(cacheKey);
}
const result = await provider.getOne(params);
cache.set(cacheKey, result);
return result;
},
create: async (params) => {
const result = await provider.create(params);
// Invalidate cache for this resource
invalidateCache(params.resource);
return result;
},
update: async (params) => {
const result = await provider.update(params);
// Invalidate cache for this resource
invalidateCache(params.resource);
return result;
},
delete: async (params) => {
const result = await provider.delete(params);
// Invalidate cache for this resource
invalidateCache(params.resource);
return result;
}
};
function invalidateCache(resource) {
for (const key of cache.keys()) {
if (key.includes(`:${resource}:`)) {
cache.delete(key);
}
}
}
};
// Compose providers
const baseProvider = providers.supabase.createProvider({
url: process.env.SUPABASE_URL,
key: process.env.SUPABASE_KEY
});
const enhancedProvider = withCaching(withLogging(baseProvider));
API Reference
For a complete API reference, please refer to the TypeScript definitions in the package.