# @cmdln/result

This module implements my error type for Typescript, `Result<T, E>`. After trying out `@snowfrog/result`, I implemented my own for a couple of reasons.

1. The `@snowfrog` package does not play well with async code.
1. The `isOk` and `isErr` methods could have done more in `@snowfrog` to help access values and errors safely with less boiler plate. In the `@snowfrog` code, the guard methods do not narrow in all cases. In particular, the implicit else of a short-circuit pattern doesn't work. This creates toil in that the call has to use `expect`/`expectErr` which reduces readability and with Typescript's flow control analysis shouldn't be necessary.

In addition to `Result`, this package includes some useful facilities for working with that type and converting code that uses thrown exceptions into `Result` returning code.

## Converting Thrown Exceptions

The high order functions `trySync` and `tryAsync` each accept two function as arguments. The first is a no argument closure for running some code that may thrown an exception. The second is a closure accepting an `unknown` error that can then be narrowed, wrapped, and returned to change any thrown exception into an `Err`.

## Chains of Promises and Result

`Result` shines when using its various combinators, like `andThenAsync` and `yield`. These high order functions accepts closures that allow for changing the success and error types to new values of different types and even changing an `Ok` to an `Err` and vice versa.

`Promise` has a similar design and in theory the types work well together. There are some problems due to the special handling `Promise` gets when used with the `await` keyword. The compiler and runtime literally expect a `Promise` and won't accept a shape that is merely assignable. This forces some diagonal code, similar to nested callbacks, to call `Result` combinators from closures passed to `Promise`'s own combinators, especially `then`.

The `resultify` function sugars over the two types to allow use with `await` without the added boiler plate needed to access the `Result` combinators without always fully resolving the promise.

## Example

This example is excerpted from a past project and uses some structured http errors from another package I wrote.

```typescript
import { BaseContext, BaseError, Result } from "@cmdln/error";
import { passOrYield, resultify, trySync } from "@cmdln/result";
import { NextFunction, Request, Response } from "express";
import { FanOutName } from "fanout";
import { IncomingHttpHeaders } from "http";
import { getLogger } from "index";
import { sendMessage } from "producer";
import { Webhook } from "svix";
import { ID_HEADER, SIGNATURE_HEADER, TIMESTAMP_HEADER } from "./constants";

export async function handleClerkWebhook(
  request: Request,
  response: Response,
  next: NextFunction
) {
  const logger = getLogger(__filename);
  logger.info("Received Clerk webhook request.");
  const { body: payload, headers } = request;
  // without resultify here, the result's combinators would have to be nested
  // in a .then call
  await resultify(tryVerify(payload, headers))
    .andThenAsync(async (verified) => {
      logger.debug({ verified }, "Verified call from Clerk");
      return await sendMessage(FanOutName.Clerk, verified);
    })
    .yield(() => {
      request.log.debug("Webhook successfully received message");
      response.status(200).json({ message: "Success" });
      next();
    })
    .yieldErr((cause) => {
      request.log.error({ cause }, "Clerk webhook failed");
      response.status(400).json({});
      next(cause);
    });
}

function tryVerify(
  payload: string,
  headers: IncomingHttpHeaders
): Result<unknown> {
  const wh = new Webhook(process.env.CLERK_WEBHOOK_SECRET);
  // there is also tryAsync which works the same way for async code
  // remember that if your logic in the first argument returns a value, you
  // will need to return the call as well; a common mistake is leaving out a
  // return or worse an await with tryAsync
  return trySync<unknown, BaseError>(
    () => {
      // a common practice in our code is to use assertion functions in
      // trySync/tryAsync since often we are using a client library and need to
      // narrow the result; this use case is a little different but a valid
      // example of the pattern
      assertClerkHeaders(headers);
      return wh.verify(payload, headers);
    },
    // if we know of any thrown errors that are compatible with our Result's
    // generic bindings, this helper function saves more boiler plate
    passOrYield(VerifyError, (cause) => {
      return VerifyError.err(VerifyErrorKind.InvalidSignature, { cause });
    })
  );
}

enum VerifyErrorKind {
  InvalidSignature = "Failed to verify signature on call from Clerk",
  MissingHeaders = "Missing required headers on call from Clerk",
}

type VerifyContext = BaseContext & {
  headers?: unknown;
};

class VerifyError extends BaseError {
  static err(kind: VerifyErrorKind, context: VerifyContext) {
    return new VerifyError(500, kind, context);
  }

  get name() {
    return VerifyError.name;
  }
}

function assertClerkHeaders(
  headers: IncomingHttpHeaders
): asserts headers is Record<string, string> {
  if (
    !headers[ID_HEADER] ||
    !headers[TIMESTAMP_HEADER] ||
    !headers[SIGNATURE_HEADER]
  ) {
    throw VerifyError.err(VerifyErrorKind.MissingHeaders, { headers });
  }
}
```

## To Do

- [ ] Figure out if async and sync combinators can be cleanly merged similar to Jest's signature for its builder HOF's
- [ ] Remove `Async` suffix for methods names, use `Sync` for non-awaitable code since it is likely less frequently used
