This document outlines the plan to refactor the writing application to use tRPC for type-safe API communication between the client and server.
-
LLM Service
- Handles AI model interactions
- Manages different providers (OpenAI, Anthropic, Google)
- Processes templates and generates responses
-
Template Service
- Manages document templates
- Processes templates with variables
- Handles template storage and retrieval
-
Document Store
- Manages document CRUD operations
- Handles document metadata and content
- Manages document relationships
-
AI Role System
- Manages AI role configurations
- Handles role-specific prompts and behaviors
-
LLM Dialog
- Handles AI interactions
- Manages template selection and processing
- Handles context files and document selection
-
Template Tester
- Tests template processing
- Validates template variables
- Shows template previews
-
Document Editor
- Manages document editing
- Handles AI-assisted editing
- Manages document state
npm install @trpc/server @trpc/client @trpc/react-query @trpc/next @tanstack/react-query zod// src/server/trpc.ts
import { initTRPC } from '@trpc/server';
import { ZodError } from 'zod';
const t = initTRPC.create({
errorFormatter({ shape, error }) {
return {
...shape,
data: {
...shape.data,
zodError:
error.cause instanceof ZodError ? error.cause.flatten() : null,
},
};
},
});
export const router = t.router;
export const publicProcedure = t.procedure;- LLM Router
// src/server/routers/llm.ts
export const llmRouter = router({
generateText: publicProcedure
.input(z.object({
prompt: z.string(),
stream: z.boolean().optional(),
aiRole: z.string().optional(),
}))
.mutation(async ({ input }) => {
// Implementation
}),
getModels: publicProcedure
.query(async () => {
// Implementation
}),
});- Template Router
// src/server/routers/template.ts
export const templateRouter = router({
getTemplates: publicProcedure
.query(async () => {
// Implementation
}),
processTemplate: publicProcedure
.input(z.object({
templateId: z.string(),
variables: z.record(z.string()),
}))
.mutation(async ({ input }) => {
// Implementation
}),
});- Document Router
// src/server/routers/document.ts
export const documentRouter = router({
getDocuments: publicProcedure
.query(async () => {
// Implementation
}),
updateDocument: publicProcedure
.input(z.object({
id: z.string(),
content: z.string(),
}))
.mutation(async ({ input }) => {
// Implementation
}),
});- AI Roles Router
// src/server/routers/ai-roles.ts
export const aiRolesRouter = router({
getAvailableRoles: publicProcedure
.query(async () => {
// Implementation
}),
getRoleSystemPrompt: publicProcedure
.input(z.object({
role: z.string(),
}))
.query(async ({ input }) => {
// Implementation
}),
});- KV Cache Router
// src/server/routers/kv-cache.ts
export const kvCacheRouter = router({
flushCache: publicProcedure
.mutation(async () => {
// Implementation
}),
getDebugInfo: publicProcedure
.input(z.object({
sessionId: z.string(),
}))
.query(async ({ input }) => {
// Implementation
}),
});- Config Router
// src/server/routers/config.ts
export const configRouter = router({
getConfig: publicProcedure
.query(async () => {
// Implementation
}),
updateConfig: publicProcedure
.input(z.object({
// Configuration fields
apiKeys: z.record(z.string()).optional(),
defaultModel: z.string().optional(),
}))
.mutation(async ({ input }) => {
// Implementation
}),
});- Folders Router
// src/server/routers/folders.ts
export const foldersRouter = router({
getFolders: publicProcedure
.query(async () => {
// Implementation
}),
createFolder: publicProcedure
.input(z.object({
name: z.string(),
parentId: z.string().optional(),
}))
.mutation(async ({ input }) => {
// Implementation
}),
});- Admin Router
// src/server/routers/admin.ts
export const adminRouter = router({
getSystemStatus: publicProcedure
.query(async () => {
// Implementation
}),
clearAllData: publicProcedure
.input(z.object({
confirmation: z.literal('CONFIRM_CLEAR_ALL_DATA'),
}))
.mutation(async ({ input }) => {
// Implementation
}),
});// src/server/routers/_app.ts
import { router } from '../trpc';
import { llmRouter } from './llm';
import { templateRouter } from './template';
import { documentRouter } from './document';
import { aiRolesRouter } from './ai-roles';
import { kvCacheRouter } from './kv-cache';
import { configRouter } from './config';
import { foldersRouter } from './folders';
import { adminRouter } from './admin';
export const appRouter = router({
llm: llmRouter,
template: templateRouter,
document: documentRouter,
aiRoles: aiRolesRouter,
kvCache: kvCacheRouter,
config: configRouter,
folders: foldersRouter,
admin: adminRouter,
});
export type AppRouter = typeof appRouter;// src/utils/trpc.ts
import { createTRPCReact } from '@trpc/react-query';
import { type AppRouter } from '@/server/routers/_app';
export const trpc = createTRPCReact<AppRouter>();- LLM Store
// src/lib/store.ts
import { trpc } from '@/utils/trpc';
export const useLLMStore = create<LLMStore>((set) => ({
// ... existing state
generateText: async (params) => {
const result = await trpc.llm.generateText.mutate(params);
return result;
},
}));- Document Store
// src/lib/store.ts
export const useDocumentStore = create<DocumentStore>((set) => ({
// ... existing state
updateDocument: async (id, content) => {
const result = await trpc.document.updateDocument.mutate({ id, content });
return result;
},
}));- LLM Dialog
// src/components/llm-dialog.tsx
export function LLMDialog({ isOpen, onClose, selectedText, position, editor, selection }: LLMDialogProps) {
const utils = trpc.useUtils();
const generateText = trpc.llm.generateText.useMutation({
onSuccess: (data) => {
// Handle success
},
});
// ... rest of component
}- Template Tester
// src/components/template-tester.tsx
export function TemplateTester() {
const { data: templates } = trpc.template.getTemplates.useQuery();
const processTemplate = trpc.template.processTemplate.useMutation();
// ... rest of component
}- Remove Existing API Routes
- Delete
/api/llm/route.ts - Delete
/api/templates/route.ts - Delete
/api/documents/route.ts - Delete
/api/ai-roles/route.ts - Delete
/api/chat/route.ts - Delete
/api/config/route.ts - Delete
/api/admin/route.ts - Delete
/api/folders/route.ts - Delete
/api/backlinks/route.ts - Delete
/api/patterns/route.ts - Delete
/api/pattern-folders/route.ts - Delete
/api/vault/route.ts - Delete
/api/filter/route.ts
- Create tRPC API Handler
// src/app/api/trpc/[trpc]/route.ts
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
import { appRouter } from '@/server/routers/_app';
const handler = (req: Request) =>
fetchRequestHandler({
endpoint: '/api/trpc',
req,
router: appRouter,
createContext: () => ({}),
});
export { handler as GET, handler as POST };- Type Safety
- Ensure all API endpoints are properly typed
- Validate input/output schemas
- Test error handling
- Performance Testing
- Measure API response times
- Test concurrent requests
- Validate caching behavior
- Integration Testing
- Test end-to-end workflows
- Validate error scenarios
- Test offline behavior
- Environment Setup
- Update environment variables
- Configure production endpoints
- Set up monitoring
- Build Process
- Update build configuration
- Optimize bundle size
- Configure caching
- Install dependencies
- Set up tRPC server
- Create basic routers
- Update client configuration
- Migrate LLM service
- Migrate template service
- Migrate document service
- Update store management
- Update LLM Dialog
- Update Template Tester
- Update Document Editor
- Test component integration
- Implement comprehensive testing
- Optimize performance
- Add error handling
- Document changes
- Version Control
- Maintain separate branches for refactoring
- Keep detailed commit history
- Document all changes
- Backup Strategy
- Backup current API routes
- Document current functionality
- Maintain test coverage
- Rollback Procedure
- Revert to previous version
- Restore API routes
- Validate functionality
- Functionality
- All current features work as expected
- No regression in existing functionality
- Improved type safety
- Performance
- Equal or better response times
- Reduced bundle size
- Improved caching
- Developer Experience
- Better type safety
- Improved error handling
- Easier debugging
- Week 1: Setup and Basic Integration
- Week 2: Core Functionality Migration
- Week 3: Component Updates
- Week 4: Testing and Optimization
- Review and approve refactoring plan
- Set up development environment
- Begin Phase 1 implementation
- Schedule regular progress reviews
The current plan needs to account for streaming responses from LLM services. tRPC provides streaming capabilities that should be leveraged:
// src/server/routers/llm.ts
export const llmRouter = router({
// ... existing routes
generateTextStream: publicProcedure
.input(z.object({
prompt: z.string(),
aiRole: z.string().optional(),
}))
.subscription(async ({ input }) => {
// Return an observable that emits chunks of the response
return observable<string>((emit) => {
// Implementation of streaming
// Each emit.next() will send a chunk to the client
// Function to clean up when subscription is cancelled
return () => {
// Cleanup logic
};
});
}),
});The plan should include context creation for authentication and request tracking:
// src/server/context.ts
import { inferAsyncReturnType } from '@trpc/server';
import { FetchCreateContextFnOptions } from '@trpc/server/adapters/fetch';
export async function createContext({ req }: FetchCreateContextFnOptions) {
// Get session from cookie
const sessionId = req.cookies.get('session-id')?.value;
return {
sessionId,
isAdmin: false, // Implement admin check logic
};
}
export type Context = inferAsyncReturnType<typeof createContext>;The application appears to have a JSX VM component that should be included in the refactoring:
// src/server/routers/jsx-vm.ts
export const jsxVmRouter = router({
executeTemplate: publicProcedure
.input(z.object({
template: z.string(),
variables: z.record(z.any()),
}))
.mutation(async ({ input }) => {
// Implementation
}),
});Implement consistent error handling across all routers:
// src/server/utils/error-handling.ts
export class TRPCCustomError extends Error {
constructor(
message: string,
public code: 'BAD_REQUEST' | 'UNAUTHORIZED' | 'FORBIDDEN' | 'NOT_FOUND' | 'INTERNAL_SERVER_ERROR',
public cause?: unknown
) {
super(message);
this.name = 'TRPCCustomError';
}
}- Install required dependencies:
@trpc/server,@trpc/client,@trpc/react-query,@tanstack/react-query,zod - Set up tRPC server infrastructure
- Create
src/server/trpc.tswith base setup - Create context provider in
src/server/context.ts - Set up error handling utilities
- Create
- Create core routers:
- LLM Router (
src/server/routers/llm.ts) - Templates Router (
src/server/routers/template.ts) - Documents Router (
src/server/routers/document.ts) - AI Roles Router (
src/server/routers/ai-roles.ts) - Config Router (
src/server/routers/config.ts) - KV Cache Router (
src/server/routers/kv-cache.ts) - Folders Router (
src/server/routers/folders.ts) - Admin Router (
src/server/routers/admin.ts) - Backlinks Router (
src/server/routers/backlinks.ts) - Patterns Router (
src/server/routers/patterns.ts)
- LLM Router (
- Create and export root router (
src/server/routers/_app.ts) - Implement subscription handlers for streaming responses
- Set up tRPC client in
src/utils/trpc.ts - Create Provider wrapper for the application
- Update Next.js Provider in
src/app/providers.tsx
- Update all stores to use tRPC client:
- LLM Store
- Document Store
- Template Store
- Config Store
- Folders Store
- Refactor store methods to handle optimistic updates
- Update UI components to use tRPC hooks:
- LLM Dialog
- Template Tester
- Document Editor
- AI Role Switcher
- Settings Panel
- Folder Browser
- Admin Panel
- Create tRPC handler at
src/app/api/trpc/[trpc]/route.ts - Remove all existing API routes after confirming functionality
- Update environment configuration for production deployment
- Create automated tests for all routers
- Test streaming functionality
- Test error handling
- Performance testing for core workflows
- Cross-browser testing
- Update project documentation
- Create type definitions for common types
- Remove unused code and dependencies
- Optimize bundle size
-
Streaming Performance
- Risk: Streaming implementations might not perform as well as the current solution
- Mitigation: Benchmark and optimize streaming performance, consider fallback options
-
Migration Complexity
- Risk: Large-scale refactoring might introduce bugs or regressions
- Mitigation: Implement in phases, maintain comprehensive tests, provide fallback mechanisms
-
Browser Compatibility
- Risk: Some features might not work in all browsers
- Mitigation: Test across browsers, implement polyfills where necessary