Skip to content

Latest commit

 

History

History
122 lines (95 loc) · 3.92 KB

File metadata and controls

122 lines (95 loc) · 3.92 KB

TypeScript Entity.ModelFrom Bug Analysis

Summary

Entity.ModelOf<Market<V, Asset<V>, Asset<V>, 'spot'>> produces an empty type with NO fields at all.

Files for Analysis

1. entity-projection-bug.ts (281 lines)

  • Full Entity.ModelFrom machinery
  • Simple test cases
  • Result: COMPILES SUCCESSFULLY
  • Shows Entity.ModelFrom works in isolation

2. entity-modelunion-bug.ts (100 lines)

  • Tests union discriminant access
  • Progressive complexity scenarios
  • Result: COMPILES SUCCESSFULLY
  • Shows union types work in isolation

3. actual-failing-case.ts (405 lines)

  • Complete real-world types from codebase
  • Result: REPRODUCES THE BUG
  • All fields missing from Model

The Bug

When TypeScript processes:

Market.Model<V, Asset<V>, Asset<V>, 'spot'>
// which expands to:
Omit<Entity.ModelOf<Market<V, Asset<V>, Asset<V>, 'spot'>>, 'lastPrice'>

The resulting type has ZERO fields. Attempts to access ANY field fail:

  • model.id - "Property 'id' does not exist"
  • model.symbol - "Property 'symbol' does not exist"
  • model.active - "Property 'active' does not exist"
  • model.type - "Property 'type' does not exist"

Yet the Market entity clearly declares all these fields:

class Market<V, B, Q, T> extends Entity<Market<V, B, Q, T>, V> {
  readonly id!: string;
  readonly symbol!: string;
  readonly type!: T;  
  readonly active!: boolean;
  readonly venue!: V;
  readonly base: B;
  readonly quote: Q;
}

Diagnostic Types in actual-failing-case.ts

Check these exported types to identify the break point:

  1. IsSpotModelNever - Is the entire Model type resolving to never?
  2. AreSpotKeysNever - Are the keys being computed as never?
  3. MarketSpotKeys - What does Entity.ModelFrom.Keys<> return?
  4. AreMarketSpotKeysNever - Are the Keys helper returning never?
  5. MarketSpotMethodKeys - Which fields are being excluded as methods?
  6. MarketSpotComponentKeys - Which fields are being excluded as components?

Working Theory (from user analysis)

When all fields vanish, the Keys computation is likely returning never because:

  1. Distributive Conditionals on Unresolved Generics

    • E[K] extends Entity<any, any> check runs while generics are unfixed
    • TypeScript pessimistically returns never for each key
  2. Key Remapping with as Clause Running Too Early

    • The mapped type filters run before TypeScript fully resolves generic constraints
    • Each key gets remapped to never, resulting in empty object
  3. Component/Entity Detection Failing

    • The check Self[K] extends Component<any> ? (Self[K] extends Entity<any, any> ? never : K) : never
    • Might be incorrectly classifying fields when generics are involved

Investigation Steps

Run the following in TypeScript to see values:

import { IsSpotModelNever, AreSpotKeysNever, MarketSpotKeys, SpotKeys } from './actual-failing-case.js';

// These should be 'NOT_NEVER' and 'KEYS_EXIST'
type Check1 = IsSpotModelNever;        
type Check2 = AreSpotKeysNever;
type Check3 = MarketSpotKeys; // Should be 'id' | 'symbol' | 'type' | 'active' | ...

If MarketSpotKeys is never, the problem is in the Keys computation. If SpotKeys is never but MarketSpotKeys has values, the problem is in Build. If both have values but fields still don't exist, it's likely a TypeScript compiler bug.

Expected vs Actual

Expected:

  • Entity.ModelOf<Market<V, Asset<V>, Asset<V>, 'spot'>> should produce:
    {
      id: string;
      symbol: string;
      type: 'spot';
      active: boolean;
      venueId: string;
      base: PropertyKey;
      quote: PropertyKey;
    }

Actual:

  • Produces empty type with zero accessible fields
  • All property access fails with "Property '...' does not exist"

To Reproduce

npx tsc --noEmit actual-failing-case.ts

Look for errors at lines 282-288 showing all field access failures.