Skip to content

Commit

Permalink
feat: refactor package & prepare for upcoming additions (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
ivanhofer authored Jan 11, 2023
1 parent ce4c828 commit 9393163
Show file tree
Hide file tree
Showing 20 changed files with 275 additions and 194 deletions.
5 changes: 5 additions & 0 deletions .changeset/giant-mugs-drum.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"exceptionally": minor
---

allow to use multiple versions and instances of `exceptionally` in a project
7 changes: 7 additions & 0 deletions .changeset/lucky-jeans-dress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"exceptionally": major
---

rename `Extract*` types

take a look at the exported types in the README
7 changes: 7 additions & 0 deletions .changeset/pink-cats-dance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"exceptionally": major
---

get rid of Exceptionally as a class since it can't cover all use cases in a typesafe way

use `isExceptionallyResult` instead
7 changes: 7 additions & 0 deletions .changeset/tall-items-stare.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"exceptionally": major
---

export assertions under `/assert` subfolder

import `assertSuccess` and `assertException` from `'exceptionally/assert'`
6 changes: 3 additions & 3 deletions .size-limit.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
const config = [
{
path: 'lib/index.js',
limit: '197 b',
limit: '132 b',
},
{
path: 'lib/index.cjs',
limit: '296 b',
limit: '226 b',
},
{
path: 'lib/index.global.js',
limit: '276 b',
limit: '208 b',
},
]

Expand Down
215 changes: 132 additions & 83 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
**A fully type-safe and lightweight way of using exceptions instead of throwing errors**

:safety_vest: fully typesafe\
:baby_chick: lightweight (197 bytes)\
:baby_chick: lightweight core library (132 bytes)\
:gear: useful utility functions\
:running: almost no runtime overhead\
:ok_hand: easy to use syntax\
:handshake: works everywhere (browser, node, cjs, esm)\
:handshake: works everywhere (browser, node, cjs, esm, etc.)\
:warning: can warn you about unhandled exceptions\
:no_entry: no external dependencies

Expand All @@ -20,7 +21,7 @@
- [Best Practices](#best-practices)
- [Glossary](#glossary)

<!-- ---------------------------------------------------------------------------------------------------- -->
<!---------------------------------------------------------------------------------------------------------->

## Problem Description

Expand All @@ -36,7 +37,7 @@ While it requires just a little of effort to look into a function to see what ki

Adding a new kind of exception deep down in the nested functions would require you to take a look at all the code parts that use the function and check whether they should handle the exception or pass it to the next level.

<!-- ---------------------------------------------------------------------------------------------------- -->
<!---------------------------------------------------------------------------------------------------------->

## Solution

Expand All @@ -56,7 +57,7 @@ And because this is no rocket science, we don't need hundreds of dependencies to

This packages delivers a solution to all the problems described above.

<!-- ---------------------------------------------------------------------------------------------------- -->
<!---------------------------------------------------------------------------------------------------------->

## Installation

Expand All @@ -76,7 +77,7 @@ const doSomething = () => {
// whenever you usually throw, return an `exception` instead
// you can pass additional payload if you want
if (value < 0.1) return exception('please try again')

// if a function can return an `exception`, you should wrap the returned element with `success`
return success(value)
}
Expand All @@ -94,18 +95,27 @@ if (result.isException) {
console.info(result().toPrecision(2)) // => e.g. '0.57'
```

<!---------------------------------------------------------------------------------------------------------->

## Examples

- [input validation](https://www.typescriptlang.org/play?noUncheckedIndexedAccess=true&noUnusedLocals=true&noUnusedParameters=true&target=99&jsx=0&useUnknownInCatchVariables=true&noImplicitOverride=true&noFallthroughCasesInSwitch=true&exactOptionalPropertyTypes=true&pretty=true#code/JYWwDg9gTgLgBAbzgUwB4GNlhsCA7AGjgGcBXdTY4uAXzgDMoIQ4ByNTbXPAQwBs+AT1YAoEQHpxcALSy58hYqXKVqteo2a1Y9PmLwAbv2AATHjGRwAvHAA8AEXM8AfAAozMHgC44jz0SM+U3NoH3cnHz8eAEprZzgYKFJLAB8SROA8AHNYq3iEEQBIXTx9OGBiAHkAa2s4QOCYaHDPaKLgejhXCprrKxtE5NioZBhSKDwSckpiFpixQpGxiZQMLBx8bqrqtpoxSRktI+OT07PVMRhBMEsABQgymwLCnlIYAAtQ9KhMrKKcGB8ZA+fQ-bJFEoWPAwEEZcF7EQlMqQfQANWMHmgdVcKJhcHu+ly+SKByW40mIGQPDwv3opD4KCgTCgcEpVB4WWQ1AA7u9kJNiMxRu9fuVqJkGiZ2p1XABCXEAOleH2gw1G5LYACJlZ8oJrWRViL9RNKuvKHjAFQCgWrlpNWJrrch9SBDcbTTiLVbgIDkAqgdkPnB4gBGABMAAZbRqHU6XaQygAjSzEXUWFkfalwcMRuDod48KA8dDp4gmwodM2KyH8mDRlYOmvQl1u7KiCsyxVO-38rJB2xwACsEajcDJDc1TZg8bKkJ4mTg5jgQJ4ZWHufzheLpfLpPVKwABoNkAe4Lz+fUMeZuHAwKviFyiuPJseRAikfBiDwDMgCfAbKugh4OgXS4j4f5EogEJ6IYV4bHgABKXL0v+l5BB4yCevoRC4ui6EhFAbQdl0krXvgSFkHwloVAAomsXD4PWkykfBFEoXABx3lQDDQNyhYmKsnDwQsBxfgYoofJYHg8GefLMXBN4FtQXEPlKxQweUAk2CGHFSDwfE+iQ36-haAAqEBRImq6YbibRPvuArTFysymLs+xSOcnled5PnSGIaCQLAeYaUkkwAcQQEga4kHPB+Rk-n+bFUXU+nzp+xl-q4zwvG8uo+KwABSEDvJM9gQMgrAEEUhROvlNFMliBZ4CYQTZCQnz0gJyYoKuwDIFAlXVVO+UADLQMgLDAGAZAsCYEB8NARBGvAPCUvASLICW+6LiYU0VMA6CisgQSWnAACCa1EAAjqQwAKoNhQ0HZxGuGJJn6El1HEHRQncLEHxMNycB4MgQP1cyr0ZRan3Rc9SLzX6mT0BArgHiDQO4vFyAmD4AAkCBvYlyFUdFNAHm5IihdFIhAA)
- [fetching data](https://www.typescriptlang.org/play?noUncheckedIndexedAccess=true&noUnusedLocals=true&noUnusedParameters=true&target=99&jsx=0&useUnknownInCatchVariables=true&noImplicitOverride=true&noFallthroughCasesInSwitch=true&exactOptionalPropertyTypes=true&pretty=true#code/JYWwDg9gTgLgBAbzgQwM6oKawMoFcDG+G6ANHBgB5FgzAQB2ZAolRjXfcgDZcCeZqAkXRwAvnABmUCCDgByStVoNufOQCh1Aei1wAtAcNHjJ02fMXLV6zcuadcACYYJwehifAJErBnrwWJQ44fC40TFRJaBQeOABrN0c4CAlyVnYGVG1dADkAeQAVJgAuOFQZDAALCAB3OALeMAxsfChgGhDkejl4Z1RaegBzXFRKuAAjDBgajD9Pb19-OABhMPRiOBhK5F6sYAA3DykZOAADJihpKFOAOmy4B8e4AEFI5Dga6DjkaVx6JJmnXoZSmKDgYGkTVgvDgblQwGcm228FC4U2EDgIGQcQ8bh8UD8RA+XxQg2QbjuqJEOSmnygcUCbGUwMoMD8jkiFyuiHUAEgHAABGCoPTAQb0aAeLYeAAKYV4g1+-ycEA2EvgUPh-SRHlOEoAqvQRhhHAAZCD4bioU4hBiuQZ8gnIRwMPhwADECLgAF44NheCBxhAuAAKOQ06ZfRkZboASnUonUVMiABEMPgIM4AFLlejR5lpNn-TmXaIIPmC4Wi8WSnVwOXIBVKpIutUQDVYLXwaVnA1GzBmi1Wm0Z+j2x0YZ2umGepK+-2B4NhtMZ7O5-McOTxxPJuAACRgMDAG4YhfZJe55f5uiFIrFEoJdYbTYgfxbqsi6vBneA2p7eogQ1jUHS0uGtW0xzFCcp3oN1Zx9P0AyDUM5API8TzjBMkzWTlwBgXgUx2NApgws9izgLkywrG8q3vWse2fRVX2VVtP3bb8oC7OsAKAgdzVA8DR3HXknRdWCZy9eckKXOQmDwgiiMwGAMK3LD7hqSo5h8GB8EqNxBicIiyHeHw6i2fTIktYFtjAJp6BuG4Pk04FyhAKY9KGSRyTAshgHgX84EGCBM3ReIJTqDSdjgbZIhgNpBkGXwkh7X9BAwJNMngbTdMImB3l9NBeHofA4AAHgAJSmXAoHoBomgAPhDdlIDcGBSn6NohljH16p5XlR21bLKkqwQuHgAqanJLKpl0pr-ha-x415XkbktHTKianq0iCBgQ3cOoIzpBl0mZJrY3OuAHB7TAoEOKBbVwLgkkmOA-mQfZvOQcYuA8By7l5Lw4BDIaRse-z6H6LoiBSCiTo4VReG6gkYGq4EQeIMHLt0MA0QkaBJqgJJFCZDhNGvOA-rrAkAEdcGIFFXyerzgC4D4-LGd5IZR1BVw8AAWCgKGSe6AFZBYphy+UBkMAEJ0dGmAbggOIkaqmrtpJ3b9v3Q9jzh3bTgAEgQeWwZuLmRlEUpjdNsbzby7mClZURTnOsmBt6IjQbGhDkEmvzJBm4aMbtgArXMQyWlbpXoENHCIrb47ylBIkqlGarqjAo9WnZZo8b1euJmM9owOoV0zDAcwYDCzouhxKeiHscd4LgIGdB6mYG394BhtwPq4L0s2wPIcgl-7paT5BvfByHiowGGMIR1X0+BSfp6x8Fcfxn4if1+gyel55LkbG5fyPqBGzjr2Q5gbqADI77gGW15vm4fqGLZuqvES1ZZPeS7qHJGgCk8pKRrnXXQDd7pYl4C9Hs-cEQ7GCDDHsk8O5JAkN5CWfJEwThXmUIQxBUBXzytPbq9cHIoGVK4TgPAYRDX0oZZOGZHpJCtBiF6ghCBEIkI9NSDhbCCKEcIkRoi7DqHwk0OA+oboISvAidqcV9J8k4G5RRnUHS4I9oFKYMjOy+1QEVEqkctpXm0UNPRnF14TSmoHdauVkClUsQAbQALqNTkJUXWqBig6FchgPQyAwDAFWjILQxpOKqWok5PwGA7ofA8IleA4xkD4DiGCAkCsyCAlGIzJIul0zpLxvdIuzJIiuE4jAKWqRgZB0sagaep9UAYS-nyfqmVtoIQsTdBpN9I5tIcNsf4P0wREFgOSYEkiPAw1KcERIBwES4ARm06WihYQQzynPGGh0ox71actH++DZm7TkMsPJcAvxDVej0xycpJyYE2FAGEyAyRuDgGENkUAbiqWWrg5aDhARiR6HAJ0dDOiPi+q+bsmkzhAPwg4sBe8bSaQJAIDEgJkaoyoeQeSKBj68BWTUtZcJNnQ1SHCkBxFlJ7L6stTF6suHCGIW4qOfzyZvNUMkaU91LQRGyR4elwJ3iJXcBfVmWBuRuXQC89KdLf4a2LnIQ0lAmj4DZETUsXz6w-WIhBPKaq6yCFstAGAeg2TIBAN8pafyrowsyZjKy5B2ZYAmB4RlPDHpUPyQwPKbzXz3QiZEaIL0UGNF1BShFJEkV8m0YGrpdSenT36dEl0ZQKjmU8jUdmdZJ4xsyMGDAp8xwQBDKcd16BeFgqGiaOAxtA1vz8IMLY4hA2uzJoKgh3D0AhkDeQ7GW8oAExbERfhugxHjonZOqdehNDaKSc8HgljVEbAKoY4qQNuoFz6rGxNN9fb+3gEk+p-TyZxLmMgCQnzMRg3aCM9w-Qa28OKgWUCYEElAkxNiN11UPCRXgJyyA6BgDfWmbgHSFRLJdFddUoGgbGm-habShwUUezsmSKkf14JpCKgtZIVuEUjhuARkiaQZkYUSsbsicgNxBiOSSXISIjhfxgHlDWmAGIewRLaVsUjFzS4UU1T23dCtI7WrJgCjwjqsQ4gIY+LYUVATbEOJ0daCg2GxGORDaJmcWhtA6Fm2IP14C8FfGFWosJUgmdwFEKASTQpDMcCMqDmnolxRhOxkFGAQAQGUz2Ae7gkQocqKR2KMLKL3UhcproSQrNyFiJgDwznNXRV8HycIWAYB4C7cQuDN94zRKCx4NDGL5WoITlmrYUKPgX1sowvyaLYQoigy9AauA3JJA5cCEYSzWaTQJYcrFuWROxhuFiMAIYQxIGXWITdvVl3bnsGO6dy2VureMLOjpUA-gGKMRu0xeaNnXKwMuyINiA7zsXTdE7J6BoFqLXjITx2LXEAW+oLbsd4xAA)

### exposed functions
<!---------------------------------------------------------------------------------------------------------->

- **`success`**:
```ts
const success: <Data>(data: Data) => Success<Data>
```
## API

### `exceptionally` - core functionality

All the core functionalty to use in any project.

`import * from 'exceptionally'`

#### exposed functions

- **`success`**
- Wrap any value into a `Success` object.

```ts
import { success } from 'exceptionally'
Expand All @@ -121,10 +131,9 @@ console.info(result().toPrecision(2)) // => e.g. '0.57'
result() // => `'hello world'`
```

- **`exception`**:
```ts
const exception: <Data>(data: Data) => Exception<Data>
```
- **`exception`**

Wrap any value into an `Exception` object.

```ts
import { exception } from 'exceptionally'
Expand All @@ -140,125 +149,165 @@ console.info(result().toPrecision(2)) // => e.g. '0.57'
result() // => `"Don't tell me what to do!"`
```

- **`assertSuccess`**:
```ts
const assertSuccess: <Result extends Success<unknown>>(result: Result) => asserts result is Result
```
```ts
import { assertSuccess, exception } from 'exceptionally'
- **`isExceptionallyResult`**

const doSomething = () => {
const result = Math.random() > 0.5 ? success(1) : exception(0)
To check if any given value is a `Success` or `Exception` object.

if (result.isException) throw new Error(result())
```ts
import { Exceptionally, success } from 'exceptionally'

assertSuccess(result)
const result = Math.random() > 0.5 ? success(1) : 0

return success()
if (isExceptionallyResult(result)) {
const data = result()
console.info(data) // => `1`
} else {
console.info(result) // => `0`
}
```

- **`assertException`**:
#### exposed types

- **`Success`**

The type returned by `success()`.

```ts
const assertException: <Result extends Exception<unknown>>(result: Result) => asserts result is Result
import { type Success, success } from 'exceptionally'

const result: Success = success(1)
```

```ts
import { assertException, exception } from 'exceptionally'
- **`Exception`**

const doSomething = () => {
const result = Math.random() > 0.5 ? success(1) : exception(0)
The type returned by `exception()`.

if (result.isSuccess) return result()
```ts
import { type Exception, exception } from 'exceptionally'

assertException(result)
throw new Error(result())
}
const result: Exception = exception(1)
```

- **`Exceptionally`**:
- **`ExceptionallyResult`**

Either a `Success` or a `Exception`.

```ts
type Inverted<Success extends boolean> = Success extends true ? false : true
import { exception, type ExceptionallyResult } from 'exceptionally'

class Exceptionally<Success extends boolean> {
readonly isSuccess: Success
readonly isException: Inverted<Success>
}
const result: ExceptionallyResult = Math.random() > 0.5 ? success(1) : exception(0)
```

- **`ExtractSuccess`**

Get the type of the `Success` object from a `ExceptionallyResult`.

```ts
import { success, Exceptionally } from 'exceptionally'
import { exception, type ExtractSuccess, success } from 'exceptionally'

const result = Math.random() > 0.5 ? success(1) : 0
const result = Math.random() > 0.5 ? success(new Date()) : exception('error')

if (result instanceOf Exceptionally) {
const data = result()
console.info(data) // => `1`
} else {
console.info(result) // => `0`
}
type Data = ExtractSuccess<typeof result> // => `Success<Date>`
```
### exposed types
- **`ExtractException`**
- Get the type of the `Exception` object from a `ExceptionallyResult`.
- **`ExceptionallyResult`**:
```ts
type ExceptionallyResult<Success extends boolean, Data> = () => Data & Exceptionally<Success>
```
- **`Success`**
```ts
type Success<Data> = ExceptionallyResult<true, Data>
```
import { exception, type ExtractException, success } from 'exceptionally'

- **`Exception`**
```ts
type Exception<Data> = ExceptionallyResult<false, Data>
```
const result = Math.random() > 0.5 ? success(new Date()) : exception('error')

- **`ExtractDataType`**
```ts
type ExtractDataType<Result extends ExceptionallyResult<boolean, unknown>> = Result extends
ExceptionallyResult<boolean, infer Data> ? Data : never
type Data = ExtractException<typeof result> // => `Exception<string>`
```
- **`ExtractData`**
Get the type of the data wrapped in a `ExceptionallyResult`.
```ts
import { ExtractDataType, success } from 'exceptionally'
import { type ExtractData, success } from 'exceptionally'

const result = success(1)
const result = Math.random() > 0.5 ? success(new Date()) : exception('error')

type Data = ExtractDataType<typeof result> // => `number`
type Data = ExtractData<typeof result> // => `Date | string`
```
- **`ExtractSuccessType`**
- **`ExtractSuccessData`**
Get the type of the data wrapped in a `Success`.
```ts
type ExtractSuccessType<Result extends ExceptionallyResult<boolean, unknown>> = Result extends
ExceptionallyResult<true, infer Data> ? Success<Data> : never
import { exception, type ExtractSuccessData, success } from 'exceptionally'

const result = Math.random() > 0.5 ? success(new Date()) : exception('error')

type Data = ExtractSuccessData<typeof result> // => `Date`
```
- **`ExtractExceptionData`**
Get the type of the data wrapped in an `Exception` object.
```ts
import { exception, ExtractSuccessType, success } from 'exceptionally'
import { exception, type ExtractExceptionData, success } from 'exceptionally'

const result = Math.random() > 0.5 ? success(new Date()) : exception('error')

type Data = ExtractSuccessType<typeof result> // => `Success<Date>`
type Data = ExtractExceptionData<typeof result> // => `string`
```
- **`ExtractExceptionType`**
<!---------------------------------------------------------------------------->
### `exceptionally/assert`
Useful assertion functions.
`import * from 'exceptionally/assert'`
#### exposed functions
- **`assertSuccess`**
To really make sure that you have handled all exceptions above.
```ts
type ExtractExceptionType<Result extends ExceptionallyResult<boolean, unknown>> = Result extends
ExceptionallyResult<false, infer Data> ? Exception<Data> : never
import { assertSuccess, exception } from 'exceptionally'

const doSomething = () => {
const result = Math.random() > 0.5 ? success(1) : exception(0)

// oops, some important code was commented out
// if (result.isException) throw new Error(result())

// will show a `TypeScript` error and throw a runtime error
assertSuccess(result)

return success()
}
```

- **`assertException`**

To really make sure that you are dealing with an exception.

```ts
import { exception, ExtractExceptionType, success } from 'exceptionally'
import { assertException, exception } from 'exceptionally'

const result = Math.random() > 0.5 ? success(new Date()) : exception('error')
const doSomething = () => {
const result = Math.random() > 0.5 ? success(1) : exception(0)

type Data = ExtractExceptionType<typeof result> // => `Exception<string>`
// oops, some important code was commented out
// if (result.isSuccess) return result()

// will show a `TypeScript` error and throw a runtime error
assertException(result)

throw new Error(result())
}
```

<!-- ---------------------------------------------------------------------------------------------------- -->
<!---------------------------------------------------------------------------------------------------------->

## Best Practices

Expand All @@ -272,7 +321,7 @@ console.info(result().toPrecision(2)) // => e.g. '0.57'
\
Having an **unique meaningful identifier** for each kind of error (e.g. validation, network-issues, etc.) will help you understand what has happened even after 3 or more levels of function calls. It makes it easy to handle only specific exceptions and deliver better error messages to your users.

<!-- ---------------------------------------------------------------------------------------------------- -->
<!---------------------------------------------------------------------------------------------------------->

## Glossary

Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@
"types": "./lib/index.d.ts",
"require": "./lib/index.cjs",
"import": "./lib/index.js"
},
"./assert": {
"types": "./lib/assert/index.d.ts",
"require": "./lib/assert/index.cjs",
"import": "./lib/assert/index.js"
}
},
"files": [
Expand Down
Loading

0 comments on commit 9393163

Please sign in to comment.