### oRPC Client and TanStack Query Setup Source: https://context7.com/aldotestino/hono-orpc/llms.txt Configures an oRPC client using OpenAPI link and generates type-safe utilities for TanStack Query. Includes QueryClient configuration for specific query stale times. ```typescript // apps/web/src/lib/orpc-client.ts import contract from "@hono-orpc/api/contract"; import { createORPCClient } from "@orpc/client"; import { OpenAPILink } from "@orpc/openapi-client/fetch"; import { createTanstackQueryUtils } from "@orpc/tanstack-query"; const link = new OpenAPILink(contract, { url: `${window.location.origin}/api/rpc`, eventIteratorKeepAliveEnabled: true, eventIteratorKeepAliveInterval: 5000, }); export const client = createORPCClient(link); export const orpc = createTanstackQueryUtils(client); // QueryClient config — zero staleTime for message queries, 5 min for everything else const queryClient = new QueryClient({ defaultOptions: { queries: { refetchOnWindowFocus: false, staleTime: (query) => { const keys = query.queryKey.flat(); if (keys.includes("streamChannelMessages") || keys.includes("getChannelMessages")) return 0; return 300_000; }, }, }, }); ``` -------------------------------- ### User Search API cURL Example Source: https://context7.com/aldotestino/hono-orpc/llms.txt Example of how to call the user search API using cURL, including request method, headers, and JSON payload. ```bash // curl example // curl -X POST http://localhost:3000/api/rpc/user/search \ // -H "Content-Type: application/json" \ // -H "Cookie: " \ // -d '{"query": "alice"}' // => [{ "id": "...", "name": "Alice", "email": "alice@example.com", "image": null }] ``` -------------------------------- ### Better-Auth Configuration and Usage Source: https://context7.com/aldotestino/hono-orpc/llms.txt Configures Better-Auth for email/password and Google OAuth, integrating with a Drizzle ORM adapter for PostgreSQL. Includes examples for frontend client setup and usage in React components. ```typescript // apps/api/src/lib/auth.ts import db from "@hono-orpc/db"; import { betterAuth } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; export const auth = betterAuth({ database: drizzleAdapter(db, { provider: "pg" }), emailAndPassword: { enabled: true }, socialProviders: { google: { prompt: "select_account", clientId: process.env.GOOGLE_CLIENT_ID as string, clientSecret: process.env.GOOGLE_CLIENT_SECRET as string, }, }, }); // Frontend client (apps/web/src/lib/auth.ts) import { createAuthClient } from "better-auth/react"; export const authClient = createAuthClient({ baseURL: window.location.origin }); // Usage in a React sign-in form authClient.signIn.email( { email: "user@example.com", password: "secret123" }, { onError: ({ error }) => toast.error(error.message), onSuccess: () => navigate({ to: "/chat", reloadDocument: true }), } ); // Google OAuth sign-in (single call) authClient.signIn.social({ provider: "google" }); // Reading current session in a route context const { data: session } = authClient.useSession(); // session.user.id, session.user.name, session.user.email ``` -------------------------------- ### oRPC Client Setup with TanStack Query Integration Source: https://github.com/aldotestino/hono-orpc/blob/main/README.md Configure the oRPC client to integrate with TanStack Query. This setup automatically generates query options compatible with TanStack Query. ```typescript import contract from "@hono-orpc/api/contract"; import { createORPCClient } from "@orpc/client"; import { OpenAPILink } from "@orpc/openapi-client/fetch"; import { createTanstackQueryUtils } from "@orpc/tanstack-query"; const link = new OpenAPILink(contract, { url: `${window.location.origin}/api/rpc`, eventIteratorKeepAliveEnabled: true, eventIteratorKeepAliveInterval: 5000, }); export const client = createORPCClient(link); export const orpc = createTanstackQueryUtils(client); ``` -------------------------------- ### Mutations with Query Invalidation for Channel Creation Source: https://github.com/aldotestino/hono-orpc/blob/main/README.md Shows how to use `useMutation` to create a channel and invalidate the channels list query upon successful creation. This ensures the UI reflects the latest data. Requires `useQueryClient`, `useMutation`, and form handling setup. ```typescript // apps/web/src/components/new-channel.tsx const queryClient = useQueryClient(); const { mutateAsync: createChannel, isPending } = useMutation( orpc.chat.channel.createChannel.mutationOptions({ onSuccess: () => { // Invalidate channels list to refetch updated data queryClient.invalidateQueries({ queryKey: orpc.chat.channel.getChannels.queryKey(), }); form.reset(); setOpen(false); }, }) ); const handleSubmit = form.handleSubmit((data) => createChannel(data)); ``` -------------------------------- ### DateTime Tool Implementations Source: https://context7.com/aldotestino/hono-orpc/llms.txt Provides tools to get the current date and time, and to calculate a date based on a day offset. These functions utilize date formatting utilities. ```typescript // packages/ai/src/tools/datetime.ts export const getCurrentDateTime = tool({ description: "Get current date and time. Useful when discussing forecasts.", inputSchema: z.object({}), execute: () => ({ date: format(new Date(), "EEEE, MMMM do, yyyy"), time: format(new Date(), "h:mm a"), dayOfWeek: format(new Date(), "EEEE"), }), }); export const getRelativeDate = tool({ description: "Get date for N days from today.", inputSchema: z.object({ daysOffset: z.number().describe("0=today, 1=tomorrow, -1=yesterday") }), execute: ({ daysOffset }) => ({ date: format(addDays(new Date(), daysOffset), "EEEE, MMMM do"), dayOfWeek: format(addDays(new Date(), daysOffset), "EEEE"), relativeText: daysOffset === 0 ? "today" : daysOffset === 1 ? "tomorrow" : `in ${daysOffset} days`, }), }); ``` -------------------------------- ### Health Check Endpoint cURL Example Source: https://context7.com/aldotestino/hono-orpc/llms.txt Example of how to call the health check API using cURL. ```bash // curl http://localhost:3000/api/rpc/health // => { "status": "ok" } ``` -------------------------------- ### Get Channel Details Source: https://context7.com/aldotestino/hono-orpc/llms.txt Retrieves details of a specific channel, including its participants. Requires authentication and channel membership. ```APIDOC ## GET /chat/channel/{uuid} ### Description Retrieves details of a specific channel, including its participants. ### Method GET ### Endpoint /chat/channel/{uuid} ### Parameters #### Path Parameters - **uuid** (string) - Required - The unique identifier of the channel. #### Query Parameters None #### Request Body None ### Response #### Success Response (200) - **channel** (object) - The channel object with its participants. #### Response Example { "id": "uuid-of-channel", "name": "Project Alpha", "createdAt": "2023-01-02T10:00:00Z", "updatedAt": "2023-01-02T10:00:00Z", "participants": [ { "userId": "user-id-1", "channelId": "uuid-of-channel", "user": { "id": "user-id-1", "username": "Alice" } } ] } ### Errors - UNAUTHORIZED: If the user is not authenticated. - FORBIDDEN: If the user is not a member of the channel. - NOT_FOUND: If the channel with the specified UUID does not exist. ``` -------------------------------- ### Advanced Query Configuration with Custom Stale Time Source: https://github.com/aldotestino/hono-orpc/blob/main/README.md Configures default options for TanStack Query, including a custom `staleTime` function. This example sets a 0 stale time for streaming and message queries to ensure they always refetch, while other data has a 5-minute stale time. ```typescript // apps/web/src/integrations/tanstack-query/root-provider.tsx const queryClient = new QueryClient({ defaultOptions: { queries: { refetchOnWindowFocus: false, staleTime: (query) => { // Custom stale time based on query type const flatQueryKeys = query.queryKey.flat(); if ( flatQueryKeys.includes("streamChannelMessages") || flatQueryKeys.includes("getChannelMessages") ) { return 0; // Always refetch messages } return 300_000; // 5 minutes for other data }, }, }, }); ``` -------------------------------- ### API Layer AI Response Generation Source: https://context7.com/aldotestino/hono-orpc/llms.txt Example of how to use the `generateResponse` function within an API layer to fetch message history and return a text response. It expects a text response and throws an error otherwise. ```typescript export const generateAIResponse = async (channelUuid: string, settings: ChannelSettings["ai"]) => { const lastMessages = await db.query.message.findMany({ where: eq(message.channelUuid, channelUuid), orderBy: desc(message.createdAt), limit: settings.maxMessages, with: { sender: true }, }); const response = await generateResponse({ messages: lastMessages.reverse().map(toModelMessage), model: settings.model, }); const lastPart = response.content.at(-1); if (lastPart?.type !== "text") throw new Error("Expected text response"); return lastPart.text; }; ``` -------------------------------- ### Optimistic Message Sending with Form Reset Source: https://context7.com/aldotestino/hono-orpc/llms.txt Implements a message input component that sends messages using `useMutation`. It includes optimistic UI updates by resetting the form immediately on mutation start and success. ```typescript // apps/web/src/components/message-input.tsx function MessageInput({ channelUuid }: { channelUuid: string }) { const form = useForm({ resolver: zodResolver(z.object({ content: z.string().min(1) })), defaultValues: { content: "" }, }); const { mutateAsync: sendMessage, isPending } = useMutation( orpc.chat.message.sendMessageToChannel.mutationOptions({ onMutate: () => form.reset(), // clear input immediately onSuccess: () => form.reset(), }) ); return (
sendMessage({ content: v.content, uuid: channelUuid }))}>
); } ``` -------------------------------- ### Configure Authentication with Better-Auth Source: https://github.com/aldotestino/hono-orpc/blob/main/README.md Sets up authentication using Better-Auth, supporting email/password and Google OAuth. Requires database adapter configuration. ```typescript // apps/api/src/lib/auth.ts import { betterAuth } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; export const auth = betterAuth({ database: drizzleAdapter(db, { provider: "pg", }), emailAndPassword: { enabled: true, }, socialProviders: { google: { prompt: "select_account", clientId: process.env.GOOGLE_CLIENT_ID as string, clientSecret: process.env.GOOGLE_CLIENT_SECRET as string, }, }, }); ``` -------------------------------- ### Real-time Message Streaming with useQuery Source: https://github.com/aldotestino/hono-orpc/blob/main/README.md Demonstrates how to use `useQuery` with `experimental_streamedQuery` for real-time message streaming. Ensure the `orpc` client and `uuid` are properly defined. ```typescript // Real-time message streaming const { data: liveMessages, isError: isLiveMessagesError, fetchStatus: liveMessagesFetchStatus, } = useQuery({ queryKey: orpc.chat.message.streamChannelMessages.queryKey({ input: { uuid }, }), queryFn: experimental_streamedQuery({ streamFn: ({ signal }) => client.chat.message.streamChannelMessages({ uuid }, { signal }), }), }); ``` -------------------------------- ### Drizzle ORM Migration Commands Source: https://context7.com/aldotestino/hono-orpc/llms.txt Provides common commands for managing Drizzle ORM database migrations using bun. Use `db:generate` to create migration files, `db:migrate` to apply them, `db:push` for development, and `db:studio` to open the Drizzle Studio UI. ```bash // Run migrations // bun run db:generate — generate migration files // bun run db:migrate — apply migrations // bun run db:push — push schema directly (dev) // bun run db:studio — open Drizzle Studio UI ``` -------------------------------- ### Implement Channel API Router Source: https://context7.com/aldotestino/hono-orpc/llms.txt Sets up the Channel API router using the 'implement' function from '@orpc/server'. Includes context for request headers. ```typescript // apps/api/src/modules/chat/channel/channel.router.ts import db from "@hono-orpc/db"; import { channel, channelParticipant } from "@hono-orpc/db/tables"; import { implement } from "@orpc/server"; import { and, eq } from "drizzle-orm"; import { authMiddleware } from "../../../middlewares/auth-middleware"; import { userIsChannelOwnerMiddleware } from "../../../middlewares/user-is-channel-owner-middleware"; import channelContract from "./channel.contract"; const chatRouter = implement(channelContract).$context<{ headers: Headers }>(); ``` -------------------------------- ### Hono Application Entry Point Source: https://context7.com/aldotestino/hono-orpc/llms.txt Sets up the Hono server, integrating oRPC for API endpoints, Better-Auth for authentication, and serving the React SPA. It configures middleware for logging and defines base paths for API routes. ```typescript // apps/api/src/index.ts import { OpenAPIHandler } from "@orpc/openapi/fetch"; import { OpenAPIReferencePlugin } from "@orpc/openapi/plugins"; import { ZodToJsonSchemaConverter } from "@orpc/zod/zod4"; import { Hono } from "hono"; import { logger } from "hono/logger"; import { auth } from "./lib/auth"; import { seedChatAIUser } from "./lib/seed"; import { serveWebApp } from "./middlewares/serve-web-app"; import router from "./modules/router"; await seedChatAIUser(); // Ensure the AI bot user exists in the DB const handler = new OpenAPIHandler(router, { plugins: [ new OpenAPIReferencePlugin({ schemaConverters: [new ZodToJsonSchemaConverter()], specGenerateOptions: { info: { title: "Hono oRPC Chat", version: "0.0.1" }, servers: [{ url: "/api/rpc" }], }, }), ], }); const app = new Hono() .use("*", serveWebApp) // SPA fallback for non-API routes .use(logger()) .basePath("/api") .on(["POST", "GET"], "/auth/**", (c) => auth.handler(c.req.raw)) // Better-Auth .use("/rpc/*", async (c, next) => { const { matched, response } = await handler.handle(c.req.raw, { prefix: "/api/rpc", context: { headers: c.req.raw.headers }, }); if (matched) return c.newResponse(response.body, response); await next(); }); export default app; ``` -------------------------------- ### Suspense Queries for Instant Loading with oRPC Source: https://github.com/aldotestino/hono-orpc/blob/main/README.md Implement instant loading experiences using `useSuspenseQueries` from TanStack Query. This hook works seamlessly with oRPC-generated query options for fetching multiple data points. ```typescript function RouteComponent() { const { uuid } = Route.useParams(); // Multiple suspense queries with type safety const [{ data: channel }, { data: messages }] = useSuspenseQueries({ queries: [ orpc.chat.channel.getChannel.queryOptions({ input: { uuid } }), orpc.chat.message.getChannelMessages.queryOptions({ input: { uuid }, }), ], }); return (

#{channel.name}

{messages.map((message) => ( ))}
); } ``` -------------------------------- ### oRPC Channel API Contract Definitions Source: https://context7.com/aldotestino/hono-orpc/llms.txt Defines the oRPC contract for channel-related APIs using Zod for input/output validation. This contract is shared between backend implementation and frontend client generation. It includes routes for getting, creating, and updating channel settings. ```typescript // apps/api/src/modules/chat/channel/channel.contract.ts import { oc } from "@orpc/contract"; import { z } from "zod/v4"; import { channelSchema, channelParticipantSchema, channelSettingsSchema, userSchema } from "@hono-orpc/db/schema"; // GET /chat/channel — list user's channels const getChannels = oc .route({ method: "GET", path: "/chat/channel", tags: ["chat", "channel"] }) .errors({ UNAUTHORIZED: {} }) .output(z.array(channelSchema)); // POST /chat/channel — create a channel const createChannel = oc .route({ method: "POST", path: "/chat/channel", successStatus: 201, tags: ["chat", "channel"] }) .errors({ UNAUTHORIZED: {}, INTERNAL_SERVER_ERROR: {} }) .input(z.object({ name: z.string().min(1).describe("The name of the channel"), members: z.array(z.string()).min(1).describe("Initial member user IDs"), })) .output(channelSchema); // GET /chat/channel/{uuid} — get channel with participants const getChannel = oc .route({ method: "GET", path: "/chat/channel/{uuid}", tags: ["chat", "channel"] }) .errors({ UNAUTHORIZED: {}, FORBIDDEN: { message: "You are not a member" }, NOT_FOUND: { message: "Channel not found" } }) .input(z.object({ uuid: z.uuid() })) .output(channelSchema.extend({ participants: z.array(channelParticipantSchema.extend({ user: userSchema.nullable() })).nullable(), })); // POST /chat/channel/{uuid}/settings — update AI settings (owner only) const setChannelSettings = oc .route({ method: "POST", path: "/chat/channel/{uuid}/settings", tags: ["chat", "channel"] }) .errors({ UNAUTHORIZED: {}, FORBIDDEN: { message: "You are not the owner" } }) .input(z.object({ uuid: z.uuid(), settings: channelSettingsSchema })) .output(channelSettingsSchema); export default { getChannels, createChannel, getChannel, deleteChannel, leaveChannel, removeMemberFromChannel, addMembersToChannel, getChannelSettings, setChannelSettings, }; ``` -------------------------------- ### Weather Tool Implementations Source: https://context7.com/aldotestino/hono-orpc/llms.txt Includes tools for geocoding locations, fetching current weather, and retrieving weather forecasts. These require an OPENWEATHERMAP_API_KEY and use the OpenWeatherMap API. ```typescript // packages/ai/src/tools/weather.ts — requires OPENWEATHERMAP_API_KEY export const getGeocodeLocation = tool({ description: "Geocode a city name to lat/lon.", inputSchema: z.object({ location: z.string().describe('e.g. "London" or "New York, US"') }), execute: async ({ location }) => { const res = await fetch(`http://api.openweathermap.org/geo/1.0/direct?q=${encodeURIComponent(location)}&limit=1&appid=${API_KEY}`); return (await res.json())[0]; // { lat, lon, name, country } }, }); export const getCurrentWeather = tool({ description: "Get current weather for a lat/lon.", inputSchema: z.object({ lat: z.number(), lon: z.number() }), execute: async ({ lat, lon }) => { const res = await fetch(`https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${API_KEY}&units=metric`); const d = await res.json(); return { temperature: Math.round(d.main.temp), description: d.weather[0].description, humidity: d.main.humidity, windSpeed: Math.round(d.wind.speed * 3.6) }; }, }); export const getWeatherForecast = tool({ description: "Get weather forecast (1–5 days).", inputSchema: z.object({ lat: z.number(), lon: z.number(), days: z.number().optional().default(3) }), execute: async ({ lat, lon, days = 3 }) => { const res = await fetch(`https://api.openweathermap.org/data/2.5/forecast?lat=${lat}&lon=${lon}&appid=${API_KEY}&units=metric`); const d = await res.json(); const cutoff = Date.now() + days * 86400000; return { forecast: d.list .filter((i: any) => i.dt * 1000 <= cutoff) .map((i: any) => ({ datetime: format(new Date(i.dt * 1000), "EEE MMM d, h:mm a"), temperature: Math.round(i.main.temp), description: i.weather[0].description, humidity: i.main.humidity, windSpeed: Math.round(i.wind.speed * 3.6), })), }; }, }); ``` -------------------------------- ### Integrating TanStack Query and Router Devtools Source: https://github.com/aldotestino/hono-orpc/blob/main/README.md Sets up TanStack Query Devtools with custom plugins for Tanstack Router, enabling real-time inspection of route transitions, loader states, query cache, and network requests within the development environment. ```typescript // Integrated devtools for development {import.meta.env.DEV && ( , }, TanStackQueryDevtools, ]} /> )} ``` -------------------------------- ### Real-Time Message Streaming with Suspense and SSE Source: https://context7.com/aldotestino/hono-orpc/llms.txt Fetches historical messages and subscribes to a live SSE stream for new messages. Uses `useSuspenseQueries` for initial data and `useQuery` for the live stream, appending new messages reactively. ```typescript // apps/web/src/routes/_protected/chat.$uuid/index.tsx export const Route = createFileRoute("/_protected/chat/$uuid/")({ loader: async ({ context: { queryClient }, params }) => { await Promise.all([ queryClient.ensureQueryData(orpc.chat.channel.getChannel.queryOptions({ input: { uuid: params.uuid } })), queryClient.ensureQueryData(orpc.chat.message.getChannelMessages.queryOptions({ input: { uuid: params.uuid } })), ]); }, component: RouteComponent, }); function RouteComponent() { const { uuid } = Route.useParams(); // Load historical messages via Suspense const [{ data: channel }, { data: messages }] = useSuspenseQueries({ queries: [ orpc.chat.channel.getChannel.queryOptions({ input: { uuid } }), orpc.chat.message.getChannelMessages.queryOptions({ input: { uuid } }), ], }); // Subscribe to live SSE stream — appends new messages reactively const { data: liveMessages, fetchStatus: liveMessagesFetchStatus, isError } = useQuery({ queryKey: orpc.chat.message.streamChannelMessages.queryKey({ input: { uuid } }), queryFn: experimental_streamedQuery({ streamFn: ({ signal }) => client.chat.message.streamChannelMessages({ uuid }, { signal }), }), }); const { chatContainerRef, handleScroll } = useAutoScroll( messages.length + (liveMessages?.length ?? 0) ); return (

#{channel.name}

{messages.map((m) => )} {liveMessages?.map((m) => )}
); } ``` -------------------------------- ### Create Multi-Model AI Assistant Source: https://github.com/aldotestino/hono-orpc/blob/main/README.md Initializes an OpenRouter client and defines a function to generate AI responses. Supports conditional tool enablement based on the selected model. ```typescript // packages/ai/src/index.ts import { createOpenRouter } from "@openrouter/ai-sdk-provider"; import { generateText, type ModelMessage, stepCountIs } from "ai"; import tools from "./tools"; const SYSTEM_PROMPT = ` You are ChatAI, a human-like participant in a group chat. Reply like a normal user: brief, helpful, and conversational. Do not mention you are an AI unless asked. Style and behavior: - Match the chat's tone; keep replies under 5 sentences - Address people by name when helpful - Ask at most one focused clarifying question when needed - Use emojis sparingly; avoid sounding like a support bot Tool use: - You may call tools when appropriate - Always send a final normal text reply summarizing the result `; const openRouter = createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY, }); export function generateResponse({ messages, model }: GenerateResponseProps) { const modelMessages = messages.map(toModelMessage); const _model = model || "openai/gpt-oss-120b:free"; const enableTools = [ "openrouter/sonoma-dusk-alpha", "openrouter/sonoma-sky-alpha", ].includes(_model); return generateText({ model: openRouter(_model), system: SYSTEM_PROMPT, stopWhen: stepCountIs(10), ...(enableTools && { tools }), // Conditionally enable tools messages: modelMessages, }); } ``` -------------------------------- ### Implement Channel Creation Handler Source: https://github.com/aldotestino/hono-orpc/blob/main/README.md Implements the handler for creating a new channel using oRPC, including authentication middleware and database operations for inserting channel and participant data. ```typescript // apps/api/src/modules/chat/channel/channel.router.ts import { implement } from "@orpc/server"; import { authMiddleware } from "../../../middlewares/auth-middleware"; const chatRouter = implement(channelContract).$context<{ headers: Headers }>(); const createChannel = chatRouter.createChannel .use(authMiddleware) .handler(async ({ context, input, errors }) => { const [ch] = await db .insert(channel) .values({ ...input, ownerId: context.user.id, }) .returning(); if (!ch) { throw errors.INTERNAL_SERVER_ERROR({ message: "Failed to create channel", }); } // Add members and owner to channel await db.insert(channelParticipant).values([ { channelUuid: ch.uuid, userId: context.user.id, role: "owner", }, ...input.members.map((userId) => ({ channelUuid: ch.uuid, userId, })), ]); return ch; }); ``` -------------------------------- ### Prefetching Channel List with Route Loader Source: https://context7.com/aldotestino/hono-orpc/llms.txt Uses a route loader to prefetch the channel list using `queryClient.ensureQueryData` before rendering the chat route component. This ensures data is available upon route entry. ```typescript // apps/web/src/routes/_protected/_bottom-navigation/chat.tsx // Route loader prefetches channel list before render export const Route = createFileRoute("/_protected/_bottom-navigation/chat")({ loader: ({ context: { queryClient } }) => queryClient.ensureQueryData(orpc.chat.channel.getChannels.queryOptions()), component: RouteComponent, }); function RouteComponent() { const { data } = useSuspenseQuery(orpc.chat.channel.getChannels.queryOptions()); return (
{data.map((channel) => (
#{channel.name}
))}
); } ``` -------------------------------- ### Drizzle ORM CLI Configuration Source: https://context7.com/aldotestino/hono-orpc/llms.txt Configuration file for Drizzle Kit CLI. Specifies the output directory for migrations, schema location, dialect, and database credentials. Use this to manage your database schema. ```typescript // packages/db/drizzle.config.ts import { defineConfig } from "drizzle-kit"; export default defineConfig({ out: "./drizzle", schema: "./src/tables", dialect: "postgresql", dbCredentials: { url: process.env.DATABASE_URL! }, verbose: true, }); ``` -------------------------------- ### Create Channel Source: https://github.com/aldotestino/hono-orpc/blob/main/README.md Allows users to create a new chat channel. Requires channel name and a list of initial members. The authenticated user is automatically added as the owner. ```APIDOC ## POST /chat/channel ### Description Create a new channel with a specified name and members. ### Method POST ### Endpoint /chat/channel ### Parameters #### Request Body - **name** (string) - Required - The name of the channel - **members** (array of strings) - Required - The user IDs of the initial members ### Request Example { "name": "general", "members": ["user-id-1", "user-id-2"] } ### Response #### Success Response (201) - **uuid** (string) - The unique identifier for the created channel - **name** (string) - The name of the channel - **ownerId** (string) - The ID of the channel owner #### Response Example { "uuid": "a1b2c3d4-e5f6-7890-1234-567890abcdef", "name": "general", "ownerId": "authenticated-user-id" } ``` -------------------------------- ### Generate AI Response with OpenRouter Source: https://context7.com/aldotestino/hono-orpc/llms.txt This function wraps the Vercel AI SDK's `generateText` to work with OpenRouter. It applies a system prompt and conditionally enables tool-calling. Ensure the OPENROUTER_API_KEY environment variable is set. ```typescript import { createOpenRouter } from "@openrouter/ai-sdk-provider"; import { generateText, type ModelMessage, stepCountIs } from "ai"; import * as tools from "./tools"; import { availableModelsInfo, type OpenRouterModel } from "./types"; import { SYSTEM_PROMPT } from "./utils"; const openRouter = createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY }); export function generateResponse({ messages, model = "openai/gpt-oss-120b:free", }: { messages: ModelMessage[]; model?: OpenRouterModel; }) { const modelInfo = availableModelsInfo[model]; return generateText({ model: openRouter(model), system: SYSTEM_PROMPT, stopWhen: stepCountIs(10), // max 10 tool-call steps ...(modelInfo.tools && { tools }), // conditionally enable tools messages, }); } ``` -------------------------------- ### createChannel Source: https://context7.com/aldotestino/hono-orpc/llms.txt Creates a new channel and seeds initial participants, including the owner, in a single transaction. Requires authentication. ```APIDOC ## createChannel ### Description Creates a new channel and seeds initial participants in a single transaction. ### Method POST (inferred from contract implementation) ### Endpoint /channels (inferred from contract implementation) ### Parameters #### Request Body - **name** (string) - Required - The name of the channel. - **members** (array of string) - Required - A list of user IDs to add as initial members. ### Request Example ```json { "name": "New Project Discussion", "members": ["user-id-1", "user-id-2"] } ``` ### Response #### Success Response (200) - **uuid** (string) - The unique identifier for the created channel. - **name** (string) - The name of the channel. - **ownerId** (string) - The ID of the channel owner. - **settings** (object) - Channel-specific settings. #### Response Example ```json { "uuid": "channel-uuid-123", "name": "New Project Discussion", "ownerId": "owner-user-id", "settings": {} } ``` ``` -------------------------------- ### Update Channel Settings with Direct Cache Update Source: https://context7.com/aldotestino/hono-orpc/llms.txt Update channel settings using `useMutation`. Directly update the cache with the new data on success instead of refetching. ```typescript // apps/web/src/components/channel-settings.tsx const { mutateAsync: updateChannelSettings } = useMutation( orpc.chat.channel.setChannelSettings.mutationOptions({ onSuccess: (data) => { // Update cache directly instead of refetching queryClient.setQueryData( orpc.chat.channel.getChannelSettings.queryKey({ input: { uuid: channelUuid } }), data ); form.reset(data); }, }) ); // Form submits { uuid, settings: { ai: { enabled: true, model: "x-ai/grok-4-fast:free", maxMessages: 15 } } } form.handleSubmit((data) => updateChannelSettings({ uuid: channelUuid, settings: data })); ``` -------------------------------- ### Create Channel with Cache Invalidation Source: https://context7.com/aldotestino/hono-orpc/llms.txt Use `useMutation` to create a new channel. Invalidate the channel list query on success to refetch and display the new channel. ```typescript // apps/web/src/components/new-channel.tsx const queryClient = useQueryClient(); const { mutateAsync: createChannel, isPending } = useMutation( orpc.chat.channel.createChannel.mutationOptions({ onSuccess: () => { // Refetch channel list after creation queryClient.invalidateQueries({ queryKey: orpc.chat.channel.getChannels.queryKey() }); form.reset(); setOpen(false); }, }) ); // Trigger form.handleSubmit((data) => createChannel(data)); // data = { name: "general", members: ["user-id-1", "user-id-2"] } ``` -------------------------------- ### Implement User Search API Handler Source: https://context7.com/aldotestino/hono-orpc/llms.txt Implements the user search handler, querying the database for users matching the query and excluding the current user and the AI bot. ```typescript // Handler — excludes current user and the ChatAI bot const searchUserHandler = userRouter.searchUser .use(authMiddleware) .handler(async ({ input, context }) => { return db.query.user.findMany({ where: and( not(eq(user.id, context.user.id)), not(eq(user.id, CHAT_AI_USER.id)), or( like(user.name, `%${input.query}%`), like(user.email, `%${input.query}%`) ) ), }); }); ``` -------------------------------- ### Serve Static SPA Middleware in Hono Source: https://context7.com/aldotestino/hono-orpc/llms.txt This middleware handles routing for a Single Page Application. API calls are passed through, static assets are served from the './public' directory, and all other requests are directed to 'index.html' for client-side routing. ```typescript // apps/api/src/middlewares/serve-web-app.ts const STATIC_FILE_REGEX = /\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|map)$/; export const serveWebApp: MiddlewareHandler = (c, next) => { if (c.req.path.startsWith("/api")) return next(); // pass API calls through if (c.req.path.match(STATIC_FILE_REGEX)) return serveStatic({ root: "./public" })(c, next); // static assets return serveStatic({ path: "/index.html", root: "./public" })(c, next); // SPA fallback }; ``` -------------------------------- ### Calculator Tool Implementation Source: https://context7.com/aldotestino/hono-orpc/llms.txt Defines a calculator tool with basic arithmetic operations. Requires 'ai' and 'zod' imports. The input schema specifies two numbers and an operation. ```typescript // packages/ai/src/tools/calculator.ts import { tool } from "ai"; import { z } from "zod/v4"; export const calculator = tool({ description: "A calculator tool", inputSchema: z.object({ a: z.number(), b: z.number(), operation: z.enum(["add", "subtract", "multiply", "divide", "power"]), }), execute: ({ a, b, operation }) => { if (operation === "add") return a + b; if (operation === "subtract") return a - b; if (operation === "multiply") return a * b; if (operation === "divide") return a / b; return a ** b; // power }, }); ``` -------------------------------- ### Complex Query Management for Channel Leaving Source: https://github.com/aldotestino/hono-orpc/blob/main/README.md Illustrates how to manage complex queries when leaving a channel, including removing specific channel queries and invalidating the channels list. This function should be called within the `onSuccess` callback of a leave channel mutation. ```typescript // apps/web/src/routes/_protected/chat/$uuid/details.tsx function cleanupQueries() { return Promise.all([ // Remove specific channel queries from cache queryClient.removeQueries({ queryKey: orpc.chat.channel.getChannel.queryKey({ input: { uuid }, }), }), queryClient.removeQueries({ queryKey: orpc.chat.message.getChannelMessages.queryKey({ input: { uuid }, }), }), // Invalidate channels list to reflect changes queryClient.invalidateQueries({ queryKey: orpc.chat.channel.getChannels.queryKey(), }), ]); } const { mutateAsync: leaveChannel } = useMutation( orpc.chat.channel.leaveChannel.mutationOptions({ onSuccess: async () => { await cleanupQueries(); navigate({ to: "/chat" }); }, }) ); ``` -------------------------------- ### Leave/Delete Channel with Query Cleanup Source: https://context7.com/aldotestino/hono-orpc/llms.txt Mutations for leaving or deleting a channel. Includes a `cleanupQueries` function to remove specific queries and invalidate others upon successful mutation. ```typescript // apps/web/src/routes/_protected/chat.$uuid/details.tsx function cleanupQueries() { return Promise.all([ queryClient.removeQueries({ queryKey: orpc.chat.channel.getChannel.queryKey({ input: { uuid } }) }), queryClient.removeQueries({ queryKey: orpc.chat.message.getChannelMessages.queryKey({ input: { uuid } }) }), queryClient.invalidateQueries({ queryKey: orpc.chat.channel.getChannels.queryKey() }), ]); } const { mutateAsync: leaveChannel } = useMutation( orpc.chat.channel.leaveChannel.mutationOptions({ onSuccess: async () => { await cleanupQueries(); navigate({ to: "/chat" }); }, }) ); const { mutateAsync: deleteChannel } = useMutation( orpc.chat.channel.deleteChannel.mutationOptions({ onSuccess: async () => { await cleanupQueries(); navigate({ to: "/chat" }); }, }) ); ``` -------------------------------- ### Create Channel with Participants Transaction Source: https://context7.com/aldotestino/hono-orpc/llms.txt Handles the creation of a new channel and seeds participants in a single database transaction. Requires authentication. ```typescript // Create channel + seed participants in a single transaction const createChannel = chatRouter.createChannel .use(authMiddleware) .handler(async ({ context, input, errors }) => { const [ch] = await db .insert(channel) .values({ ...input, ownerId: context.user.id }) .returning(); if (!ch) throw errors.INTERNAL_SERVER_ERROR({ message: "Failed to create channel" }); await db.insert(channelParticipant).values([ { channelUuid: ch.uuid, userId: context.user.id, role: "owner" }, ...input.members.map((userId) => ({ channelUuid: ch.uuid, userId })), ]); return ch; }); ``` -------------------------------- ### Implement Stream Handler with EventPublisher Source: https://github.com/aldotestino/hono-orpc/blob/main/README.md Implements a stream handler for channel messages using EventPublisher for real-time updates. Requires auth and user channel middleware. Publishes messages to subscribers. ```typescript // apps/api/src/modules/chat/message/message.router.ts import { EventPublisher } from "@orpc/server"; const publisher = new EventPublisher< Record >(); const streamChannelMessages = messageRouter.streamChannelMessages .use(authMiddleware) .use(userInChannelMiddleware) .handler(async function* ({ input, signal }) { // Generator function for streaming data for await (const payload of publisher.subscribe(input.uuid, { signal, // Abort signal for cleanup })) { yield payload; // Stream each message as it arrives } }); // Publishing messages to subscribers const saveAndPublishMessage = async ({ channelUuid, content, sender }) => { const [msg] = await db .insert(message) .values({ channelUuid, content, senderId: sender.id }) .returning(); // Publish to all subscribers of this channel publisher.publish(channelUuid, { ...msg, sender, }); return msg; }; ``` -------------------------------- ### Set Channel Settings with Owner Guard Source: https://context7.com/aldotestino/hono-orpc/llms.txt Updates AI settings for a channel. Access is restricted to the channel owner via middleware. Requires authentication. ```typescript // Update AI settings — owner-only guard applied via middleware const setChannelSettings = chatRouter.setChannelSettings .use(authMiddleware) .use(userIsChannelOwnerMiddleware) .handler(async ({ input }) => { await db.update(channel).set({ settings: input.settings }).where(eq(channel.uuid, input.uuid)); return input.settings; }); ``` -------------------------------- ### List User's Channels Source: https://context7.com/aldotestino/hono-orpc/llms.txt Retrieves a list of channels the authenticated user is a part of. Requires authentication. ```APIDOC ## GET /chat/channel ### Description Retrieves a list of channels the authenticated user is a part of. ### Method GET ### Endpoint /chat/channel ### Parameters #### Query Parameters None #### Request Body None ### Response #### Success Response (200) - **channels** (array) - An array of channel objects. #### Response Example { "channels": [ { "id": "uuid-of-channel-1", "name": "General Discussion", "createdAt": "2023-01-01T12:00:00Z", "updatedAt": "2023-01-01T12:00:00Z" } ] } ### Errors - UNAUTHORIZED: If the user is not authenticated. ``` -------------------------------- ### Define Database Tables with Drizzle ORM Source: https://github.com/aldotestino/hono-orpc/blob/main/README.md Defines 'channel' and 'message' tables using Drizzle ORM for PostgreSQL. Includes schema definitions, default values, and foreign key constraints. ```typescript // packages/db/src/tables/chat.ts import { pgTable, text, uuid, timestamp, json } from "drizzle-orm/pg-core"; export const channel = pgTable("channel", { uuid: uuid().notNull().primaryKey().defaultRandom(), name: text().notNull(), settings: json() .notNull() .default(channelSettingsSchema.parse({ ai: {} })) .$type(), ownerId: text() .notNull() .references(() => user.id, { onDelete: "cascade", }), createdAt: timestamp({ mode: "string", withTimezone: true }) .notNull() .defaultNow(), }); export const message = pgTable("message", { uuid: uuid().notNull().primaryKey().defaultRandom(), senderId: text() .notNull() .references(() => user.id, { onDelete: "cascade", }), content: text().notNull(), channelUuid: uuid() .notNull() .references(() => channel.uuid, { onDelete: "cascade", }), createdAt: timestamp({ mode: "string", withTimezone: true }) .notNull() .defaultNow(), }); ``` -------------------------------- ### Root Context and Auth Guard in TanStack Router Source: https://context7.com/aldotestino/hono-orpc/llms.txt Defines the root context for TanStack Router, including a query client and authentication status. Implements an authentication guard to protect routes, redirecting unauthenticated users. ```typescript // apps/web/src/routes/__root.tsx — root context carrying queryClient + auth type MyRouterContext = { queryClient: QueryClient; auth: ReturnType; }; export const Route = createRootRouteWithContext()({ component: () => ( <>
{import.meta.env.DEV && ( }, TanStackQueryDevtools, ]} /> )} ), }); // apps/web/src/routes/_protected/route.tsx — auth guard export const Route = createFileRoute("/_protected")({ beforeLoad: ({ context: { auth } }) => { if (!auth.data) throw redirect({ to: "/" }); // redirect unauthenticated users }, component: Outlet, }); ``` -------------------------------- ### Drizzle ORM Neon Serverless PostgreSQL Configuration Source: https://context7.com/aldotestino/hono-orpc/llms.txt Configures Drizzle ORM to connect to a Neon serverless PostgreSQL database. Ensure the DATABASE_URL environment variable is set. ```typescript // packages/db/src/index.ts — Neon serverless PostgreSQL import { drizzle } from "drizzle-orm/neon-http"; const db = drizzle(process.env.DATABASE_URL!, { schema }); export default db; ``` -------------------------------- ### TanStack Router: Route Loaders with Prefetching Source: https://github.com/aldotestino/hono-orpc/blob/main/README.md Utilize the `loader` function in TanStack Router to prefetch data during route transitions. This ensures data is available when the component mounts, improving perceived performance. ```typescript export const Route = createFileRoute("/_protected/chat/$uuid/")({ loader: async ({ context: { queryClient }, params }) => { // Prefetch data during route transition await Promise.all([ queryClient.ensureQueryData( orpc.chat.channel.getChannel.queryOptions({ input: { uuid: params.uuid }, }) ), queryClient.ensureQueryData( orpc.chat.message.getChannelMessages.queryOptions({ input: { uuid: params.uuid }, }) ), ]); }, component: RouteComponent, }); ```