typedux
Version:
Slightly adjusted Redux (awesome by default) for TS
112 lines (100 loc) • 3.66 kB
text/typescript
import {State} from "../reducers"
/**
* Copyright (C) 2019-present, Rimeto, LLC.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* A generic type that cannot be `undefined`.
*/
type Defined<T> = Exclude<T, undefined>;
/**
* `SelectorChainObjectWrapper` gives TypeScript visibility into the properties of
* an `SelectorChainType` object at compile-time.
*/
type SelectorChainObjectWrapper<S extends State<string>, T> = { [K in keyof T]-?:SelectorChainType<S, T[K]> };
/**
* Data accessor interface to dereference the value of the `TSOCType`.
*/
export interface SelectorChainDataAccessor<S extends State<string>, T> {
/**
* Data accessor without a default value. If no data exists,
* `undefined` is returned.
*/
(noDefaultValue?:undefined):Defined<T> | undefined;
/**
* Data accessor with default value.
*/
(defaultValue:NonNullable<T>):NonNullable<T>;
(nullDefaultValue:T extends null ? null : never):Defined<T>; // Null case
}
/**
* `SelectorChainArrayWrapper` gives TypeScript visibility into the `SelectorChainType` values of an array
* without exposing Array methods (it is problematic to attempt to invoke methods during
* the course of an optional chain traversal).
*/
export interface SelectorChainArrayWrapper<S extends State<string>, T> {
length:SelectorChainType<S, number>;
[K:number]:SelectorChainType<S, T>;
}
/**
* Data accessor interface to dereference the value of an `any` type.
* @extends SelectorChainDataAccessor<any>
*/
export interface SelectorChainAny extends SelectorChainDataAccessor<any, any> {
[K:string]:SelectorChainAny; // Enable deep traversal of arbitrary props
}
/**
* `SelectorChainDataWrapper` selects between `SelectorChainArrayWrapper`, `SelectorChainObjectWrapper`, and `SelectorChainDataAccessor`
* to wrap Arrays, Objects and all other types respectively.
*/
export type SelectorChainDataWrapper<S extends State<string>, T> =
0 extends (1 & T) // Is T any? (https://stackoverflow.com/questions/49927523/disallow-call-with-any/49928360#49928360)
? SelectorChainAny
: T extends any[] // Is T array-like?
? SelectorChainArrayWrapper<S, T[number]>
: T extends object // Is T object-like?
? SelectorChainObjectWrapper<S, T>
: SelectorChainDataAccessor<S, T>;
/**
* An object that supports optional chaining
*/
export type SelectorChainType<S extends State<string>, T> =
SelectorChainDataAccessor<S, T>
& SelectorChainDataWrapper<S, NonNullable<T>>
/**
* Optional chaining with default values. To inspect a property value in
* a tree-like structure, invoke it as a function, optionally passing a default value.
*
* @example
* // Given:
* const x = oc<T>({
* a: 'hello',
* b: { d: 'world' },
* c: [-100, 200, -300],
* });
*
* // Then:
* x.a() === 'hello'
* x.b.d() === 'world'
* x.c[0]() === -100
* x.c[100]() === undefined
* x.c[100](1234) === 1234
* x.c.map((e) => e()) === [-100, 200, -300]
* x.d.e() === undefined
* x.d.e('optional default value') === 'optional default value'
* (x as any).y.z.a.b.c.d.e.f.g.h.i.j.k() === undefined
*/
//export declare function oc<T>(data?: T): SelectorChainType<T>;
export function selector<S extends State<string>, T>(data?:T):SelectorChainType<S, T> {
return new Proxy(
((defaultValue?:Defined<T>) => (data == null ? defaultValue : data)) as SelectorChainType<S, T>,
{
get: (target, key) => {
const obj:any = target()
return selector(typeof obj === "object" ? obj[key] : undefined)
}
}
)
}