Generic types for flexibility
Why it matters
An API that returns Promise<any> for network requests or data operations forces every TypeScript consumer to cast results manually — const users = await client.get('/users') as User[]. This eliminates compile-time type checking at exactly the boundary where runtime errors are most common. ISO 25010 modifiability suffers because any change to the response shape goes undetected until runtime. The consumer cannot trust the type system to catch contract mismatches between their models and your API's actual output.
Severity rationale
Low because the impact is limited to TypeScript consumers and does not cause runtime failures — the type ergonomics are poor but the functionality remains intact.
Remediation
Add generic type parameters to data-handling methods so consumers can specify their expected response types.
// Before — returns any:
export class Client {
async request(path: string): Promise<any> { /* ... */ }
}
// After — generic return type:
export class Client {
async request<T>(path: string): Promise<T> { /* ... */ }
}
// Consumer gets full type safety:
const users = await client.request<User[]>('/api/users')
// ^? User[]
For event emitters, use a typed event map: on<K extends keyof Events>(event: K, handler: (data: Events[K]) => void). At least 50% of data-handling APIs should use generics before this check passes.
Detection
-
ID:
type-generics -
Severity:
low -
What to look for: Count all public API functions that handle generic data. For each, examine the TypeScript types in the public API for places where generics would improve the consumer experience:
- Response types that are always
anyorunknowninstead of genericT - Collection/container types that hardcode their element type
- Event emitter patterns without typed events
- Configuration objects where parts could be generic (e.g., middleware types)
Common pattern:
client.get('/users')returnsPromise<any>instead ofPromise<T>where the consumer can specifyclient.get<User[]>('/users').
- Response types that are always
-
Pass criteria: The public API uses generics where they would benefit consumers — particularly for response types, collection operations, and event handling. The consumer can specify types for their use case rather than casting from
any— at least 50% of data-handling APIs should use TypeScript generics for type safety. Report: "X generic-capable APIs found, Y use TypeScript generics." -
Fail criteria: The public API uses
anyor untyped returns for operations where generics would be natural (API responses, data transformations, event payloads). Consumers must cast results to get type safety. -
Skip (N/A) when: The package is written in plain JavaScript with no TypeScript. Also skip if the package's API has no natural generic points — every function takes and returns concrete types (e.g., a date formatting library). Skip for Go (uses different generics pattern), Rust (check for appropriate use of generics in the public API).
-
Detail on fail: Quote the actual return type showing the missing generic. Example:
"The client.request() method returns Promise<any>. Consumers must cast: const users = await client.request('/users') as User[]. Add a generic: client.request<User[]>('/users') for type-safe responses." -
Remediation: Generics let consumers supply their own types, getting type safety without your package needing to know their data structures.
// Before — returns any: export class Client { async request(path: string): Promise<any> { /* ... */ } } // After — generic return type: export class Client { async request<T>(path: string): Promise<T> { /* ... */ } } // Consumer gets type safety: const users = await client.request<User[]>('/api/users') // ^? User[]
External references
- iso-25010:2011 · maintainability.modifiability — Modifiability — generics allow API evolution without breaking consumers
- iso-25010:2011 · compatibility.interoperability — Interoperability — typed generics integrate with consumer TypeScript toolchains
Taxons
History
- 2026-04-18·v1.0.0·Initial import from sdk-package-quality·automated