npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

typescript-functional-extensions

v2.0.0

Published

A TypeScript implementation of synchronous and asynchronous Maybe and Result monads

Downloads

1,527

Readme

Typescript Functional Extensions

NPM

A TypeScript implementation of the C# library CSharpFunctionalExtensions, including synchronous and asynchronous Maybe and Result monads.

Community

Related Projects

Influences

Further Reading

How to Use

npm

npm i typescript-functional-extensions

Unpkg

Supported since v1.4.0+

Full distributed source

https://unpkg.com/browse/[email protected]/

ES Modules

https://unpkg.com/typescript-functional-extensions@version/dist/esm/file

Example:

https://unpkg.com/[email protected]/dist/esm/maybe.js

const { Maybe } = await import(
  'https://unpkg.com/[email protected]/dist/esm/maybe.js'
);

const maybe = Maybe.some('test');

Module Sizes

The distributed library is currently not minified. Below are the module sizes when minified (using UglifyJs) and GZipped:

  • api.js: 0.15 kb
  • index.js: 0.09 kb
  • maybe.js: 0.81 kb
  • maybe.utilities.js: 0.27 kb
  • maybeAsync.js: 0.64 kb
  • result.js: 1.28 kb
  • resultAsync.js: 0.76 kb
  • unit.js: 0.13 kb
  • utilities.js: 0.27 kb

Total: 4.39 kb

Core Monads

import {
  Maybe,
  MaybeAsync,
  Result,
  ResultAsync,
} from 'typescript-functional-extensions';

Utilities

import {
  never,
  isDefined,
  isSome,
  isNone,
  isFunction,
  isPromise,
  noop,
} from 'typescript-functional-extensions';
import {
  zeroAsNone,
  emptyStringAsNone,
  emptyOrWhiteSpaceStringAsNone,
} from 'typescript-functional-extensions';
import {
  fetchResponse,
  fetchJsonResponse,
} from 'typescript-functional-extensions';

Monads

Below are the monads included in this package and examples of their use.

More examples of all monads and their methods can be found in the library unit tests or in the dedicated documentation files for each type.

Maybe

Maybe represents a value that might or might not exist. You can use it to declaratively describe a process (series of steps) without having to check if there is a value present.

type Employee = {
  email: string;
  firstName: string;
  lastName: string;
  manager: Employee | undefined;
};

function yourBusinessProcess(): Employee[] {
  // ...
}

const employees = yourBusinessProcess();

Maybe.tryFirst(employees)
  .tap(({ firstName, lastName, email }) =>
    console.log(`Found Employee: ${firstName} ${lastName}, ${email}`))
  .bind(employee =>
    Maybe.from(employee.manager)
      .or({
        email: '[email protected]',
        firstName: 'Company',
        lastName: 'Supervisor',
        manager: undefined
      })
      .map(manager => ({ manager, employee }))
  )
  .match({
    some(attendees => scheduleMeeting(attendees.manager, attendees.employee)),
    none(() => console.log(`The business process did not return any employees`))
  });
  1. tryFirst finds the first employee in the array and wraps it in a Maybe. If the array is empty, a Maybe with no value is returned.
  2. tap's callback is only called if an employee was found and logs out that employee's information.
  3. bind's callback is only called if an employee was found and converts the Maybe wrapping it into to another Maybe.
  4. from wraps the employee's manager in a Maybe. If the employee has no manager, a Maybe with no value is returned.
  5. or supplies a fallback in the case that the employee has no manager so that as long as an employee was originally found, all the following operations will execute.
  6. map converts the manager to a new object which contains both the manager and employee.
  7. match executes its some function if an employee was originally found and that employee has a manager. Since we supplied a fallback manager with or, the some function of match will execute if we found an employee. The none function of match executes if we didn't find any employees.

See more examples of Maybe in the docs or in the tests.

MaybeAsync

MaybeAsync represents a future value (Promise) that might or might not exist.

MaybeAsync works just like Maybe, but since it is asynchronous, its methods accept a Promise<T> in most cases and all of its value accessing methods/getters return a Promise<T>.

See more examples of MaybeAsync in the docs or in the tests.

Result

Result represents a successful or failed operation. You can use it to declaratively define a process without needing to check if previous steps succeeded or failed. It can replace processes that use throwing errors and try/catch to control the flow of the application, or processes where errors and data are returned from every function.

type Employee = {
  id: number;
  email: string;
  firstName: string;
  lastName: string;
  managerId: number | undefined;
};

function getEmployee(employeeId): Employee | undefined {
  const employee = getEmployee(employeeId);

  if (!employee) {
    throw Error(`Could not find employee ${employeeId}!`);
  }

  return employee;
}

Result.try(
  () => getEmployee(42),
  (error) => `Retrieving the employee failed: ${error}`
)
  .ensure(
    (employee) => employee.email.endsWith('@business.com'),
    ({ firstName, lastName }) =>
      `Employee ${firstName} ${lastName} is a contractor and not a full employee`
  )
  .bind(({ firstName, lastName, managerId }) =>
    Maybe.from(managerId).toResult(
      `Employee ${firstName} ${lastName} does not have a manager`
    )
  )
  .map((managerId) => ({
    managerId,
    employeeFullName: `${firstName} ${lastName}`,
  }))
  .bind(({ managerId, employeeFullName }) =>
    Result.try(
      () => getEmployee(managerId),
      (error) => `Retrieving the manager failed: ${error}`
    ).map((manager) => ({ manager, employeeFullName }))
  )
  .match({
    success: ({ manager: { email }, employeeFullName }) =>
      sendReminder(email, `Remember to say hello to ${employeeFullName}`),
    failure: (error) => sendSupervisorAlert(error),
  });
  1. try executes the function to retrieve the employee, converting any thrown errors into a failed Result with the error message defined by the second parameter. If the employee is found, it returns a successful Result.
  2. ensure's callback is only called if an employee was successfully found. It checks if the employee works for the company by looking at their email address. If the address doesn't end in @business.com, a failed Result is returned with the error message defined in the second parameter. If the check passes, the original successful Result is returned.
  3. bind's callback is only called if the employee was found and works for the company. It converts the employee Result into another Result.
  4. toResult converts a missing managerId into a failed Result. If there is a managerId value, it's converted into a successful Result.
  5. map's callback is only called if the managerId exists and converts the managerId into a new object to capture both the id and the employee's full name.
  6. bind's callback is only called if the original employee was found and that employee had a managerId. It converts the id and employee name into a new Result.
  7. try now attempts to get the employee's manager and works the same as the first try.
  8. map's callback is only called if the original employee was found, has a managerId and that manager was also found. It converts the manager returned by try to a new object capturing both the manager and employee's name.
  9. match's success callback is only called if all the required information was retrieved and sends a reminder to the employee's manager. The failure callback is called if any of the required data could not be retrieved and sends an alert to the business supervisor with the error message.

See more examples of Result in the docs or in the tests.

ResultAsync

ResultAsync represents a future result of an operation that either succeeds or fails.

ResultAsync works just like Result, but since it is asynchronous, its methods accept a Promise<T> in most cases and all of its value accessing methods/getters return a Promise<T>.

function getLatestInventory(): Promise<{ apples: number }> {
  return Promise.reject('connection failure');
}

const resultAsync = ResultAsync.from(async () => {
  try {
    const value = await getLatestInventory();
    return Result.success(value);
  } catch (error: unknown) {
    return Result.failure(`Could not retrieve inventory: ${error}`);
  }
});

const result = await resultAsync.toPromise();

console.log(result.getErrorOrThrow()); // 'Could not retrieve inventory: connection failure'

See more examples of ResultAsync in the docs or in the tests.

Contributing

To build this project, you must have v18.12.1 or higher of the Node.js installed.

If you've found a bug or have a feature request, please open an issue on GitHub.

If you'd like to make a contribution, you can create a Pull Request on GitHub.