Skip to content

Commit

Permalink
feat: initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
JanMalch committed Oct 7, 2020
0 parents commit 2b4a3c6
Show file tree
Hide file tree
Showing 16 changed files with 10,672 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
dist
.eslintrc.js
23 changes: 23 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
jest: true,
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
'prettier/@typescript-eslint',
'plugin:prettier/recommended',
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 12,
sourceType: 'module',
},
plugins: ['@typescript-eslint'],
rules: {},
};
25 changes: 25 additions & 0 deletions .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Build
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:
build:

runs-on: ubuntu-latest

strategy:
matrix:
node-version: [10.x, 12.x, 14.x]

steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm run build
- run: npm test
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/node_modules/
/.idea/
/index.js
/index.d.ts
/index.test.d.ts
/index.test.js
/index.test-d.d.ts
/index.test-d.js
/coverage
7 changes: 7 additions & 0 deletions .lintstagedrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// The configuration object for [lint-staged](https://www.npmjs.com/package/lint-staged)
module.exports = {
// Lint source with [ESLint](https://www.npmjs.com/package/eslint)
'index.ts': 'eslint --fix',
// Format all files with [prettier](https://www.npmjs.com/package/prettier).
'*.{ts,js,json,md}': 'prettier --write',
};
16 changes: 16 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/.idea/
/.eslintrc.js
/.gitignore
/.lintstagedrc.js
/.versionrc.json
/jest.config.js
/tsconfig.json
/tsconfig.test.json
/index.ts
/index.test.ts
/index.test.d.ts
/index.test.js
/index.test-d.ts
/index.test-d.d.ts
/index.test-d.js
/coverage
42 changes: 42 additions & 0 deletions .versionrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"header": "",
"types": [
{
"type": "feat",
"section": "Features"
},
{
"type": "fix",
"section": "Bug Fixes"
},
{
"type": "chore",
"hidden": true
},
{
"type": "docs",
"hidden": true
},
{
"type": "style",
"hidden": true
},
{
"type": "refactor",
"section": "Code Refactoring"
},
{
"type": "perf",
"section": "Performance Improvements"
},
{
"type": "test",
"hidden": true
},
{
"type": "build",
"hidden": true
}
]
}
226 changes: 226 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
# ts-code-contracts <a href="https://www.github.com/JanMalch/ts-code-contracts"><img src="https://user-images.githubusercontent.com/25508038/63974103-75242700-caac-11e9-8ca4-71cc5b905e90.png" width="90" height="90" align="right"></a>

[![npm](https://badgen.net/bpm/v/ts-code-contracts)](https://www.npmjs.com/package/ts-code-contracts)
[![Build](https://github.com/JanMalch/ts-code-contracts/workflows/Build/badge.svg)](https://github.com/JanMalch/ts-code-contracts/workflows/Build)
[![coverage](https://img.shields.io/badge/coverage-100%25-success)](https://github.com/JanMalch/ts-code-contracts/blob/master/jest.config.js#L14-L17)
[![minified + gzip](https://badgen.net/bundlephobia/minzip/ts-code-contracts)](https://bundlephobia.com/result?p=ts-code-contracts)

_Code contracts to improve your TypeScript code._

## Installation & Usage

```
npm i ts-code-contracts
```

> Requires TypeScript^3.7
You can now import the following functions `from 'ts-code-contracts'`:

- Contracts
- [`requires` for preconditions](#requires)
- [`checks` for illegal states](#checks)
- [`ensures` for postconditions](#ensures)
- [`unreachable` for unreachable code branches](#unreachable)
- [`asserts` for any other assertion](#asserts)
- Utils
- [`error` to make code more concise](#error)
- [`isDefined` type guard](#isdefined)
- [`useIf` for assignments](#useif)

Make sure to read the `@example`s in the documentation below
or refer to the [test cases](https://github.com/JanMalch/ts-code-contracts/blob/master/index.test.ts#L137-L165)
and [typing assistance](https://github.com/JanMalch/ts-code-contracts/blob/master/index.test-d.ts#L54-L65)!

## Contracts

The following functions are the core of this library.
They are just handy shorthands to throw an error, if the given condition is not met.
And yet they greatly help the compiler and the readability of your code. Make sure to fail fast!

### `requires`

Use it for preconditions, like validating arguments.

```ts
/**
* Requires the given condition to be met, if not a `PreconditionError` will be thrown.
* Use it to verify argument values.
* @param condition the condition that must be `true`
* @param message an optional message for the error
* @see PreconditionError
* @example
* function myFun(name: string) {
* requires(name.length > 10, 'Name must be longer than 10 chars');
* }
*/
export function requires(
condition: boolean,
message: string = 'Unmet precondition'
): asserts condition;
```

### `checks`

Use it to check for an illegal state.

```ts
/**
* Checks that the given condition is met, if not a `IllegalStateError` will be thrown.
* Use it to verify that the object is in a correct state.
* @param condition the condition that must be `true`
* @param message an optional message for the error
* @see IllegalStateError
* @example
* class Socket {
* send(data: Data) {
* check(this.isOpen, 'Socket must be open');
* }
* }
*/
export function checks(
condition: boolean,
message: string = 'Callee invariant violation'
): asserts condition;
```

### `ensures`

Use it to verify that your code behaved correctly.

```ts
/**
* Ensures that the given condition is met, if not a `PostconditionError` will be thrown.
* Use it to verify that your function behaved correctly.
* @param condition the condition that must be `true`
* @param message an optional message for the error
* @see PostconditionError
* @example
* async function myFun() {
* await post({ id: 0, name: 'John' });
* const entity = await get(0);
* ensures(isDefined(entity), 'Failed to persist entity on server');
* }
*/
export function ensures(
condition: boolean,
message: string = 'Unmet postcondition'
): asserts condition;
```

### `unreachable`

Use it to assert that a code branch is unreachable.

```ts
/**
* Asserts that a code branch is unreachable. If it is, the compiler will throw a type error.
* If this function is reached at runtime, an error will be thrown.
* @param _value a value
* @example
* function myFun(foo: MyEnum): string {
* switch(foo) {
* case MyEnum.A: return 'a';
* case MyEnum.B: return 'b';
* default: unreachable(foo);
* }
* }
*/
export function unreachable(_value: never): never;
```

### `asserts`

Use it for any other assertions, that don't quite fit the other contexts.

```ts
/**
* Asserts that the given condition is met, if not a `AssertionError` will be thrown.
* @param condition the condition that must be `true`
* @param message an optional message for the error
* @see AssertionError
*/
export function asserts(
condition: boolean,
message: string = 'Failed Assertion'
): asserts condition;
```

## Utils

The following functions do help you write even more concise code.

### `error`

This function will always throw the given error and helps keeping code easy to read.

```ts
/**
* Always throws an error of the given type with the given message.
* It can come in handy when assigning values with a ternary operator or the null operators.
* @param errorType an error class, defaults to `AssertionError`
* @param message the error message
* @see IllegalStateError
* @example
* function myFun(foo: string | null) {
* const bar: string = foo ?? error(PreconditionError, 'Argument may not be null');
* const result = bar.length > 0 ? 'OK' : error();
* }
*/
export function error<T>(
errorType: new (...args: any[]) => Error = IllegalStateError,
message?: string
): T;
```

### `isDefined`

A common type guard, to check that a value is defined.
Make sure to use [`strictNullChecks`](https://basarat.gitbook.io/typescript/intro/strictnullchecks).

```ts
/**
* Type guard to check if the given value is not nullable.
* @param value the given value
* @example
* const x: string | null = 'Hello';
* check(isDefined(x));
* x.toLowerCase(); // no error!
*/
export function isDefined<T>(value: T): value is NonNullable<T>;
```

### `useIf`

A function that helps with validating and typing when assigning variables.

```ts
/**
* Returns a function that will return the passed in value, if it passes the given predicate.
* If not, the given contract will throw an error with the given message.
* @param predicate the predicate that the value must pass
* @param contract the contract for context
* @param message the message for the contract
* @example
* function myFun(foo: string | null) {
* const bar = useIf(isDefined)(foo);
* }
*/
export function useIf<T, O extends T = T>(
predicate: (value: T | O) => value is O,
contract: (
condition: boolean,
message?: string
) => asserts condition = requires,
contractMessage?: string
): (value: T) => O;
```

## Errors

The following error classes are included:

- `PreconditionError` &rarr; An error thrown, if a precondition for a function or method is not met.
- `IllegalStateError` &rarr; An error thrown, if an object is an illegal state.
- `PostconditionError` &rarr; An error thrown, if a function or method could not fulfil a postcondition.
- `AssertionError` &rarr; An error thrown, if an assertion has failed.
Loading

0 comments on commit 2b4a3c6

Please sign in to comment.