|
| 1 | +import { TFeatureDefinition } from '@blgc/types/features'; |
| 2 | +import { TState } from '../types'; |
| 3 | + |
| 4 | +// We tried two approaches with array parameters that didn't work due to TypeScript limitations: |
| 5 | +// |
| 6 | +// 1. Using array of feature functions with return type constraint: |
| 7 | +// function applyFeatures< |
| 8 | +// GValue, |
| 9 | +// GInitialFeatures extends TFeatureDefinition[], |
| 10 | +// GF0 extends TFeatureDefinition, |
| 11 | +// GF1 extends TFeatureDefinition |
| 12 | +// >( |
| 13 | +// initialState: TState<GValue, GInitialFeatures>, |
| 14 | +// [(state: TState<GValue, GInitialFeatures>) => GF0, |
| 15 | +// (state: TState<GValue, [GF0, ...GInitialFeatures]>) => GF1] |
| 16 | +// ): TState<GValue, [GF1, GF0, ...GInitialFeatures]> |
| 17 | +// Issue: Generic type inference fails (see: https://stackoverflow.com/q/54955340) |
| 18 | +// Example: TUndoFeature<GValue> becomes TUndoFeature<unknown> |
| 19 | +// When we tried working around this by using TState<GValue, [any, ...GInitialFeatures]>, |
| 20 | +// it fixed type inference issue, but at the cost of type safety at a different place: |
| 21 | +// TypeScript could no longer detect missing feature dependencies. |
| 22 | +// Thus e.g. applying withMultiUndo without its required withUndo feature was possible. |
| 23 | +// |
| 24 | +// 2. Using function type constraints: |
| 25 | +// function applyFeatures< |
| 26 | +// GValue, |
| 27 | +// GInitialFeatures extends TFeatureDefinition[], |
| 28 | +// GF0 extends (state: TState<GValue, GInitialFeatures>) => TFeatureDefinition, |
| 29 | +// GF1 extends (state: TState<GValue, [ReturnType<GF0>, ...GInitialFeatures]>) => TFeatureDefinition |
| 30 | +// >( |
| 31 | +// initialState: TState<GValue, GInitialFeatures>, |
| 32 | +// [GF0, GF1] |
| 33 | +// ): TState<GValue, [ReturnType<GF1>, ReturnType<GF0>, ...GInitialFeatures]> |
| 34 | +// Issue: ReturnType<GF0> loses generic type information (see: https://stackoverflow.com/q/64948037) |
| 35 | +// Example: TUndoFeature<GValue> becomes TUndoFeature<unknown> |
| 36 | +// |
| 37 | +// Current working solution uses separate function parameters to preserve type inference: |
| 38 | +// function applyFeatures<GValue, ...>( |
| 39 | +// initialState: TState<GValue, GInitialFeatures>, |
| 40 | +// f1: (state: TState<GValue, GInitialFeatures>) => GF0, |
| 41 | +// f2: (state: TState<GValue, [GF0, ...GInitialFeatures]>) => GF1 |
| 42 | +// ): TState<GValue, [GF1, GF0, ...GInitialFeatures]> |
| 43 | + |
| 44 | +// Overload for one feature |
| 45 | +export function applyFeatures< |
| 46 | + GValue, |
| 47 | + GInitialFeatures extends TFeatureDefinition[], |
| 48 | + GF0 extends TFeatureDefinition |
| 49 | +>( |
| 50 | + initialState: TState<GValue, GInitialFeatures>, |
| 51 | + f1: (state: TState<GValue, GInitialFeatures>) => TState<GValue, [GF0, ...GInitialFeatures]> |
| 52 | +): TState<GValue, [GF0, ...GInitialFeatures]>; |
| 53 | + |
| 54 | +// Overload for two features |
| 55 | +export function applyFeatures< |
| 56 | + GValue, |
| 57 | + GInitialFeatures extends TFeatureDefinition[], |
| 58 | + GF0 extends TFeatureDefinition, |
| 59 | + GF1 extends TFeatureDefinition |
| 60 | +>( |
| 61 | + initialState: TState<GValue, GInitialFeatures>, |
| 62 | + f1: (state: TState<GValue, GInitialFeatures>) => TState<GValue, [GF0, ...GInitialFeatures]>, |
| 63 | + f2: ( |
| 64 | + state: TState<GValue, [GF0, ...GInitialFeatures]> |
| 65 | + ) => TState<GValue, [GF1, GF0, ...GInitialFeatures]> |
| 66 | +): TState<GValue, [GF1, GF0, ...GInitialFeatures]>; |
| 67 | + |
| 68 | +// Overload for three features |
| 69 | +export function applyFeatures< |
| 70 | + GValue, |
| 71 | + GInitialFeatures extends TFeatureDefinition[], |
| 72 | + GF0 extends TFeatureDefinition, |
| 73 | + GF1 extends TFeatureDefinition, |
| 74 | + GF2 extends TFeatureDefinition |
| 75 | +>( |
| 76 | + initialState: TState<GValue, GInitialFeatures>, |
| 77 | + f1: (state: TState<GValue, GInitialFeatures>) => TState<GValue, [GF0, ...GInitialFeatures]>, |
| 78 | + f2: ( |
| 79 | + state: TState<GValue, [GF0, ...GInitialFeatures]> |
| 80 | + ) => TState<GValue, [GF1, GF0, ...GInitialFeatures]>, |
| 81 | + f3: ( |
| 82 | + state: TState<GValue, [GF1, GF0, ...GInitialFeatures]> |
| 83 | + ) => TState<GValue, [GF2, GF1, GF0, ...GInitialFeatures]> |
| 84 | +): TState<GValue, [GF2, GF1, GF0, ...GInitialFeatures]>; |
| 85 | + |
| 86 | +// Implementation |
| 87 | +export function applyFeatures< |
| 88 | + GValue, |
| 89 | + GInitialFeatures extends TFeatureDefinition[], |
| 90 | + GF0 extends TFeatureDefinition, |
| 91 | + GF1 extends TFeatureDefinition, |
| 92 | + GF2 extends TFeatureDefinition |
| 93 | +>( |
| 94 | + initialState: TState<GValue, GInitialFeatures>, |
| 95 | + f1: (state: TState<GValue, GInitialFeatures>) => GF0, |
| 96 | + f2?: (state: TState<GValue, [GF0, ...GInitialFeatures]>) => GF1, |
| 97 | + f3?: (state: TState<GValue, [GF1, GF0, ...GInitialFeatures]>) => GF2 |
| 98 | +): TState<GValue, any> { |
| 99 | + // TODO: Implement |
| 100 | + return null as any; |
| 101 | +} |
0 commit comments