type SystemResource = 'users' | 'billing' | 'invoices';
type SystemAction = 'read' | 'create' | 'update' | 'delete';
// Generăm automat uniunea: "api:users:read" | "api:users:create" | ...
type StandardPermission = `api:${SystemResource}:${SystemAction}`;
// Putem adăuga permisiuni specifice care nu respectă tiparul
type CustomPermission = 'api:reports:export' | 'system:god_mode';
type AppPermission = StandardPermission | CustomPermission;
function checkUserPermission(permission: AppPermission): boolean {
// Implementare runtime
return true;
}
// ✅ Valid - IDE-ul oferă autocomplete complet
checkUserPermission('api:users:read');
// ❌ Eroare la compilare: Type '"api:user:read"' is not assignable...
// checkUserPermission('api:user:read');Salutare tuturor! Am rescris recent modulul de RBAC la un microserviciu cu vreo 45 de resurse și 6 acțiuni posibile per resursă. Trecerea la Template Literal Types ne-a salvat de la cel puțin 10 bug-uri stupide de typo care ajungeau pe staging fără să ne dăm seama.
De la string generic la contract strict în tipuri
Până acum ceva timp, clasica funcție de verificare arăta cam așa: checkPermission(perm: string). Problematic? Enorm. Cineva scria în frontend api:user:read (la singular), pe backend era așteptat api:users:read (la plural), iar rezultatul era un bug silentios cu 403 la runtime. Nimeni nu prindea asta la build.
Template Literal Types au schimbat complet ecuația din TypeScript 4.1 încoace. Ideea e că poți folosi sintaxa clasică de string interpolation din JavaScript (${...}), dar direct în definirea tipurilor. Astfel, prinzi greșelile direct în IDE, înainte să dai commit.
Cum construiești tipul din bucăți atomicizate
Secretul este să îți spargi resursele și acțiunile în uniuni simple. Dacă ai o resursă users și una billing, plus acțiunile read, write, delete, TypeScript știe să facă o produs cartezian între ele. Când tastezi checkPermission('api:'), autocomplete-ul din VS Code îți afișează instant absolut toate variantele valide.
Dar ce faci când o resursă are o acțiune specială pe care celelalte nu o au? De exemplu, reports are nevoie de export, dar users nu. Aici folosești uniuni compuse sau tipuri ajutătoare (mapped types). Nu le amesteca pe toate într-un singur string uriaș, ci compune-le curat.
Trade-off-ul real: Compilatorul de TS și explozia combinatorială
Sună superb în teorie, dar am plătit prețul într-un proiect anul trecut. Am avut caz real unde un coleg a vrut să combine Environment (4 variante), Service (15 variante), Resource (30 variante) și Action (8 variante) într-un singur template literal.
Rezultatul? TypeScript a trebuit să evalueze o uniune de peste 14.400 de string-uri literale. Timpul de build la tsc --noEmit pe pipeline-ul de CI/CD a sărit brusc de la 4 secunde la 22 de secunde, iar serverul de Language Server din VS Code consuma 3 GB de RAM și dădea crash o dată la jumătate de oră.
Regula mea de aur acum: dacă produsul cartezian depășește 300-500 de variante, oprește-te. Mai bine folosești o verificare la runtime cu o schemă Zod / Valibot încărcată la startup, decât să-ți prăjești procesorul pentru tipuri ultra-complexe.
Voi cum gestionați permisiunile dinamice în aplicațiile mari de Node/TS? Mergeți pe enume, template literals sau validări exclusiv la runtime?