feat(shared): types API, logger structuré, validation env.ts

This commit is contained in:
Hermann_Kitio 2026-04-17 17:24:21 +03:00
parent dbc9360b36
commit 476dfeeb08
3 changed files with 63 additions and 0 deletions

22
src/shared/config/env.ts Normal file
View file

@ -0,0 +1,22 @@
import { z } from 'zod'
const envSchema = z.object({
VITE_API_URL: z.string().url(),
VITE_SUPABASE_URL: z.string().url(),
VITE_SUPABASE_ANON_KEY: z.string().min(1),
VITE_ENABLE_T2_LIVE: z.enum(['true', 'false']).optional(),
VITE_SENTRY_DSN: z.string().url().optional(),
})
const parsed = envSchema.safeParse(import.meta.env)
if (!parsed.success) {
const issues = parsed.error.issues
.map((i) => ` - ${i.path.join('.')}: ${i.message}`)
.join('\n')
throw new Error(
`Variables d'environnement invalides ou manquantes :\n${issues}\n\nVérifier .env.example`,
)
}
export const env = parsed.data

18
src/shared/lib/logger.ts Normal file
View file

@ -0,0 +1,18 @@
type LogLevel = 'debug' | 'info' | 'warn' | 'error'
type LogContext = Record<string, unknown>
function log(level: LogLevel, message: string, context?: LogContext): void {
if (import.meta.env.DEV) {
console[level](`[${level.toUpperCase()}] ${message}`, context ?? '')
return
}
const entry = { level, message, timestamp: new Date().toISOString(), ...context }
console[level](JSON.stringify(entry))
}
export const logger = {
debug: (msg: string, ctx?: LogContext) => log('debug', msg, ctx),
info: (msg: string, ctx?: LogContext) => log('info', msg, ctx),
warn: (msg: string, ctx?: LogContext) => log('warn', msg, ctx),
error: (msg: string, ctx?: LogContext) => log('error', msg, ctx),
}

23
src/shared/types/api.ts Normal file
View file

@ -0,0 +1,23 @@
// SOURCE OF TRUTH: expria-backend — format réel des erreurs, confirmé par audit 2026-04-17
// Les modifications doivent être synchronisées avec le backend.
export interface ApiError {
error: true
code: ApiErrorCode
message: string
status?: number
}
export type ApiErrorCode =
| 'AUTH_REQUIRED'
| 'PLAN_INSUFFICIENT'
| 'QUOTA_REACHED'
| 'VALIDATION_ERROR'
| 'INVALID_BODY'
| 'INVALID_PLAN'
| 'NO_ACTIVE_SUBSCRIPTION'
| 'SIMULATION_NOT_FOUND'
| 'STRIPE_WEBHOOK_INVALID'
| 'INTERNAL_ERROR'
export type FrontendErrorCode = 'TIMEOUT' | 'NETWORK_ERROR' | 'PARSE_ERROR'