Instruction file imported from bymaxone/nest-cache (
.github/instructions/code.instructions.md). Copyright stays with the author.
TypeScript source code standards
TypeScript compiler flags — practical implications
tsconfig.json enables strict, noUncheckedIndexedAccess, exactOptionalPropertyTypes, noImplicitOverride, noImplicitReturns. Review impact:
noUncheckedIndexedAccess:array[0]isT | undefined. Every index access must be guarded. Flag unguarded accesses.exactOptionalPropertyTypes:{ prop?: string }≠{ prop: string | undefined }. Flag conflation.noImplicitOverride: NestJS lifecycle hooks that override a parent must haveoverride. Flag missing keyword.noImplicitReturns: every code path must return. Flag conditional fall-through.
ESLint rules enforced as errors
no-explicit-any: error— noanyin source. Useunknown, generics (get<T>), or explicit types.no-non-null-assertion: error— never!. Narrow the type instead.consistent-type-imports: error— type-only imports must useimport type { ... }.explicit-function-return-type: error— explicit return type on all functions.explicit-module-boundary-types: error— explicit types on all exported parameters.import/no-cycle: error— circular imports are forbidden.no-restricted-imports: error—node:cryptoonly; nevercrypto(bare),bcrypt,argon2,uuid,nanoid,crypto-js.
Types and interfaces
interfacefor DI ports/contracts that classesimplement(ISerializer,ICacheEvents). TheIprefix is reserved for these only.typefor unions, intersections, mapped types, aliases (CacheEventName,CacheErrorCode,CacheConnectionStatus).
NestJS patterns
- DI only — no
new ServiceClass()outside tests. - Injection tokens must use
Symbol(), never string literals (string tokens cause silent collisions in multi-module apps). Example:export const BYMAX_CACHE_OPTIONS = Symbol('BYMAX_CACHE_OPTIONS'). - Dynamic module requires both
forRoot(options)andforRootAsync({ useFactory, useClass, useExisting }), built onConfigurableModuleBuilder, global by default viasetExtras→DynamicModule.global(not a manual@Global()— see spec §0). - Singletons only — no
Scope.REQUEST. - Core logic depends only on interfaces (
ISerializer,ICacheEvents) — never imports concrete implementations beyond theioredispeer. OnModuleInitconnects (unlesslazyConnect);OnModuleDestroytears the connection down gracefully (quit()with a shutdown timeout, thendisconnect()).- Unconfigured optional features (Pub/Sub, scripts) must not register providers in DI.
Import ordering
node:* → external → internal (@bymax-one/nest-cache) → parent/sibling → index. Alphabetical within each group (enforced by import/order).
Redis / ioredis patterns
- One singleton
ioredisclient owned by the connection manager — never instantiatenew Redis()ad hoc in services. - Namespace every key through the key builder; raw
getClient()keys bypass tenant isolation and are an anti-pattern. enableOfflineQueue: false(fail fast); boundedretryStrategy;reconnectOnErroronly onREADONLYfailover.- Lua scripts: register up front, run via
EVALSHAwith aNOSCRIPTfallback — neverEVALraw untrusted bodies. - Use
SCAN(cursors), neverKEYS(blocks Redis); batch withpipeline().
Security
- Deserialization must fail closed — malformed input throws
CacheException(DESERIALIZATION_FAILED), never returns a partial or wrongly-typed value. - Never put secret values in
CacheExceptiondetailsor event payloads; truncate previews. - Any secret/hash comparison must use
node:cryptotimingSafeEqual— never===. flushNamespacemust be guarded against accidental production use.