Hone logo
Hone
Problems

Crafting Type-Safe Property Decorators in TypeScript

Decorators in TypeScript offer a powerful way to add metadata or modify the behavior of class properties. However, ensuring these decorators are type-safe can be challenging, especially when dealing with generic types or when the decorator needs to understand the type of the property it's decorating. This challenge focuses on building a generic, type-safe property decorator that can inject a specific type of value into a property.

Problem Description

Your task is to create a generic TypeScript decorator factory named InjectValue that can be applied to class properties. This decorator should accept a value of a specified type as an argument and assign that value to the decorated property. The key requirement is that the decorator factory and the resulting decorator must be type-safe, meaning TypeScript should enforce that the type of the injected value matches the type of the property it's being assigned to.

Key Requirements:

  1. Generic Decorator Factory: InjectValue should be a generic function that accepts a type parameter T. This T represents the type of the value to be injected and the type of the property.
  2. Value Injection: The decorator factory should accept a single argument: the value to be injected. This value must be of type T.
  3. Property Decoration: The decorator itself will be applied to class properties. It should ensure that the assigned value is compatible with the property's declared type.
  4. Type Safety: TypeScript's type checker should prevent assigning a value of a different type than the property's declaration.
  5. Decorator Behavior: When applied, the decorator should assign the provided value to the decorated property during class instantiation.

Expected Behavior:

When InjectValue<T>(value) is used to decorate a property prop: T, the value passed to the decorator factory should be assignable to prop. If a type mismatch occurs (e.g., attempting to inject a string into a number property), TypeScript should raise a compile-time error.

Edge Cases to Consider:

  • Decorator applied to non-properties: While typically applied to properties, consider how the decorator signature handles other targets (though the primary focus is properties).
  • readonly properties: How does the injection mechanism interact with readonly properties? (For this challenge, assume properties are mutable for simplicity of injection).

Examples

Example 1: Injecting a String into a String Property

class UserProfile {
  @InjectValue<string>("Alice Smith")
  name: string;

  constructor() {
    // The 'name' property should automatically be "Alice Smith"
    // thanks to the decorator.
    console.log(this.name); // Expected: Alice Smith
  }
}

const user = new UserProfile();

Explanation: The @InjectValue<string>("Alice Smith") decorator factory is used. It's explicitly typed with <string> to indicate that the injected value and the property type are strings. The value "Alice Smith" is correctly assigned to the name property, which is also typed as string.

Example 2: Injecting a Number into a Number Property

class AppSettings {
  @InjectValue<number>(1000)
  timeout: number;

  @InjectValue<boolean>(true)
  isEnabled: boolean;

  constructor() {
    console.log(this.timeout);   // Expected: 1000
    console.log(this.isEnabled); // Expected: true
  }
}

const settings = new AppSettings();

Explanation: This example demonstrates injecting both a number and a boolean. @InjectValue<number>(1000) correctly infers that timeout should be a number. Similarly, @InjectValue<boolean>(true) infers the boolean type for isEnabled.

Example 3: Type Mismatch (Compile-time Error)

class Product {
  @InjectValue<string>("Laptop") // Expects to inject a string
  price: number; // Property is typed as a number

  constructor() {
    console.log(this.price);
  }
}

// This code should produce a TypeScript compile-time error because
// "Laptop" (string) cannot be assigned to 'price' (number).

Explanation: The intention here is to highlight the type safety. The decorator factory is told to inject a string ("Laptop"), but the price property is declared as a number. TypeScript should catch this incompatibility at compile time, preventing the creation of potentially erroneous code.

Constraints

  • The decorator implementation should be pure JavaScript syntax for decorators (as supported by TypeScript's decorator experimental feature).
  • The solution should leverage TypeScript's generic types and type inference to enforce type safety.
  • The value provided to the decorator factory should be directly assigned to the property. No complex transformations or logic beyond assignment is required for this challenge.
  • Focus on property decorators.

Notes

Consider the standard signature for property decorators in TypeScript: (target: Object, propertyKey: string | symbol) => void. You'll need to adapt this to your generic decorator factory. Think about how to capture the generic type T and the injected value within the decorator's scope. The use of a decorator factory (a function that returns the actual decorator) is crucial here.

Loading editor...
typescript