Skip to content

Commit

Permalink
code submit
Browse files Browse the repository at this point in the history
'
  • Loading branch information
nickfla1 committed Nov 3, 2023
1 parent 44cc47d commit 295b7bc
Show file tree
Hide file tree
Showing 11 changed files with 2,088 additions and 0 deletions.
10 changes: 10 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
root = true

[*]
end_of_line = lf
insert_final_newline = true

[*.{js,json,yml}]
charset = utf-8
indent_style = space
indent_size = 2
4 changes: 4 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/.yarn/** linguist-vendored
/.yarn/releases/* binary
/.yarn/plugins/**/* binary
/.pnp.* binary linguist-generated
16 changes: 16 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions

# Swap the comments on the following lines if you wish to use zero-installs
# In that case, don't forget to run `yarn config set enableGlobalCache false`!
# Documentation here: https://yarnpkg.com/features/zero-installs

#!.yarn/cache
.pnp.*


dist
154 changes: 154 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# resful

Type safe result utilities for TypeScript.

## Who should use this library?

This library is intended for developer that want to void explicitly uses `Error` and `throw` in their TypeScript applications.

If used in libraries is not advised to expose `Result` to the library public API. This library is thought for internal use only.

## How to use

### Install

```sh
yarn add -D resful

npm install -D resful

pnpm install -D resful
```

### Basic usage

```ts
import { ok, err, isOk } from 'resful'
import type { Result } from 'resful'

const myFunc = (): Result<string, string> => {
if (Math.random() > 0.5) {
return err("bad odds")
}

return ok("good odds")
}

const res = myFunc()

if (isOk(res)) {
// nice stuff
}
```

## API

### `ok`

Creates an immutable (`Object.freeze`) `OkResult` object of the provided type.

```ts
import { ok } from 'resful'

interface User {
id: string
email: string
}

const res = ok<User>({
id: '123-456',
email: 'cool@email.com'
})
```

### `err`

Creates an immutable (`Object.freeze`) `ErrResult` object of the provided type.

```ts
import { err } from 'resful'

const BAD_ERROR = 'error.bad' as const

const res = err(BAD_ERROR) // The type of the error is inferred
```

### `isOk` and `isErr`

Utilities asserting if a result is either an `OkResult` or an `ErrResult`.

```ts
import { isOk, isErr } from 'resful'

const res = /* ok(...) or err(...) */

if (isOk(res)) {
// `res.ok` is accessible, res is OkResult
}

if (isErr(res)) {
// `res.err` is accessible, res is ErrResult
}
```

### `unwrap`

Utility to unwrap the content of a result.

> NOTE: This utility will throw a `TypeError` if its input is an `ErrResult`
```ts
import { unwrap, ok } from 'resful'

const res = ok('foobar')

unwrap(res) === 'foobar' // true
```

### `unwrap`

Utility to unwrap the content of a result. Returning a compatible fallback value if it's an `ErrResult`.

```ts
import { unwrapOr, ok } from 'resful'

const res = ok('foobar')

unwrapOr(res, 'barbar') === 'foobar' // true
```

```ts
import { unwrapOr, err } from 'resful'

const res = err('foobar')

unwrapOr(res, 'barbar') === 'barbar' // true
```

### `map`

Utility to map the content of an `OkResult` into another type.

> NOTE: This utility will throw a `TypeError` if its input is an `ErrResult`
```ts
import { map, ok } from 'resful'

const res = ok('foobar')

map(res, (value) => value.toUpperCase()) // 'FOOBAR'
```

### `mapErr`

Utility to map the content of an `ErrResult` into another type.

> NOTE: This utility will throw a `TypeError` if its input is an `OkResult`
```ts
import { mapErr, err } from 'resful'

const res = err('barbar')

mapErr(res, (value) => value.toUpperCase()) // 'BARBAR'
```
1 change: 1 addition & 0 deletions node_modules/.vitest/results.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 23 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "resful",
"type": "module",
"version": "0.1.0",
"description": "Type safe result utilities for TypeScript",
"author": {
"name": "Flavio (nickfla1) Lanternini Strippoli"
},
"scripts": {
"test": "vitest",
"build": "tsc"
},
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"/dist"
],
"packageManager": "yarn@4.0.1",
"devDependencies": {
"typescript": "^5.2.2",
"vitest": "^0.34.6"
}
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./result";
58 changes: 58 additions & 0 deletions src/result.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { expect, test } from "vitest";
import { ok, err, isOk, isErr, unwrap, unwrapOr, map, mapErr } from "./result";

test("creates an immutable ok result", () => {
const res = ok("hello");

expect(res).toEqual({ ok: "hello" });

expect(() => {
/* @ts-expect-error */
res.ok = "goodbye";
}).toThrowError(/^Cannot assign to read only property 'ok'*/i);
});

test("creates an immutable error result", () => {
const res = err("badcode");

expect(res).toEqual({ err: "badcode" });

expect(() => {
/* @ts-expect-error */
res.err = "goodbye";
}).toThrowError(/^Cannot assign to read only property 'err'*/i);
});

test("isOk returns true if result is ok", () => {
expect(isOk(ok("hello"))).toBeTruthy();
expect(isOk(err("badcode"))).toBeFalsy();
});

test("isErr returns true if result is an error", () => {
expect(isErr(err("badcode"))).toBeTruthy();
expect(isErr(ok("hello"))).toBeFalsy();
});

test("should unwrap a success result", () => {
expect(unwrap(ok("hello"))).toStrictEqual("hello");
});

test("should throw if we try to unwrap an error", () => {
expect(() => {
unwrap(err("hello"));
}).toThrowError(/failed to unwrap/);
});

test("should return a fallback if an error is unwrapped", () => {
expect(unwrapOr(err("badcode"), "hello")).toStrictEqual("hello");
});

test("it should map a result", () => {
expect(map(ok("hello"), (str) => str.toUpperCase())).toStrictEqual("HELLO");
});

test("it should map an error result", () => {
expect(
mapErr(err("badcode"), (str) => str.replace("bad", "good"))
).toStrictEqual("goodcode");
});
46 changes: 46 additions & 0 deletions src/result.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
export type OkResult<T> = { readonly ok: T };
export type ErrResult<E> = { readonly err: E };
export type Result<T, E> = OkResult<T> | ErrResult<E>;

export const ok = <T>(data: T): OkResult<T> => Object.freeze({ ok: data });
export const err = <E>(error: E): ErrResult<E> => Object.freeze({ err: error });

export const isOk = <T, E>(maybeOk: Result<T, E>): maybeOk is OkResult<T> =>
"ok" in maybeOk;
export const isErr = <T, E>(maybeErr: Result<T, E>): maybeErr is ErrResult<E> =>
"err" in maybeErr;

export const unwrap = <T, E>(res: Result<T, E>): T | never => {
if (isErr(res)) {
throw new TypeError("failed to unwrap result");
}

return res.ok;
};

export const unwrapOr = <T, E, O extends T>(
res: Result<T, E>,
fallback: O
): T | O => {
if (isErr(res)) {
return fallback;
}

return res.ok;
};

export type MapFn<T, R> = (item: T) => R;

export const map = <T, E, R>(res: Result<T, E>, fn: MapFn<T, R>): R | never =>
fn(unwrap(res));

export const mapErr = <T, E, R>(
res: Result<T, E>,
fn: MapFn<E, R>
): R | never => {
if (isOk(res)) {
throw new TypeError("cannot error map an ok result");
}

return fn(res.err);
};
14 changes: 14 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"module": "CommonJS",
"target": "ES2020",
"declaration": true,
"outDir": "./dist"
},
"include": [
"src/**/*"
],
"exclude": [
"src/**/*.test.ts"
]
}
Loading

0 comments on commit 295b7bc

Please sign in to comment.