Coder Social home page Coder Social logo

peterboyer / esresult Goto Github PK

View Code? Open in Web Editor NEW
18.0 1.0 0.0 511 KB

A Rust-like Result utility for TypeScript.

License: MIT License

TypeScript 82.81% Shell 13.93% JavaScript 3.25%
error result typescript es ok async promise monad monads monadic

esresult's Introduction

* [/unenum] universal adt utilities for typescript
* [/dotfiles] personal configs for neovim/tmux/etc
* [/restful-api-design-tips] compilation of restful design principles

* use tabs for indentation (dev accessibility) 1 2
* typescript: use explicit return types (clear intent, faster tsc) 1 2
* typescript: use `type` unless you _need_ `interface` features (declaration merging) 1
* typescript: avoid enums (only supports string/number, use objects as const, or try unenum) 1

esresult's People

Contributors

peterboyer avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

esresult's Issues

Swap out apache license for MIT and remove personal copyright

Why?

Error handling is critical and generally tightly coupled with the rest of your code. If I were to roll esresult throughout my codebase, there would be a large dependency on it. Because of this, I would want as nonrestrictive a license as possible, particularly if I were evaluating a fairly new library without a lot of usage/stars. I would be more comfortable using a new library like this were it to have an MIT license.

Add fromThrowable support for fns returning Promise

Currently fromThrowable doesn't play well with functons that return a Promise:

fromThrowable(fnReturnPromise) => Result<Promise<Value>, Error> where we actually want Promise<Result<Value, Error>>

This could be solved as a seperate method, like fromThrowableAsync which accepts only Promise<T> returning functions, or maybe I can infer the return type statically, and then also correctly wrap the given fn to inspect it's return value and wrap if it's a promise accordingly (which would keep the library's API terse and simple).

$.is(...) should discriminate to Err type if true

From an if condition, $.is("MY_ERROR") will return true if the result is an error of that type, however within that condition closure $ will remain as Result requiring re-discrimination to $.ok as an Err, and $.error === "MY_ERROR" to isolate that error type from the union.

Research if it's possible to isolate from the union of Result "MY_ERROR", rather than $.ok for all Err types, then $.error etc.

If needed, do I need to remove .is? And simplify everything to only worry about !$.ok to get Err types and then $.error onwards?

Doesn't support intersected Promise types

When trying to integrate with tRPC fromThrowable breaks if the infered return value is not purely a Promise. The query method returns a Promise<T> & { cancel: () => void } as a CancellablePromise, which breaks with:

Argument of type 'CancellablePromise<{ id: string; }>' is not assignable to parameter of type '(...args: any[]) => any'.
  Type 'Promise<{ id: string; }> & { cancel: CancelFn; }' provides no match for the signature '(...args: any[]): any'.

fromThrowable should support insected Promise types when inferring the ReturnType of a given function.

Improve README, Library Comparisions + Gradual Introduction

Library Comparions

Write comparisons to other libaries:

Potential comparison table style https://github.com/sindresorhus/got#comparison

Key selling points:

  • error chaining
  • native error info structure
  • partial errors via ok return type
  • single from throwable converter for both promise/async and sync

Gradual Introduction

Improve introduction of library usage. Start with simply ok() wrapper, then use err() to handle error conditions, and being able to match/check the result in order to type narrow and handle errors as part of the program flow.

function myFunction() {
  if (something) return err("MY_ERROR")
  return ok("result")
}

Also working in partialErrors as a way to canoically handle a success value and accessing partialErrors easily.

Consider renaming package?

Can't easily find @armix/terror package on npm with a simple search.

typed error error type error typed all show up a package called typed-error -- do I rename to @armix/typed-error?


Actually, how about @armix/result -- considering that this is really a library about handling Results with rich type annotations for errors also.

Fetch convenience wrapper

Similar idea to #2, however because fetch is so commonly poly-filled etc, perhaps this should offer a factory function that accepts fetch as well as a default that attempts to use window.fetch (as offered by the global environment).

// default exported `fetch` as from `Fetch(fetch)`.
import { fetch } from `@armix/terror/fetch`;
const $response = await fetch("/api/...", { options });

// create from your own polyfill and wrap with `Fetch(...)`
import { Fetch } from `@armix/terror/fetch`;
import { fetch as _fetch } from "fetch-polyfill";
export const fetch = Fetch(_fetch);
const $response = await fetch(...);

Allow Result.ok and Result.err via { Result } namespace?

Would be interesting if we could enable import { Result } from esresult to allow Result.ok() and Result.err() as part of merging Result type over namespace?

Type some experiments, considering that Result is a type only, and Result could also be used as an object from which to access ok and err. And the Result type can also be merged over by a Result namespace to export Result.Ok and Result.Err.

Woah.

v2 err interface

Instead of Err exposing a .$cause-like methods for enhancing Err objects, consider using .cause() to get the cause, and .cause(CAUSE_ARG) to set the cause (or corresponding property).

const $ = err(ERROR)
  .cause(ERR_INST)
  .message("Something went wrong!")
  .info({ foo: "bar" });

$.cause() // Err
$.message() // string
$.info() // { foo: string }

Also consider abandoning the $.is() method in favour of a better way to descriminate from a Result union (rather than also needing .ok.

Keep: .ok as descriminator.
Keep: .or and .orUndefined
Remove: .is() unless truthy result can be descriminated as Err to save extra .ok and pull real .error from object.
Consider: .error on both ok and err as ok.error === undefined, and err.error !== undefined and use instead of .is()?

Introduce .map and .mapErr ?

Suggested improvements for the README

First off, love the library - though I will admit the learning curve felt unnecessarily steep (or rather, I feel like I had to read the entire README to get a complete understanding of the library and the value it brings). Some suggested improvements:

  • Implement a table of contents: when you heavily depend on a library throughout your project (as you would if you were using esresult) you often initially spend a bit of time cross-referencing/searching through the documentation. Larger libraries often have websites dedicated to documentation (see: date-fns), whereas you often have to rely on the README for smaller repositories. When this is the case, it's important you can quickly access the information you want without having to scroll/read through the entire readme (see: zod for a great example). Even as a first time user, I'm probably not interested in reading through your entire README, a table of contents quickly allows me to navigate to the areas I'm interested in.

  • Swap out yarn installation example for npm - in terms of usage, npm is more popular than yarn. You should either swap out the yarn installation example for npm (particularly as its the default package manager), or add two installation instructions - one for npm and one for yarn (and I'd argue your npm installation instruction should come first). If you're looking for mass adoption of the library, you'll be hoping new developers pick it up, and many of them won't understand the implications of npm versus yarn, and by defaulting to your installation instructions may very well find themselves with both a yarn.lock and package-lock.json file.

  • Add a motivation/why at the top of your README. I think it's enough to begin your README with:

esresult is a zero-dependency, TypeScript-first utility for better error-handling patterns in your code by making domain-specific errors an explicit part of a function's public API.

However, you then need to clearly state the motivation for the project: what problem are you trying to solve, and then whats the quick elevator pitch for how you solve the problem? What's wrong with default try/catch error handling in javascript and then abstractly (without getting into implementation), how does your library fix it?

  • Add a basic example: once you've introduced your library, explained the problem and given a high level overview of what your library does, and shown how to install it, you should give me a very simple/basic example (Getting Started). In as few lines as possible, what is the most common practical application of your library to resolve error handling? Pick a common problem and show how it can be resolved with esresult:

Getting started

For example, you may often need to write a function which takes as input from the user a potential JSON request body, hit a remote API with details within that request body, and return a portion of that to the user. Without esresult, this may look something like:

async function handleCreateUserRequest(request) {
  let body;
  try {
    body = JSON.parse(request);
  } catch (err) {
    return {
      error: "Invalid request body";
    };
  }

  try {
    const response = await fetch('https://example.com/users', {
      method: 'post',
      headers: {
        'content-type': 'application/json',
      },
      body: JSON.stringify({ username: request.username }),
    });

    const data = await response.json();
    if (data.valid) {
      return {
        error: "Error creating user",
      };
    }

    return data;
  } catch (err) {
    return {
      error: 'Unknown error';
    }
}

With esresult, you could rewrite this function as:

async function handleCreateUserRequest(request) {
  ..
}

This is a bad example. Ideally though, you should give a real-world example of how one might currently write a common function/design pattern without esresult, how they then may write the same function with esresult, and clearly demonstrate/articulate the advantage of the latter.

  • Remove motivation links or clearly explain how you're different - you mention you're motivated by neverthrow and provide a link. I click through to that link and see a library with more usage, more stars and more complete documentation. I now have to read through all of neverthrow's documentation to understand how they work and how they're different to esresult (assuming I read the complete esresult documentation). I feel like you're overestimating people's willingness to read up on library documentation before running a quick npm install. Short on time, I would just opt with neverthrow.

Rework the README.

Add/update sections of the readme to better help understand the project.

Why?

Because the state of error handling sucks in JS and common errors in the domain space. etc.

Usage

For someone who is new to the show a simple function that has a small jest test for it.

function parseSomething(input: string): Result<number, "INVALID"> {
  const value = parseInt(input, 10);
  if (Number.isNaN(value)) {
    return Result.err("INVALID")
  }
  return Result.ok(value);
}

const input = "123";

// deal directly with the result
const $something = parseSomething(input);
if ($something.ok) {
  return Result.err("SOMETHING").$cause($something).$info({ input })
}

// if it fails, can fallback to some other value that matches the expected type of the result
const something = parseSomething(input).or(456);

// or fail fallback to undefined
const orUndefined = parseSomething(input).orUndefined();

Add README examples for .info .message

-- Move common usages (like .or("asdf") and .orUndefined())

Add table of contents, helps massively to understand concepts of the library.

Add JSON library to the docs

import { JSON } from "esresult/json";

const value = JSON.parse("super invalid json input").orUndefined()

if (!value) console.log("the json is invalid")

Dealing with Ok + Partial Errors

Currently there is only two Result types: Ok and Err and to represent accumulated errors we need to do something like (below) to wrap the "real" return value of "success" in an object that also holds partial errors.

const errs: Err<"...">[] = []

return ok({ value: "success", errors: errs });

Proposing an API that extends ok() and the Ok type to support partial errors:

const $ = ok("success", errors);
ok(value: VALUE, partialErrors?: ERROR[])

$.ok // true
$.value // "success"
$.errors // [Err(), Err(), Err(), ...] or undefined

Would be best if errors would be undefined if empty, to allow for easier $.errors truthy tests, rather than needing to inspect for the truthiness of .length.

EDIT

Going with $.partialErrors (instead of .errors) as a more universal descriptor of that property on ok and to further disambiguate from the .error property on an Err object.

JSON convenience wrapper

Very commonly use JSON.parse and JSON.stringify and always need to wrap them with fromThrowable.

Ideally want to be able to:

import { JSON } from `@armix/terror/json`;

const $request = JSON.stringify(...);
const $response = JSON.parse(...);

To have access to .parse and .stringify both as fromThrowable functions, and then prototype onto the default JSON object to fallback to anything else (for better future useability).

Freeze Ok and Err prototype objects

Just for cleanliness, we should .freeze the Ok and Err prototype objects (and perhaps even more, like the return objects of ok(...) and err(...)) ending up with readonly properties in most places.

Fix changelog generation

CI flow for CHANGELOG generation is messy and broken when generating for feat: / fix: commits between tags.

Remove @deprecated Ok/Err error/value hack

To get .is(...) and .or(...) to work with intellisense I needed to compromise with adding error?: ... to Ok and value?: ... to Err in order to stop .is(...) only accepting never when attempting to merge a union of multiple Ok and Err objects from a function's return.

Perhaps defaulting to undefined instead of never may work?

Who knows at this point... I just want to remove the hack-workaround for better intellisense.

Screenshot_20211108_161125
Screenshot_20211108_161147

Easier CONTEXT interface for adding to Err object

It sometimes a little annoying to have to wrap contextual information in a err(ERROR, { context: { ... } }) object, rather than just err(ERROR, { ... }).

If its possible, can we make the options arg as CONTEXT | { context: CONTEXT, cause: ..., message: ..., [key: string]: unknown } and then pick out the variables given from context?

Or alternatively, add a .context(CONTEXT) (or .ctx, or both) error method similar to cause but returns a new Err with CONTEXT as type, so that it becomes err(ERROR).context(CONTEXT).

return err("BAD").context({ issues: $foo.partialErrors }).because($);

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.