type Resource = "user" | "invoice" | "product";
type Action = "create" | "read" | "update" | "delete";
// Generăm automat toate combinațiile posibile
type Permission = `api:${Resource}:${Action}`;
// Funcționează perfect:
const userRead: Permission = "api:user:read";
// Eroare la compilare în IDE:
// Type '"api:users:read"' is not assignable to type 'Permission'
const badPermission: Permission = "api:users:read";Am terminat recent un refactoring pe un sistem de permisiuni și mi-am adus aminte cât de mult m-am chinuit în trecut cu string-urile chioare trimise prin API. Dacă folosești TypeScript și încă scrii manual tipuri pentru fiecare permisiune de tipul api:user:write, pierzi timp de pomană și riști bug-uri stupide. Azi vreau să vă arăt cum rezolvăm asta elegant cu Template Literal Types.
La un proiect de acum doi ani, cu vreo 12k utilizatori activi, aveam în backend 18 resurse și 4 acțiuni standard (create, read, update, delete). Inițial, echipa definise permisiunile ca un ditamai string union scris de mână. Normal că la un moment dat cineva a scris api:invoice:update în loc de api:invoices:update (observați pluralul buclucaș) și ne-am trezit cu bug direct în producție fiindcă verificarea de permisiuni a picat silențios.
După incidentul ăla, am trecut totul pe template literals. În TypeScript, poți combina tipuri de string-uri exact cum faci în JS cu backticks, dar direct la nivel de compilator.
Ce e cu adevărat mișto e că primești auto-complete nativ în IDE. Când începi să scrii api:, editorul îți sugerează instant toate resursele disponibile, iar după ce pui următorul : îți arată acțiunile valabile. Pentru colegii noi care intră în codebase, chestia asta salvează zeci de minute de căutat prin fișiere de configurare.
Unde apare problema: trade-off-ul de performanță
Hai să fim sinceri, nicio funcționalitate din TS nu vine gratis. Șmecheria asta funcționează de minune pentru seturi mici și medii de date. Însă, dacă ai un sistem gigant cu 80 de resurse și 10 acțiuni posibile, TypeScript va genera în spate o uniune de 800 de string-uri unice.
La proiectul menționat, pe măsură ce am adăugat module noi, am observat că build-ul local a crescut cu vreo 3-4 secunde. Dacă exagerezi și combini trei sau patru template-uri de tipuri complexe, te lovești rapid de celebra eroare de compilare: Expression produces a union type that is too complex to represent. Compilatorul are o limită internă de combinatorică pentru a nu-ți bloca complet procesorul.
Un alt minus e că TypeScript dispare la runtime. Dacă permisiunile îți vin din baza de date ca un simplu array de string-uri, tot va trebui să scrii un type guard sau să folosești o librărie de validare (precum Zod) ca să te asiguri că ce primești de la API respectă structura ta strictă înainte să faci cast la tipul generat.
Una peste alta, de când am făcut trecerea asta, am eliminat complet erorile de tip typo pe zona de drepturi în aplicație.
Cum gestionați voi permisiunile granulare în proiectele mari? Mergeți pe template literals sau preferați să le țineți în enum-uri clasice?