AI Prompt: Forma3D.Connect — Phase 5f: Shared API Types¶
Purpose: This prompt instructs an AI to eliminate duplicate DTO definitions by creating shared types between frontend and backend
Estimated Effort: 2-3 days (~12-18 hours)
Prerequisites: Phase 5e completed (Typed JSON Schemas)
Output: Unified API types in libs/domain-contracts, type-safe frontend API client
Status: 🟡 PENDING
🎯 Mission¶
You are continuing development of Forma3D.Connect, building on the Phase 5e foundation. Your task is to implement Phase 5f: Shared API Types — specifically addressing TD-004 (Duplicated DTO Definitions Between Frontend and Backend) from the technical debt register.
Why This Matters:
The frontend manually defines response types that duplicate backend DTOs, causing:
- Type Drift: Frontend/backend types become inconsistent over time
- Double Maintenance: Every API change requires updates in two places
- No Compile-Time Safety: Mismatches only caught at runtime
- Nullable Confusion: Frontend may assume non-null when backend returns null
Phase 5f delivers:
- Unified API response types in
libs/domain-contracts - Frontend API client using shared types
- Single source of truth for all API contracts
- Compile-time type checking across the stack
📋 Context: Technical Debt Item¶
TD-004: Duplicated DTO Definitions Between Frontend and Backend¶
| Attribute | Value |
|---|---|
| Type | Architecture Debt |
| Priority | High |
| Location | apps/web/src/lib/api-client.ts and apps/api/src/**/dto/*.ts |
| Interest Rate | Medium-High (sync issues cause bugs) |
| Principal (Effort) | 2-3 days |
Current State¶
Duplicated Types:
| Type | Frontend Location | Backend Location |
|---|---|---|
OrderResponse |
api-client.ts |
order.dto.ts |
PrintJobResponse |
api-client.ts |
print-job.dto.ts |
DashboardStats |
api-client.ts |
dashboard.dto.ts |
ProductMappingResponse |
api-client.ts |
product-mapping.dto.ts |
ShipmentResponse |
api-client.ts |
shipment.dto.ts |
EventLogResponse |
api-client.ts |
event-log.dto.ts |
🛠️ Implementation Phases¶
Phase 1: Create Shared API Response Types (4 hours)¶
Priority: Critical | Impact: High | Dependencies: None
1. Create API Response Types Directory¶
Create libs/domain-contracts/src/api/ directory structure:
libs/domain-contracts/src/api/
index.ts
common.types.ts
order.api.ts
print-job.api.ts
dashboard.api.ts
product-mapping.api.ts
shipment.api.ts
event-log.api.ts
health.api.ts
2. Create Common Types¶
Create libs/domain-contracts/src/api/common.types.ts:
/**
* Standard paginated response wrapper.
*/
export interface PaginatedResponse<T> {
data: T[];
total: number;
page: number;
pageSize: number;
totalPages: number;
}
/**
* Standard pagination query parameters.
*/
export interface PaginationParams {
page?: number;
pageSize?: number;
}
/**
* Standard API error response.
*/
export interface ApiErrorResponse {
statusCode: number;
message: string;
error?: string;
details?: Record<string, unknown>;
timestamp?: string;
path?: string;
}
/**
* Standard success response for mutations.
*/
export interface MutationResponse<T = void> {
success: boolean;
data?: T;
message?: string;
}
3. Create Order API Types¶
Create libs/domain-contracts/src/api/order.api.ts:
import { OrderStatus, LineItemStatus } from '../lib/types';
import { ShippingAddress } from '@forma3d/domain';
import { PaginationParams, PaginatedResponse } from './common.types';
/**
* Line item within an order response.
*/
export interface LineItemApiResponse {
id: string;
orderId: string;
shopifyLineItemId: string;
productMappingId: string | null;
sku: string;
productName: string;
quantity: number;
unitPrice: string;
totalPrice: string;
status: LineItemStatus;
requiresPrinting: boolean;
completedParts: number;
totalParts: number;
createdAt: string;
updatedAt: string;
}
/**
* Full order response from API.
*/
export interface OrderApiResponse {
id: string;
shopifyOrderId: string;
shopifyOrderNumber: string;
status: OrderStatus;
customerName: string;
customerEmail: string;
shippingAddress: ShippingAddress | null;
totalPrice: string;
currency: string;
totalParts: number;
completedParts: number;
notes: string | null;
createdAt: string;
updatedAt: string;
completedAt: string | null;
lineItems: LineItemApiResponse[];
}
/**
* Order list item (without nested line items).
*/
export interface OrderListItemApiResponse {
id: string;
shopifyOrderId: string;
shopifyOrderNumber: string;
status: OrderStatus;
customerName: string;
customerEmail: string;
totalPrice: string;
currency: string;
totalParts: number;
completedParts: number;
createdAt: string;
updatedAt: string;
completedAt: string | null;
}
/**
* Order list query parameters.
*/
export interface OrderListParams extends PaginationParams {
status?: OrderStatus;
search?: string;
sortBy?: 'createdAt' | 'updatedAt' | 'shopifyOrderNumber';
sortOrder?: 'asc' | 'desc';
}
/**
* Paginated order list response.
*/
export interface OrderListApiResponse {
orders: OrderListItemApiResponse[];
total: number;
page: number;
pageSize: number;
}
/**
* Update order status request.
*/
export interface UpdateOrderStatusRequest {
status: OrderStatus;
}
4. Create Print Job API Types¶
Create libs/domain-contracts/src/api/print-job.api.ts:
import { PrintJobStatus } from '../lib/types';
import { PaginationParams } from './common.types';
/**
* Print job response from API.
*/
export interface PrintJobApiResponse {
id: string;
lineItemId: string;
assemblyPartId: string | null;
simplyPrintJobId: string | null;
status: PrintJobStatus;
copyNumber: number;
printerId: string | null;
printerName: string | null;
fileId: string | null;
fileName: string | null;
queuedAt: string | null;
startedAt: string | null;
completedAt: string | null;
estimatedDuration: number | null;
actualDuration: number | null;
progress: number;
errorMessage: string | null;
retryCount: number;
maxRetries: number;
createdAt: string;
updatedAt: string;
// Joined fields
orderId: string;
shopifyOrderNumber: string;
productSku: string;
productName: string;
}
/**
* Print job list query parameters.
*/
export interface PrintJobListParams extends PaginationParams {
status?: PrintJobStatus;
orderId?: string;
printerId?: string;
}
/**
* Paginated print job list response.
*/
export interface PrintJobListApiResponse {
data: PrintJobApiResponse[];
total: number;
page: number;
pageSize: number;
}
/**
* Cancel print job request.
*/
export interface CancelPrintJobRequest {
reason?: string;
}
5. Create Dashboard API Types¶
Create libs/domain-contracts/src/api/dashboard.api.ts:
/**
* Dashboard statistics response.
*/
export interface DashboardStatsApiResponse {
pendingOrders: number;
processingOrders: number;
completedToday: number;
failedOrders: number;
activePrintJobs: number;
completedPrintJobsToday: number;
queuedPrintJobs: number;
failedPrintJobs: number;
printersOnline: number;
averagePrintTime: number | null;
}
/**
* Dashboard order summary.
*/
export interface DashboardOrderSummary {
pending: number;
processing: number;
completed: number;
failed: number;
cancelled: number;
}
/**
* Dashboard print job summary.
*/
export interface DashboardPrintJobSummary {
queued: number;
printing: number;
completed: number;
failed: number;
}
6. Create Product Mapping API Types¶
Create libs/domain-contracts/src/api/product-mapping.api.ts:
import { PrintProfile } from '@forma3d/domain';
import { PaginationParams } from './common.types';
/**
* Assembly part within a product mapping.
*/
export interface AssemblyPartApiResponse {
id: string;
productMappingId: string;
name: string;
modelFileId: string;
modelFileName: string;
quantity: number;
printProfile: PrintProfile | null;
sortOrder: number;
createdAt: string;
updatedAt: string;
}
/**
* Product mapping response from API.
*/
export interface ProductMappingApiResponse {
id: string;
shopifyProductId: string;
shopifyVariantId: string | null;
sku: string;
productName: string;
description: string | null;
isAssembly: boolean;
isActive: boolean;
modelFileId: string | null;
modelFileName: string | null;
defaultPrintProfile: PrintProfile | null;
createdAt: string;
updatedAt: string;
assemblyParts: AssemblyPartApiResponse[];
}
/**
* Product mapping list query parameters.
*/
export interface ProductMappingListParams extends PaginationParams {
isActive?: boolean;
isAssembly?: boolean;
search?: string;
}
/**
* Paginated product mapping list response.
*/
export interface ProductMappingListApiResponse {
mappings: ProductMappingApiResponse[];
total: number;
page: number;
pageSize: number;
}
/**
* Create/update product mapping request.
*/
export interface CreateProductMappingRequest {
shopifyProductId: string;
shopifyVariantId?: string;
sku: string;
productName: string;
description?: string;
isAssembly?: boolean;
modelFileId?: string;
modelFileName?: string;
defaultPrintProfile?: PrintProfile;
}
export interface UpdateProductMappingRequest extends Partial<CreateProductMappingRequest> {
isActive?: boolean;
}
7. Create Shipment API Types¶
Create libs/domain-contracts/src/api/shipment.api.ts:
import { ShipmentStatus } from '../lib/types';
/**
* Shipment response from API.
*/
export interface ShipmentApiResponse {
id: string;
orderId: string;
sendcloudParcelId: string | null;
status: ShipmentStatus;
carrier: string | null;
trackingNumber: string | null;
trackingUrl: string | null;
labelUrl: string | null;
weight: number | null;
shippedAt: string | null;
deliveredAt: string | null;
createdAt: string;
updatedAt: string;
}
/**
* Shipping integration status.
*/
export interface ShippingStatusApiResponse {
enabled: boolean;
provider: 'sendcloud' | 'manual' | null;
configured: boolean;
}
/**
* Create shipping label request.
*/
export interface CreateShippingLabelRequest {
orderId: string;
weight?: number;
carrier?: string;
}
8. Create Event Log API Types¶
Create libs/domain-contracts/src/api/event-log.api.ts:
import { EventMetadata } from '@forma3d/domain';
import { PaginationParams } from './common.types';
/**
* Event severity levels.
*/
export type EventSeverity = 'DEBUG' | 'INFO' | 'WARNING' | 'ERROR';
/**
* Event log entry response.
*/
export interface EventLogApiResponse {
id: string;
orderId: string | null;
printJobId: string | null;
eventType: string;
severity: EventSeverity;
message: string;
metadata: EventMetadata | null;
createdAt: string;
}
/**
* Event log list query parameters.
*/
export interface EventLogListParams extends PaginationParams {
orderId?: string;
printJobId?: string;
severity?: EventSeverity;
eventType?: string;
startDate?: string;
endDate?: string;
}
/**
* Paginated event log list response.
*/
export interface EventLogListApiResponse {
logs: EventLogApiResponse[];
total: number;
page: number;
pageSize: number;
}
9. Create Health API Types¶
Create libs/domain-contracts/src/api/health.api.ts:
/**
* Health check response.
*/
export interface HealthApiResponse {
status: 'ok' | 'degraded' | 'unhealthy';
timestamp: string;
version?: string;
}
/**
* Detailed health check response.
*/
export interface HealthDetailedApiResponse extends HealthApiResponse {
database: 'connected' | 'disconnected';
redis?: 'connected' | 'disconnected';
simplyPrint?: 'connected' | 'disconnected' | 'unconfigured';
sendcloud?: 'connected' | 'disconnected' | 'unconfigured';
uptime: number;
memoryUsage: {
heapUsed: number;
heapTotal: number;
rss: number;
};
}
10. Create API Index¶
Create libs/domain-contracts/src/api/index.ts:
// Common types
export * from './common.types';
// Resource-specific types
export * from './order.api';
export * from './print-job.api';
export * from './dashboard.api';
export * from './product-mapping.api';
export * from './shipment.api';
export * from './event-log.api';
export * from './health.api';
Update libs/domain-contracts/src/index.ts:
export * from './lib/types';
export * from './api';
Phase 2: Update Frontend API Client (4 hours)¶
Priority: High | Impact: High | Dependencies: Phase 1
1. Update API Client to Use Shared Types¶
Update apps/web/src/lib/api-client.ts:
import {
// Order types
OrderApiResponse,
OrderListApiResponse,
OrderListParams,
UpdateOrderStatusRequest,
// Print job types
PrintJobApiResponse,
PrintJobListApiResponse,
PrintJobListParams,
CancelPrintJobRequest,
// Dashboard types
DashboardStatsApiResponse,
// Product mapping types
ProductMappingApiResponse,
ProductMappingListApiResponse,
ProductMappingListParams,
CreateProductMappingRequest,
UpdateProductMappingRequest,
// Shipment types
ShipmentApiResponse,
ShippingStatusApiResponse,
CreateShippingLabelRequest,
// Event log types
EventLogListApiResponse,
EventLogListParams,
// Health types
HealthApiResponse,
HealthDetailedApiResponse,
} from '@forma3d/domain-contracts';
const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:3000';
// Remove all local interface definitions - now imported from shared types
async function request<T>(
endpoint: string,
options: RequestInit = {},
apiKey?: string,
): Promise<T> {
const headers: HeadersInit = {
'Content-Type': 'application/json',
...options.headers,
};
if (apiKey) {
(headers as Record<string, string>)['X-API-Key'] = apiKey;
}
const response = await fetch(`${API_BASE}${endpoint}`, {
...options,
headers,
});
if (!response.ok) {
const error = await response.json().catch(() => ({ message: 'Request failed' }));
throw new Error(error.message || `HTTP ${response.status}`);
}
if (response.status === 204) {
return undefined as T;
}
return response.json();
}
export const apiClient = {
health: {
check: () => request<HealthApiResponse>('/health'),
live: () => request<HealthApiResponse>('/health/live'),
ready: () => request<HealthDetailedApiResponse>('/health/ready'),
},
orders: {
list: (params?: OrderListParams) => {
const query = new URLSearchParams();
if (params?.page) query.set('page', String(params.page));
if (params?.pageSize) query.set('pageSize', String(params.pageSize));
if (params?.status) query.set('status', params.status);
if (params?.search) query.set('search', params.search);
const queryString = query.toString();
return request<OrderListApiResponse>(
`/api/v1/orders${queryString ? `?${queryString}` : ''}`,
);
},
get: (id: string) => request<OrderApiResponse>(`/api/v1/orders/${id}`),
updateStatus: (id: string, status: UpdateOrderStatusRequest['status'], apiKey: string) =>
request<OrderApiResponse>(
`/api/v1/orders/${id}/status`,
{ method: 'PUT', body: JSON.stringify({ status }) },
apiKey,
),
cancel: (id: string, apiKey: string) =>
request<OrderApiResponse>(
`/api/v1/orders/${id}/cancel`,
{ method: 'PUT' },
apiKey,
),
},
printJobs: {
list: (params?: PrintJobListParams) => {
const query = new URLSearchParams();
if (params?.page) query.set('page', String(params.page));
if (params?.pageSize) query.set('pageSize', String(params.pageSize));
if (params?.status) query.set('status', params.status);
if (params?.orderId) query.set('orderId', params.orderId);
const queryString = query.toString();
return request<PrintJobListApiResponse>(
`/api/v1/print-jobs${queryString ? `?${queryString}` : ''}`,
);
},
getByOrderId: (orderId: string) =>
request<PrintJobApiResponse[]>(`/api/v1/print-jobs/order/${orderId}`),
getActive: () => request<PrintJobApiResponse[]>('/api/v1/print-jobs/active'),
retry: (id: string, apiKey: string) =>
request<PrintJobApiResponse>(
`/api/v1/print-jobs/${id}/retry`,
{ method: 'POST' },
apiKey,
),
cancel: (id: string, reason: string | undefined, apiKey: string) =>
request<PrintJobApiResponse>(
`/api/v1/print-jobs/${id}/cancel`,
{ method: 'POST', body: JSON.stringify({ reason } satisfies CancelPrintJobRequest) },
apiKey,
),
},
dashboard: {
getStats: () => request<DashboardStatsApiResponse>('/api/v1/dashboard/stats'),
},
mappings: {
list: (params?: ProductMappingListParams) => {
const query = new URLSearchParams();
if (params?.page) query.set('page', String(params.page));
if (params?.pageSize) query.set('pageSize', String(params.pageSize));
if (params?.isActive !== undefined) query.set('isActive', String(params.isActive));
if (params?.search) query.set('search', params.search);
const queryString = query.toString();
return request<ProductMappingListApiResponse>(
`/api/v1/product-mappings${queryString ? `?${queryString}` : ''}`,
);
},
get: (id: string) => request<ProductMappingApiResponse>(`/api/v1/product-mappings/${id}`),
create: (data: CreateProductMappingRequest, apiKey: string) =>
request<ProductMappingApiResponse>(
'/api/v1/product-mappings',
{ method: 'POST', body: JSON.stringify(data) },
apiKey,
),
update: (id: string, data: UpdateProductMappingRequest, apiKey: string) =>
request<ProductMappingApiResponse>(
`/api/v1/product-mappings/${id}`,
{ method: 'PUT', body: JSON.stringify(data) },
apiKey,
),
delete: (id: string, apiKey: string) =>
request<void>(
`/api/v1/product-mappings/${id}`,
{ method: 'DELETE' },
apiKey,
),
},
logs: {
list: (params?: EventLogListParams) => {
const query = new URLSearchParams();
if (params?.page) query.set('page', String(params.page));
if (params?.pageSize) query.set('pageSize', String(params.pageSize));
if (params?.orderId) query.set('orderId', params.orderId);
if (params?.severity) query.set('severity', params.severity);
if (params?.eventType) query.set('eventType', params.eventType);
const queryString = query.toString();
return request<EventLogListApiResponse>(
`/api/v1/logs${queryString ? `?${queryString}` : ''}`,
);
},
},
shipping: {
getByOrderId: async (orderId: string) => {
try {
return await request<ShipmentApiResponse>(`/api/v1/shipments/order/${orderId}`);
} catch {
return null;
}
},
getStatus: () => request<ShippingStatusApiResponse>('/api/v1/shipping/status'),
createLabel: (data: CreateShippingLabelRequest, apiKey: string) =>
request<ShipmentApiResponse>(
'/api/v1/shipping/label',
{ method: 'POST', body: JSON.stringify(data) },
apiKey,
),
},
};
2. Update Frontend Hooks to Use Shared Types¶
Update hooks to import types from @forma3d/domain-contracts instead of local definitions.
Example update for apps/web/src/hooks/use-orders.ts:
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import {
OrderApiResponse,
OrderListApiResponse,
OrderListParams,
PrintJobApiResponse,
} from '@forma3d/domain-contracts';
import { apiClient } from '../lib/api-client';
import { useAuth } from '../contexts/auth-context';
export function useOrders(params?: OrderListParams) {
return useQuery<OrderListApiResponse>({
queryKey: ['orders', params],
queryFn: () => apiClient.orders.list(params),
});
}
export function useOrder(id: string) {
return useQuery<OrderApiResponse>({
queryKey: ['order', id],
queryFn: () => apiClient.orders.get(id),
enabled: !!id,
});
}
// ... rest of hooks using imported types
Phase 3: Update Backend DTOs to Extend Shared Types (4 hours)¶
Priority: High | Impact: High | Dependencies: Phase 1
1. Update Order DTOs¶
Update apps/api/src/orders/dto/order.dto.ts:
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import {
OrderApiResponse,
OrderListItemApiResponse,
LineItemApiResponse,
} from '@forma3d/domain-contracts';
import { ShippingAddress } from '@forma3d/domain';
/**
* Line item DTO - implements shared interface.
*/
export class LineItemDto implements LineItemApiResponse {
@ApiProperty()
id!: string;
@ApiProperty()
orderId!: string;
@ApiProperty()
shopifyLineItemId!: string;
@ApiPropertyOptional()
productMappingId!: string | null;
@ApiProperty()
sku!: string;
@ApiProperty()
productName!: string;
@ApiProperty()
quantity!: number;
@ApiProperty()
unitPrice!: string;
@ApiProperty()
totalPrice!: string;
@ApiProperty()
status!: string;
@ApiProperty()
requiresPrinting!: boolean;
@ApiProperty()
completedParts!: number;
@ApiProperty()
totalParts!: number;
@ApiProperty()
createdAt!: string;
@ApiProperty()
updatedAt!: string;
}
/**
* Order DTO - implements shared interface.
*/
export class OrderDto implements OrderApiResponse {
@ApiProperty()
id!: string;
@ApiProperty()
shopifyOrderId!: string;
@ApiProperty()
shopifyOrderNumber!: string;
@ApiProperty()
status!: string;
@ApiProperty()
customerName!: string;
@ApiProperty()
customerEmail!: string;
@ApiPropertyOptional()
shippingAddress!: ShippingAddress | null;
@ApiProperty()
totalPrice!: string;
@ApiProperty()
currency!: string;
@ApiProperty()
totalParts!: number;
@ApiProperty()
completedParts!: number;
@ApiPropertyOptional()
notes!: string | null;
@ApiProperty()
createdAt!: string;
@ApiProperty()
updatedAt!: string;
@ApiPropertyOptional()
completedAt!: string | null;
@ApiProperty({ type: [LineItemDto] })
lineItems!: LineItemApiResponse[];
}
Phase 4: Documentation Updates (30 minutes)¶
Priority: Medium | Impact: Medium | Dependencies: Phase 3
1. Update Technical Debt Register¶
Update docs/04-development/techdebt/technical-debt-register.md:
### ~~TD-004: Duplicated DTO Definitions Between Frontend and Backend~~ ✅ RESOLVED
**Type:** Architecture Debt
**Status:** ✅ **Resolved in Phase 5f**
**Resolution Date:** 2026-XX-XX
#### Resolution
Created shared API types in `libs/domain-contracts` used by both frontend and backend:
- **Shared Types**: All API response/request types in `libs/domain-contracts/src/api/`
- **Frontend**: API client imports from shared types
- **Backend**: DTOs implement shared interfaces
- **Type Safety**: Compile-time checking across the full stack
**Files Created:**
- `libs/domain-contracts/src/api/common.types.ts`
- `libs/domain-contracts/src/api/order.api.ts`
- `libs/domain-contracts/src/api/print-job.api.ts`
- `libs/domain-contracts/src/api/dashboard.api.ts`
- `libs/domain-contracts/src/api/product-mapping.api.ts`
- `libs/domain-contracts/src/api/shipment.api.ts`
- `libs/domain-contracts/src/api/event-log.api.ts`
- `libs/domain-contracts/src/api/health.api.ts`
**Files Modified:**
- `apps/web/src/lib/api-client.ts` - Uses shared types
- `apps/web/src/hooks/*.ts` - Import shared types
- `apps/api/src/**/dto/*.ts` - Implement shared interfaces
📁 Files to Create/Modify¶
New Files¶
libs/domain-contracts/src/api/
index.ts
common.types.ts
order.api.ts
print-job.api.ts
dashboard.api.ts
product-mapping.api.ts
shipment.api.ts
event-log.api.ts
health.api.ts
Modified Files¶
libs/domain-contracts/src/index.ts # Export API types
apps/web/src/lib/api-client.ts # Use shared types
apps/web/src/hooks/use-orders.ts # Import shared types
apps/web/src/hooks/use-dashboard.ts
apps/web/src/hooks/use-mappings.ts
apps/web/src/hooks/use-logs.ts
apps/web/src/hooks/use-shipments.ts
apps/web/src/hooks/use-health.ts
apps/api/src/orders/dto/order.dto.ts # Implement interfaces
apps/api/src/print-jobs/dto/print-job.dto.ts
apps/api/src/dashboard/dto/dashboard.dto.ts
docs/04-development/techdebt/technical-debt-register.md
✅ Validation Checklist¶
Phase 1: Shared Types¶
- Common pagination types created
- Order API types created
- Print job API types created
- Dashboard API types created
- Product mapping API types created
- Shipment API types created
- Event log API types created
- Health API types created
- All types exported from domain-contracts
Phase 2: Frontend Integration¶
- API client updated to use shared types
- All local interface definitions removed
- Hooks updated to import shared types
- Frontend builds without type errors
Phase 3: Backend Integration¶
- DTOs implement shared interfaces
- Swagger decorators preserved
- Backend builds without type errors
Final Verification¶
# All builds pass
pnpm nx build domain-contracts
pnpm nx build web
pnpm nx build api
# Type checking
pnpm nx lint domain-contracts
pnpm nx lint web
pnpm nx lint api
# Tests pass
pnpm nx test web
pnpm nx test api
🚫 Constraints and Rules¶
MUST DO¶
- Create shared types in libs/domain-contracts
- Export all types from package index
- Update frontend to use shared types
- Make backend DTOs implement shared interfaces
- Maintain Swagger decorators on DTOs
MUST NOT¶
- Duplicate type definitions
- Use
anyfor API responses - Skip nullable fields
- Change API response shapes
- Break existing API contracts
END OF PROMPT
This prompt resolves TD-004 from the technical debt register by creating shared API types in libs/domain-contracts used by both frontend and backend.