Skip to content

Commit

Permalink
feat!: add overloads for invariantFactory()
Browse files Browse the repository at this point in the history
- remove asyncInvariantFactory because async assertion functions are not
supported. See microsoft/TypeScript#37681
  • Loading branch information
leumasic committed Jun 1, 2024
1 parent d378eca commit 38f6b13
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 90 deletions.
25 changes: 0 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,31 +59,6 @@ invariant(falsyValue, { severity: 'WARN', message: 'Exception data message' })
// Error('WARN: Exception data message')
```

### `asyncInvariantFactory(asyncExceptionProducer)`

Use the `asyncInvariantFactory` function to throw a custom exception asynchronously.

```ts
// utils.ts
import { asyncInvariantFactory } from 'flexible-invariant'

const asyncExceptionProducer = async (exceptionData: any) => {
const response = await fetch(url)

return new Error(response.status)
}

export const asyncInvariant: async (
condition: any,
exceptionData: Parameters<typeof asyncExceptionProducer>[0],
) => asserts condition = asyncInvariantFactory(asyncExceptionProducer)


// module.ts
import { asyncInvariant } from './utils'

await asyncInvariant(condition, exceptionData)
```

## Credits

Expand Down
52 changes: 22 additions & 30 deletions src/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, it, expect, vi } from 'vitest'
import { asyncInvariantFactory, invariant, invariantFactory } from './index'
import { invariant, invariantFactory } from './index'

describe('invariant()', () => {
describe('when condition is truthy', () => {
Expand Down Expand Up @@ -33,39 +33,31 @@ describe('invariant()', () => {
})

describe('invariantFactory()', () => {
it('returns an invariant function that throws return value of exception producer', () => {
const exceptionProducerMock = vi.fn(
(exceptionData: { severity: string; message: string }) =>
new Error(`${exceptionData.severity}: ${exceptionData.message}`),
)

const invariantFn: (
condition: any,
exceptionData: Parameters<typeof exceptionProducerMock>[0],
) => asserts condition = invariantFactory(exceptionProducerMock)
describe('when called with `exceptionProducer` that does not take an argument', () => {
it('returns an invariant function that throws return value of exception producer', () => {
const invariantFn: (condition: any) => asserts condition = invariantFactory(
() => new Error('Custom error message'),
)

const exceptionData = { severity: 'ERROR', message: 'My error message' }
expect(() => invariantFn(false, exceptionData)).toThrow(new Error('ERROR: My error message'))
expect(exceptionProducerMock).toHaveBeenCalledWith(exceptionData)
expect(() => invariantFn(false)).toThrow(new Error('Custom error message'))
})
})
})

describe('asyncInvariantFactory()', () => {
it('returns an async invariant function that throws resolved value of exception producer', async () => {
const exceptionProducerMock = vi.fn(
async (exceptionData: { severity: string; message: string }) =>
new Error(`${exceptionData.severity}: ${exceptionData.message}`),
)
describe('when called with `exceptionProducer` that takes an argument', () => {
it('returns an invariant function that throws return value of exception producer', () => {
const exceptionProducerMock = vi.fn(
(exceptionData: { severity: string; message: string }) =>
new Error(`${exceptionData.severity}: ${exceptionData.message}`),
)

const invariantFn: (
condition: any,
exceptionData: Parameters<typeof exceptionProducerMock>[0],
) => asserts condition = asyncInvariantFactory(exceptionProducerMock)
const invariantFn: (
condition: any,
exceptionData: Parameters<typeof exceptionProducerMock>[0],
) => asserts condition = invariantFactory(exceptionProducerMock)

const exceptionData = { severity: 'ERROR', message: 'My error message' }
await expect(invariantFn(false, exceptionData)).rejects.toThrow(
new Error('ERROR: My error message'),
)
expect(exceptionProducerMock).toHaveBeenCalledWith(exceptionData)
const exceptionData = { severity: 'ERROR', message: 'My error message' }
expect(() => invariantFn(false, exceptionData)).toThrow(new Error('ERROR: My error message'))
expect(exceptionProducerMock).toHaveBeenCalledWith(exceptionData)
})
})
})
43 changes: 8 additions & 35 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
/**
* Factory for creating invariant functions that throw custom exceptions.
*
* Takes an `exceptionProducer` function that handles the 2nd argument of the
* resulting `invariant` function and returns the exception to throw.
* Takes an `exceptionProducer` function returns the exception to throw.
*
* @important Type-annotating the resulting `invariant` function is mandatory
* because it is an assertion function.
Expand All @@ -20,48 +19,22 @@
* // CustomError('Expected condition to be truthy')
* ```
*/
export function invariantFactory<K>(
exceptionProducer: () => K extends Promise<any> ? never : K,
): (condition: any) => asserts condition
export function invariantFactory<T, K>(
exceptionProducer: (exceptionData: T) => K extends Promise<any> ? never : K,
) {
): (condition: any, exceptionData: T) => asserts condition
export function invariantFactory<T, K>(
exceptionProducer: (exceptionData: T) => K extends Promise<any> ? never : K,
): (condition: any, exceptionData: T) => asserts condition {
return (condition: any, exceptionData: T): asserts condition => {
if (condition) return

throw exceptionProducer(exceptionData)
}
}

/**
* Factory for creating async invariant functions that throw custom exceptions.
*
* Takes an async `exceptionProducer` function that handles the 2nd argument of
* the resulting `invariant` function and returns the exception to throw.
*
* @important Type-annotating the resulting `invariant` function is mandatory
* because it is an assertion function.
*
* @example
*
* ```ts
* const asyncExceptionProducer = async (exceptionData: string) => new CustomError(exceptionData)
* const invariant: (
* condition: any,
* exceptionData: Parameters<typeof asyncExceptionProducer>[0]
* ) => asserts condition = asyncInvariantFactory(asyncExceptionProducer)
*
* await invariant(falsyValue, 'Expected condition to be truthy')
* // CustomError('Expected condition to be truthy')
* ```
*/
export const asyncInvariantFactory = <T, K>(
exceptionProducer: (exceptionData: T) => Promise<K>,
) => {
return async (condition: any, exceptionData: T) => {
if (condition) return

throw await exceptionProducer(exceptionData)
}
}

const defaultExceptionProducer = (exceptionData?: string | (() => string)) => {
if (exceptionData == null) return new Error('Invariant error')

Expand Down

0 comments on commit 38f6b13

Please sign in to comment.