UNPKG

@toreda/strong-types

Version:

Better TypeScript code in fewer lines.

770 lines (561 loc) 19.3 kB
# `@toreda/strong-types` ![CI](https://github.com/toreda/strong-types/workflows/CI/badge.svg?branch=master) [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=toreda_strong-types&metric=coverage)](https://sonarcloud.io/dashboard?id=toreda_strong-types) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=toreda_strong-types&metric=alert_status)](https://sonarcloud.io/dashboard?id=toreda_strong-types) Guaranteed types with validation in 1 line of code. Improve code quality & reliability while writing fewer lines of code. What does it do? ```typescript import type {Int} from '@toreda/strong-types'; import {intMake} from '@toreda/strong-types'; // int with initial value 10. const int = intMake(10); // Prints 10. It always return an int. console.log(int()); // Set the value int(11); // Prints 11. console.log(int()); // Won't set the value - it's not an int. int(null); int(undefined); int(3.33); int({}); // Prints 11 console.log(int()); ``` # Contents * [**Code Documentation**](/docs/index.html) * [**StrongType API**](#strongtype-api) * [**Data Types**](#data-types) - **Primary Types** - [**`StrongMap`**](#StrongMap) - [**`StrongArray`**](#StrongArray) - [**`Bool`**](#Bool) - Strict `true` or `false` values. - [**`Dbl`**](#Dbl) - Decimal values with arbitrary precision. - [**`Float`**](#Float) - Standard JavaScript `number` values with a `StrongType` wrapper. - [**`Int`**](#Int) - Positive & negative integers. - [**`Text`**](#Text) - String values - [**`UInt`**](#UInt) - Unsigned integers. * [**Expressive Types**](#expressive-types) * `Email` - `string` * Email address with format `mailbox@domain`. * **`Date`** - `string` * ISO standard date strings. * `Id` - `string` * `string` with configurable value restrictions. * `HexColorCode` - `string` * Any full or partial hex value representing a color code in `RGB` range (`#000000` to `#FFFFFF`). * `Port` - `unsigned integer` * Port number in the range `1` to `65535`. * `OS` - `OS Value` * Common OS platform names `android`, `darwin`, `linux`, and `windows`. * `Size` - `{width: float; height: float}` * Object with properties `float x` and `float y`. * `SystemPort` - `unsigned integer` * Port number in the eserved system port range `1` to `1024`. * `Time` - `string` * ISO standard `12h` or `24h` time value. * `Url` - `string` * Any valid absolute `Url` according to [`RFC-3986`](https://tools.ietf.org/html/rfc3986). * `Vec1` - `{x: float}` * Vector object with `float` validated property `float x`. * `Vec2` - `{x: float; y: float}` * Vector object with `float` validated properties `float x` and `float y`. * `Vec3` - `{x: float; y: float; z: float}` * Vector object with `float` validated properties `float x`, `float y`, and `float z`. * `Vec4` - `{x: float; y: float; z: float; w: float}` * Vector object with `float` validated properties `float x`, `float y`, `float z`, and `float w`. * [**Examples**](#examples) * [**Conversion Functions**](#conversion-functions) * [**Custom Types**](#custom-types) - [Validators](#validators) * [**Package**](#package) - [Build](#build) - [Testing](#testing) - [License](#license) &nbsp; # `StrongType` API All types in this package implement the `StrongType` interface which provide ## General API ## All Types ### `get(fallback: ValueT): ValueT` Get current value. The provided `fallback` is returned when `StrongType` has no current value. Always returns a type `ValueT` value. ### `getNull(): ValueT | null` Get and return current value if set, otherwise returns `null`. Useful when you need to know if there is a current value. ### `check(value?: ValueT): boolean` Check whether `value` is passes validation as type `ValueT`. Called by `set(...)` internally, but allows value validation before setting. ### `reset(): void` Reset current value to `null`. Useful for testing, Lamba environments, and object pools. &nbsp; ## Numeric Types ### **Add `add(input: ValueT): ValueT | null`** Add `input` to `value` (`value + input`). #### **Returns** * `null` when `result` value is not a valid `ValueT`. * ex: `uint.add(-1)` returns `null` when `value` is `0`. `0 + -1` is `-1` and not a valid `UInt`. * ex: `int.add(1)` returns `null &nbsp; ### **Subtract `sub(input: ValueT): ValueT | null`*** Subtract `input` from `value` (`value - input`). #### **Returns** * `null` when `result` is not a valid `ValueT`. * ex: `uint.sub(1)` returns `null` when `value` is `0`, because `-1` is not a valid `UInt`. * `result` of subtraction when operation succeeds and result is a valid `ValueT`. &nbsp; ### **Multiply `mul(input: ValueT): ValueT | null`** Multiply `value` by `input`. #### **Returns** * `null` if result of multiplication is not a valid `ValueT`. * `0` when either number is `0`. &nbsp; ### **Divide `div(input: ValueT): ValueT | null`** Divide `value` by `input`. #### **Returns** * `null` when `result` is not a valid `ValueT` * ex: `uint.mul(-1)` will always return `null` because `UInt` cannot be < `0`. * `result` of divsion when operation succeeds. * `0` when either `value` or `input` is `0`. * `null` when `StrongType` has no current value. &nbsp; ### `pow(n: ValueT): ValueT | null` Raise `value` to the `n`th power. #### **Returns** * `null` when current value is `null`. * `null` when `result` value is too big, or too small for `ValueT`. * `result` of `valueⁿ` if `result` is a valid `ValueT`. &nbsp; ### `increment(): ValueT | null` Increment value by 1 if `StrongType` has a value. Returns `null` when increment is not successful. ### **Returns** * `null` when `result` of `value + 1` is not a valid `ValueT`. * `null` when `StrongType` has no value. * `result` when `increment` succeeds. &nbsp; ### `decrement(): ValueT | null` Decrease value by 1 if `StrongType` has a value. Returns `null` when decrement is not successful. #### **Returns** * `null` when result of `value - 1` is not a valid `ValueT`. * `null` when `StrongType` has no value. * `result` when `decrement` succeeds. &nbsp; # Data Types # `StrongArray<T>` Strict `Array` type. ### **Accepts** * Arrays matching type `T` provided to `StrongArray<T>` during init. ### **Rejects** * Non-array values. ## Make a `StrongArray<T>` ```typescript import type {StrongArray} from '@toreda/strong-types'; import {arrayMake} from '@toreda/strong-types'; // Create a new StrongArray of type string[]. // Fallback value is an empty array. const arr = arrayMake<string>([]); ``` &nbsp; # `Bool` Strict **`boolean`** values. &nbsp; ### **Accepts** * `true` * `false` ### **Rejects** * Everything else (no type coercion). &nbsp; ## Making `Bool` ```typescript import type {Bool} from '@toreda/strong-types'; import {boolMake} from '@toreda/strong-types'; // Create bool with fallback `false`. // No initial value set. const bool = boolMake(false); ``` &nbsp; ## Get `Bool` Get the current value invoking the `Bool` object directly, calling `bool.get(fallback)`, or `bool.getNull()`. &nbsp; ## Invoke `Bool` All `StrongType` objects are also functions. Call the function without args to return the current value. &nbsp; **Create bool with fallback `false` & no initial value:** ```typescript // Create bool with: // fallbackDefault - false // value - null (not provided) const bool = boolMake(false); // Prints 'false' console.log(bool()); ``` &nbsp; **Create bool with value `true` & fallback `false`:** ```typescript // Create bool with: // fallbackDefault - false // value - true const bool = boolMake(false, true); // Prints 'true' // Second arg of boolMake is value. console.log(bool()); ``` &nbsp; **Create bool with value `false` & fallback `true`:** ```typescript // Create bool with: // fallbackDefault - true // value - false const bool = boolMake(true, false); // Prints 'false'. console.log(bool()); ``` &nbsp; ## Reset `Bool` Reset any `Bool` object back to it's `fallbackDefault` value provided in `boolMake(...)`. ```typescript const bool = boolMake(true); // Set value to false bool(false); // Prints 'false' console.log(bool()); // Reset object and remove current value. // Object returns fallbackDefault 'true' until value is set. bool.reset(); // Prints 'true', but bool has no current value. console.log(bool()); ``` &nbsp; # `Int` Positive & negative Integers stored as a JavaScript **`number`**. No type coercion used. &nbsp; ## Values ### **Accepts** - Integers in range: **`Number.MIN_SAFE_INT`** to **`Number.MAX_SAFE_INT`** (inclusive). ### **Rejects** - Non-integers values. - Values with decimal places (`4.44`, `0.11`, etc). &nbsp; ## Make `Int` ### With **`number`** ```typescript import type {Int} from '@toreda/strong-types'; import intMake from '@toreda/strong-types'; const value = intMake(0, 444111); ``` &nbsp; ## Reset `Int` ```typescript const int = intMake(0); // Set value to 101. int(101); // Prints 101 console.log(int()); // Reset object and remove current value. // Returns fallbackDefault (0) until value is set. int.reset(); // Prints 0. Int has no value. console.log(int()); ``` ```typescript const int = intMake(10); // Set value to 100. int(100); // Prints 100. console.log(log()); int.reset(); // Prints 10. Value null & fallback returned instead. console.log(int()); // Prints null - value is null. getNull() does not use fallback. console.log(int.getNull()); ``` # `UInt` Unsigned Integers stored as a JavaScript **`number`**. &nbsp; ## Values ### Accepts * Rejects `NaN` * Rejects negative integers (e.g. `-22`). - Positive Integers from `0` to `Number.MAX_SAFE_INT` (inclusive). ### Rejects * `NaN` * Non-finite values `Number.POSITIVE_INFINITY` and `Number.NEGATIVE_INFINITY`. - Negative Integers. - ex: `-10`, `-1` - Positive & negative decimals. - ex: `5.1`, `100.9`, `-1.1`, `-99.99`, `0.11`, `-0.099` &nbsp; ## Make `UInt` ### From **`number`**: ```typescript import type {UInt} from '@toreda/strong-types'; import uIntMake from '@toreda/strong-types'; const value = uIntMake(0, 444111); ``` &nbsp; # `Dbl` Decimal values supporting arbitrary precision. &nbsp; ## Values ### Accepts - `strings` containing arbitrarily large positive or negative decimal & integer values. Numeric values for **`Dbl`** in `string` form may exceed `Number.MAX_VALUE` and `Number.MAX_SAFE_INT`. - ex: `'1'`, `'-100'`, `'1.1111111'`, `'0.00009'`, `'1.000009'`, `'-99.0009'` - `numbers` from `Number.MIN_VALUE` to `Number.MAX_VALUE` (inclusive). - Note: Represent numbers exceeding `Number.MAX_VALUE` or below `Number.MIN_VALUE` as `string` or `Big`. ### Rejects - Non-finite values `Number.POSITIVE_INFINITY`, `Number.NEGATIVE_INFINITY`. &nbsp; ## Make `Dbl` ### From **`number`**: ```typescript import type {Dbl} from '@toreda/strong-types'; import dblMake from '@toreda/strong-types'; const value = dblMake(0, 1111187); ``` ### From **`string`**: ```typescript import type {Dbl} from '@toreda/strong-types'; import dblMake from '@toreda/strong-types'; const value = dblMake(0, '1081987419714971411.441'); ``` ### From `Big`: ```typescript import Big from 'big.js'; import type {Dbl} from '@toreda/strong-types'; import dblMake from '@toreda/strong-types'; const value = dblMake(0, Big(8714817418741)); ``` &nbsp; # Examples Each built-in type exports a type and make function. The below examples use Int but work the same using: `StrongArray`, `Bool`, `Dbl`, `Float`, `Int`, `Text`, and `UInt`. ## Instantiate with initial value ```typescript import {Int, intMake} from '@toreda/strong-types'; const initial = 11; const fallback = 55; const int = intMake(initial, fallback); // Returns 11 - initial value was 11. const value = int(); ``` ## Instantiate without initial value ```typescript import {Int, intMake} from '@toreda/strong-types'; const fallback = 919; const int = intMake(null, fallback); // value is 919 - initial value was null, so fallback was // returned instead to maintain the function's return type guarantee. const value = int(); ``` ## Get with Fallback * `get(fallback: T): T` Call `container.get(fallback)` when a per-call fallback is preferred instead of the container's default fallback. ```typescript import {Int, intMake} from '@toreda/strong-types'; const int = intMake(null, 555); const fallback = 331; // Prints 331 (fallback) instead of 555 (container default fallback). console.log(value.get(fallback)); ``` ## GetNull * `getNull(): T | null` Call `container.getNull()` to the current value, even if null. Value will never be undefined and will always return either null, or a value of container's generic type T. NOTE: `getNull` **DOES NOT** take a fallback argument and will not return the container's default fallback. It always returns value (`null` or `StrongType<T>`). ```typescript import {Int, intMake} from '@toreda/strong-types'; const fallback = 919; const int = intMake(null, fallback); // Prints null. Default fallback is not used for `getNull` calls. console.log(int.getNull()); ``` ## Set Value ```typescript import type {Int} from '@toreda/strong-types'; import {intMake} from '@toreda/strong-types'; const initial = 331; const fallback = 400; const int = intMake(initial, fallback); // value is 331 - the int's initial value. const value = int(); // Set int to a new value. int(555); // value is 555 - the value we updated int to. const value = int(); ``` ## Set `null` ```typescript import {Int, intMake} from '@toreda/strong-types'; const initial = 414; const fallback = 500; const int = intMake(initial, fallback); // value is 414 - the int's initial value. const value = int(); // Set value to null. int(null); // value is 500 - Fallback returned because value was null. const value = int(); ``` ## Reset Value Call `myContainer.reset()` to reset value without creating a new StrongType container. Default Fallback will not be reset. Useful for unit testing and serverless environments where the previous container value or state is unknown. ```typescript import {Int, intMake} from '@toreda/strong-types'; const initial = 515; const fallback = 600; const int = intMake(initial, fallback); // value is 515 (the initial value). const value = int(); // Reset container value int.reset(); // Prints 600 (fallback). value was reset to null, // fallback was returned to maintain return type guarantee. console.log(int()); ``` &nbsp; ### Validation `StrongType` containers validate value inputs before setting. Bad values are ignored and will not cause a throw. Each built-in container type provides specific guarantees for which values are allowed. ```typescript import {Int, intMake} from '@toreda/strong-types'; const int = intMake(50, 100); // success is false. // container.value is still it's initial value 50 because 1.5 is not an int. // Int does not round or truncate non-integers. They are simply ignored. const success = int(1.5); ``` &nbsp; # `StrongMap` Creating and using a StrongMap class. ```typescript import {StrongMap, Int, Text, intMake, textMake} from '@toreda/strong-types'; export class SomeConfig extends StrongMap { public readonly counter: Int; public readonly name: Text; constructor(json: any) { super(); this.counter = intMake(0, 0); this.name = textMake('', 'TreeBeard'); this.parse(json); } } // Use it const myConfig = new SomeConfig(); // Prints '0' console.log(myConfig.counter()); // Prints 'Treebeard' console.log(myConfig.name()); ``` Creating a `StrongMap` and loading values from JSON ```typescript import {StrongMap, StringInt, Text, intMake, textMake} from '@toreda/strong-types'; export class SomeConfig extends StrongMap { public readonly counter: Int; public readonly name: Text; constructor(json?: any) { super(); this.counter = intMake(0, 0); this.name = textMake(null, 'TreeBeard'); this.parse(json); } } const myJSON = { 'counter': 99, 'name': 'Sauron' }; // Load the recursively parse a JSON object. const myConfig = new SomeConfig(myJSON); // Prints 99 - myJSON.counter was loaded into SomeConfig.counter at instantiation. console.log(myConfig.counter()); // Prints 'Sauron' - myJSON.name was loaded into SomeConfig.name at instantiation. console.log(myConfig.name()); ``` Converting a `StrongMap` to a json object ```typescript import {StrongMap, StringInt, Text, intMake, textMake} from '@toreda/strong-types'; export class SomeConfig extends StrongMap { public readonly counter: Int; public readonly name: Text; constructor(json?: any) { super(); this.counter = intMake(0, 0); this.name = textMake(null, 'TreeBeard'); this.parse(json); } } const myJSON = { 'counter': 99, 'name': 'Sauron' }; // Create the StrongMap with myJSON data const myConfig = new SomeConfig(myJSON); // Change a value in the StrongMap myConfig.name('Gandalf'); // {counter: 99, name: 'Gandalf'} const configAsJSON = myConfig.jsonify(); ``` &nbsp; &nbsp; # Validators &nbsp; # Custom Types Each built-in type like `Int` and `UInt` are helper functions wrapping `Strong<T>`. They also apply validators which guarantee the `Strong<T>` value behaves as expected. While built-ins are provided for convenience, you can create custom types with your own validators. ## Instantiate `Strong<T>` ```typescript import {Strong, strongMake} from '@toreda/strong-types'; export type MyOwnType = { id: string | null; name: string | null; }; const initial: MyOwnType = { id: 'hello', name: 'my name is' }; const fallback: MyOwnType = { id: null, name: null }; const myObj = strongMake<MyOwnType>(initial, fallback); ``` &nbsp; # Install Install `@toreda/strong-types` directly from NPM or [clone the Github repo](https://github.com/toreda/strong-types). ### Install using Yarn (preferred) 1. Open a shell (or console). 2. Navigate to the the StrongTypes root project folder. 3. Enter the following commands in order. Wait for each to complete before typing the next. ```bash yarn ``` ### Install using NPM 1. Open a shell (or console). 2. Navigate to the the StrongTypes root project folder. 3. Enter the following commands in order. Wait for each to complete before typing the next. ```bash npm install ``` # Run Unit Tests Install or clone StrongTypes [(see above)](#install). StrongTypes tests are written with [Jest](https://jestjs.io/) which is also a project dev dependency. Installing jest is not required after project dependencies are installed ([see above](#install)). ```bash yarn test ``` # Build from source The next steps are the same whether you installed the package using NPM or cloned the repo from Github. ### Build with Yarn Enter the following commands in order from the StrongTypes root project folder. ```bash yarn build ``` ### Build with NPM Enter the following commands in order from the StrongTypes root project folder. ```bash npm run-script build ``` &nbsp; # Legal ## License [MIT](LICENSE) &copy; Toreda, Inc. &nbsp; ## Copyright Copyright &copy; 2019 - 2021 Toreda, Inc. All Rights Reserved. https://www.toreda.com