# @cmdln/result This module implements my error type for Typescript, `Result`. 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 { 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( () => { // 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 { 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