
Security News
Oracle Drags Its Feet in the JavaScript Trademark Dispute
Oracle seeks to dismiss fraud claims in the JavaScript trademark dispute, delaying the case and avoiding questions about its right to the name.
type-safe-errors
Advanced tools
type-safe-errors
provides custom type-safe errors to Typescript.
The library offers an async promise-like interface and ensures type safety with easy-to-handle errors.
The type-safe-errors library was made to solve these problems:
npm i type-safe-errors
In this example, we demonstrate how to use the type-safe-errors
library to handle user authorization with a simple username and password check.
import { Ok, Err } from 'type-safe-errors';
class InvalidCredentialsError extends Error {
name = 'InvalidCredentialsError' as const;
}
// Function to authorize an user based on their username and password
function authorizeUser(username: string, password: string) {
if (username === 'admin' && password === 'admin') {
// Return an Ok result with the user information
return Ok.of({
name: 'admin',
isAdmin: true,
});
} else {
// Return an Err result with an InvalidCredentialsError instance
return Err.of(new InvalidCredentialsError());
}
}
authorizeUser('admin', 'admin')
// Handle successful authorization
.map((user) => {
console.log('authorized! hello ', user.name);
})
// Handle error in case of invalid credentials
.mapErr(InvalidCredentialsError, (err) => {
// here `err` is fully typed object of InvalidCredentialsError class
console.log('Invalid credentials!', err);
})
// Map the result to classic promise
// This is optional, but highly recommended, as it allows TypeScript to detect
// not handled errors, in current code and in the future
.promise();
It's common to start a result chain with an async call, such as a call to your database or an external API.
There are a few ways to handle this, but the simplest is to use the dedicated helper function: Result.from.
import { Ok, Err, Result } from 'type-safe-errors';
class InvalidCredentialsError extends Error {
name = 'InvalidCredentialsError' as const;
}
class UserNotFoundError extends Error {
name = 'UserNotFoundError' as const;
}
async function authorizeUser(username: string, password: string) {
if (username !== 'admin') {
return Err.of(new UserNotFoundError());
}
// simulate async call
const storedPassword = await Promise.resolve('admin');
if (password !== storedPassword) {
return Err.of(new InvalidCredentialsError());
}
return Ok.of({
name: 'admin',
isAdmin: true,
});
}
Result.from(() => authorizeUser('admin', 'admin'))
.map((user) => {
// here `user` type is { name: string, isAdmin: boolean }, from `authorizeUser` return type
console.log('authorized! hello ', user.name);
})
.mapErr(UserNotFoundError, (err) => {
// here `err` is fully typed object of InvalidCredentialsError class
console.log('Invalid user name!', err);
})
.mapErr(InvalidCredentialsError, (err) => {
// here `err` is fully typed object of InvalidCredentialsError class
console.log('Invalid credentials!', err);
});
If you work with rich business logic, it's common to use exceptions to represent different states of the application. The problem with this solution and TypeScript is that when you catch an exception, you lose information about its types.
Let consider this simplified example from an express controller:
try {
await payForProduct(userCard, product);
} catch (err) {
res.send(500, "Internal server error");
}
By looking at this code, you cannot determine what kind of exception can happen.
Of course, you can check the payForProduct
body, but what if it's called by other functions,
which in turn call additional functions?
When dealing with advanced business logic, it becomes difficult to maintain an understanding of all possible scenarios that can lead to errors solely by reading the code.
That's why it's common to just return 500
in such cases (express
does it by default). However, there can be many errors that should be handled differently than a 500
status code. For example:
In each of these cases, the server should return a different status code along with specific error details. The client app should be informed of the reason, for example, by a 400
status code and error details in the response body. But first, to properly handle the errors, the developer must be aware of what errors can happen.
This is the problem that type-safe-errors
library is trying to solve.
(Full example: ./examples/basic-example)
Keeping the API minimal reduces the learning curve and makes it easier for developers to understand and use the library effectively.
It's one of the reasons why the result class is always async (for example, neverthrow has two different result types, one for synchronous and one for asynchronous results handling).
The long-term goal is not to handle every possible use case. Instead, it's to do one thing well - providing a way to handle domain exceptions in a strong-typed, future-proof way.
Using type-safe-erros
should be similar in feel to work with traditional js promises. You can map any success result (same like you can then fulfilled promise) or mapAnyErr (same as you can catch rejected promise).
You may notice that the type-safe-error
project is somehow based on Either concept from functional programming. But the goal was not to follow the idea closely but to provide an easy-to-use API in practical TypeScript work, focused on async programming.
.mapErr(...)
functionQuick fix: Update your tsconfig.json
file compilerOptions.target
option to at least es6
.
The type-safe-errors
library depends on instanceof
standard JS feature.
The instanceof
feature allows checking if an object is an instance of a particular class or constructor. In the context of type-safe-errors
, it is used to verify if an error object is an instance of a specific error class.
However, extending the JavaScript Error does not work correctly for TypeScript compilation targets es5
and below. This issue is explained on TypeScript wiki.
One option is to update tsconfig.json
file compilerOptions.target
to es6
or a higher version.
An alternative is to abstain from extending by JavaScript Error class, e.g.
// original
class InvalidCredentialsError extends Error {
name = 'InvalidCredentials' as const;
}
// without Error parent
class InvalidCredentialsError {
name = 'InvalidCredentials' as const;
}
This works for most cases, but be aware that sometimes it can result in a rejection of a non-Error JavaScript object (instance of your class). This may interfere with some other tools (for example, Mocha can sometimes show cryptic error messages when a test fails).
FAQs
Simple type-safe errors for TypeScript
We found that type-safe-errors demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Oracle seeks to dismiss fraud claims in the JavaScript trademark dispute, delaying the case and avoiding questions about its right to the name.
Security News
The Linux Foundation is warning open source developers that compliance with global sanctions is mandatory, highlighting legal risks and restrictions on contributions.
Security News
Maven Central now validates Sigstore signatures, making it easier for developers to verify the provenance of Java packages.