Refactor resequences epic for better declarative composition
The current resequences epic in client/lib/keys.ts handles grid selection → time range conversion in a single monolithic function. While functional, it could be more literate and composable by breaking down the transformation pipeline into smaller, named operations.
Current Issues
- Dense transformation logic - The
concatMap contains complex coordinate conversion, data lookup, and edge case handling all in one place
- Mixed abstraction levels - Low-level array operations mixed with high-level domain logic
- Hard to test - The entire transformation is one big function
- Not reusable - Grid coordinate logic is embedded in the epic
Proposed Refactor
Break the epic into a pipeline of smaller, named transformations:
// Domain-specific operators
const gridSelectionsOnly = () => pipe(
map((s: State) => s.view),
filter((view): view is GridView => view.type === 'grid'),
distinctBy(selectionModeKey),
filter(isDoubleSelection)
);
const toGridCoordinates = () => pipe(
map(({ input: { selection } }) =>
parseGridSelection(selection.value)
)
);
const toTimeRange = (gridData: Observable<GridData>) => pipe(
withLatestFrom(gridData),
map(([coordinates, data]) =>
coordinatesToTimeRange(coordinates, data)
)
);
// Pure functions for transformations
const parseGridSelection = (selection: string[]): [number, number] => { /* ... */ };
const coordinatesToTimeRange = (coords: [number, number], data: GridData): TraversalCommand => { /* ... */ };
const selectionModeKey = (view: GridView) => /* ... */;
const isDoubleSelection = (view: GridView): view is DoubleSelectionView => /* ... */;
// Composed epic
export const resequences: AppEpic<TraversalCommand> = (cmds, state) => {
const gridData$ = state.pipe(map(select.data), shareReplay(1));
return state.pipe(
gridSelectionsOnly(),
toGridCoordinates(),
toTimeRange(gridData$)
);
};
Benefits
- Testable components - Each transformation can be unit tested
- Reusable logic - Grid coordinate parsing could be used elsewhere
- Clear intent - Each operator has a single, named responsibility
- Easier debugging - Can log/tap between stages
- Type safety - Smaller functions are easier to type correctly
Implementation Notes
- Keep the existing behavior exactly the same
- Extract pure functions first, then build operators around them
- Consider moving grid coordinate logic to a separate module
- Maintain the same error handling and edge cases
Refactor resequences epic for better declarative composition
The current
resequencesepic inclient/lib/keys.tshandles grid selection → time range conversion in a single monolithic function. While functional, it could be more literate and composable by breaking down the transformation pipeline into smaller, named operations.Current Issues
concatMapcontains complex coordinate conversion, data lookup, and edge case handling all in one placeProposed Refactor
Break the epic into a pipeline of smaller, named transformations:
Benefits
Implementation Notes