skills/api-client/SKILL.md
Build typed API client layers for React frontends. Covers fetch wrapper, error interceptors, request/response types from API envelope, auth header injection, cancellation, and retry patterns. Use when: creating API service modules, handling API errors, building typed fetch wrappers, or integrating with the backend ApiResponse envelope.
npx skillsauth add congiuluc/my-awesome-copilot api-clientInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
ApiResponse<T> envelopeAbortControllerApiResponse<T> envelope to typed results.response.success, throw typed errors.AbortSignal to every fetch call.const API_BASE_URL = import.meta.env.VITE_API_URL ?? '/api';
interface ApiResponse<T> {
success: boolean;
data: T | null;
error: string | null;
}
class ApiError extends Error {
constructor(
message: string,
public status: number,
public code?: string
) {
super(message);
this.name = 'ApiError';
}
}
async function request<T>(
path: string,
options: RequestInit = {},
signal?: AbortSignal
): Promise<T> {
const token = getAccessToken();
const response = await fetch(`${API_BASE_URL}${path}`, {
...options,
signal,
headers: {
'Content-Type': 'application/json',
...(token ? { Authorization: `Bearer ${token}` } : {}),
...options.headers,
},
});
if (!response.ok) {
const body = await response.json().catch(() => null);
throw new ApiError(
body?.error ?? response.statusText,
response.status
);
}
const envelope: ApiResponse<T> = await response.json();
if (!envelope.success) {
throw new ApiError(envelope.error ?? 'Unknown error', response.status);
}
return envelope.data as T;
}
export const productService = {
getAll: (signal?: AbortSignal) =>
request<Product[]>('/products', {}, signal),
getById: (id: string, signal?: AbortSignal) =>
request<Product>(`/products/${encodeURIComponent(id)}`, {}, signal),
create: (data: CreateProductRequest) =>
request<Product>('/products', {
method: 'POST',
body: JSON.stringify(data),
}),
update: (id: string, data: UpdateProductRequest) =>
request<Product>(`/products/${encodeURIComponent(id)}`, {
method: 'PUT',
body: JSON.stringify(data),
}),
delete: (id: string) =>
request<void>(`/products/${encodeURIComponent(id)}`, {
method: 'DELETE',
}),
};
export function useProducts() {
return useQuery({
queryKey: ['products'],
queryFn: ({ signal }) => productService.getAll(signal),
staleTime: 5 * 60 * 1000,
});
}
signal automatically for cancellation.AbortSignal as optional last parameter.if (error instanceof ApiError) {
if (error.status === 401) redirectToLogin();
if (error.status === 404) return <NotFound />;
return <ErrorMessage message={error.message} />;
}
fetch directly in components (use service modules)response.success from the API envelopeencodeURIComponent)AbortSignal)tools
Build VS Code extensions with TypeScript. Covers extension anatomy, activation events, commands, tree views, webview panels, language features, testing, and publishing. Use when: creating a new VS Code extension, adding commands/views/providers, building webview UIs, implementing language server features, testing extensions, or packaging for the marketplace.
development
Track implementations, features, bugs, and releases in a versioning document. Use when: adding a commit, completing a feature, fixing a bug, or preparing a release. Automatically updates CHANGELOG.md following Keep a Changelog format and Semantic Versioning.
development
Write frontend tests using Vitest and React Testing Library. Use when: testing React components, hooks, user interactions, form submissions, accessibility assertions, or mocking API services.
development
Write Angular frontend tests using Jasmine, Karma, and Angular TestBed. Use when: testing Angular components, services, pipes, directives, user interactions, form submissions, accessibility assertions, or mocking HTTP services.