Aliou Diallo
til cube-records

Type-Safe Domain Models with Interface Augmentation in TypeScript

While working on @general-dexterity/cube-records, I needed a way to defineOr use codegen with another library. my domain-specific Cube models in a way that would provide type-safe autocompletion. The solution was ended up pretty simple: global interface augmentation.

The Problem

The lib defines an empty global interface that users can augment with their own cube definitions. However, after publishing, I discovered that tsup was optimizing the empty interface into a type alias during the build:

// What we write
export interface CubeRecordMap {}

// What tsup outputs with dts: true
export type CubeRecordMap = {}

// Now augmentation fails
declare global {
  interface CubeRecordMap { // Error: can't augment a type alias
    users: { /* ... */ }
  }
}

The Solution

Adding a dummy property prevents this optimization:

export interface CubeRecordMap {
  __empty: {
    measures: {};
    dimensions: {};
    joins: [];
  };
}

That __empty property isn’t arbitrary - it ensures the interface remains augmentable through the entire build pipelineThis is specifically a build tool optimization when generating declaration files, not a TypeScript language limitation. .

Implementation

Users can now extend the interface in their projects:

declare global {
  interface CubeRecordMap {
    users: {
      measures: { count: { type: number } };
      dimensions: {
        id: { type: string };
        email: { type: string };
      };
      joins: readonly ['orders'];
    };
    // ... other models and views
  }
}

The library extracts type-safe cube names and fields:

type CubeRecordName = keyof CubeRecordMap;
type CubeRecordMeasure<T extends CubeRecordName> =
  keyof CubeRecordMap[T]['measures'] & string;

Trade-offs

Benefits:

Drawbacks:

Notes

This isn’t just for my personal benefitsEven though it was pretty fun to try to figure this out. , this pattern provides domain-specific autocompletion without runtime cost. Instead of searching through Cube models for field names, TypeScript provides instant feedback as you type.