import { chas } from 'ts-chas';
import { Task } from 'ts-chas/task';
// 1. Define your error set
const AppError = chas.defineErrs({
NotFound: (resource: string, id: string) => ({ resource, id }),
Unauthorized: () => ({}),
Database: (message: string, cause?: Error) => ({ message, cause }),
});
type AppErr = chas.InferErrs<typeof AppError>;
// 2. Use them in your functions
function getUser(id: string): Task<User, AppErr> {
return Task.from(async () => {
const session = getSession();
if (!session) throw AppError.Unauthorized();
const user = await db.users.findById(id);
if (!user) throw AppError.NotFound('user', id);
return user;
}, (e) => {
if (AppError.NotFound.is(e) || AppError.Unauthorized.is(e)) return e as AppErr;
return AppError.Database(`DB error: ${e}`) as AppErr;
});
}
// 3. Handle errors exhaustively at the call site
const result = await getUser('42')
.tapTag(AppError.Database, e => logger.error('DB error', e))
.catchTag(AppError.NotFound, e =>
Task.from(() => Promise.resolve(defaultUser))
)
.execute();
if (result.isErr()) {
// Only UnauthorizedErr and DatabaseErr remain in the union at this point
const message = chas.matchErr(result.error, {
Unauthorized: () => 'Please log in to continue.',
Database: (e) => `Database issue: ${e.message}`,
});
showError(message);
} else {
renderUser(result.value);
}