Hone logo
Hone
Problems

Temporal Type Tagging: Implementing Chronomorphisms in TypeScript

TypeScript's type system is powerful for enforcing data integrity and structuring code. This challenge explores how to leverage advanced type-level programming to represent temporal properties of data, specifically by creating "chronomorphic" types. This technique allows you to distinguish between data that is currently valid, data that has expired, or data that is yet to become valid, directly within the type system, leading to more robust and self-documenting code.

Problem Description

Your task is to design and implement a system of TypeScript types that can represent the temporal state of data. Specifically, you need to create a mechanism to tag a data type with its validity period, distinguishing between three states: ValidNow, Expired, and NotYetValid.

You will achieve this by:

  1. Defining a TemporalState discriminated union: This union will represent the three possible temporal states.
  2. Creating a Chronomorphic<T, State> type: This generic type will take a base type T and a TemporalState State. It should ensure that the State is one of the allowed TemporalState values.
  3. Implementing helper types/functions (optional but encouraged): Consider how you might create utility types or functions to easily convert between Chronomorphic types, check their temporal state, or extract the original data.

The goal is to enforce these temporal constraints at compile time. For instance, a function expecting Chronomorphic<MyData, ValidNow> should not be callable with data tagged as Expired.

Examples

Example 1: Representing a currently active user profile.

// Assume we have a base type:
interface UserProfile {
  id: string;
  name: string;
}

// We want to represent a user profile that is currently valid.
type ActiveUserProfile = Chronomorphic<UserProfile, ValidNow>;

// This should be a valid type.
declare const activeUser: ActiveUserProfile;
// activeUser.data should be { id: string, name: string }
// activeUser.state should be "valid-now"

Example 2: Representing a coupon that has expired.

// Assume a base type:
interface Coupon {
  code: string;
  discountPercentage: number;
}

// We want to represent a coupon that is no longer valid.
type ExpiredCoupon = Chronomorphic<Coupon, Expired>;

// This should be a valid type.
declare const expiredCoupon: ExpiredCoupon;
// expiredCoupon.data should be { code: string, discountPercentage: number }
// expiredCoupon.state should be "expired"

Example 3: Representing a feature flag that will be enabled in the future.

// Assume a base type:
interface FeatureFlag {
  name: string;
  enabled: boolean;
}

// We want to represent a feature flag that is not yet active.
type FutureFeatureFlag = Chronomorphic<FeatureFlag, NotYetValid>;

// This should be a valid type.
declare const futureFlag: FutureFeatureFlag;
// futureFlag.data should be { name: string, enabled: boolean }
// futureFlag.state should be "not-yet-valid"

Constraints

  • Your solution must be written entirely in TypeScript.
  • The Chronomorphic type must correctly constrain the State parameter to only accept the defined TemporalState values.
  • The Chronomorphic type should encapsulate both the original data (T) and its temporal state.
  • No runtime checks should be necessary to enforce these temporal properties; they should be handled by the TypeScript compiler.

Notes

Think about how to use TypeScript's features like:

  • Discriminated Unions: To define the different temporal states.
  • Generic Types: To make your Chronomorphic type reusable for any data type T.
  • Conditional Types or infer: Potentially useful for creating utility types.
  • Template Literal Types: Might be helpful for naming conventions or specific state representations.

Consider how you will structure the final Chronomorphic type. Should it be an object with data and state properties? Or perhaps a tuple?

Loading editor...
typescript