···77### ✅ What Was Implemented
8899**Infrastructure (100% Complete)**
1010+1011- ✅ Created `@semble/types` npm workspace package
1112- ✅ Configured npm workspaces in root package.json
1213- ✅ Set up TypeScript compilation with proper paths
···1415- ✅ Built and compiled successfully
15161617**Shared Types Package** (`src/types/src/api/`)
1818+1719- ✅ `common.ts` - User, Pagination, Sorting base types
1820- ✅ `requests.ts` - All API request types (30+ types)
1921- ✅ `responses.ts` - All API response types (30+ types)
···2123- ✅ Compiles cleanly with TypeScript
22242325**Backend Migration (100% Complete)**
2626+2427- ✅ **All 8 card query use cases** migrated to `@semble/types`
2528 - GetCollectionsForUrlUseCase
2629 - GetGlobalFeedUseCase
···3639- ✅ Removed old DTO directories
37403841**Frontend Migration (100% Complete)**
4242+3943- ✅ ApiClient.ts imports from `@semble/types`
4044- ✅ All client files updated (QueryClient, CardClient, etc.)
4145- ✅ Removed old `src/webapp/api-client/types/` directory
···158162## 🔧 Development Workflow
159163160164### Starting Development
165165+161166```bash
162167# Terminal 1: Watch and rebuild types on changes
163168npm run dev:types
···170175```
171176172177### Making Type Changes
178178+1731791. Edit files in `src/types/src/api/`
1741802. Types package auto-rebuilds (if dev:types is running)
1751813. Both backend and frontend see changes immediately
1761824. TypeScript catches any mismatches
177183178184### Example: Adding a New Endpoint
185185+179186```typescript
180187// 1. Add types to src/types/src/api/requests.ts
181188export interface CreateCommentRequest {
···218225## 📚 Reference Implementations
219226220227### Example Use Case
228228+221229`src/modules/cards/application/useCases/queries/GetCollectionsForUrlUseCase.ts`
230230+222231```typescript
223232import { GetCollectionsForUrlResponse, Collection } from '@semble/types';
224233···231240232241 return ok({
233242 collections: enrichedCollections,
234234- pagination: { /* ... */ },
235235- sorting: { /* ... */ },
243243+ pagination: {
244244+ /* ... */
245245+ },
246246+ sorting: {
247247+ /* ... */
248248+ },
236249 });
237250 }
238251}
239252```
240253241254### Example Controller with Zod
255255+242256`src/modules/cards/infrastructure/http/controllers/GetCollectionsForUrlController.ts`
257257+243258```typescript
244259import { z } from 'zod';
245260import { GetCollectionsForUrlResponse } from '@semble/types';
···264279```
265280266281### Example Frontend Usage
282282+267283`src/webapp/api-client/ApiClient.ts`
284284+268285```typescript
269286import { GetCollectionsForUrlParams, GetCollectionsForUrlResponse } from '@semble/types';
270287···281298## 🎯 Future Enhancements (Optional)
282299283300### Short Term
301301+284302- [ ] Add Zod validation to remaining 23 controllers
285303- [ ] Create shared Zod utility schemas for pagination/sorting
286304- [ ] Add request/response logging middleware
287305288306### Medium Term
307307+289308- [ ] Generate OpenAPI spec from Zod schemas + types
290309- [ ] Create API documentation from types
291310- [ ] Add integration tests using shared types
292311- [ ] Runtime response validation in development mode
293312294313### Long Term
314314+295315- [ ] Type versioning strategy for breaking changes
296316- [ ] Generate client SDKs for mobile apps
297317- [ ] Publish types to private npm registry
···323343## 📝 Key Files Modified
324344325345### Created
346346+326347- ✅ `src/types/` - Entire @semble/types package
327348- ✅ `docs/plan/shared_type_unification.md` - Implementation plan
328349- ✅ `docs/shared_types_implementation_status.md` - Status tracking
329350- ✅ `IMPLEMENTATION_COMPLETE.md` - This file
330351331352### Modified
353353+332354- ✅ `package.json` - Added workspaces, @semble/types dependency, zod
333355- ✅ `src/webapp/package.json` - Added @semble/types dependency
334356- ✅ `tsconfig.json` - Added paths for @semble/types
···340362- ✅ All webapp client files - Import from @semble/types
341363342364### Deleted
365365+343366- ✅ `src/modules/cards/application/dtos/` - Moved to @semble/types
344367- ✅ `src/modules/user/application/dtos/` - Moved to @semble/types
345368- ✅ `src/webapp/api-client/types/` - Moved to @semble/types
+127-26
docs/plan/shared_type_unification.md
···99**TL;DR**: You already have the important mapper (Domain → Application), and adding another layer (Application → Infrastructure) would just be ceremony when the types are identical.
10101111**You're already doing this (correct DDD):**
1212+1213```
1314Domain Entity → Use Case (maps to DTO) → Controller (passes through) → Frontend
1415 ↓ ↓ ↓
···1819**The shared types represent the Application Layer**, not Infrastructure. Both backend controllers and frontend clients consume the same Application Layer contract - this is **textbook Ports & Adapters pattern**.
19202021**When you WOULD need mappers:**
2222+2123- API versioning (supporting v1 and v2)
2224- Multiple protocols (REST + GraphQL + gRPC)
2325- Public API (hiding internal structures)
···3234### What Exists Today
33353436**Backend DTOs** (`src/modules/cards/application/dtos/`):
3737+3538- `UserProfileDTO`, `UrlCardDTO`, `NoteCardDTO`, `CollectionDTO`
3639- `PaginationDTO`, `CardSortingDTO`, `CollectionSortingDTO`
3740- `FeedItemDTO`
3841- These are used as return types from Use Cases
39424043**Frontend Types** (`src/webapp/api-client/types/`):
4444+4145- `requests.ts` - Request parameter interfaces
4246- `responses.ts` - Response type interfaces (User, UrlCard, Collection, etc.)
4347- Nearly identical to backend DTOs (already unified in recent work)
44484549**Current Flow**:
5050+4651```
4752Domain Model → Use Case (returns DTO) → Controller (returns DTO as-is) → Frontend (expects matching type)
4853```
···5863### DDD Layered Architecture Review
59646065**Classic DDD Layers:**
6666+61671. **Domain Layer**: Entities, Value Objects, Domain Services, Domain Events
62682. **Application Layer**: Use Cases, DTOs, Application Services
63693. **Infrastructure Layer**: Controllers, Repositories, External Services
···126132```
127133128134**When you need this:**
135135+129136- ✅ Public API that needs versioning independent of domain
130137- ✅ Domain model significantly different from API representation
131138- ✅ Multiple API formats (REST, GraphQL, gRPC) from same domain
···133140- ✅ Large team with separate domain/API teams
134141135142**Drawbacks:**
143143+136144- ❌ High ceremony when DTO ≈ HTTP Response
137145- ❌ Boilerplate mapper code
138146- ❌ Slower iteration velocity
···168176```
169177170178**When this is appropriate:**
179179+171180- ✅ Monorepo with tight frontend/backend coupling
172181- ✅ DTO and HTTP Response are identical (or nearly so)
173182- ✅ Private/internal API (not public third-party API)
···177186**Key insight:** The shared types represent the **Application Layer contract**, not Infrastructure. Both the backend Use Case and frontend are clients of this application layer contract.
178187179188**Drawbacks:**
189189+180190- ⚠️ Harder to version API independently
181191- ⚠️ Frontend sees application layer types (but this may be fine)
182192···193203```
194204195205**This is an anti-pattern** because:
206206+196207- ❌ Use Cases depend on Infrastructure (breaks DDD layering)
197208- ❌ Application layer coupled to HTTP representation
198209- ❌ Can't reuse Use Cases for non-HTTP interfaces
···202213**Why this is the right choice for your codebase:**
2032142042151. **You're already mapping Domain → Application:**
216216+205217 ```typescript
206218 // GetCollectionsForUrlUseCase.ts - lines 122-151
207219 const enrichedCollections: CollectionDTO[] = await Promise.all(
···224236 })
225237 );
226238 ```
239239+227240 This is **proper DDD** - the Use Case orchestrates domain objects and produces DTOs.
2282412292422. **Your DTOs ARE your HTTP responses:**
···251264Add a separate HTTP Response layer if:
2522652532661. **API Versioning:**
267267+254268 ```typescript
255269 // v1: { id, name }
256270 // v2: { id, title } // renamed field
···262276 ```
2632772642782. **Different Representations:**
279279+265280 ```typescript
266281 // REST API: flat structure
267282 { collectionId: '123', authorId: '456' }
···271286 ```
2722872732883. **Hide Internal Details:**
289289+274290 ```typescript
275291 // DTO has internal fields
276292 interface CollectionDTO {
277293 id: string;
278294 name: string;
279279- internalAuditLog: AuditEntry[]; // don't expose
295295+ internalAuditLog: AuditEntry[]; // don't expose
280296 }
281297282298 // HTTP response filters
···301317// These are Application Layer DTOs, not Infrastructure types
302318303319// common.ts - Core domain concepts
304304-export interface User { /* ... */ }
305305-export interface Collection { /* ... */ }
320320+export interface User {
321321+ /* ... */
322322+}
323323+export interface Collection {
324324+ /* ... */
325325+}
306326307327// requests.ts - Use Case inputs
308308-export interface GetCollectionsParams { /* ... */ }
328328+export interface GetCollectionsParams {
329329+ /* ... */
330330+}
309331310332// responses.ts - Use Case outputs
311311-export interface GetCollectionsResponse { /* ... */ }
333333+export interface GetCollectionsResponse {
334334+ /* ... */
335335+}
312336```
313337314338**Use Cases depend on these types:**
339339+315340```typescript
316341import { GetCollectionsResponse } from '@semble/types';
317342···323348```
324349325350**Controllers are thin pipes:**
351351+326352```typescript
327353import { GetCollectionsResponse } from '@semble/types';
328354···335361```
336362337363**Frontend consumes Application Layer contract:**
364364+338365```typescript
339366import { GetCollectionsResponse } from '@semble/types';
340367···348375### Summary
349376350377**What we're doing:**
378378+351379- ✅ Sharing **Application Layer** types between backend and frontend
352380- ✅ Use Cases map Domain → Application DTO (proper DDD)
353381- ✅ Controllers validate HTTP and pass through DTOs
354382- ✅ Single source of truth for application contracts
355383356384**What we're NOT doing:**
385385+357386- ❌ Skipping Domain → Application mapping (we do this!)
358387- ❌ Letting infrastructure dictate application types
359388- ❌ Breaking DDD layer dependencies
360389361390**This is pragmatic DDD because:**
391391+362392- Domain layer remains pure ✅
363393- Application layer defines contracts ✅
364394- Infrastructure depends on Application (correct direction) ✅
···371401### Three Levels of Validation
372402373403#### 1. Controller-Level Validation (Infrastructure Layer)
404404+374405**Purpose**: Validate HTTP request structure and types
375406**Implementation**: Use Zod schemas at controller entry points
376407**Examples**:
408408+377409- Is `url` parameter present and a string?
378410- Is `page` a positive integer?
379411- Are required fields in request body present?
···404436```
405437406438#### 2. Use Case-Level Validation (Application Layer)
439439+407440**Purpose**: Validate business rules and create domain objects
408441**Implementation**: Use domain value objects (already doing this!)
409442**Examples**:
443443+410444- `URL.create(query.url)` - validates URL format using domain rules
411445- `CardId.createFromString(id)` - validates ID format
412446- Business logic validation (e.g., "user can only have 50 collections")
···416450This layer is **already correct** in the codebase!
417451418452#### 3. Domain-Level Validation (Domain Layer)
453453+419454**Purpose**: Enforce invariants and domain rules
420455**Implementation**: Value Objects and Entity constructors
421456**Examples**:
457457+422458- `URL` value object validates URL format
423459- `CollectionName` might enforce length limits
424460- `Email` value object validates email format
···480516### Type Organization
481517482518**`src/types/src/api/common.ts`** - Shared domain concepts:
519519+483520```typescript
484521export interface User {
485522 id: string;
···515552```
516553517554**`src/types/src/api/requests.ts`** - API request types:
555555+518556```typescript
519557// Base interfaces
520558export interface PaginationParams {
···528566}
529567530568// Query parameter interfaces
531531-export interface GetCollectionsForUrlParams extends PaginationParams, SortingParams {
569569+export interface GetCollectionsForUrlParams
570570+ extends PaginationParams,
571571+ SortingParams {
532572 url: string;
533573}
534574···543583```
544584545585**`src/types/src/api/responses.ts`** - API response types:
586586+546587```typescript
547588import { User, Pagination, CardSorting, CollectionSorting } from './common';
548589···594635### Phase 0: Prerequisites
595636596637**Install Zod for validation:**
638638+597639```bash
598640npm install zod
599641```
···603645#### 1.1 Configure Workspace Root
604646605647Update `package.json`:
648648+606649```json
607650{
608651 "name": "semble",
609609- "workspaces": [
610610- "src/types",
611611- "src/webapp"
612612- ],
652652+ "workspaces": ["src/types", "src/webapp"],
613653 "scripts": {
614654 "build:types": "npm run build --workspace=@semble/types",
615655 "dev:types": "npm run dev --workspace=@semble/types",
···621661#### 1.2 Create Types Package
622662623663Create `src/types/package.json`:
664664+624665```json
625666{
626667 "name": "@semble/types",
···641682```
642683643684Create `src/types/tsconfig.json`:
685685+644686```json
645687{
646688 "compilerOptions": {
···674716#### 2.1 Add Dependency
675717676718Update root `package.json`:
719719+677720```json
678721{
679722 "dependencies": {
···690733**Example: `GetCollectionsForUrlUseCase.ts`**
691734692735Before:
736736+693737```typescript
694738import { CollectionDTO, PaginationDTO, CollectionSortingDTO } from '../../dtos';
695739···701745```
702746703747After:
748748+704749```typescript
705750import { GetCollectionsForUrlResponse } from '@semble/types';
706751···718763**Example: `GetCollectionsForUrlController.ts`**
719764720765Before:
766766+721767```typescript
722768async executeImpl(req: Request, res: Response): Promise<any> {
723769 const { url } = req.query;
···729775```
730776731777After:
778778+732779```typescript
733780import { z } from 'zod';
734781import { GetCollectionsForUrlParams, GetCollectionsForUrlResponse } from '@semble/types';
···771818#### 2.4 Remove Old DTOs
772819773820Once all use cases and controllers are updated:
821821+774822```bash
775823rm -rf src/modules/cards/application/dtos/
776824rm -rf src/modules/user/application/dtos/
···781829#### 3.1 Add Dependency
782830783831Update `src/webapp/package.json`:
832832+784833```json
785834{
786835 "dependencies": {
···796845**`src/webapp/api-client/ApiClient.ts`**
797846798847Before:
848848+799849```typescript
800850import type {
801851 GetCollectionsForUrlParams,
···804854```
805855806856After:
857857+807858```typescript
808859import type {
809860 GetCollectionsForUrlParams,
···822873#### 4.1 Add Development Scripts
823874824875Update root `package.json`:
876876+825877```json
826878{
827879 "scripts": {
···838890#### 4.2 Update Backend tsconfig.json
839891840892Ensure backend can resolve workspace packages:
893893+841894```json
842895{
843896 "compilerOptions": {
···851904### Phase 5: Testing & Validation
852905853906#### 5.1 Type Checking
907907+854908```bash
855909npm run type-check # Check backend
856910npm run type-check --workspace=@semble/webapp # Check frontend
857911```
858912859913#### 5.2 Build Everything
914914+860915```bash
861916npm run build:all
862917```
863918864919#### 5.3 Runtime Testing
920920+865921- Start dev servers: `npm run dev`
866922- Test each modified endpoint
867923- Verify request validation works (try invalid requests)
···870926## Migration Checklist
871927872928### Phase 1: Shared Types Package ✅
929929+873930- [ ] Update root `package.json` with workspaces configuration
874931- [ ] Create `src/types/package.json`
875932- [ ] Create `src/types/tsconfig.json`
···882939- [ ] Verify build output in `src/types/dist/`
883940884941### Phase 2: Backend Migration ✅
942942+885943- [ ] Install zod: `npm install zod`
886944- [ ] Add `@semble/types` to root dependencies
887945- [ ] Run `npm install` to link workspace
···898956- [ ] Fix any type errors
899957900958### Phase 3: Frontend Migration ✅
959959+901960- [ ] Add `@semble/types` to `src/webapp/package.json`
902961- [ ] Run `npm install` in webapp
903962- [ ] Update `src/webapp/api-client/ApiClient.ts` imports
···908967- [ ] Fix any type errors
909968910969### Phase 4: Development Workflow ✅
970970+911971- [ ] Add dev scripts to root package.json
912972- [ ] Test concurrent development: `npm run dev`
913973- [ ] Make a test change to shared types
···915975- [ ] Test build pipeline: `npm run build:all`
916976917977### Phase 5: Testing ✅
978978+918979- [ ] Backend tests pass
919980- [ ] Frontend tests pass
920981- [ ] Manual testing of key endpoints:
···931992### High Priority (Core Queries)
932993933994**Use Cases:**
995995+934996- `src/modules/cards/application/useCases/queries/GetCollectionsForUrlUseCase.ts`
935997- `src/modules/cards/application/useCases/queries/GetCollectionsUseCase.ts`
936998- `src/modules/cards/application/useCases/queries/GetLibrariesForCardUseCase.ts`
···9411003- `src/modules/feeds/application/useCases/queries/GetGlobalFeedUseCase.ts`
94210049431005**Controllers:**
10061006+9441007- All controllers in `src/modules/cards/infrastructure/http/controllers/`
9451008- `src/modules/feeds/infrastructure/http/controllers/GetGlobalFeedController.ts`
94610099471010**Frontend:**
10111011+9481012- `src/webapp/api-client/ApiClient.ts`
9491013- All files in `src/webapp/api-client/clients/`
95010149511015### Medium Priority (Commands)
95210169531017**Use Cases:**
10181018+9541019- `src/modules/cards/application/useCases/commands/AddUrlToLibraryUseCase.ts`
9551020- `src/modules/cards/application/useCases/commands/CreateCollectionUseCase.ts`
9561021- ... (other command use cases)
···9641029## Best Practices
96510309661031### Type Naming Conventions
10321032+9671033- **Requests**: `{Verb}{Resource}Request` (e.g., `AddUrlToLibraryRequest`)
9681034- **Params**: `Get{Resource}Params` (e.g., `GetCollectionsForUrlParams`)
9691035- **Responses**: `{Verb}{Resource}Response` (e.g., `GetCollectionsForUrlResponse`)
···9721038### Validation Patterns
97310399741040**Controller validation (structure & types):**
10411041+9751042```typescript
9761043const schema = z.object({
9771044 requiredString: z.string().min(1),
···9861053```
98710549881055**Use case validation (business rules):**
10561056+9891057```typescript
9901058const urlResult = URL.create(params.url);
9911059if (urlResult.isErr()) {
···9941062```
99510639961064### Development Workflow
10651065+99710661. **Always run types in watch mode** during development: `npm run dev:types`
99810672. **Make type changes first** before implementing features
99910683. **Type-check frequently**: `npm run type-check`
···10041073### What We're NOT Doing (And Why That's OK)
1005107410061075❌ **Separate DTO and HTTP Response mapping layer**:
10761076+10071077- Reason: Application DTOs and HTTP responses are identical
10081078- Reality: Controllers would just be identity mappers (pointless ceremony)
10091079- When to add: If you need API versioning, different client representations, or hide internal fields
10101080- See "When Would You Need Mappers?" section above for specific scenarios
1011108110121082❌ **Runtime validation of Use Case outputs**:
10831083+10131084- Reason: TypeScript compilation guarantees response shape from Use Cases
10141085- Alternative: Could add Zod validation of DTO construction, but not needed initially
10151086- When to add: If you have bugs where Use Cases return malformed DTOs
1016108710171088❌ **OpenAPI schema generation**:
10891089+10181090- Reason: Can add later if needed (Zod schemas make this easy)
10191091- Benefit: Shared types + Zod schemas = future OpenAPI generation is trivial
10201092- When to add: When you want API documentation, client SDK generation, or contract testing
1021109310221094❌ **Separate versioning of types package**:
10951095+10231096- Reason: Monorepo with synchronized deploys (frontend/backend always in sync)
10241097- When to add: If you publish a public API or have multiple clients on different versions
10251098- Note: For now, Git commits provide version history
1026109910271100❌ **Multiple API representations (REST, GraphQL, gRPC)**:
11011101+10281102- Reason: Only building REST API currently
10291103- When to add: When you need GraphQL, gRPC, or other protocols (then add mappers)
10301104···10491123✅ **Type safety across boundaries**: Shared Application contracts prevent drift
1050112410511125The key insight: **Sharing Application Layer types between backend and frontend is not a DDD violation** when:
11261126+105211271. Both are clients of the same Application Layer
105311282. They deploy together (monorepo)
105411293. The Application DTOs appropriately abstract the Domain Model
···10561131## Troubleshooting
1057113210581133### "Cannot find module '@semble/types'"
11341134+10591135```bash
10601136# From root
10611137npm install
···10631139```
1064114010651141### Types package not updating
11421142+10661143```bash
10671144# Restart types watch mode
10681145npm run dev:types
10691146```
1070114710711148### Import path errors in IDE
11491149+10721150- Restart TypeScript server in your IDE
10731151- Check `tsconfig.json` paths configuration
10741152- Verify `node_modules/@semble/types` symlink exists
1075115310761154### Validation errors not showing
11551155+10771156- Check Zod schema matches the expected request shape
10781157- Use `.safeParse()` not `.parse()` to get detailed errors
10791158- Log `validation.error.format()` for debugging
···10811160## Future Enhancements
1082116110831162### Short Term
11631163+10841164- [ ] Add Zod schemas for all controllers
10851165- [ ] Create shared Zod utilities for common patterns (pagination, sorting)
10861166- [ ] Add request/response logging middleware
1087116710881168### Medium Term
11691169+10891170- [ ] Generate OpenAPI spec from shared types
10901171- [ ] Create API documentation from types
10911172- [ ] Add integration tests using shared types
10921173- [ ] Create type-safe test factories
1093117410941175### Long Term
11761176+10951177- [ ] Publish types package to private npm registry
10961178- [ ] Implement breaking change detection (type diff checks)
10971179- [ ] Add runtime response validation in development mode
···11021184### What We Preserved (Proper DDD)
1103118511041186✅ **Domain Layer Purity**:
11871187+11051188- Domain entities, value objects, and domain services have no external dependencies
11061189- Domain models encapsulate business logic and invariants
11071190- Domain layer doesn't know about HTTP, DTOs, or frontend
1108119111091192✅ **Application Layer Orchestration**:
11931193+11101194- Use Cases orchestrate domain objects to fulfill application requirements
11111195- Use Cases map rich domain models → simple DTOs (proper anti-corruption layer)
11121196- DTOs hide domain complexity from external consumers
11131197- Application layer defines the contract for consumers (backend and frontend)
1114119811151199✅ **Infrastructure Layer Dependency Direction**:
12001200+11161201- Controllers depend on Application types (correct: Infrastructure → Application)
11171202- Controllers do NOT define types that Application depends on (would be wrong)
11181203- Infrastructure implements Application contracts, not the other way around
1119120411201205✅ **Three-Tier Validation Hierarchy**:
12061206+11211207- **Domain**: Invariants enforced in value objects and entities
11221208- **Application**: Business rules validated in use cases
11231209- **Infrastructure**: HTTP request structure validated in controllers
1124121011251211✅ **Separation of Concerns**:
12121212+11261213- Domain logic in entities/value objects
11271214- Application logic in use cases
11281215- HTTP transport details in controllers
···11311218### What's Actually Happening (Not a Compromise)
1132121911331220✅ **Shared Application Layer Types**:
12211221+11341222- `@semble/types` represents the **Application Layer contract**
11351223- Backend Use Cases produce these contracts
11361224- Frontend API Client consumes these contracts
11371225- Both are clients of the Application Layer (this is correct DDD!)
1138122611391227✅ **Controllers as Thin Adapters**:
12281228+11401229- Controllers adapt HTTP → Application Layer (parse request, call use case)
11411230- Controllers adapt Application Layer → HTTP (serialize DTO to JSON)
11421231- No additional mapping needed when DTO shape = HTTP response shape
···11451234### Why This Is Good DDD (Not Just "OK for Startups")
1146123511471236**From Eric Evans / Martin Fowler:**
12371237+11481238- DTOs exist to cross architectural boundaries ✅ (we do this)
11491239- Application Layer should be independent of delivery mechanism ✅ (it is)
11501240- Infrastructure should depend on Application, not vice versa ✅ (correct)
11511241- Shared kernel is acceptable when bounded contexts align ✅ (monorepo, single app)
1152124211531243**From Hexagonal Architecture (Ports & Adapters):**
12441244+11541245- Application Layer defines "ports" (interfaces/contracts) ✅ (`@semble/types`)
11551246- Infrastructure provides "adapters" (controllers, API clients) ✅ (thin HTTP adapters)
11561247- Multiple adapters can implement same port ✅ (backend controller, frontend client)
11571248- This is the **textbook pattern**!
1158124911591250**When you'd need an additional mapping layer:**
12511251+116012521. **API Versioning**: Supporting v1 and v2 simultaneously
116112532. **Different Protocols**: REST + GraphQL + gRPC from same Application Layer
116212543. **Public API**: Need to hide internal structures
···11981290The quality of this architecture depends on whether your DTOs properly abstract the domain:
1199129112001292✅ **Good DTO Design** (what you have):
12931293+12011294```typescript
12021295// Domain: Rich model with behavior
12031296class Collection {
12041204- collectionId: CollectionId; // Value object
12051205- name: CollectionName; // Value object with validation
12061206- authorId: UserId; // Value object
12071207- description?: CollectionDescription; // Value object
12081208- cardIds: CardId[]; // List of value objects
12091209- addCard(cardId: CardId) { /* logic */ }
12101210- removeCard(cardId: CardId) { /* logic */ }
12971297+ collectionId: CollectionId; // Value object
12981298+ name: CollectionName; // Value object with validation
12991299+ authorId: UserId; // Value object
13001300+ description?: CollectionDescription; // Value object
13011301+ cardIds: CardId[]; // List of value objects
13021302+ addCard(cardId: CardId) {
13031303+ /* logic */
13041304+ }
13051305+ removeCard(cardId: CardId) {
13061306+ /* logic */
13071307+ }
12111308}
1212130912131310// DTO: Simple data structure for transfer
12141311interface Collection {
12151215- id: string; // Unwrapped value
12161216- name: string; // Unwrapped value
12171217- author: User; // Enriched relationship
12181218- description?: string; // Unwrapped value
12191219- cardCount: number; // Computed property
12201220- createdAt: string; // ISO string
12211221- updatedAt: string; // ISO string
13121312+ id: string; // Unwrapped value
13131313+ name: string; // Unwrapped value
13141314+ author: User; // Enriched relationship
13151315+ description?: string; // Unwrapped value
13161316+ cardCount: number; // Computed property
13171317+ createdAt: string; // ISO string
13181318+ updatedAt: string; // ISO string
12221319}
12231320```
1224132112251322This is **good separation**:
13231323+12261324- Domain has rich behavior, validation, invariants
12271325- DTO is simple, serializable, consumer-friendly
12281326- Use Case does the mapping (proper layer responsibility)
1229132712301328❌ **Bad DTO Design** (anti-pattern):
13291329+12311330```typescript
12321331// Exposing domain internals
12331332interface CollectionDTO {
12341234- collectionId: CollectionId; // WRONG: exposing domain value object
12351235- _aggregateVersion: number; // WRONG: exposing internal details
13331333+ collectionId: CollectionId; // WRONG: exposing domain value object
13341334+ _aggregateVersion: number; // WRONG: exposing internal details
12361335 domainEvents: DomainEvent[]; // WRONG: leaking domain events
12371336}
12381337```
···14021501```
1403150214041503### Key Changes
15041504+140515051. ✅ Single `Collection` type (not duplicated)
140615062. ✅ Single `GetCollectionsForUrlResponse` type
140715073. ✅ Controller validates with Zod schema
···14221522- **Stays simple** and avoids overengineering
1423152314241524The result is a **barebones but reliable** type system that:
15251525+14251526- Catches errors at compile time
14261527- Validates requests at runtime
14271528- Maintains a single source of truth
+30-16
docs/shared_types.md
···5454{
5555 "name": "annos",
5656 "version": "1.0.0",
5757- "workspaces": [
5858- "src/types",
5959- "src/webapp",
6060- "."
6161- ],
5757+ "workspaces": ["src/types", "src/webapp", "."],
6258 "scripts": {
6359 "build:types": "npm run build --workspace=@annos/types",
6460 "dev:types": "npm run dev --workspace=@annos/types",
···8076 "description": "Shared TypeScript types for Annos API",
8177 "main": "dist/index.js",
8278 "types": "dist/index.d.ts",
8383- "files": [
8484- "dist/**/*"
8585- ],
7979+ "files": ["dist/**/*"],
8680 "scripts": {
8781 "build": "tsc",
8882 "dev": "tsc --watch",
···125119{
126120 "name": "@annos/webapp",
127121 "dependencies": {
128128- "@annos/types": "workspace:*",
122122+ "@annos/types": "workspace:*"
129123 // ... existing dependencies
130124 }
131125}
···138132Move and organize existing webapp types into the shared package:
139133140134**src/types/src/api/common.ts:**
135135+141136```typescript
142137export interface User {
143138 id: string;
···173168```
174169175170**src/types/src/api/requests.ts:**
171171+176172```typescript
177173// Copy all request types from src/webapp/api-client/types/requests.ts
178174export interface PaginationParams {
···189185```
190186191187**src/types/src/api/responses.ts:**
188188+192189```typescript
193193-import { User, Pagination, CardSorting, CollectionSorting, FeedPagination } from './common';
190190+import {
191191+ User,
192192+ Pagination,
193193+ CardSorting,
194194+ CollectionSorting,
195195+ FeedPagination,
196196+} from './common';
194197195198// Copy all response types from src/webapp/api-client/types/responses.ts
196199export interface UrlCard {
···204207```
205208206209**src/types/src/api/index.ts:**
210210+207211```typescript
208212export * from './common';
209213export * from './requests';
···211215```
212216213217**src/types/src/index.ts:**
218218+214219```typescript
215220export * from './api';
216221```
···242247} from './types/responses';
243248244249// NEW:
245245-import type {
246246- GetUrlCardsResponse,
247247- AddUrlToLibraryRequest,
248248-} from '@annos/types';
250250+import type { GetUrlCardsResponse, AddUrlToLibraryRequest } from '@annos/types';
249251```
250252251253#### Step 3.3: Remove Old Type Files
···276278277279export class GetUrlCardsUseCase {
278280 async execute(
279279- query: GetUrlCardsQuery
280280- ): Promise<Result<GetUrlCardsResponse, ValidationError | AppError.UnexpectedError>> {
281281+ query: GetUrlCardsQuery,
282282+ ): Promise<
283283+ Result<GetUrlCardsResponse, ValidationError | AppError.UnexpectedError>
284284+ > {
281285 // Implementation must return GetUrlCardsResponse type
282286 return ok({
283287 cards: enrichedCards,
···356360Add Zod schemas for runtime validation:
357361358362**src/types/src/validation/index.ts:**
363363+359364```typescript
360365import { z } from 'zod';
361366···385390## Migration Checklist
386391387392### Phase 1: Infrastructure ✅
393393+388394- [ ] Update root `package.json` with workspaces
389395- [ ] Create `src/types/package.json`
390396- [ ] Create `src/types/tsconfig.json`
···392398- [ ] Run `npm install` to setup workspace
393399394400### Phase 2: Type Migration ✅
401401+395402- [ ] Create `src/types/src/api/common.ts`
396403- [ ] Create `src/types/src/api/requests.ts`
397404- [ ] Create `src/types/src/api/responses.ts`
···400407- [ ] Build shared types: `npm run build:types`
401408402409### Phase 3: Frontend Migration ✅
410410+403411- [ ] Update all imports in webapp to use `@annos/types`
404412- [ ] Remove old type files: `rm -rf src/webapp/api-client/types/`
405413- [ ] Test webapp compilation: `npm run type-check --workspace=@annos/webapp`
406414407415### Phase 4: Backend Migration ✅
416416+408417- [ ] Add shared types dependency to root package
409418- [ ] Update use cases to import and return shared types
410419- [ ] Update controllers to use shared types
411420- [ ] Test backend compilation: `npm run type-check`
412421413422### Phase 5: Development Setup ✅
423423+414424- [ ] Add development scripts to root package.json
415425- [ ] Test concurrent development: `npm run dev`
416426- [ ] Verify hot reload works for type changes
417427418428### Phase 6: Validation ✅
429429+419430- [ ] Run full type check across all packages
420431- [ ] Test API endpoints return correct types
421432- [ ] Add runtime validation (optional)
···424435## Best Practices
425436426437### Type Naming Conventions
438438+427439- **Requests**: `{Action}{Resource}Request` (e.g., `GetUrlCardsRequest`)
428440- **Responses**: `{Action}{Resource}Response` (e.g., `GetUrlCardsResponse`)
429441- **Common types**: Descriptive names (e.g., `User`, `Pagination`)
430442431443### Development Workflow
444444+4324451. **Always run shared types in watch mode** during development
4334462. **Make type changes first** before implementing features
4344473. **Use TypeScript strict mode** to catch issues early
4354484. **Version shared types** when making breaking changes
436449437450### Error Handling
451451+438452- Define consistent error response types
439453- Use discriminated unions for different error types
440454- Include error codes and messages in shared types
+18
docs/shared_types_implementation_status.md
···33## ✅ Successfully Completed
4455### Infrastructure Setup
66+67- ✅ Created `@semble/types` package at `src/types/`
78- ✅ Configured npm workspaces in root `package.json`
89- ✅ Set up TypeScript compilation for types package
···1112- ✅ Installed zod for validation (version 3.22.4)
12131314### Shared Types Package (`src/types/src/api/`)
1515+1416- ✅ `common.ts` - User, Pagination, Sorting interfaces
1517- ✅ `requests.ts` - All request parameter types
1618- ✅ `responses.ts` - All response types
1719- ✅ Built successfully with TypeScript
18201921### Backend Migration (Cards/Feeds Modules)
2222+2023- ✅ **GetCollectionsForUrlUseCase** - Uses `GetCollectionsForUrlResponse` from `@semble/types`
2124- ✅ **GetGlobalFeedUseCase** - Uses `GetGlobalFeedResponse` from `@semble/types`
2225- ✅ **GetCollectionsForUrlController** - Added Zod validation schema
···2427- ✅ Removed old `src/modules/cards/application/dtos/` directory
25282629### Frontend Migration
3030+2731- ✅ **ApiClient.ts** - Imports all types from `@semble/types`
2832- ✅ All client files (`QueryClient`, `CardClient`, etc.) - Updated to import from `@semble/types`
2933- ✅ Removed old `src/webapp/api-client/types/` directory
···3135## ⚠️ Remaining Work (Not Critical for Core Functionality)
32363337### Use Cases to Migrate
3838+3439These use cases still import from old `../../dtos` and need migration:
35403641**Cards Module:**
4242+3743- `GetCollectionsUseCase.ts`
3844- `GetLibrariesForCardUseCase.ts`
3945- `GetLibrariesForUrlUseCase.ts`
···4248- `GetUrlStatusForMyLibraryUseCase.ts`
43494450**Pattern to follow:**
5151+4552```typescript
4653// OLD
4754import { CollectionDTO, PaginationDTO } from '../../dtos';
···5259```
53605461### User Module DTOs
6262+5563The following files reference deleted User DTOs and need new types added to `@semble/types`:
56645765**Missing types to add:**
6666+5867- `OAuthCallbackDTO` (used by OAuth flows)
5968- `TokenDTO` (used by token services)
6069- `UserDTO` (used by user mappers)
6170- `LoginWithAppPasswordDTO` (used by login use case)
62716372**Affected files:**
7373+6474- `src/modules/atproto/infrastructure/services/AtProtoOAuthProcessor.ts`
6575- `src/modules/atproto/infrastructure/services/FakeAtProtoOAuthProcessor.ts`
6676- `src/modules/user/application/mappers/UserMap.ts`
···7383Add these types to `src/types/src/api/responses.ts` or create a new `auth.ts` file.
74847585### Controllers to Add Zod Validation
8686+7687All other controllers in `src/modules/cards/infrastructure/http/controllers/` should follow the same pattern:
77887889```typescript
···94105## 🎯 What's Working Right Now
9510696107### End-to-End Type Safety
108108+971091. **Frontend** → API request using types from `@semble/types`
981102. **Controller** → Validates request with Zod schema
991113. **Use Case** → Returns response typed as `@semble/types` interface
···1011135. **Frontend** → Receives response with full type safety
102114103115### Example Flow (Working)
116116+104117```
105118Frontend: getCollectionsForUrl(params: GetCollectionsForUrlParams)
106119 ↓
···112125```
113126114127### Compile Status
128128+115129- **Frontend**: ✅ Should compile (needs verification with `npm run type-check --workspace=@semble/webapp`)
116130- **Backend**: ⚠️ 20 type errors (all in non-migrated use cases and user module)
117131- **Types Package**: ✅ Compiles successfully
···121135### For Each Remaining Use Case
1221361231371. **Update imports:**
138138+124139```typescript
125140// Replace
126141import { SomeDTO } from '../../dtos';
···129144```
1301451311462. **Update return type:**
147147+132148```typescript
133149// Replace
134150export interface GetSomeResult {
···144160### For User Module
1451611461621. **Add missing types to `@semble/types`:**
163163+147164```typescript
148165// src/types/src/api/auth.ts
149166export interface TokenResponse {
···209226## 📚 Reference Implementation
210227211228See these files for the pattern to follow:
229229+212230- **Use Case**: `src/modules/cards/application/useCases/queries/GetCollectionsForUrlUseCase.ts`
213231- **Controller**: `src/modules/cards/infrastructure/http/controllers/GetCollectionsForUrlController.ts`
214232- **Frontend**: `src/webapp/api-client/ApiClient.ts`
···11-import type { GetLibrariesForUrlResponse } from '@/api-client/types';
11+import { GetLibrariesForUrlResponse } from '@/api-client';
22import { getRelativeTime } from '@/lib/utils/time';
33import { Avatar, Card, Group, Stack, Text } from '@mantine/core';
44import Link from 'next/link';