Building a Flexible Indexed Types Framework in TypeScript
This challenge focuses on creating a reusable framework for manipulating and deriving types based on their keys. Indexed types are a powerful feature in TypeScript, allowing you to select, transform, and filter properties based on their names. Building a framework around them promotes code reusability and type safety across your projects.
Problem Description
You are tasked with creating a TypeScript module that provides a set of utility functions for working with indexed types. This framework should allow users to:
- Pick by Key Pattern: Select properties from a type object that match a specific key pattern (e.g., all keys starting with "user").
- Omit by Key Pattern: Remove properties from a type object that match a specific key pattern.
- Transform Value by Key Pattern: Modify the type of a property based on its key. For example, change the type of all properties starting with "id" to
number. - Filter by Key Pattern: Create a new type containing only the properties that match a given key pattern.
Your module should be designed to be generic and reusable across different type structures. The key pattern matching should be flexible, allowing for simple prefixes, suffixes, or more complex regular expressions (though regular expression support is optional for this challenge).
Key Requirements:
- The functions should be type-safe and provide accurate type inference.
- The functions should handle edge cases gracefully (e.g., empty objects, no matching keys).
- The functions should be well-documented with JSDoc comments.
- The functions should be generic to work with any type.
Expected Behavior:
The functions should return a new type based on the input type and the provided key pattern. The original type should remain unchanged.
Edge Cases to Consider:
- Empty input types.
- Key patterns that don't match any keys.
- Types with optional properties.
- Types with union types as property values.
- Nested objects (consider if you want to support this or not - clarify in your documentation).
Examples
Example 1: Pick by Key Pattern
Input:
type User = {
firstName: string;
lastName: string;
age: number;
userId: string;
email: string;
};
const pickUserKeys = indexedTypes.pickByPattern(/^user/);
type PickedUser = pickUserKeys<User>;
Output:
type PickedUser = {
userId: string;
email: string;
}
Explanation: The `pickUserKeys` function, when applied to the `User` type, selects only the properties whose keys start with "user".
Example 2: Omit by Key Pattern
Input:
type Product = {
id: number;
name: string;
price: number;
discountId: string;
};
const omitDiscountKeys = indexedTypes.omitByPattern(/Id$/);
type WithoutDiscounts = omitDiscountKeys<Product>;
Output:
type WithoutDiscounts = {
id: number;
name: string;
price: number;
}
Explanation: The `omitDiscountKeys` function removes properties whose keys end with "Id" from the `Product` type.
Example 3: Transform Value by Key Pattern
Input:
type Data = {
itemId: string;
itemName: string;
itemPrice: number;
otherId: string;
};
const transformIdsToNumbers = indexedTypes.transformValueByPattern(/^itemId$/);
type TransformedData = transformIdsToNumbers<Data>;
Output:
type TransformedData = {
itemId: number;
itemName: string;
itemPrice: number;
otherId: string;
}
Explanation: The `transformIdsToNumbers` function changes the type of the `itemId` property to `number`.
Constraints
- Key Pattern Matching: The key pattern matching should support simple string prefixes and suffixes. Regular expression support is optional but encouraged.
- Performance: The functions should be reasonably efficient for common type structures. Avoid unnecessary type recursion.
- Input Type: The input type should be an object type. The functions should return
neverif the input is not an object type. - Complexity: The solution should be maintainable and readable. Avoid overly complex or obscure TypeScript constructs.
Notes
- Consider using conditional types and mapped types to implement the utility functions.
- Think about how to handle optional properties and union types correctly.
- Document your code thoroughly with JSDoc comments, explaining the purpose of each function and its parameters.
- You can choose to implement only a subset of the required functions if time is limited, but clearly indicate which functions you have implemented.
- Consider how you might extend this framework in the future to support more advanced type manipulations.
- The key pattern matching can be implemented using
string.startsWith(),string.endsWith(), or regular expressions. Choose the approach that best suits your needs.