Type-Safe Configuration System in TypeScript
Building robust applications often requires managing configuration settings. A type-safe configuration system in TypeScript ensures that configuration values are of the expected types, preventing runtime errors and improving code maintainability. This challenge asks you to design and implement such a system, allowing you to define configuration schemas and load configuration data while enforcing type safety.
Problem Description
You are tasked with creating a type-safe configuration system in TypeScript. The system should allow you to define configuration schemas using TypeScript types and then load configuration data (potentially from a JSON file) and validate it against the schema. The system should provide a way to access configuration values with type safety, ensuring that you only access properties that are defined in the schema and that their types match the expected types.
Key Requirements:
- Schema Definition: The system should allow you to define a configuration schema using TypeScript types. This schema should specify the names, types, and optionally, default values of configuration properties.
- Configuration Loading: The system should be able to load configuration data from a JSON-like source (e.g., a string or a file path).
- Type Validation: The system should validate the loaded configuration data against the defined schema. If the data doesn't conform to the schema, it should throw an error with a descriptive message.
- Type-Safe Access: The system should provide a way to access configuration values with type safety. Accessing a property should result in a value of the type defined in the schema.
- Default Values: The system should support default values for configuration properties. If a property is missing in the loaded configuration data, the default value should be used.
Expected Behavior:
- When loading a valid configuration, the system should return an object with the configured values, typed according to the schema.
- When loading an invalid configuration (e.g., a property with an incorrect type), the system should throw an error.
- When a property is missing from the configuration data, the system should use the default value (if provided).
- Accessing a non-existent property should throw an error.
Edge Cases to Consider:
- Empty configuration schemas.
- Configuration data with properties not defined in the schema.
- Nested configuration objects within the schema.
- Handling of different data types (string, number, boolean, array, object).
- Circular dependencies in nested objects (should be handled gracefully, potentially with a warning).
Examples
Example 1:
// Schema
interface Config {
apiKey: string;
port: number;
debug: boolean;
timeout: number;
}
// Configuration Data (JSON string)
const configData = `{
"apiKey": "your_api_key",
"port": 3000,
"debug": true
}`;
// Expected Output (after successful loading and validation)
// { apiKey: 'your_api_key', port: 3000, debug: true, timeout: 5000 }
Explanation: The Config interface defines the schema. The configData string contains the configuration values. The system should parse the JSON, validate it against the schema, and return an object of type Config with the provided values. timeout will default to 5000.
Example 2:
// Schema
interface Config {
name: string;
age: number;
isActive: boolean;
}
// Configuration Data (JSON string) - Invalid
const configData = `{
"name": "John Doe",
"age": "thirty", // Incorrect type
"isActive": true
}`;
// Expected Output:
// Error: Invalid configuration: age must be a number, but got string.
Explanation: The age property in the configuration data is a string, but the schema defines it as a number. The system should throw an error indicating the type mismatch.
Example 3:
// Schema
interface Config {
apiUrl: string;
retries: number;
options: {
timeout: number;
cacheEnabled: boolean;
};
}
// Configuration Data (JSON string) - Missing options.timeout
const configData = `{
"apiUrl": "https://example.com/api",
"retries": 3
}`;
// Expected Output:
// { apiUrl: 'https://example.com/api', retries: 3, options: { timeout: 10000, cacheEnabled: false } }
Explanation: The options.timeout property is missing from the configuration data. The system should use the default value of 10000 for options.timeout and false for cacheEnabled.
Constraints
- The solution must be written in TypeScript.
- The configuration data should be loaded from a string (representing JSON). File system access is not required for this challenge.
- The schema definition must be based on TypeScript interfaces or types.
- Error messages should be informative and clearly indicate the location and type of the error.
- The solution should be reasonably performant for configurations of moderate size (e.g., up to 100 properties).
Notes
- Consider using a library like
zodorio-tsfor schema validation, but you are not required to. Implementing your own validation logic is encouraged. - Think about how to handle nested configuration objects and arrays.
- Pay close attention to type safety and ensure that the system provides compile-time guarantees.
- Focus on creating a clean, well-documented, and maintainable solution.
- Error handling is crucial. Provide meaningful error messages to help users debug their configuration.