# Shared Types Architecture - npm Workspaces Implementation This document outlines the implementation plan for sharing types between the backend (`src/`) and webapp (`src/webapp/`) using npm workspaces in our monorepo structure. ## Overview We use npm workspaces to create a shared types package that both backend and frontend import from as a proper npm dependency, ensuring type safety and consistency across the entire application. ## Architecture Decision: npm Workspaces We chose npm workspaces over simpler approaches because: - ✅ **Industry standard** - Professional monorepo structure - ✅ **Proper dependency management** - npm handles versioning and dependencies - ✅ **Scalability** - Easy to add more packages (mobile app, CLI tools, etc.) - ✅ **Build isolation** - Each package has its own build process - ✅ **Publishing ready** - Can publish shared types as separate npm package - ✅ **IDE support** - Better IntelliSense and go-to-definition - ✅ **Version management** - Can version shared types independently ## Final Directory Structure ``` annos/ ├── package.json # Workspace root ├── src/ │ ├── types/ # @annos/types package │ │ ├── package.json │ │ ├── tsconfig.json │ │ ├── src/ │ │ │ ├── api/ │ │ │ │ ├── index.ts # Re-exports all types │ │ │ │ ├── common.ts # Common types (User, Pagination, etc.) │ │ │ │ ├── requests.ts # Request types for all API endpoints │ │ │ │ └── responses.ts # Response types for all API endpoints │ │ │ └── index.ts # Main entry point │ │ └── dist/ # Compiled output │ ├── shared/ # EXISTING: Backend shared utilities │ ├── modules/ # Backend modules │ └── webapp/ │ ├── package.json # @annos/webapp package │ └── ... ``` ## Implementation Plan ### Phase 1: Setup Workspace Infrastructure #### Step 1.1: Configure Root Workspace Update root `package.json`: ```json { "name": "annos", "version": "1.0.0", "workspaces": ["src/types", "src/webapp", "."], "scripts": { "build:types": "npm run build --workspace=@annos/types", "dev:types": "npm run dev --workspace=@annos/types", "build:webapp": "npm run build --workspace=@annos/webapp", "dev:webapp": "npm run dev --workspace=@annos/webapp", "dev:all": "npm run dev:types & npm run dev:webapp & npm run dev:app:inner" } } ``` #### Step 1.2: Create Shared Types Package Create `src/types/package.json`: ```json { "name": "@annos/types", "version": "1.0.0", "description": "Shared TypeScript types for Annos API", "main": "dist/index.js", "types": "dist/index.d.ts", "files": ["dist/**/*"], "scripts": { "build": "tsc", "dev": "tsc --watch", "clean": "rm -rf dist" }, "devDependencies": { "typescript": "^5.8.3" } } ``` Create `src/types/tsconfig.json`: ```json { "compilerOptions": { "target": "ES2020", "module": "CommonJS", "lib": ["ES2020"], "outDir": "./dist", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "declaration": true, "declarationMap": true, "sourceMap": true }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] } ``` #### Step 1.3: Update Webapp Package Update `src/webapp/package.json`: ```json { "name": "@annos/webapp", "dependencies": { "@annos/types": "workspace:*" // ... existing dependencies } } ``` ### Phase 2: Migrate Types to Shared Package #### Step 2.1: Create Shared Type Files Move and organize existing webapp types into the shared package: **src/types/src/api/common.ts:** ```typescript export interface User { id: string; name: string; handle: string; avatarUrl?: string; description?: string; } export interface Pagination { currentPage: number; totalPages: number; totalCount: number; hasMore: boolean; limit: number; } export interface BaseSorting { sortOrder: 'asc' | 'desc'; } export interface CardSorting extends BaseSorting { sortBy: 'createdAt' | 'updatedAt' | 'libraryCount'; } export interface CollectionSorting extends BaseSorting { sortBy: 'name' | 'createdAt' | 'updatedAt' | 'cardCount'; } export interface FeedPagination extends Pagination { nextCursor?: string; } ``` **src/types/src/api/requests.ts:** ```typescript // Copy all request types from src/webapp/api-client/types/requests.ts export interface PaginationParams { page?: number; limit?: number; } export interface SortingParams { sortBy?: string; sortOrder?: 'asc' | 'desc'; } // ... all other request types ``` **src/types/src/api/responses.ts:** ```typescript import { User, Pagination, CardSorting, CollectionSorting, FeedPagination, } from './common'; // Copy all response types from src/webapp/api-client/types/responses.ts export interface UrlCard { id: string; type: 'URL'; url: string; // ... rest of UrlCard interface } // ... all other response types ``` **src/types/src/api/index.ts:** ```typescript export * from './common'; export * from './requests'; export * from './responses'; ``` **src/types/src/index.ts:** ```typescript export * from './api'; ``` #### Step 2.2: Build Shared Types ```bash cd src/types npm run build ``` ### Phase 3: Update Frontend to Use Shared Types #### Step 3.1: Install Shared Types Dependency ```bash npm install --workspace=@annos/webapp ``` #### Step 3.2: Update Frontend Imports Replace all imports in webapp files: ```typescript // OLD: src/webapp/api-client/ApiClient.ts import type { GetUrlCardsResponse, AddUrlToLibraryRequest, } from './types/responses'; // NEW: import type { GetUrlCardsResponse, AddUrlToLibraryRequest } from '@annos/types'; ``` #### Step 3.3: Remove Old Type Files ```bash rm -rf src/webapp/api-client/types/ ``` ### Phase 4: Update Backend to Use Shared Types #### Step 4.1: Install Shared Types in Backend Add to root `package.json` dependencies: ```json { "dependencies": { "@annos/types": "workspace:*" } } ``` #### Step 4.2: Update Use Cases ```typescript // src/modules/cards/application/useCases/queries/GetUrlCardsUseCase.ts import { GetUrlCardsResponse } from '@annos/types'; export class GetUrlCardsUseCase { async execute( query: GetUrlCardsQuery, ): Promise< Result > { // Implementation must return GetUrlCardsResponse type return ok({ cards: enrichedCards, pagination: { currentPage: page, totalPages: Math.ceil(result.totalCount / limit), totalCount: result.totalCount, hasMore: page * limit < result.totalCount, limit, }, sorting: { sortBy, sortOrder, }, }); } } ``` #### Step 4.3: Update Controllers ```typescript // src/modules/cards/infrastructure/http/controllers/GetMyUrlCardsController.ts import { GetUrlCardsResponse } from '@annos/types'; export class GetMyUrlCardsController extends Controller { async executeImpl(req: AuthenticatedRequest, res: Response): Promise { const result = await this.getUrlCardsUseCase.execute(query); if (result.isErr()) { return this.fail(res, result.error); } // result.value is GetUrlCardsResponse type - guaranteed by TypeScript return this.ok(res, result.value); } } ``` ### Phase 5: Development Workflow #### Step 5.1: Development Scripts Add to root `package.json`: ```json { "scripts": { "dev": "concurrently \"npm run dev:types\" \"npm run dev:webapp\" \"npm run dev:app:inner\"", "dev:types": "npm run dev --workspace=@annos/types", "build:all": "npm run build:types && npm run build:webapp && npm run build" } } ``` #### Step 5.2: Type Development Workflow 1. **Make type changes** in `src/types/src/api/` 2. **Shared types auto-rebuild** (if using `npm run dev:types`) 3. **Both frontend and backend** get updated types automatically 4. **TypeScript compiler** catches any mismatches immediately ### Phase 6: Testing and Validation #### Step 6.1: Type Safety Validation ```bash # Check all TypeScript compilation npm run type-check npm run type-check --workspace=@annos/webapp npm run build:types ``` #### Step 6.2: Runtime Validation (Optional) Add Zod schemas for runtime validation: **src/types/src/validation/index.ts:** ```typescript import { z } from 'zod'; export const UrlCardSchema = z.object({ id: z.string(), type: z.literal('URL'), url: z.string().url(), // ... other fields }); export const GetUrlCardsResponseSchema = z.object({ cards: z.array(UrlCardSchema), pagination: z.object({ currentPage: z.number(), totalPages: z.number(), totalCount: z.number(), hasMore: z.boolean(), limit: z.number(), }), sorting: z.object({ sortBy: z.enum(['createdAt', 'updatedAt', 'libraryCount']), sortOrder: z.enum(['asc', 'desc']), }), }); ``` ## Migration Checklist ### Phase 1: Infrastructure ✅ - [ ] Update root `package.json` with workspaces - [ ] Create `src/types/package.json` - [ ] Create `src/types/tsconfig.json` - [ ] Update `src/webapp/package.json` dependencies - [ ] Run `npm install` to setup workspace ### Phase 2: Type Migration ✅ - [ ] Create `src/types/src/api/common.ts` - [ ] Create `src/types/src/api/requests.ts` - [ ] Create `src/types/src/api/responses.ts` - [ ] Create `src/types/src/api/index.ts` - [ ] Create `src/types/src/index.ts` - [ ] Build shared types: `npm run build:types` ### Phase 3: Frontend Migration ✅ - [ ] Update all imports in webapp to use `@annos/types` - [ ] Remove old type files: `rm -rf src/webapp/api-client/types/` - [ ] Test webapp compilation: `npm run type-check --workspace=@annos/webapp` ### Phase 4: Backend Migration ✅ - [ ] Add shared types dependency to root package - [ ] Update use cases to import and return shared types - [ ] Update controllers to use shared types - [ ] Test backend compilation: `npm run type-check` ### Phase 5: Development Setup ✅ - [ ] Add development scripts to root package.json - [ ] Test concurrent development: `npm run dev` - [ ] Verify hot reload works for type changes ### Phase 6: Validation ✅ - [ ] Run full type check across all packages - [ ] Test API endpoints return correct types - [ ] Add runtime validation (optional) - [ ] Update documentation ## Best Practices ### Type Naming Conventions - **Requests**: `{Action}{Resource}Request` (e.g., `GetUrlCardsRequest`) - **Responses**: `{Action}{Resource}Response` (e.g., `GetUrlCardsResponse`) - **Common types**: Descriptive names (e.g., `User`, `Pagination`) ### Development Workflow 1. **Always run shared types in watch mode** during development 2. **Make type changes first** before implementing features 3. **Use TypeScript strict mode** to catch issues early 4. **Version shared types** when making breaking changes ### Error Handling - Define consistent error response types - Use discriminated unions for different error types - Include error codes and messages in shared types ## Troubleshooting ### Common Issues 1. **"Cannot find module '@annos/types'"** - Run `npm install` in root to setup workspace links - Ensure shared types are built: `npm run build:types` 2. **Type mismatches between frontend and backend** - Check that both are using the same version of shared types - Rebuild shared types: `npm run build:types` 3. **Hot reload not working for type changes** - Ensure `npm run dev:types` is running in watch mode - Restart development servers if needed ### Debugging Tips 1. Use `npm ls @annos/types` to check workspace linking 2. Check `src/types/dist/` for compiled output 3. Use IDE "Go to Definition" to verify imports are resolving correctly ## Future Enhancements - **OpenAPI generation**: Generate OpenAPI specs from shared types - **Runtime validation**: Add Zod schemas for all shared types - **Documentation**: Auto-generate API docs from types - **Testing**: Create type-safe test utilities - **Publishing**: Publish shared types to private npm registry - **Versioning**: Implement semantic versioning for breaking changes