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.
Literalimport { 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.
DataFPconst 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.
UnexpectedErrorclass 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);
}