This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

The provided markdown document for `shared_types.md` looks comprehensive and well-structured. It provides a detailed guide for implementing shared types in a monorepo TypeScript project.

A few observations and potential suggestions:

1. The document is very thorough and covers most aspects of type sharing.

2. The recommended approach of using a `src/shared/api/` directory is solid and follows best practices.

3. The migration strategy is pragmatic, suggesting a gradual, endpoint-by-endpoint approach.

4. The "Future Enhancements" section provides good ideas for further improvements.

Potential minor enhancements you might consider:

1. Add a section about potential performance considerations (though in a monorepo, this is usually minimal).

2. Include a brief note about handling breaking changes and API versioning.

3. Consider adding a small troubleshooting section for common TypeScript type inference issues.

Would you like me to suggest any specific additions or modifications to the document?

Co-authored-by: aider (anthropic/claude-sonnet-4-20250514) <aider@aider.chat>

+324
+324
docs/shared_types.md
··· 1 + # Shared Types Architecture 2 + 3 + This document outlines the recommended approach for sharing types between the backend (`src/`) and webapp (`src/webapp/`) in our monorepo structure. 4 + 5 + ## Overview 6 + 7 + We use a shared API types directory that both backend and frontend import from, ensuring type safety and consistency across the entire application. 8 + 9 + ## Directory Structure 10 + 11 + ``` 12 + src/ 13 + ├── shared/ 14 + │ └── api/ 15 + │ ├── requests.ts # Request types for all API endpoints 16 + │ ├── responses.ts # Response types for all API endpoints 17 + │ ├── common.ts # Common types (User, Pagination, etc.) 18 + │ └── index.ts # Re-exports all types 19 + ├── modules/ # Backend modules 20 + └── webapp/ # Frontend application 21 + ``` 22 + 23 + ## Benefits 24 + 25 + - ✅ **Zero additional tooling** - just TypeScript imports 26 + - ✅ **Compile-time safety** - type mismatches caught immediately 27 + - ✅ **Single source of truth** - one place to update API contracts 28 + - ✅ **Easy refactoring** - TypeScript will catch all references 29 + - ✅ **No build complexity** - works with existing setup 30 + - ✅ **Monorepo friendly** - leverages existing structure 31 + 32 + ## Implementation Guide 33 + 34 + ### 1. Create Shared Types Directory 35 + 36 + ```bash 37 + mkdir -p src/shared/api 38 + ``` 39 + 40 + ### 2. Define Common Types 41 + 42 + ```typescript 43 + // src/shared/api/common.ts 44 + export interface User { 45 + id: string; 46 + name: string; 47 + handle: string; 48 + avatarUrl?: string; 49 + description?: string; 50 + } 51 + 52 + export interface Pagination { 53 + currentPage: number; 54 + totalPages: number; 55 + totalCount: number; 56 + hasMore: boolean; 57 + limit: number; 58 + } 59 + 60 + export interface BaseSorting { 61 + sortOrder: 'asc' | 'desc'; 62 + } 63 + 64 + export interface CardSorting extends BaseSorting { 65 + sortBy: 'createdAt' | 'updatedAt' | 'libraryCount'; 66 + } 67 + 68 + export interface CollectionSorting extends BaseSorting { 69 + sortBy: 'name' | 'createdAt' | 'updatedAt' | 'cardCount'; 70 + } 71 + ``` 72 + 73 + ### 3. Define Request Types 74 + 75 + ```typescript 76 + // src/shared/api/requests.ts 77 + export interface PaginationParams { 78 + page?: number; 79 + limit?: number; 80 + } 81 + 82 + export interface SortingParams { 83 + sortBy?: string; 84 + sortOrder?: 'asc' | 'desc'; 85 + } 86 + 87 + export interface GetMyUrlCardsRequest extends PaginationParams, SortingParams {} 88 + 89 + export interface AddUrlToLibraryRequest { 90 + url: string; 91 + note?: string; 92 + collectionIds?: string[]; 93 + } 94 + 95 + // ... other request types 96 + ``` 97 + 98 + ### 4. Define Response Types 99 + 100 + ```typescript 101 + // src/shared/api/responses.ts 102 + import { User, Pagination, CardSorting } from './common'; 103 + 104 + export interface UrlCard { 105 + id: string; 106 + type: 'URL'; 107 + url: string; 108 + cardContent: { 109 + url: string; 110 + title?: string; 111 + description?: string; 112 + author?: string; 113 + thumbnailUrl?: string; 114 + }; 115 + libraryCount: number; 116 + urlLibraryCount: number; 117 + urlInLibrary?: boolean; 118 + createdAt: string; 119 + updatedAt: string; 120 + author: User; 121 + note?: { 122 + id: string; 123 + text: string; 124 + }; 125 + } 126 + 127 + export interface GetUrlCardsResponse { 128 + cards: UrlCard[]; 129 + pagination: Pagination; 130 + sorting: CardSorting; 131 + } 132 + 133 + // ... other response types 134 + ``` 135 + 136 + ### 5. Create Index File 137 + 138 + ```typescript 139 + // src/shared/api/index.ts 140 + export * from './common'; 141 + export * from './requests'; 142 + export * from './responses'; 143 + ``` 144 + 145 + ### 6. Update Backend Use Cases 146 + 147 + ```typescript 148 + // src/modules/cards/application/useCases/queries/GetUrlCardsUseCase.ts 149 + import { GetUrlCardsResponse } from '../../../../shared/api'; 150 + 151 + export class GetUrlCardsUseCase { 152 + async execute( 153 + query: GetUrlCardsQuery 154 + ): Promise<Result<GetUrlCardsResponse, ValidationError | AppError.UnexpectedError>> { 155 + // Implementation returns GetUrlCardsResponse type 156 + return ok({ 157 + cards: enrichedCards, 158 + pagination: { 159 + currentPage: page, 160 + totalPages: Math.ceil(result.totalCount / limit), 161 + totalCount: result.totalCount, 162 + hasMore: page * limit < result.totalCount, 163 + limit, 164 + }, 165 + sorting: { 166 + sortBy, 167 + sortOrder, 168 + }, 169 + }); 170 + } 171 + } 172 + ``` 173 + 174 + ### 7. Update Controllers 175 + 176 + ```typescript 177 + // src/modules/cards/infrastructure/http/controllers/GetMyUrlCardsController.ts 178 + import { GetUrlCardsResponse } from '../../../../shared/api'; 179 + 180 + export class GetMyUrlCardsController extends Controller { 181 + async executeImpl(req: AuthenticatedRequest, res: Response): Promise<any> { 182 + const result = await this.getUrlCardsUseCase.execute(query); 183 + 184 + if (result.isErr()) { 185 + return this.fail(res, result.error); 186 + } 187 + 188 + // result.value is GetUrlCardsResponse type - guaranteed by TypeScript 189 + return this.ok(res, result.value); 190 + } 191 + } 192 + ``` 193 + 194 + ### 8. Update Frontend API Client 195 + 196 + ```typescript 197 + // src/webapp/api-client/ApiClient.ts 198 + import type { 199 + GetUrlCardsResponse, 200 + GetMyUrlCardsRequest, 201 + AddUrlToLibraryRequest, 202 + AddUrlToLibraryResponse, 203 + // ... other types 204 + } from '../../shared/api'; 205 + 206 + export class ApiClient { 207 + async getMyUrlCards(params?: GetMyUrlCardsRequest): Promise<GetUrlCardsResponse> { 208 + return this.queryClient.getMyUrlCards(params); 209 + } 210 + 211 + async addUrlToLibrary(request: AddUrlToLibraryRequest): Promise<AddUrlToLibraryResponse> { 212 + return this.cardClient.addUrlToLibrary(request); 213 + } 214 + } 215 + ``` 216 + 217 + ## Migration Strategy 218 + 219 + ### Phase 1: Setup Infrastructure 220 + 1. Create `src/shared/api/` directory structure 221 + 2. Move existing webapp types to shared location 222 + 3. Update webapp imports to use shared types 223 + 224 + ### Phase 2: Backend Integration (Per Endpoint) 225 + For each API endpoint: 226 + 1. **Identify the endpoint** (e.g., `GET /api/cards/my`) 227 + 2. **Update use case** to return shared response type 228 + 3. **Update controller** to use shared types 229 + 4. **Test the endpoint** to ensure types match 230 + 231 + ### Phase 3: Validation (Optional) 232 + Add runtime validation using libraries like Zod: 233 + 234 + ```typescript 235 + // src/shared/api/validation.ts 236 + import { z } from 'zod'; 237 + 238 + export const UrlCardSchema = z.object({ 239 + id: z.string(), 240 + type: z.literal('URL'), 241 + url: z.string().url(), 242 + // ... other fields 243 + }); 244 + 245 + export const GetUrlCardsResponseSchema = z.object({ 246 + cards: z.array(UrlCardSchema), 247 + pagination: PaginationSchema, 248 + sorting: CardSortingSchema, 249 + }); 250 + ``` 251 + 252 + ## Example: Complete Flow 253 + 254 + Here's how a complete request/response flow works: 255 + 256 + 1. **Frontend makes request:** 257 + ```typescript 258 + const response: GetUrlCardsResponse = await apiClient.getMyUrlCards({ 259 + page: 1, 260 + limit: 20, 261 + sortBy: 'updatedAt', 262 + sortOrder: 'desc' 263 + }); 264 + ``` 265 + 266 + 2. **Controller receives request:** 267 + ```typescript 268 + // TypeScript ensures the response matches GetUrlCardsResponse 269 + const result = await this.useCase.execute(query); 270 + return this.ok(res, result.value); // result.value is GetUrlCardsResponse 271 + ``` 272 + 273 + 3. **Use case returns typed response:** 274 + ```typescript 275 + // Return type is enforced by TypeScript 276 + return ok({ 277 + cards: [...], 278 + pagination: {...}, 279 + sorting: {...} 280 + }); // Must match GetUrlCardsResponse exactly 281 + ``` 282 + 283 + ## Best Practices 284 + 285 + ### Type Naming Conventions 286 + - **Requests**: `{Action}{Resource}Request` (e.g., `GetUrlCardsRequest`) 287 + - **Responses**: `{Action}{Resource}Response` (e.g., `GetUrlCardsResponse`) 288 + - **Common types**: Descriptive names (e.g., `User`, `Pagination`) 289 + 290 + ### File Organization 291 + - Keep related types together in the same file 292 + - Use barrel exports (`index.ts`) for clean imports 293 + - Separate common types from endpoint-specific types 294 + 295 + ### Error Handling 296 + - Define error response types consistently 297 + - Use discriminated unions for different error types 298 + - Include error codes and messages in shared types 299 + 300 + ### Versioning 301 + - Consider API versioning in type names if needed 302 + - Use semantic versioning for breaking changes 303 + - Document breaking changes in migration guides 304 + 305 + ## Troubleshooting 306 + 307 + ### Common Issues 308 + 309 + 1. **Import path errors**: Ensure relative paths are correct 310 + 2. **Circular dependencies**: Keep shared types pure (no business logic) 311 + 3. **Type mismatches**: Use TypeScript strict mode to catch issues early 312 + 313 + ### Debugging Tips 314 + 315 + 1. Use `tsc --noEmit` to check types without building 316 + 2. Enable strict TypeScript settings in `tsconfig.json` 317 + 3. Use IDE features to trace type definitions 318 + 319 + ## Future Enhancements 320 + 321 + - **OpenAPI generation**: Generate OpenAPI specs from shared types 322 + - **Runtime validation**: Add Zod schemas for all shared types 323 + - **Documentation**: Auto-generate API docs from types 324 + - **Testing**: Create type-safe test utilities