285 lines
8.1 KiB
TypeScript
285 lines
8.1 KiB
TypeScript
|
|
// /**
|
||
|
|
// * Example: Using the Type-Safe RPC Client
|
||
|
|
// *
|
||
|
|
// * This file demonstrates how to use the Hono RPC client for type-safe API calls
|
||
|
|
// * from a frontend application or another service.
|
||
|
|
// *
|
||
|
|
// * Based on: https://hono.dev/docs/guides/rpc
|
||
|
|
// */
|
||
|
|
|
||
|
|
// import { hc } from "hono/client";
|
||
|
|
// import type { ApiRoutes } from "../src/client.js";
|
||
|
|
|
||
|
|
// // ============================================================================
|
||
|
|
// // 1. Basic Setup
|
||
|
|
// // ============================================================================
|
||
|
|
|
||
|
|
// // Create a typed client pointing to your API
|
||
|
|
// const client = hc<ApiRoutes>("https://api.yourdomain.com/api/v1", {
|
||
|
|
// headers: {
|
||
|
|
// // Add any default headers here
|
||
|
|
// "Content-Type": "application/json",
|
||
|
|
// },
|
||
|
|
// });
|
||
|
|
|
||
|
|
// // ============================================================================
|
||
|
|
// // 2. Making Authenticated Requests
|
||
|
|
// // ============================================================================
|
||
|
|
|
||
|
|
// async function getUserProfile(token: string) {
|
||
|
|
// // The client knows all available routes and their types!
|
||
|
|
// const res = await client.users.me.$get(
|
||
|
|
// {},
|
||
|
|
// {
|
||
|
|
// headers: {
|
||
|
|
// Authorization: `Bearer ${token}`,
|
||
|
|
// },
|
||
|
|
// }
|
||
|
|
// );
|
||
|
|
|
||
|
|
// if (!res.ok) {
|
||
|
|
// throw new Error(`Failed to fetch user: ${res.status}`);
|
||
|
|
// }
|
||
|
|
|
||
|
|
// // Response is fully typed - TypeScript knows the shape!
|
||
|
|
// const data = await res.json();
|
||
|
|
// return data; // TypeScript knows: { user: User }
|
||
|
|
// }
|
||
|
|
|
||
|
|
// // ============================================================================
|
||
|
|
// // 3. POST Requests with Body
|
||
|
|
// // ============================================================================
|
||
|
|
|
||
|
|
// async function createTablo(token: string, tabloData: { name: string; color: string }) {
|
||
|
|
// const res = await client.tablos.create.$post(
|
||
|
|
// {
|
||
|
|
// json: tabloData, // Typed request body
|
||
|
|
// },
|
||
|
|
// {
|
||
|
|
// headers: {
|
||
|
|
// Authorization: `Bearer ${token}`,
|
||
|
|
// },
|
||
|
|
// }
|
||
|
|
// );
|
||
|
|
|
||
|
|
// if (!res.ok) {
|
||
|
|
// const error = await res.json();
|
||
|
|
// throw new Error(`Failed to create tablo: ${error.error}`);
|
||
|
|
// }
|
||
|
|
|
||
|
|
// return await res.json(); // Typed response
|
||
|
|
// }
|
||
|
|
|
||
|
|
// // ============================================================================
|
||
|
|
// // 4. Route Parameters
|
||
|
|
// // ============================================================================
|
||
|
|
|
||
|
|
// async function getTabloNotes(token: string, tabloId: string) {
|
||
|
|
// // TypeScript will ensure tabloId is provided and typed correctly
|
||
|
|
// const res = await client.notes[":tabloId"].$get(
|
||
|
|
// {
|
||
|
|
// param: { tabloId }, // Type-checked parameter
|
||
|
|
// },
|
||
|
|
// {
|
||
|
|
// headers: {
|
||
|
|
// Authorization: `Bearer ${token}`,
|
||
|
|
// },
|
||
|
|
// }
|
||
|
|
// );
|
||
|
|
|
||
|
|
// if (!res.ok) {
|
||
|
|
// throw new Error("Failed to fetch notes");
|
||
|
|
// }
|
||
|
|
|
||
|
|
// const data = await res.json();
|
||
|
|
// return data.notes; // Fully typed array of notes
|
||
|
|
// }
|
||
|
|
|
||
|
|
// // ============================================================================
|
||
|
|
// // 5. Query Parameters
|
||
|
|
// // ============================================================================
|
||
|
|
|
||
|
|
// async function searchPublicSlots(shortUserId: string, standardName: string) {
|
||
|
|
// // Public routes don't need authentication
|
||
|
|
// const publicClient = hc<ApiRoutes>("https://api.yourdomain.com/api/public");
|
||
|
|
|
||
|
|
// const res = await publicClient.slots[":shortUserId"][":standardName"].$get({
|
||
|
|
// param: {
|
||
|
|
// shortUserId,
|
||
|
|
// standardName,
|
||
|
|
// },
|
||
|
|
// });
|
||
|
|
|
||
|
|
// if (!res.ok) {
|
||
|
|
// throw new Error("Failed to fetch slots");
|
||
|
|
// }
|
||
|
|
|
||
|
|
// return await res.json();
|
||
|
|
// }
|
||
|
|
|
||
|
|
// // ============================================================================
|
||
|
|
// // 6. Error Handling with Type Safety
|
||
|
|
// // ============================================================================
|
||
|
|
|
||
|
|
// type ApiError = {
|
||
|
|
// error: string;
|
||
|
|
// };
|
||
|
|
|
||
|
|
// async function safeApiCall<T>(
|
||
|
|
// apiCall: () => Promise<Response>
|
||
|
|
// ): Promise<{ data?: T; error?: string }> {
|
||
|
|
// try {
|
||
|
|
// const res = await apiCall();
|
||
|
|
|
||
|
|
// if (!res.ok) {
|
||
|
|
// const errorData = (await res.json()) as ApiError;
|
||
|
|
// return { error: errorData.error };
|
||
|
|
// }
|
||
|
|
|
||
|
|
// const data = (await res.json()) as T;
|
||
|
|
// return { data };
|
||
|
|
// } catch (err) {
|
||
|
|
// return {
|
||
|
|
// error: err instanceof Error ? err.message : "Unknown error",
|
||
|
|
// };
|
||
|
|
// }
|
||
|
|
// }
|
||
|
|
|
||
|
|
// // Usage
|
||
|
|
// async function example(token: string) {
|
||
|
|
// const result = await safeApiCall(() =>
|
||
|
|
// client.users.me.$get(
|
||
|
|
// {},
|
||
|
|
// {
|
||
|
|
// headers: { Authorization: `Bearer ${token}` },
|
||
|
|
// }
|
||
|
|
// )
|
||
|
|
// );
|
||
|
|
|
||
|
|
// if (result.error) {
|
||
|
|
// console.error("API Error:", result.error);
|
||
|
|
// return;
|
||
|
|
// }
|
||
|
|
|
||
|
|
// console.log("User data:", result.data);
|
||
|
|
// }
|
||
|
|
|
||
|
|
// // ============================================================================
|
||
|
|
// // 7. React Hook Example
|
||
|
|
// // ============================================================================
|
||
|
|
|
||
|
|
// /*
|
||
|
|
// import { useState, useEffect } from 'react';
|
||
|
|
|
||
|
|
// function useUser(token: string | null) {
|
||
|
|
// const [user, setUser] = useState(null);
|
||
|
|
// const [loading, setLoading] = useState(true);
|
||
|
|
// const [error, setError] = useState<string | null>(null);
|
||
|
|
|
||
|
|
// useEffect(() => {
|
||
|
|
// if (!token) {
|
||
|
|
// setLoading(false);
|
||
|
|
// return;
|
||
|
|
// }
|
||
|
|
|
||
|
|
// getUserProfile(token)
|
||
|
|
// .then(data => {
|
||
|
|
// setUser(data.user);
|
||
|
|
// setError(null);
|
||
|
|
// })
|
||
|
|
// .catch(err => {
|
||
|
|
// setError(err.message);
|
||
|
|
// })
|
||
|
|
// .finally(() => {
|
||
|
|
// setLoading(false);
|
||
|
|
// });
|
||
|
|
// }, [token]);
|
||
|
|
|
||
|
|
// return { user, loading, error };
|
||
|
|
// }
|
||
|
|
// */
|
||
|
|
|
||
|
|
// // ============================================================================
|
||
|
|
// // 8. Advanced: Custom Fetch with Interceptors
|
||
|
|
// // ============================================================================
|
||
|
|
|
||
|
|
// // You can customize the fetch function for things like:
|
||
|
|
// // - Adding auth tokens automatically
|
||
|
|
// // - Logging requests
|
||
|
|
// // - Retry logic
|
||
|
|
// // - Error handling
|
||
|
|
|
||
|
|
// function createAuthenticatedClient(getToken: () => string) {
|
||
|
|
// return hc<ApiRoutes>("https://api.yourdomain.com/api/v1", {
|
||
|
|
// fetch: async (input, init) => {
|
||
|
|
// // Add token to every request
|
||
|
|
// const token = getToken();
|
||
|
|
// const headers = new Headers(init?.headers);
|
||
|
|
// headers.set("Authorization", `Bearer ${token}`);
|
||
|
|
|
||
|
|
// // Log requests in development
|
||
|
|
// if (process.env.NODE_ENV === "development") {
|
||
|
|
// console.log(`API Request: ${input}`);
|
||
|
|
// }
|
||
|
|
|
||
|
|
// // Make the request
|
||
|
|
// const response = await fetch(input, { ...init, headers });
|
||
|
|
|
||
|
|
// // Log responses in development
|
||
|
|
// if (process.env.NODE_ENV === "development") {
|
||
|
|
// console.log(`API Response: ${response.status}`);
|
||
|
|
// }
|
||
|
|
|
||
|
|
// return response;
|
||
|
|
// },
|
||
|
|
// });
|
||
|
|
// }
|
||
|
|
|
||
|
|
// // Usage
|
||
|
|
// const authenticatedClient = createAuthenticatedClient(() => {
|
||
|
|
// // Get token from your auth system
|
||
|
|
// return localStorage.getItem("auth_token") || "";
|
||
|
|
// });
|
||
|
|
|
||
|
|
// // Now all requests automatically include the token!
|
||
|
|
// async function autoAuthExample() {
|
||
|
|
// const res = await authenticatedClient.users.me.$get({});
|
||
|
|
// // Token is automatically added
|
||
|
|
// }
|
||
|
|
|
||
|
|
// // ============================================================================
|
||
|
|
// // 9. Type Exports for Props/State
|
||
|
|
// // ============================================================================
|
||
|
|
|
||
|
|
// // You can extract types from the API responses for use in your components
|
||
|
|
// type User = Awaited<ReturnType<typeof getUserProfile>>["user"];
|
||
|
|
// type TabloNotes = Awaited<ReturnType<typeof getTabloNotes>>;
|
||
|
|
|
||
|
|
// // Usage in React props
|
||
|
|
// /*
|
||
|
|
// interface UserProfileProps {
|
||
|
|
// user: User;
|
||
|
|
// }
|
||
|
|
|
||
|
|
// function UserProfile({ user }: UserProfileProps) {
|
||
|
|
// return <div>{user.name}</div>;
|
||
|
|
// }
|
||
|
|
// */
|
||
|
|
|
||
|
|
// // ============================================================================
|
||
|
|
// // Export for use in your app
|
||
|
|
// // ============================================================================
|
||
|
|
|
||
|
|
// export {
|
||
|
|
// client,
|
||
|
|
// getUserProfile,
|
||
|
|
// createTablo,
|
||
|
|
// getTabloNotes,
|
||
|
|
// searchPublicSlots,
|
||
|
|
// safeApiCall,
|
||
|
|
// createAuthenticatedClient,
|
||
|
|
// };
|
||
|
|
|
||
|
|
// export type { User, TabloNotes };
|
||
|
|
|