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

monads-io

v4.0.2

Published

πŸš€ Efficient Monads for JS: Maybe (Option) and Either (Result), Identity

Downloads

124

Readme

Monads IO

πŸš€ Efficient Monads for JS: Maybe (Option) and Either (Result)

Test Status Downloads last commit codecov GitHub monads-io Known Vulnerabilities Quality npm license MIT Size

Why use this lib

  1. Small and Tree-Shakable. Either - 3kb minified, Maybe - 3kb minified, can be imported separately
  2. No dependencies.
  3. Memory-Efficient. 8 bytes overhead per instance (only class pointer)
  4. Tested. 100% coverage
  5. Practical. Just 2 wrappers: Either and Maybe - easy for non-fp people

Credits

Huge credit to @JSMonk. This library is based on JSMonk/sweet-monads

Installation

  • Using npm
    npm i monads-io
  • Using Yarn
    yarn add monads-io
  • Using pnpm
    pnpm add monads-io

Usage

Either

The Either type represents values with two possibilities: a value of type Either a b is either Left a or Right b. (source)

  1. Makes error path of function strongly typed
  2. Separates errors from exceptions
  3. Minimal memory overhead (see benchmarks)
import {
  Either,
  fromPromise,
  fromTryAsync,
  left,
  mergeInOne,
  right
} from "monads-io/either";

class NetworkError extends Error {}
class HttpError extends Error {}
class JsonParsingError extends Error {}
class NotFoundError extends Error {}

type FetchError = NetworkError | HttpError | JsonParsingError;

type ID = string;
type User = { id: ID; username: string; name: string /* ... */ };
type Post = { id: ID; userId: User["id"]; body: string /* ... */ };

async function getJson<T>(url: string): Promise<Either<FetchError, T>> {
  const response = await fromPromise(
    fetch(`https://jsonplaceholder.typicode.com/${url}`),
    (cause) => new NetworkError("Unable to connect", { cause })
  );

  const okResponse = response.chain((response) => {
    if (response.ok) return right(response);

    return left(
      new HttpError(
        `Response status is ${response.status} ${response.statusText}`,
        { cause: response }
      )
    );
  });

  const json = await okResponse.asyncChain((response) => {
    return fromTryAsync(
      async () => (await response.json()) as T,
      (cause) => new JsonParsingError("Unable to parse JSON", { cause })
    );
  });

  return json;
}

async function getUserByUsername(username: string) {
  const users = await getJson<User[]>(`/users?username=${username}`);

  return users.chain((users) => {
    const user = users[0];

    if (!user) {
      return left(new NotFoundError(`User not found`, { cause: { username } }));
    }

    return right(user);
  });
}

const getPosts = (userId: string) =>
  getJson<Post[]>(`/posts?ownerId=${userId}`);

class PageLoadError extends Error {
  /* ... */

  constructor(public returnStatus: number, message: string, cause?: unknown) {
    super(message, { cause });
  }
}

async function getUserPageData(username: string) {
  const user = await getUserByUsername(username);
  const posts = await user.asyncChain((user) => getPosts(user.id));

  return mergeInOne([user, posts])
    .map(([user, posts]) => ({ user, posts }))
    .mapLeft((error) => {
      if (error instanceof NotFoundError) {
        return new PageLoadError(404, "User not found", error);
      }

      // error: FetchError
      console.log("Error fetching data for User Page", error);
      return new PageLoadError(500, "Internal server error", error);
    });
}

Maybe

The Maybe monad represents computations which might "go wrong" by not returning a value. (source)

  1. Allows to separate empty/present state from undefined
  2. Minimal memory overhead (see benchmarks)
// Real world example
// This maybe is not tree-shakable. Used in NodeJS code
import * as Maybe from "monads-io/maybe";

export async function getTargets(
api: TelegramAPI,
tokens: formattedText,
{ mentionLimit = 1, message = undefined as message | undefined } = {}
): Promise<Map<number, chat | undefined>> {
const mentions = getMentions(tokens).slice(0, mentionLimit);

const targets = new Map<number, chat | undefined>();
let replyTarget: [number, chat | undefined] | undefined;
const { messagesService, chatsService } = getServices(api);

...

// 1. Get message
// 2. Get message reply id (0 = no reply)
// 3. Get reply message by message id
// 4. Get reply message sender
// 5. Get his/her profile
// 6. Set local variable to profile

const reply = await Maybe.fromNullable(message)
  .filter((message) => message.reply_to_message_id !== 0)
  .asyncChain((message) =>
    messagesService.getReply(message.chat_id, message.id)
  );

const sender = await reply
  .map(MemberId.fromMessage)
  .tap(({ memberId }) => {
    replyTarget = [memberId, undefined];
  })
  .asyncChain(({ memberId }) => chatsService.getById(memberId));

sender.tap((sender) => {
  replyTarget = [sender.id, sender];
});

...

return replyTarget ? new Map([replyTarget, ...targets]) : targets;
}

Identity

The Identity monad is a monad that does not embody any computational strategy. It simply applies the bound function to its input without any modification. (source)

Example

import * as Identity from "monads-io/identity";

// Before
app.use(express.static(path.resolve(getDirname(import.meta.url), "../public")));

// After
Identity.from(import.meta.url)
  .map(getDirname)
  .map((dir) => path.resolve(dir, "../public"))
  .map(express.static)
  .map(app.use);