I have created this playground and here is the code:
type BundlerError = Error;
type BundlerWarning = Error;
export type BundlerState =
| { type: 'UNBUNDLED' }
| { type: 'BUILDING'; warnings: BundlerWarning[] }
| { type: 'GREEN'; path: string; warnings: BundlerWarning[] }
| { type: 'ERRORED'; error: BundlerError }
const logEvent = (event: BundlerState) => {
switch (event.type) {
case 'UNBUNDLED': {
console.log('received bundler start');
break;
}
case 'BUILDING':
console.log('build started');
break;
case 'GREEN':
if(event.warnings.length > 0) {
console.log('received the following bundler warning');
for (let warning of event.warnings) {
warning
console.log(warning.message);
}
}
console.log("build successful!");
console.log('manifest ready');
break;
case 'ERRORED':
console.log("received build error:");
console.log(event.error.message);
break;
}
}
BundlerState is a discriminated union and the switch narrows the type.
The problem is that it does not scale and big expanding switch statements are pretty horrible.
Is there a better way I can write this and still keep the nice type narrowing?
You cannot do this:
const eventHandlers = {
BUNDLED: (event: BundlerState) => event.type // type is not narrowed
// etc,
};
const logEvent = (event: BundlerState) => eventHandlers['BUNDLED'](event);
Because the type is not narrowed.
I noticed the
fp-ts
tag so I figure I'll give the approach with that library in mind.fp-ts
defines a lot offold
operations that achieve essentially the result you're looking for for their various algebraic types. The general idea is to define a function that does the narrowing for you, then you define handlers for each of the cases.Simple Example
So for your type, you could write something like this:
Playground so you can inspect each of the states.
So
fold
is a higher order function for creating a handler for your type (in the same way as it is forOption
).