Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP #142

Draft
wants to merge 36 commits into
base: main
Choose a base branch
from
Draft

WIP #142

Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 62 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,24 @@
[![Release](https://github.com/hacomono-lib/type-assurer/actions/workflows/release.yml/badge.svg)](https://github.com/hacomono-lib/type-assurer/actions/workflows/release.yml)
[![Test](https://github.com/hacomono-lib/type-assurer/actions/workflows/test.yml/badge.svg)](https://github.com/hacomono-lib/type-assurer/actions/workflows/test.yml)

`type-assurer` is a TypeScript-first type checking library, providing compatibility with lodash's type guard functions while ensuring type safety. Designed with ESModules in mind, it allows for tree-shaking to minimize bundle sizes.
`type-assurer` is a TypeScript-first type checking library. Designed with ESModules in mind, it allows for tree-shaking to minimize bundle sizes.

## Features

- Compatible with lodash type guard functions
- TypeScript-first implementation with accurate type inference
- ESModule ready for tree-shaking and bundle size optimization
- No external dependencies
- A collection of 7 type guard functions:
a. isString - Similar to lodash's type guard functions
b. assertString - Provides TypeScript's type assertion feature
c. ensureString - Evaluates the argument's type and returns the value if the type guard passes, otherwise throws an exception
d. fallbackString - Evaluates the first argument's type and returns the value if the type guard passes, otherwise returns the second argument's value
- 7 type guard functions:
a. `isFoo` (e.g. isString) - type guard functions
b. `assertFoo` (e.g. assertString) - Provides TypeScript's type assertion feature
c. `ensureFoo` (e.g. ensureString) - Evaluates the argument's type and returns the value if the type guard passes, otherwise throws an exception
d. `fallbackFoo` (e.g. fallbackString) - Evaluates the first argument's type and returns the value if the type guard passes, otherwise returns the second argument's value
- The reversed versions
- Generator provided for custom type guards for non-primitive types
- 2 type modification functions:
a. `coerceFoo` (e.g. coerceNumber) - Evaluates the argument's type and returns the value if the type guard passes, otherwise throws an exception
b. `fixFoo` (e.g. fixNumber) - Evaluates the first argument's type and returns the value if the type guard passes, otherwise returns the second argument's value
- If there is a type check for a parsable or modifiable value, there is a corresponding function. (e.g. NumberParsable, Jsonifiable, etc.)

## Installation

Expand All @@ -31,15 +34,13 @@ The library provides 8 utility functions for each type guard, such as `isString`

And, note that `fallbackNotNil` can be replaced with the `??` operator. Functions that can be simplified using standard JavaScript expressions, like this example, are not targeted for implementation.

### is, isNot
### is

Functions such as `is` simply provide type guards that can be used in conditional branches.

```typescript
import { isString } from 'type-assurer'

declare const value: unknown

if (isString(value)) {
console.log(`This is a string: ${value}`)
} else {
Expand All @@ -52,13 +53,20 @@ Functions such as `isNot` are useful in cases that require a type guard function
```typescript
import { isNotNil } from 'type-assurer'

declare const values: string | null

const result = values.filter(isNotNil)
// ^? string[]
```

### assert, assertNot
Also, type guard functions such as `isXxx` are implemented in the form of `not(isXxx)`.
If you have implemented your own type guard, you can use the `not` function directly.

```typescript
import { not } from 'type-assurer'

const result = values.filter(not(isFoo))
```

### assert

Functions with names like `assert` `assertNot` are type assertion functions.
If the type check does not pass, it throws a TypeError.
Expand All @@ -74,7 +82,7 @@ assertString(value, 'Value must be a string')
// No error if value is a string, otherwise throws an error with the message "Value must be a string"
```

### ensure, ensureNot
### ensure

Functions with names like `ensure` `ensureNot` are type assertion functions, but return the same value if the type check passes.
It is convenient to write type assertions on a single line.
Expand All @@ -91,7 +99,7 @@ const value = ensureString(await fetchData(), 'Value must be a string')
// No error if fetchData returns a string, otherwise throws an error with the message "Value must be a string"
```

### fallback, fallbackNot
### fallback

Functions like `fallback` `fallbackNot` are type modification functions.

Expand All @@ -107,6 +115,45 @@ const value = fallbackString(await fetchData(), 'default')
// Returns value if it's a string, otherwise returns the fallbackValue
```

### coerce

Functions like `coerce` are type modification functions.

If there is a type check for a parsable or modifiable value, there is a corresponding function. (e.g. NumberParsable, Jsonifiable, etc.)

This function returns the modifiable value if the type is parsable, otherwise it throws an exception.

```typescript
import { coerceNumber } from 'type-assurer'

const value1 = coerceNumber('123')
// ^? 123

const value2 = coerceNumber('abc')
// thrown TypeAssertionError
```

### fix

Functions like `fix` are type modification functions.

If there is a type check for a parsable or modifiable value, there is a corresponding function. (e.g. NumberParsable, Jsonifiable, etc.)

This function returns the modifiable value if the type is parsable, otherwise it returns the fallback value specified in the second argument.

```typescript
import { fixNumber } from 'type-assurer'

const value1 = fixNumber('123')
// ^? 123

const value2 = fixNumber('abc')
// ^? NaN

const value3 = fixNumber('abc', 0)
// ^? 0
```

## Contributing

Contributions are welcome! Please submit a pull request or open an issue to discuss any proposed changes or feature requests.
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"access": "public"
},
"scripts": {
"dev": "vitest",
"dev": "vitest --ui --coverage",
"build": "tsup",
"test": "run-p test:*",
"test:spec": "vitest --run",
Expand All @@ -39,6 +39,8 @@
"@types/lodash": "^4.14.201",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"@typescript-eslint/parser": "^6.11.0",
"@vitest/coverage-v8": "^0.34.6",
"@vitest/ui": "^0.34.6",
"eslint": "^8.54.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-import": "^2.29.0",
Expand Down
4 changes: 2 additions & 2 deletions src/lib/error/error.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export class TypeAssertionError extends Error {
constructor(message: string, public readonly actualData: unknown) {
super(message)
constructor(message: string, public readonly actualData: unknown, opt?: ErrorOptions) {
super(message, opt)
this.name = 'TypeAssertionError'
}
}
2 changes: 1 addition & 1 deletion src/lib/error/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ function actualTypeOf(target: unknown): string {
if (target === null) {
return `null ${suffix}`
}
return `object (constructor: ${target.constructor.name}) ${suffix}`
return `object (constructor: ${target.constructor?.name ?? 'object'}) ${suffix}`
}

export function errorMessage(
Expand Down
37 changes: 28 additions & 9 deletions src/lib/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import type {
TypeFallback,
TypeGuard,
VoidAssert
} from './type'
} from './types'

import { TypeAssertionError } from './error'

Expand Down Expand Up @@ -153,7 +153,10 @@ export function createAssertion<T extends TypeGuard>(
* const assertString = createAssertion((arg: unknown): arg is string => typeof arg === 'string', 'target must be a string')
* ```
*/
export function createAssertion<T>(guard: (target: unknown) => target is T, message: TypeErrorMessage): TypeAssert<T>
export function createAssertion<T>(
guard: (target: unknown) => target is T,
message: TypeErrorMessage
): TypeAssert<T>

/**
* @description create a type assertion from a type predicate. This is a type-unsafe version of `createAssertion`.
Expand All @@ -165,7 +168,10 @@ export function createAssertion<T>(guard: (target: unknown) => target is T, mess
* const assertString = createAssertion((arg: unknown) => typeof arg === 'string', 'target must be a string')
* ```
*/
export function createAssertion(guard: (target: unknown) => boolean, message: TypeErrorMessage): VoidAssert
export function createAssertion(
guard: (target: unknown) => boolean,
message: TypeErrorMessage
): VoidAssert

export function createAssertion(
guard: (target: unknown) => boolean,
Expand All @@ -190,7 +196,10 @@ export function createAssertion(
* const ensureString = createEnsure(isString, 'target must be a string')
* ```
*/
export function createEnsure<T extends TypeGuard>(guard: TypeGuard<T>, message: TypeErrorMessage): TypeEnsureOf<T>
export function createEnsure<T extends TypeGuard>(
guard: TypeGuard<T>,
message: TypeErrorMessage
): TypeEnsureOf<T>

/**
* @description create a type ensure from a inverted type guard.
Expand All @@ -204,7 +213,10 @@ export function createEnsure<T extends TypeGuard>(guard: TypeGuard<T>, message:
* const ensureNotString = createEnsure(not(isString), 'target must not be a string')
* ```
*/
export function createEnsure<T extends TypeGuard>(guard: Not<T>, message: TypeErrorMessage): InvertedTypeEnsureOf<T>
export function createEnsure<T extends TypeGuard>(
guard: Not<T>,
message: TypeErrorMessage
): InvertedTypeEnsureOf<T>

/**
* @description create a type ensure from a type predicate.
Expand All @@ -218,8 +230,10 @@ export function createEnsure<T extends TypeGuard>(guard: Not<T>, message: TypeEr
* const ensureString = createEnsure((arg: unknown): arg is string => typeof arg === 'string', 'target must be a string')
* ```
*/
export function createEnsure<T>(guard: (target: unknown) => target is T, message: TypeErrorMessage): TypeEnsure<T>

export function createEnsure<T>(
guard: (target: unknown) => target is T,
message: TypeErrorMessage
): TypeEnsure<T>

/**
* @description create a type ensure from a type predicate. This is a type-unsafe version of `createEnsure`.
Expand All @@ -231,7 +245,10 @@ export function createEnsure<T>(guard: (target: unknown) => target is T, message
* const ensureString = createEnsure((arg: unknown) => typeof arg === 'string', 'target must be a string')
* ```
*/
export function createEnsure(guard: (target: unknown) => boolean, message: TypeErrorMessage): TypeEnsure
export function createEnsure(
guard: (target: unknown) => boolean,
message: TypeErrorMessage
): TypeEnsure

export function createEnsure(
guard: (target: unknown) => boolean,
Expand Down Expand Up @@ -299,5 +316,7 @@ export function createFallback(guard: (target: unknown) => boolean): TypeFallbac
export function createFallback(
guard: (target: unknown) => boolean
): TypeFallback | InvertedTypeFallback {
return ((target: unknown, fallback: unknown) => (guard(target) ? target : fallback)) as TypeFallback | InvertedTypeFallback
return ((target: unknown, fallback: unknown) => (guard(target) ? target : fallback)) as
| TypeFallback
| InvertedTypeFallback
}
2 changes: 2 additions & 0 deletions src/lib/test/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export * from './tester'
export * from './type'
export * from './value'
Loading
Loading