Rules Hub
Coding Rules Library
Rule priority, scope & exceptions
Use this to align rules with the senior-level structure (P0/P1/P2, scope, exceptions/tradeoffs).
Type cached ORM entities as plain objects, not live class instances
A value read back from a JSON cache is a deserialized plain object, not an ORM/class instance; type it accordingly so callers don't depend on lost instance behavior.
Bad example
| 1 | async function fetchUser(id: string): Promise<User> { |
| 2 | const cached = await cache.getJson<User>(userKey(id)); |
| 3 | if (cached) return cached; // claims to be a User entity, but it's a plain object |
| 4 |
|
| 5 | const user = await userRepo.findOneByOrFail({ id }); |
| 6 | await cache.setJson(userKey(id), user, TTL); |
| 7 | return user; |
| 8 | } |
| 9 |
|
| 10 | // Caller assumes entity behavior that no longer exists for cached values |
| 11 | const user = await fetchUser(id); |
| 12 | user.computeDisplayName(); // throws for cache hits: method is gone after JSON round-trip |
Explanation (EN)
The function advertises Promise<User>, but a JSON cache hit returns a deserialized plain object with no prototype, methods, getters, or lazy relations. Callers that invoke entity methods will break only on cache hits, producing intermittent, hard-to-trace bugs.
Objašnjenje (HR)
Funkcija obecava Promise<User>, ali pogodak u JSON cacheu vraca deserijalizirani obicni objekt bez prototipa, metoda, gettera ili lijenih relacija. Pozivatelji koji koriste metode entiteta puknut ce samo kod pogotka u cacheu, sto stvara povremene i tesko ulovljive greske.
Good example
| 1 | type PlainUser = Omit<User, 'computeDisplayName' | 'posts'>; // data fields only |
| 2 |
|
| 3 | async function fetchUser(id: string): Promise<PlainUser> { |
| 4 | const cached = await cache.getJson<PlainUser>(userKey(id)); |
| 5 | if (cached) return cached; |
| 6 |
|
| 7 | const user = await userRepo.findOneByOrFail({ id }); |
| 8 | await cache.setJson(userKey(id), user, TTL); |
| 9 | return user; |
| 10 | } |
| 11 |
|
| 12 | // Caller works with data only; if instance behavior is needed, rehydrate explicitly |
| 13 | const plain = await fetchUser(id); |
| 14 | const displayName = buildDisplayName(plain); // pure function over data |
Explanation (EN)
The return type now reflects what is actually returned across both paths: a plain data shape. Callers cannot accidentally rely on entity methods, and if instance behavior is genuinely needed they must rehydrate explicitly (e.g. repo.create(plain)) instead of getting silent runtime failures.
Objašnjenje (HR)
Povratni tip sada odrazava ono sto se zaista vraca na obje grane: obicni podatkovni oblik. Pozivatelji se ne mogu slucajno osloniti na metode entiteta, a ako im stvarno treba ponasanje instance, moraju ga eksplicitno rehidrirati (npr. repo.create(plain)) umjesto da dobiju tihe greske u izvodjenju.
Notes (EN)
Applies to any serialize/deserialize boundary (Redis, HTTP, structuredClone, message queues), not just ORM entities. If callers truly need instance behavior, rehydrate via the ORM's create/merge before use.
Bilješke (HR)
Vrijedi za svaku granicu serijalizacije/deserijalizacije (Redis, HTTP, structuredClone, redovi poruka), ne samo za ORM entitete. Ako pozivateljima stvarno treba ponasanje instance, rehidrirajte ga preko ORM create/merge prije koristenja.