I'm working on an application written in functional TypeScript using fp-ts and io-ts. I need to retrieve a set of JSON configuration files. Some of these JSON files contain information necessary for retrieving some of the other JSON files. I'm wondering what would be a good abstraction for such dependencies.
I currently find myself writing code that defines hard coded stages (see the pseudo code below). The problem with this approach is that the names for these stages are utterly meaningless expressing a technical detail rather than the intended behaviour of the code.
type Stage1 = { config1: Config1 }
const stage1 = (): TaskEither<Err, Stage1> => // ...
type Stage2 = Stage1 & { config2: Config2, config3: Config3 }
const stage2 = (s1: Stage1): TaskEither<Err, Stage2> => // ...
type Stage3 = Stage2 & { config4: Config4 }
const stage3 = (s2: Stage2): TaskEither<Err, Stage3> => // ...
const complete = pipe(
stage1(),
chain(stage2),
chain(stage3),
)
I wrote the following helper function wheel
that solves the problem. The remaining question is whether or not this is a new invention. Perhaps this programming pattern has a name. Maybe it is already part of fp-ts in some shape or form.
import * as P from 'maasglobal-prelude-ts';
const wheel = <I extends {}, O extends {}, E>(cb: (i: I) => P.TaskEither<E, O>) => (
ei: P.TaskEither<E, I>,
): P.TaskEither<E, I & O> =>
P.pipe(
ei,
P.TaskEither_.chain((i) =>
P.pipe(
cb(i),
P.TaskEither_.map((o) => ({ ...i, ...o })),
),
),
);
const complete = P.pipe(
P.TaskEither_.right({}),
wheel(() => P.TaskEither_.right({ foo: 123 })),
wheel(() => P.TaskEither_.right({ bar: 456 })),
wheel(({ foo }) => P.TaskEither_.right({ quux: 2 * foo })),
wheel(({ quux, bar }) => P.TaskEither_.right({ quuxbar: quux + '-' + bar })),
);
expect(complete).toStrictEqual({ foo: 123, bar: 456, quux: 246, quuxbar: '246-456' });