Set up @data-client/endpoint for custom async operations. Wraps existing async functions with Endpoint for use with Data Client hooks. Use after data-client-setup detects non-REST/GraphQL async patterns.
64
75%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Fix and improve this skill with Tessl
tessl review fix ./.cursor/skills/data-client-endpoint-setup/SKILL.mdThis skill configures @data-client/endpoint for wrapping existing async functions. It should be applied after data-client-setup detects custom async patterns that aren't REST or GraphQL.
Install the endpoint package alongside the core package:
# npm
npm install @data-client/endpoint
# yarn
yarn add @data-client/endpoint
# pnpm
pnpm add @data-client/endpointUse @data-client/endpoint when:
See Endpoint for full API documentation.
Scan for existing async functions that fetch data:
Promise<T>Before (existing code):
// src/api/users.ts
export async function getUser(id: string): Promise<User> {
const response = await sdk.users.get(id);
return response.data;
}
export async function listUsers(filters: UserFilters): Promise<User[]> {
const response = await sdk.users.list(filters);
return response.data;
}After (with Endpoint wrapper):
// src/api/users.ts
import { Endpoint } from '@data-client/endpoint';
import { User } from '../schemas/User';
// Original functions (keep for reference or direct use)
async function fetchUser(id: string): Promise<User> {
const response = await sdk.users.get(id);
return response.data;
}
async function fetchUsers(filters: UserFilters): Promise<User[]> {
const response = await sdk.users.list(filters);
return response.data;
}
// Wrapped as Endpoints for use with Data Client hooks
export const getUser = new Endpoint(fetchUser, {
schema: User,
name: 'getUser',
});
export const listUsers = new Endpoint(fetchUsers, {
schema: [User],
name: 'listUsers',
});Configure based on the function's behavior:
export const getUser = new Endpoint(fetchUser, {
// Required for normalization
schema: User,
// Unique name (important if function names get mangled in production)
name: 'getUser',
// Mark as side-effect if it modifies data
sideEffect: true, // for mutations
// Cache configuration
dataExpiryLength: 60000, // 1 minute
errorExpiryLength: 5000, // 5 seconds
// Enable polling
pollFrequency: 30000, // poll every 30 seconds
// Optimistic updates
getOptimisticResponse(snap, id) {
return snap.get(User, { id });
},
});If the default key function doesn't work for your use case:
export const searchUsers = new Endpoint(fetchSearchUsers, {
schema: [User],
name: 'searchUsers',
key({ query, page }) {
// Custom key for complex parameters
return `searchUsers:${query}:${page}`;
},
});import { Endpoint } from '@data-client/endpoint';
import { doc, getDoc, collection, getDocs } from 'firebase/firestore';
import { db } from './firebase';
import { User } from '../schemas/User';
async function fetchUser(id: string): Promise<User> {
const docRef = doc(db, 'users', id);
const docSnap = await getDoc(docRef);
return { id: docSnap.id, ...docSnap.data() } as User;
}
async function fetchUsers(): Promise<User[]> {
const querySnapshot = await getDocs(collection(db, 'users'));
return querySnapshot.docs.map(doc => ({
id: doc.id,
...doc.data(),
})) as User[];
}
export const getUser = new Endpoint(fetchUser, {
schema: User,
name: 'getUser',
});
export const listUsers = new Endpoint(fetchUsers, {
schema: [User],
name: 'listUsers',
});import { Endpoint } from '@data-client/endpoint';
import { supabase } from './supabase';
import { User } from '../schemas/User';
async function fetchUser(id: string): Promise<User> {
const { data, error } = await supabase
.from('users')
.select('*')
.eq('id', id)
.single();
if (error) throw error;
return data;
}
async function fetchUsers(filters?: { role?: string }): Promise<User[]> {
let query = supabase.from('users').select('*');
if (filters?.role) {
query = query.eq('role', filters.role);
}
const { data, error } = await query;
if (error) throw error;
return data;
}
export const getUser = new Endpoint(fetchUser, {
schema: User,
name: 'getUser',
});
export const listUsers = new Endpoint(fetchUsers, {
schema: [User],
name: 'listUsers',
});import { Endpoint } from '@data-client/endpoint';
import { User } from '../schemas/User';
async function fetchUserFromCache(id: string): Promise<User | undefined> {
const db = await openDB('myapp', 1);
return db.get('users', id);
}
async function fetchUsersFromCache(): Promise<User[]> {
const db = await openDB('myapp', 1);
return db.getAll('users');
}
export const getCachedUser = new Endpoint(fetchUserFromCache, {
schema: User,
name: 'getCachedUser',
dataExpiryLength: Infinity, // Never expires
});
export const listCachedUsers = new Endpoint(fetchUsersFromCache, {
schema: [User],
name: 'listCachedUsers',
dataExpiryLength: Infinity,
});import { Endpoint } from '@data-client/endpoint';
import { socket } from './socket';
import { Message } from '../schemas/Message';
async function fetchMessages(roomId: string): Promise<Message[]> {
return new Promise((resolve, reject) => {
socket.emit('getMessages', { roomId }, (response: any) => {
if (response.error) reject(response.error);
else resolve(response.data);
});
});
}
export const getMessages = new Endpoint(fetchMessages, {
schema: [Message],
name: 'getMessages',
});export const createUser = new Endpoint(
async (userData: Omit<User, 'id'>): Promise<User> => {
const { data, error } = await supabase
.from('users')
.insert(userData)
.select()
.single();
if (error) throw error;
return data;
},
{
schema: User,
name: 'createUser',
sideEffect: true,
},
);
export const deleteUser = new Endpoint(
async (id: string): Promise<{ id: string }> => {
const { error } = await supabase.from('users').delete().eq('id', id);
if (error) throw error;
return { id };
},
{
name: 'deleteUser',
sideEffect: true,
},
);const baseUserEndpoint = new Endpoint(fetchUser, {
schema: User,
name: 'getUser',
});
// With different cache settings
export const getUserFresh = baseUserEndpoint.extend({
dataExpiryLength: 0, // Always refetch
});
// With polling
export const getUserLive = baseUserEndpoint.extend({
pollFrequency: 5000, // Poll every 5 seconds
});In production builds, function names may be mangled. Always provide explicit name option:
// Bad - name may become 'a' or similar in production
const getUser = new Endpoint(fetchUser);
// Good - explicit name survives minification
const getUser = new Endpoint(fetchUser, { name: 'getUser' });useSuspense(getUser, id);
ctrl.fetch(createUser, userData);Both hooks and controller methods take endpoint as first argument, with the endpoint's function arguments following.
e823560
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.