Skip to content

Latest commit

 

History

History
179 lines (163 loc) · 8.95 KB

File metadata and controls

179 lines (163 loc) · 8.95 KB

Object

The @perfective/common/object package provides functions to work with the standard JS Object class.

  • Types:

    • ObjectWithDefined<T, K extends keyof T> — an object of type T with a defined value of property K.

    • ObjectWithUndefined<T, K extends keyof T> — an object of type T with an undefined value of property K.

    • ObjectWithNotNull<T, K extends keyof T> — an object of type T with a non-null value of property K.

    • ObjectWithNull<T, K extends keyof T> — an object of type T with a null value of property K.

    • ObjectWithPresent<T, K extends keyof T> — an object of type T with a present value of property K.

    • ObjectWithAbsent<T, K extends keyof T> — an object of type T with an absent value of property K.

    • RecursivePartial<T> — a generic type that recursively marks all properties of a given type T as optional.

    • Entry<K = string, V = unknown> — a key-value pair (array).

  • Constructors:

    • recordFromArray(array: string[]): Record<string, number> — creates an object from a given array with the array values as keys and their indexes as values.

    • recordFromEntries(entries: Entry[]): Record<string, unknown> — creates an object from a given array of entries. An inverse for Object.entries().

    • pick<T, K extends keyof T>(record: NonNullable<T>, …​properties: readonly K[]): Pick<T, K> — creates a copy of a given record only with given properties.

    • recordWithPicked<T, K extends keyof T>(…​properties: readonly K[]): Unary<NonNullable<T>, Pick<T, K>> — creates a function to pick() given properties from its argument.

    • omit<T, K extends keyof T>(record: NonNullable<T>, …​properties: readonly K[]): Omit<T, K> — creates a copy of a given record without given properties.

    • recordWithOmitted<T, K extends keyof T>(…​property: readonly K[]): Unary<NonNullable<T>, Omit<T, K>> — creates a function to omit() given properties from its argument.

    • filter<T, K extends keyof T>(record: NonNullable<T>, condition: Predicate<T[K]>): Partial<T> — creates a copy of a given record only with properties that meet a given condition.

    • recordFiltered<T, K extends keyof T = keyof T>(condition: Predicate<T[K]>): Unary<NonNullable<T>, Partial<T>> — creates a function to filter() properties that satisfy a given condition from its argument.

    • assigned<T, V = Partial<T>>(value: T, …​overrides: (V | Partial<T>)[]): T & V — creates a shallow copy of the given value with the given overrides.

    • function recordWithAssigned<T, V = Partial<T>>(…​overrides: (V | Partial<T>)[]): (value: T) ⇒ T & V — creates a function to assign given overrides to its argument.

  • Type guards:

    • hasDefinedProperty<T, K extends keyof T>(property: K, …​properties: readonly K[]): (value: T) ⇒ value is ObjectWithDefined<T, K> — returns a type guard that returns true if its argument has a defined property and all given properties.

    • hasUndefinedProperty<T, K extends keyof T>(property: K, …​properties: readonly K[]): (value: T) ⇒ value is ObjectWithUndefined<T, K> — returns a type guard that returns true if its argument has an undefined property and all given properties.

    • hasNotNullProperty<T, K extends keyof T>(property: K, …​properties: readonly K[]): (value: T) ⇒ value is ObjectWithNotNull<T, K> — returns a type guard that returns true if its argument has a non-null property and all given properties.

    • hasNullProperty<T, K extends keyof T>(property: K, …​properties: readonly K[]): (value: T) ⇒ value is ObjectWithNull<T, K> — returns a type guard that returns true if its argument has a null property and all given properties.

    • hasPresentProperty<T, K extends keyof T>(property: K, …​properties: readonly K[]): (value: T) ⇒ value is ObjectWithPresent<T, K> — returns a type guard that returns true if its argument has a present property and all given properties.

    • hasAbsentProperty<T, K extends keyof T>(property: K, …​properties: readonly K[]): (value: T) ⇒ value is ObjectWithAbsent<T, K> — returns a type guard that returns true if its argument has an absent property and all given properties.

  • Predicates:

    • isObject<T>(value: T | null): boolean — returns true when the value is not null and is not a primitive.

    • isRecord<T>(value: T): boolean — returns true when the value is an object created from the Object class (not an Array, Date, etc.).

    • isEmpty<T>(value: T): boolean — returns true when the value is falsy, an empty array or a Record without properties.

    • hasMethod(value: unknown, method: string): boolean — returns true when a given value implements a given method.

    • hasNoMethod(value: unknown, method: string): boolean — returns true when a given value implements a given method.

  • Reducers:

    • toRecordFromEntries(record: Record<string, unknown>, value: Entry): Record<string, unknown> — a reducer to build a record from entries.

  • Property functions:

    • property<T, K extends keyof T>(property: K): Unary<T, T[K]> — creates a function that for a given value returns the value of a given property.

    • property<T, K extends keyof T>(property: K, condition: Predicate<T[K]>): Predicate<T> — creates a predicate that for a given value returns true if a given property satisfies a given condition.

    • by<T, K extends keyof T>(property: K, order: Compare<T[K]>): Compare<T> — returns a function to compare two objects by their property with a given order callback.

Input

One of the challenges in API development is declaring type of requests (inputs). On the client side these types need to be as strict as possible (e.g., all fields that are required must be marked as required). On the server side the same type need to be treated as completely unknown, unvalidated data. If the type is written for the client side, compiler will not be able to enforce any checks on the server side. At the same time, server side cannot just use the plain unknown type, as any access to properties will be prohibited (with strict compiler settings).

To resolve this issue, the Input<T> type is introduced. It recursively adds unknown, null, and undefined to the type of every field or value. That allows to enforce validation of input data, while declaring the original type T can be declared in strict mode for the client side.

Validation functions and additional input types cover default JSON types: object, array, string, number, boolean, and null.

  • Types:

    • Input<T>

    • InputArray<T>

    • InputObject<T>

    • InputPrimitive<T>

  • Unit function:

    • input<T>(input: unknown): Input<T> — type cast to Input<T>.

  • Basic validation functions:

    • stringInput(input: Input<string>): string | undefined

    • numberInput(input: Input<number>): number | undefined

    • booleanInput(input: Input<boolean>): boolean | undefined

    • arrayInput<T>(input: Input<T[]>): Input<T>[] | undefined — checks that the input is an array and returns it as an array of unvalidated elements.

    • objectInput<T>(input: Input<T>): InputObject<T> | undefined — checks that the input is a non-null, non-array object, and returns it as an object with unvalidated properties.

    • nullInput(input: Input<null>): null | undefined.

Use Maybe chain to validate inputs
import { panic } from '@perfective/common/error';
import { maybe } from '@perfective/common/maybe';
import { isNatural, Natural } from '@perfective/common/number';
import { Input, InputObject, numberInput, objectInput } from '@perfective/common/object';

interface ExampleParams {
    id: number;
}

interface Example {
    params: ExampleParams;
}

function userId(request: Input<Example>): Natural {
    return maybe(request) // (1)
        .to<InputObject<Example>>(objectInput) // (2)
        .pick('params')
        .to<InputObject<ExampleParams>>(objectInput)
        .pick('id')
        .to(numberInput) // (3)
        .otherwise(panic('User ID is not defined'))
        .that(isNatural) // (4)
        .or(panic('User ID is invalid'));
}
  1. request may be undefined.

  2. At the moment type transformations are not inferred correctly, so explicit type need to provided for objectInput.

  3. Last validation of the input structure.

  4. Final validation of the input, specific for the function.

Note

A custom validation monad may be added later to allow "collecting" all validation errors and warnings.

Enum

  • Types:

    • Enum<T extends number | string> — An Object with string keys and string or number values as generated by the TypeScript for an enum definition.

    • Member<T extends number | string> — key of an enum. — Defines a type of the keys of an Enum.

  • Functions:

    • members<T extends number | string, E extends Enum<T>>(value: E): Member<T>[] — returns a list of an enum keys.