Common FP Instances
FP ships with a set of implementations of the FP
interface, each useful for different use cases.
These implementations are organized into a basic hierarchy to help you narrow down which implementation is most suited for your use case.
FP
Inheritance TreeFP Data DataFP Literal BaseErrorFP ErrorFP UnexpectedError DatabaseException NoValue Empty MissingPermission NotFound AccessForbidden EntityNotFound
Data
There are two main interfaces commonly used for representing "successful data" - DataFP
and Literal
.
Literal
Literal
has been used extensively in the documentation so far,
and is designed to be a very simple wrapper around values of any type.
Literal
import { Literal } from "@formulaic/fp";
const input = new Literal(10); // => Literal<number>
expect(input.data).toBe(10);
Literal
allows you to create an FP
object without defining any classes or potential for conflicting fields,
however it means all data is a layer below inside the data
property.
DataFP
DataFP
is an abstract class that is ready for you to define your own FP
object,
representing a successful state.
The next section will walk through extending DataFP
in depth.
By extending DataFP
, details are available at the top level, and can also be included in OpenAPI documents.
DataFP
const response = new LoginResponse("admin", "[jwt contents]"); (1)
expect(response.id).toBe("admin");
1 | LoginResponse extends DataFP , and is defined in the next section. |
Errors
FP contains a few ways to represent "errors"[1] or other failures.
UnexpectedError
We mention UnexpectedError
first, which may be the only error implementation you wish to use
if you are concerned about revealing information in the case of failures.
UnexpectedError
class UnexpectedError<T, Err = any> extends ErrorFP<T> {
kind: "UnexpectedError";
status: 500;
code?: string;
error?: Err;
serializedError?: string;
}
If you use fp-interceptor, UnexpectedError
defaults to only returning
kind
and status
in production environments,
while also including code
and error
in development.
This allows you to safely create instances and sub-classes of UnexpectedError
without any risk of revealing error causes or information in production,
which may reveal sensitive information.
ErrorFP
If you wish to create your own errors (that aren’t set to hide all properties by default),
ErrorFP
is made to be extended.
This is very often the desired behavior, however in some cases may have something that "failed" yet still should be treated as a missing value. For these cases you can extend |
No Value
FP defines an explicit "empty" or "missing value" type, which both avoids the dangers of "null" and enables chaining and other utility functions.
FP provides three ways to create "no value" responses.
Empty
Earlier sections have already used the Empty
class as a counterpart to Literal
in order to create Either-like functionality.
Empty
is often used as-is to create a null
-like "valueless-value",
or can be extended.
Other built-in implementations
Along with the minimal Empty
class, FP also includes built-in implementations
like MissingPermission
.
NotFound
Frequently applications need to handle requests for entities that users do not have permission to view.
While a permission denied request could be sent as a 401 Unauthorized response, it is often good practice to respond with a 404 Not Found response, completely obscuring all information about the entity including it’s existence.
However, during development and debugging, there normally is no reason why developers should be restricted from seeing the root cause of an error.
FP provides a built-in type to handle this specific case due to how often it comes up, even in other FP modules like our database utilities.
NotFound is a base class like UnexpectedError, where all information is stripped by default in production.
NotFound
can be used directly, taking a description of the entity, and a flag permissionError
which is set to true
if an entity exists but the user can’t access it,
and set to false
if the entity does not exist.
const notFound = new NotFound("User", false);
const notPermitted = new NotFound<User, User>("User", true);
A shortcut, AccessForbidden
exists for permission errors:
const forbidden = new AccessForbidden<User, User>("User");
And implementations like EntityNotFound
exist for failed operations,
and may include additional information (like the failed query):
import { Literal, EntityNotFound } from "@formulaic/fp";
import { Repository, FindOneOptions } from "typeorm";
async function findById(
repo: Repository<User>,
id: string,
): Promise<Literal<User> | EntityNotFound<User, User, "User", FindOneOptions>> {
const query: FindOneOptions = {
where: { id },
};
const found = await repo.findOne(query);
if(!found) {
return new EntityNotFound("User", query);
}
return new Literal(found);
}