Designs complex generic types, refactors `any` types to strict alternatives, creates type guards and utility types, and resolves TypeScript compiler errors. Use when the user asks about TypeScript (TS) types, generics, type inference, type guards, removing `any` types, strict typing, type errors, `infer`, `extends`, conditional types, mapped types, template literal types, branded/opaque types, or utility types like `Partial`, `Record`, `ReturnType`, and `Awaited`.
97
97%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Passed
No known issues
By default, TypeScript widens types when inferring objects and arrays. For advanced type-safe APIs, you often need to preserve literal types deeply within nested structures. This document covers techniques for achieving deep inference.
const makeRouter = <TConfig>(config: TConfig) => {
return { config };
};
const router = makeRouter({
"/": {},
"/search": {
search: ["query", "page"],
},
});
// TConfig is inferred as:
// {
// "/": {};
// "/search": {
// search: string[]; // NOT ["query", "page"]!
// };
// }The literal tuple ["query", "page"] is widened to string[], losing type information.
as constRequire users to add as const:
const router = makeRouter({
"/": {},
"/search": {
search: ["query", "page"],
},
} as const);
// Now TConfig preserves literals:
// {
// readonly "/": {};
// readonly "/search": {
// readonly search: readonly ["query", "page"];
// };
// }as constThe ts-toolbelt library provides F.Narrow for automatic deep narrowing:
import { F } from "ts-toolbelt";
const makeRouter = <TConfig extends BaseRouterConfig>(
config: F.Narrow<TConfig>
) => {
return { config };
};
const router = makeRouter({
"/": {},
"/search": {
search: ["query", "page"],
},
});
// TConfig is now:
// {
// "/": {};
// "/search": {
// search: ["query", "page"]; // Literal tuple preserved!
// };
// }F.Narrow recursively narrows types to their literal forms:
If you can't use ts-toolbelt, implement a simpler version:
type Narrow<T> = T extends Function
? T
: T extends []
? []
: T extends readonly [infer First, ...infer Rest]
? [Narrow<First>, ...Narrow<Rest>]
: T extends object
? { [K in keyof T]: Narrow<T[K]> }
: T;
// Note: This is simplified and may not cover all edge casesimport { F } from "ts-toolbelt";
type BaseRouterConfig = Record<string, { search?: string[] }>;
type TupleToSearchParams<T extends string[]> = {
[K in T[number]]?: string;
};
const makeRouter = <TConfig extends BaseRouterConfig>(
config: F.Narrow<TConfig>
) => {
return {
config,
goTo: <TRoute extends keyof TConfig>(
route: TRoute,
search?: TConfig[TRoute]["search"] extends string[]
? TupleToSearchParams<TConfig[TRoute]["search"]>
: never
) => {
// Implementation
},
};
};
const router = makeRouter({
"/": {},
"/dashboard": {
search: ["page", "perPage", "sort"],
},
});
// Fully type-safe!
router.goTo("/dashboard", {
page: "1",
perPage: "10",
sort: "name", // Must be one of the defined search params
});
// Error: "invalid" is not a valid search param
router.goTo("/dashboard", { invalid: "value" });TypeScript 5.0 introduced const type parameters:
const makeRouter = <const TConfig extends BaseRouterConfig>(
config: TConfig
) => {
return { config };
};
// TConfig is automatically narrowed like as const
const router = makeRouter({
"/": {},
"/search": {
search: ["query", "page"],
},
});const Type Parametersconst createTheme = <const TTheme extends Record<string, string>>(
theme: TTheme
): TTheme => theme;
const theme = createTheme({
primary: "#0066cc",
secondary: "#666666",
});
// theme.primary is "#0066cc", not stringconst routes = defineRoutes({
home: { path: "/" },
user: { path: "/users/:id" },
post: { path: "/posts/:postId" },
});
// Route names and paths are literal typesconst events = createEventMap({
click: (x: number, y: number) => {},
keydown: (key: string) => {},
});
// Event names are literal unions, handlers are properly typed| Technique | Pros | Cons |
|---|---|---|
as const | No dependencies | Manual, readonly types |
F.Narrow | Automatic, flexible | External dependency |
| Custom Narrow | No dependencies, customizable | Complex, may miss edge cases |
const type param | Built-in, clean | TypeScript 5.0+ only |
Deep inference enables powerful conditional type logic:
import { F } from "ts-toolbelt";
const makeApi = <const TConfig extends Record<string, { returns: string }>>(
config: TConfig
) => {
return {
call: <TMethod extends keyof TConfig>(
method: TMethod
): TConfig[TMethod]["returns"] => {
// Implementation
return "" as any;
},
};
};
const api = makeApi({
getUser: { returns: "User" },
getPost: { returns: "Post" },
});
const user = api.call("getUser"); // Type: "User"
const post = api.call("getPost"); // Type: "Post"// Without constraint, F.Narrow has no base to work with
const bad = <TConfig>(config: F.Narrow<TConfig>) => config;
// With constraint, inference works properly
const good = <TConfig extends Record<string, unknown>>(
config: F.Narrow<TConfig>
) => config;With as const, arrays become readonly:
const config = {
values: [1, 2, 3],
} as const;
// config.values is readonly [1, 2, 3]
config.values.push(4); // Error: Property 'push' does not exist on type 'readonly [1, 2, 3]'Very deeply nested types can slow down the compiler:
// May cause performance issues with extremely deep nesting
type DeepConfig = {
level1: {
level2: {
level3: {
// ... many more levels
};
};
};
};const type parameters when possible (TS 5.0+)