Can TypeScript's type system be exploited to provide additional information from a repository to a service in case of errors?
I have a service that needs a port for a repository (Interface that the Repository must implement), but since the service must not know the concrete implementation of the repository, the Interface must also define the errors the service can handle. To achieve this, I am using ts-results.
While defining errors as strings is an option, I wanted to pass more detailed information from the repository to the service in case of an error. So, I attempted to define the Errors as a Union Type of various Error Classes. However, the issue arises when default errors match the signature of the more specific errors.
This leads to a situation where any other error can be passed from the repository to the service (port).
// The Port Definition
export abstract class FriendshipRepositoryPort implements IFriendshipRepository {
abstract load(
userIdA: UserId, userIdB: UserId
): Promise<Result<IFriendshipAggregate, FriendshipRepositoryErrors>>;
abstract persist(
friendship: IFriendshipAggregate
): Promise<Result<void, FriendshipRepositoryErrors>>;
}
// repo implementation
async persist(friendship: IFriendshipAggregate): Promise<Result<void, FriendshipRepositoryErrors>> {
// ... preparing persisting the entities
try {
return new Ok(await this._persistenceManager.execute(querySpec));
} catch (e) {
console.error(e);
// FIXME: This should not be possible!
return new Err(new RuntimeError());
}
}
// error definition
export type FriendshipRepositoryErrors = UserNotFoundError
| DomainRuleViolation
| DatabaseWriteError
| DatabaseReadError;
Is there a way to ensure that only instances of the specified classes (or their heirs) can be accepted as error types in the Result?
I have also created a playground to illustrate the issue with a small example.