object-shape-tester
Version:
Test object properties and value types.
108 lines (78 loc) • 3.7 kB
Markdown
# object-shape-tester
Create types, run-time type requirements, and run-time default values with a single definition.
Can be used in tests and at run-time (it does not depend on any testing libraries).
Full API docs: https://electrovir.github.io/object-shape-tester
# Installation
```sh
npm i object-shape-tester
```
# Usage
This package allows you to define an object's shape in a manner which can be used for testing objects at run time to make sure they conform to your defined shapes. Because it also generates TS types from your defined shape _and_ a default run-time value from your defined shape, **you will have one single source of truth** for all types, defaults, and shape tests.
## Simple example
See this simple example for defining a simple user type:
<!-- example-link: src/readme-examples/simple-user-shape.example.ts -->
```TypeScript
import {defineShape, isValidShape} from 'object-shape-tester';
const userShapeDefinition = defineShape({
name: 'empty name',
id: 0,
});
export type User = typeof userShapeDefinition.runtimeType;
export const emptyUser = userShapeDefinition.defaultValue;
export function isUser(input: unknown): input is User {
// you don't NEED to wrap isValidShape in a type guard as it is already a type guard itself
return isValidShape(input, userShapeDefinition);
}
const myUser: User = {
name: 'my name',
id: 1000,
};
```
From a single object (passed into `defineShape`), we get a type definition, a default value, and an object which can be used for run-time object shape testing.
## Complex example
For more complex types, this package provides type specifiers that you can use. The available type specifiers are currently the following:
- `or(valueA, valueB, ...)`: for allowing multiple types. This gets converted in TypeScript to a union.
- `and(valueA, valueB, ...)`: for creating a combined type. This gets converted in TypeScript to an intersection.
- `exact(valueA, valueB, ...)`: requires the provided value to be exactly matched. This gets converted in TypeScript to a literal const type. (Example: instead of a value of `'empty name'` getting converted into a string type, `exact('empty name')` will _only_ allow the value `'empty name'`.)
- `enumShape(enumA)`: use this for enum types.
- `unknownShape()`: use this to allow any type.
Here's a more complex user example that uses all of the above specifiers:
<!-- example-link: src/readme-examples/complex-user-shape.example.ts -->
```TypeScript
import {and, defineShape, enumShape, exact, isValidShape, or, unknownShape} from 'object-shape-tester';
enum AuthLevel {
Basic = 'basic',
Admin = 'admin',
}
const complexUserShapeDefinition = defineShape({
firstName: 'first',
middleInitial: or('M', undefined),
lastName: 'last',
id: 0,
tags: and({userTags: ['']}, {creatorTags: ['']}),
primaryColor: exact('red', 'green', 'blue'),
authLevel: enumShape(AuthLevel),
extraDetails: unknownShape(),
});
export type ComplexUser = typeof complexUserShapeDefinition.runtimeType;
export const emptyComplexUser = complexUserShapeDefinition.defaultValue;
export function isComplexUser(input: unknown): input is ComplexUser {
// you don't NEED to wrap isValidShape in a type guard as it is already a type guard itself
return isValidShape(input, complexUserShapeDefinition);
}
const myComplexUser: ComplexUser = {
firstName: 'my first',
middleInitial: undefined,
lastName: 'last name',
id: 1000,
tags: {
userTags: [],
creatorTags: [],
},
primaryColor: 'blue',
authLevel: AuthLevel.Admin,
extraDetails: {
whatever: 'you want',
},
};
```