eduardweb.
TypeScript avansatAvansat#typescript#type-safety#backend

Cum m-au salvat Template Literal Types de bug-uri stupide în producție

De Cristian Barbu, 3 iun. 2026 · 1 vizualizări · 3 like-uri

Postat acum 6 zile
typescript
type Resource = "users" | "billing" | "projects";
type Action = "read" | "write" | "delete";

type Permission = `api:${Resource}:${Action}`;

// Funcție de verificare strictă a permisiunilor
function hasPermission(userPermissions: Permission[], required: Permission): boolean {
  return userPermissions.includes(required);
}

// Parsare tipizată folosind keyword-ul 'infer'
type ParsePermission<T extends string> = T extends `api:${infer R}:${infer A}`
  ? { resource: R; action: A }
  : null;

type Parsed = ParsePermission<"api:users:write">;
// Rezultat: { resource: "users"; action: "write"; }

Salutare tuturor. Am avut recent de curățat un modul de autorizare într-un proiect destul de măricel, cu vreo 12 resurse diferite și roluri dinamice. Clasic, permisiunile erau verificate prin string-uri chioare, gen checkPermission('api:user:write'). Evident, am găsit în cod și api:users:write și api:billing:deletee (cu doi de 'e'). Bug-uri stupide, greu de prins la review-uri rapide, dar care ne-au mâncat nervii în producție.

Soluția: Template Literal Types

În loc să lăsăm permisiunile la nivel de string generic, am trecut totul pe Template Literal Types, o chestie introdusă prin TS 4.1 de care mulți fug pentru că pare prea academică. Dacă ai un set finit de resurse și acțiuni, TypeScript poate genera automat toate combinațiile posibile. E exact ca un template string din JavaScript, doar că se întâmplă exclusiv la compilare.

Să zicem că ai trei resurse și trei acțiuni. În loc să scrii manual 9 tipuri sau să le lași la ghici, lași compilerul să își facă damblaua. Câștigi instant autocompletion în IDE și zero șanse de typo-uri. Dacă încerci să trimiți api:billing:deletee, codul nici nu se compilează.

Cum arată în practică și cum facem parsarea

Partea frumoasă apare când ai nevoie de parsare dynamică, de exemplu când primești un string de la un API extern sau din baza de date și vrei să te asiguri că e valid înainte să îl trimiți mai departe în aplicație ca tip strict.

Aici intervine cuvântul cheie infer. Putem construi un utilitar de tipărire care „sparge” string-ul în bucăți și ne returnează un obiect puternic tipizat. Dacă string-ul nu respectă formatul exact de api:resource:action, TypeScript ne va întoarce never sau null, forțându-ne să tratăm cazul de eroare la runtime.

Am implementat chestia asta pe un microserviciu și am observat că am eliminat complet acele bug-uri stupide de tipar. În plus, când adăugăm o resursă nouă în uniunea Resource, tot sistemul se updatează automat și primim erori de compilare exact acolo unde am uitat să tratăm noul caz.

Trade-off-ul sincer de care nimeni nu vorbește

Totul sună roz, dar există și o parte mai puțin plăcută. Dacă ai un sistem extrem de dinamic unde resursele sunt create direct de utilizatori în baza de date (de exemplu, ID-uri de proiecte generate dinamic, gen api:project-123:read), template literal types devin aproape inutile. TypeScript rulează doar la build-time, așa că nu are cum să știe ce ID-uri ai tu în Postgres.

Pentru cazurile alea, tot la type assertions (as Permission) sau type guards manuale ajungi. Noi am decis să păstrăm abordarea asta doar pentru resursele fixe din sistem (modulele mari) și să folosim validări clasice pentru resursele dinamice. Am economisit cam 30% din timpul de debugging pe zona de securitate, deci pentru noi a meritat efortul.

Voi cum gestionați permisiunile complexe în TypeScript? Mergeți pe string-uri simple cu speranța că testele unitare prind tot, sau preferați să lăsați compilerul să facă munca grea?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

Doar membrii comunității pot lăsa comentarii.