/** * Erreurs metier typees pour le bridge. * Chaque erreur a un code stable + statut HTTP + details serializables. */ export type ErrorCode = | 'AUTH_REQUIRED' | 'AUTH_INVALID' | 'FORBIDDEN' | 'FORBIDDEN_SCOPE' | 'NOT_FOUND' | 'VALIDATION_ERROR' | 'RG_VIOLATION' | 'CONFLICT' | 'RATE_LIMITED' | 'BASEROW_UNAVAILABLE' | 'DOCMOST_UNAVAILABLE' | 'INTERNAL'; export class BridgeError extends Error { constructor( public readonly code: ErrorCode, public readonly status: number, message: string, public readonly details?: Record, ) { super(message); this.name = 'BridgeError'; } toJSON() { return { error: { code: this.code, message: this.message, ...(this.details ? { details: this.details } : {}), }, }; } } export const errors = { authRequired: () => new BridgeError('AUTH_REQUIRED', 401, 'Token absent'), authInvalid: () => new BridgeError('AUTH_INVALID', 401, 'Token invalide'), forbidden: (scope: string) => new BridgeError('FORBIDDEN_SCOPE', 403, `Scope requis : ${scope}`, { scope }), forbiddenIdentity: (reason: string, details?: Record) => new BridgeError('FORBIDDEN', 403, reason, details), notFound: (entity: string, id: string | number) => new BridgeError('NOT_FOUND', 404, `${entity} introuvable`, { entity, id }), validation: (issues: unknown) => new BridgeError('VALIDATION_ERROR', 400, 'Body invalide', { issues }), rgViolation: (rule: string, message: string, details?: Record) => new BridgeError('RG_VIOLATION', 422, message, { rule, ...details }), conflict: (message: string, details?: Record) => new BridgeError('CONFLICT', 409, message, details), rateLimited: (retryAfter: number) => new BridgeError('RATE_LIMITED', 429, 'Too many requests', { retry_after: retryAfter }), baserowDown: () => new BridgeError('BASEROW_UNAVAILABLE', 502, 'Baserow API unreachable'), docmostDown: () => new BridgeError('DOCMOST_UNAVAILABLE', 502, 'Docmost API unreachable'), internal: (message: string) => new BridgeError('INTERNAL', 500, message), };