Formulaic Docs

FP Utility Methods

The last page described directly using the fields of Data<T> and other FP interfaces to interact with their content.

However, many times you can use the FP utility methods to better interact with returned data.

mapIf

mapIf allows transforming the data of an FP, operating like the .then method on a Promise.

Specify the kind of data you’re transforming by either providing a class or the value of "kind", and a transformation function.

  1. If the object matches the kind specified

    1. and the function returns an FP, the return value will be used

    2. and the function returns raw data, the data will be wrapped in Data and returned

  2. If the object does not match the kind, it will be returned as-is

new EntityNotFound<number, "number">("number")
  .mapIf(
    EntityNotFound, (1)
    notFound => new UnexpectedError(notFound),
  );
// => UnexpectedError (2)

new Data(10).mapIf(
  "data", (3)
  data => data.data + 10, (4)
);
// => Data<number>

new Data(10).mapIf(EntityNotFound, () => {
  throw new Error("This will never match.");
});
// => Data<number>
1 This example passes a class to match
2 Because the class matches, the data is transformed
3 Alternatively, you can match by name
4 See mapData for a shortcut when mapping Data objects

mapData

mapData is a useful shortcut used when an FP may be a Data instance, and works similar to calling .then on a Promise.

The function given to mapData will be provided the contents of the Data object.

  1. If mapData is called on a non-Data object, the object will be returned as-is

  2. If the function passed to mapData returns an FP object (has kind), the returned value will be passed as-is

  3. If the function returns any other object, it will be wrapped in Data

interface User {
  id: number;
  username: string;
}

/** Case 1: mapData does not transform objects that are not Data<T> */
new AccessForbidden<User, "User">("User").mapData(x => {
  throw new Error("This can never be reached.");
}); // => AccessForbidden<User, "User">

/**
 * Case 2: an FP<T> object will be returned as-is
 * (e.g. you can return Data<T> and it won't be wrapped as Data<Data<T>>)
 */
new Data<User>({ id: 1234, username: "admin" }).mapData(user => {
  if(user.username === "admin") {
    return new AccessForbidden("User");
  }
  return user;
}); // => Data<User> | AccessForbidden<User, "User">

/**
 * Returning raw data will be wrapped in a new `Data<T>` object.
 */
new Data(5).mapData(x => x + 10) // => Data<number>.data = 15

mapDataAsync

mapDataAsync is like mapData, but uses Promises.

async function getUser(username: string): Promise<Data<User> | EntityNotFound<User, "User">> { /* ... */ }
async function getUserProfile(user: User): Promise<Data<Profile> | MissingPermission<Profile>> { /* ... */ }

async function main() {
  const user = await getUser("admin");
  const profile = await user.mapDataAsync(user => getUserProfile(user));
  // => Data<Profile> | MissingPermission<Profile> | EntityNotFound<User, "User">
}