Typescript limitation workaround - javascript

I have a code, that is totaly OK from the human perspective. But it looks like typescript type system have hard time to understand it. Is there a smart way to hint compiler that everything is fine in that line?
const isMustToRun: boolean = isFunc(condition) ? condition() : condition;
code:
export const noop = function() {
};
export const isFunc = function(obj: any): boolean {
return typeof obj === 'function';
};
/**
*
* #param func funtion to run
* #param condition condition check
* #param args
*/
export const runIf = function f(condition: (Function | boolean), func: Function, ...args: any[]) {
return () => {
const isMustToRun: boolean = isFunc(condition) ? condition() : condition;
return isMustToRun ? func(...args) : noop();
};
};
If I wrote
typeof condition === 'function'
instead of "isFunc" call, then it works. But I don't want to repeat the code..

Change the return type of
export const isFunc = function(obj: any): boolean {
return typeof obj === 'function';
};
to be is Function
export const isFunc = function(obj: any): obj is Function {
return typeof obj === 'function';
};
More
This is called a user defined type guard 🌹

You need to transform isFunc in a custom type guard and your code will work as expected.:
export const isFunc = function(obj: any): obj is Function {
return typeof obj === 'function';
};
I would recommend not using Function though as it is not really very type safe, you can tighten up the function type with a function signature, and use the Extract conditional type to preserve the actual type of the passed in function to the isFunc type-guard:
export const isFunc = function<T>(obj: T): obj is Extract<T, Function> {
return typeof obj === 'function';
};
export const runIf = function f(condition: ((()=> boolean) | boolean), func: Function, ...args: any[]) {
return () => {
const isMustToRun: boolean = isFunc(condition) ? condition() : condition;
return isMustToRun ? func(...args) : noop();
};
};
Or a fully type safe version of runIf that checks the args agains the func parameters:
export const runIf = function f<T extends (...a: any)=>any>(condition: ((()=> boolean) | boolean), func: T, ...args: Parameters<T>): (()=>ReturnType<T>) {
return () => {
const isMustToRun: boolean = isFunc(condition) ? condition() : condition;
return isMustToRun ? func(...args as any[]) : noop();
};
};
function testFn(n: string) : number { return +n;}
runIf(true, testFn, "0"); //ok
runIf(true, testFn, 0); //err

Related

Can't edit an attribute, error Type 'string' is not assignable to type 'never' with TypeScript

I want to edit an attribute from an object in TypeScript and React but I get the following error:
Type 'string' is not assignable to type 'never' despite the fact that I check the type of this attribute before :
const familySetter = (newValue: string, attributeToSet: keyof Family) => {
setNewFamily((currNewFam) => {
if (!currNewFam) return;
const newNewFam: Family = { ...currNewFam };
if (typeof newNewFam[attributeToSet] === 'string') {
newNewFam[attributeToSet] = newValue; //error at this line
}
return newNewFam;
});
};
It will occured when your class properties have several types (for sample string and number)
class Family {
test: string;
test2: number;
};
in that case the compilator will not know if newNewFam[attributeToSet] is of one type or another
one solution is to declare your method by extending the keyof Family and say value is of type Family[K]
const familySetter = <K extends keyof Family>(newValue: Family[K], attributeToSet: K)
the if if (typeof newNewFam[attributeToSet] === 'string') is not enought because it's evaluate at runtime dependings on function parameter
complete sample is
class Family {
test: string;
test2: number;
};
const familySetter = <K extends keyof Family>(newValue: Family[K], attributeToSet: K) => {
setNewFamily((currNewFam) => {
if (!currNewFam) return;
const newNewFam: Family = { ...currNewFam };
if (typeof newNewFam[attributeToSet] === 'string') {
newNewFam[attributeToSet] = newValue;
}
return newNewFam;
});
};

NestJs: How can I add options for my Custom Validation Pipe?

I created a custom validation pipe where I want to add some custom logic.I want to know how can I extend it so that I can call it with the below options
Edited:
I have referred the below link of the github file and have developed my own pipe but there still seems to be something missing as its not validating as per the DTO
https://github.com/nestjs/nest/blob/1f6fca5f55e9e51705fa326654760736b254f4e5/packages/common/pipes/validation.pipe.ts#L48
I was able to solve it by creating my own pipe.
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException, Logger, Optional } from '#nestjs/common';
import { ValidatorOptions, ValidationError, isObject, validate } from 'class-validator';
import { plainToClass, classToPlain, ClassTransformOptions } from 'class-transformer';
import { iterate } from 'iterare';
const isUndefined = (obj: any): obj is undefined =>
typeof obj === 'undefined';
const isNil = (val: any): val is null | undefined =>
isUndefined(val) || val === null;
interface ValidationPipeOptions extends ValidatorOptions {
transform?: boolean;
transformOptions?: ClassTransformOptions;
validateCustomDecorators?: boolean;
}
#Injectable()
export class ValidationPipe implements PipeTransform<any> {
private readonly logger = new Logger(ValidationPipe.name);
protected isTransformEnabled: boolean;
protected transformOptions: ClassTransformOptions;
protected validatorOptions: ValidatorOptions;
protected validateCustomDecorators: boolean;
constructor(#Optional() options?: ValidationPipeOptions) {
options = options || {};
const {
transform,
transformOptions,
validateCustomDecorators,
...validatorOptions
} = options;
this.isTransformEnabled = !!transform;
this.transformOptions = transformOptions;
this.validatorOptions = validatorOptions;
this.validateCustomDecorators = validateCustomDecorators || false;
}
async transform(value: any, metadata: ArgumentMetadata) {
const metatype = metadata.metatype;
if (!metatype || !this.toValidate(metadata)) {
return this.isTransformEnabled
? this.transformPrimitive(value, metadata)
: value;
}
const originalValue = value;
value = this.toEmptyIfNil(value);
const isNil = value !== originalValue;
const isPrimitive = this.isPrimitive(value);
this.stripProtoKeys(value);
let object = plainToClass(metatype, value, this.transformOptions);
const originalEntity = object;
const isCtorNotEqual = object.constructor !== metatype;
if (isCtorNotEqual && !isPrimitive) {
object.constructor = metatype;
} else if (isCtorNotEqual) {
// when "entity" is a primitive value, we have to temporarily
// replace the entity to perform the validation against the original
// metatype defined inside the handler
object = { constructor: metatype };
}
const errors = await this.validate(object, this.validatorOptions);
if (errors.length > 0) {
for (let error of errors) {
for (let key in error.constraints) {
this.logger.error(`${error.target.constructor.name}:${error.constraints[key]}`);
}
}
throw new BadRequestException('Invalid Input Parameters');
}
if (isPrimitive) {
// if the value is a primitive value and the validation process has been successfully completed
// we have to revert the original value passed through the pipe
object = originalEntity;
}
if (this.isTransformEnabled) {
return object;
}
if (isNil) {
// if the value was originally undefined or null, revert it back
return originalValue;
}
return Object.keys(this.validatorOptions).length > 0
? classToPlain(object, this.transformOptions)
: value;
}
protected stripProtoKeys(value: Record<string, any>) {
delete value.__proto__;
const keys = Object.keys(value);
iterate(keys)
.filter(key => isObject(value[key]) && value[key])
.forEach(key => this.stripProtoKeys(value[key]));
}
protected isPrimitive(value: unknown): boolean {
return ['number', 'boolean', 'string'].includes(typeof value);
}
protected transformPrimitive(value: any, metadata: ArgumentMetadata) {
if (!metadata.data) {
// leave top-level query/param objects unmodified
return value;
}
const { type, metatype } = metadata;
if (type !== 'param' && type !== 'query') {
return value;
}
if (metatype === Boolean) {
return value === true || value === 'true';
}
if (metatype === Number) {
return +value;
}
return value;
}
protected toEmptyIfNil<T = any, R = any>(value: T): R | {} {
return isNil(value) ? {} : value;
}
private toValidate(metadata: ArgumentMetadata): boolean {
const { metatype, type } = metadata;
if (type === 'custom' && !this.validateCustomDecorators) {
return false;
}
const types = [String, Boolean, Number, Array, Object, Buffer];
return !types.some(t => metatype === t) && !isNil(metatype);
}
protected validate(
object: object,
validatorOptions?: ValidatorOptions,
): Promise<ValidationError[]> | ValidationError[] {
return validate(object, validatorOptions);
}
}

TypeScript failed to infer the correct type when calling the value of object (which is a function)

const record = {
foo: () => ({ foo: 1 }),
bar: () => ({ bar: 1 }),
}
function getRecord<T extends keyof typeof record>(type: T) {
return record[type];
}
const obj = getRecord(`foo`);
// if line 7 is: return record[type];
// typeof obj will be: () => { foo: number; }
// but if line 7 is: return record[type]();
// typeof obj will be: { foo: number; } | { bar: number; }
obj
Playground link
When the return value is not called, TypeScript can successfully infer the return type to be () => { foo: number }, but when the return value is called, the type inference broadened to { foo: number; } | { bar: number; }. Why is this happening?
The return type of:
function getRecord<T extends keyof typeof record>(type: T) {
return record[type];
}
Is (typeof record)[T]. As you can see, the generic type parameter influences the return type of the function when function is called.
In the second example:
function getRecord<T extends keyof typeof record>(type: T) {
return record[type]();
}
The return type is { foo: number } | { bar: number }. Here the generic parameter doesn't affect it (already used to pre-evaluate the return type).

Map default value

I'm looking for something like default value for Map.
m = new Map();
//m.setDefVal([]); -- how to write this line???
console.log(m[whatever]);
Now the result is Undefined but I want to get empty array [].
First of all to answer the question regarding the standard Map: Javascript Map as proposed in ECMAScript 2015 does not include a setter for default values. This, however, does not restrain you from implementing the function yourself.
If you just want to print a list, whenever m[whatever] is undefined, you can just:
console.log(m.get('whatever') || []);
as pointed out by Li357 in his comment.
If you want to reuse this functionality, you could also encapsulate this into a function like:
function getMapValue(map, key) {
return map.get(key) || [];
}
// And use it like:
const m = new Map();
console.log(getMapValue(m, 'whatever'));
If this, however, does not satisfy your needs and you really want a map that has a default value you can write your own Map class for it like:
class MapWithDefault extends Map {
get(key) {
if (!this.has(key)) {
this.set(key, this.default());
}
return super.get(key);
}
constructor(defaultFunction, entries) {
super(entries);
this.default = defaultFunction;
}
}
// And use it like:
const m = new MapWithDefault(() => []);
m.get('whatever').push('you');
m.get('whatever').push('want');
console.log(m.get('whatever')); // ['you', 'want']
As of 2022, Map.prototype.emplace has reached stage 2.
As it says on the proposal page, a polyfill is available in the core-js library.
For my purposes, I thought it would be more clear to have a DefaultMap class that extended a normal Map and added additional methods. This is really nice because it leads to more declarative code. Meaning that when you declare a new Map, not only do you declare the type of the keys and the type of the values, you also declare the default value.
As a quick example:
// Using a primitive as a default value
const myMap1 = new DefaultMap<string, number>(123);
const myMap1Value = myMap1.getAndSetDefault("some_key");
// Using a factory function to generate a default value
const myMap2 = new DefaultMap<string, number, [foo: Foo]>((_key, foo) => foo.bar);
const foo = new Foo();
const myMap2Value = myMap2.getAndSetDefault("some_key", foo);
The code is below:
type FactoryFunction<K, V, A extends unknown[]> = (k: K, ...extraArgs: A) => V;
type FirstArg<K, V, A extends unknown[]> =
| Iterable<[K, V]>
| V
| FactoryFunction<K, V, A>;
type SecondArg<K, V, A extends unknown[]> = V | FactoryFunction<K, V, A>;
interface ParsedArgs<K, V, A extends unknown[]> {
iterable: Iterable<[K, V]> | undefined;
defaultValue: V | undefined;
defaultValueFactory: FactoryFunction<K, V, A> | undefined;
}
/**
* An extended Map with some new methods:
*
* - `getAndSetDefault` - If the key exists, this will return the same thing as the `get` method.
* Otherwise, it will set a default value to the key, and then return the default value.
* - `getDefaultValue` - Returns the default value to be used for a new key. (If a factory function
* was provided during instantiation, this will execute the factory function.)
* - `getConstructorArg` - Helper method for cloning the map. Returns either the default value or
* the reference to the factory function.
*
* When instantiating a new DefaultMap, you must specify either a default value or a function that
* returns a default value.
*
* Example:
* ```ts
* // Initializes a new empty DefaultMap with a default value of "foo"
* const defaultMapWithPrimitive = new DefaultMap<string, string>("foo");
*
* // Initializes a new empty DefaultMap with a default value of a new Map
* const defaultMapWithFactory = new DefaultMap<string, Map<string, string>>(() => {
* return new Map();
* })
*
* // Initializes a DefaultMap with some initial values and a default value of "bar"
* const defaultMapWithInitialValues = new DefaultMap<string, string>([
* ["a1", "a2"],
* ["b1", "b2"],
* ], "bar");
* ```
*
* If specified, the first argument of a factory function must always be equal to the key:
*
* ```ts
* const defaultMapWithConditionalDefaultValue = new DefaultMap<number, number>((key: number) => {
* return isOdd(key) ? 0 : 1;
* });
* ```
*
* You can also specify a factory function that takes a generic amount of arguments beyond the
* first:
*
* ```ts
* const factoryFunction = (_key: string, arg2: boolean) => arg2 ? 0 : 1;
* const defaultMapWithExtraArgs = new DefaultMap<string, string, [arg2: boolean]>(factoryFunction);
* ```
*/
export class DefaultMap<K, V, A extends unknown[] = []> extends Map<K, V> {
private defaultValue: V | undefined;
private defaultValueFactory: FactoryFunction<K, V, A> | undefined;
/**
* See the DefaultMap documentation:
* [insert link here]
*/
constructor(
iterableOrDefaultValueOrDefaultValueFactory: FirstArg<K, V, A>,
defaultValueOrDefaultValueFactory?: SecondArg<K, V, A>,
) {
const { iterable, defaultValue, defaultValueFactory } = parseArguments(
iterableOrDefaultValueOrDefaultValueFactory,
defaultValueOrDefaultValueFactory,
);
if (defaultValue === undefined && defaultValueFactory === undefined) {
error(
"A DefaultMap must be instantiated with either a default value or a function that returns a default value.",
);
}
if (iterable === undefined) {
super();
} else {
super(iterable);
}
this.defaultValue = defaultValue;
this.defaultValueFactory = defaultValueFactory;
}
/**
* If the key exists, this will return the same thing as the `get` method. Otherwise, it will set
* a default value to the key, and then return the default value.
*/
getAndSetDefault(key: K, ...extraArgs: A): V {
const value = this.get(key);
if (value !== undefined) {
return value;
}
const defaultValue = this.getDefaultValue(key, ...extraArgs);
this.set(key, defaultValue);
return defaultValue;
}
/**
* Returns the default value to be used for a new key. (If a factory function was provided during
* instantiation, this will execute the factory function.)
*/
getDefaultValue(key: K, ...extraArgs: A): V {
if (this.defaultValue !== undefined) {
return this.defaultValue;
}
if (this.defaultValueFactory !== undefined) {
return this.defaultValueFactory(key, ...extraArgs);
}
return error("A DefaultMap was incorrectly instantiated.");
}
/**
* Helper method for cloning the map. Returns either the default value or a reference to the
* factory function.
*/
getConstructorArg(): V | FactoryFunction<K, V, A> {
if (this.defaultValue !== undefined) {
return this.defaultValue;
}
if (this.defaultValueFactory !== undefined) {
return this.defaultValueFactory;
}
return error("A DefaultMap was incorrectly instantiated.");
}
}
function parseArguments<K, V, A extends unknown[]>(
firstArg: FirstArg<K, V, A>,
secondArg?: SecondArg<K, V, A>,
): ParsedArgs<K, V, A> {
return secondArg === undefined
? parseArgumentsOne(firstArg)
: parseArgumentsTwo(firstArg, secondArg);
}
function parseArgumentsOne<K, V, A extends unknown[]>(
firstArg: FirstArg<K, V, A>,
): ParsedArgs<K, V, A> {
const arg = firstArg as SecondArg<K, V, A>;
const { defaultValue, defaultValueFactory } =
parseDefaultValueOrDefaultValueFactory(arg);
return {
iterable: undefined,
defaultValue,
defaultValueFactory,
};
}
function parseArgumentsTwo<K, V, A extends unknown[]>(
firstArg: FirstArg<K, V, A>,
secondArg: SecondArg<K, V, A>,
): ParsedArgs<K, V, A> {
const firstArgType = type(firstArg);
if (firstArgType !== "table") {
error(
"A DefaultMap constructor with two arguments must have the first argument be the initializer list.",
);
}
const { defaultValue, defaultValueFactory } =
parseDefaultValueOrDefaultValueFactory(secondArg);
return {
iterable: firstArg as Iterable<[K, V]>,
defaultValue,
defaultValueFactory,
};
}
function parseDefaultValueOrDefaultValueFactory<K, V, A extends unknown[]>(
arg: SecondArg<K, V, A>,
): {
defaultValue: V | undefined;
defaultValueFactory: FactoryFunction<K, V, A> | undefined;
} {
if (typeof arg === "function") {
return {
defaultValue: undefined,
defaultValueFactory: arg as FactoryFunction<K, V, A>,
};
}
if (
typeof arg === "boolean" ||
typeof arg === "number" ||
typeof arg === "string"
) {
return {
defaultValue: arg as V,
defaultValueFactory: undefined,
};
}
return error(
`A DefaultMap was instantiated with an unknown type of: ${typeof arg}`,
);
}
Most straightforward implementation:
Here's a simple MapWithDefault class that extends the built-in Map.
The clever trick is here is using symbols for specifying the default value, resulting in a nice and readable syntax:
(The snippets below use TypeScript to provide type-safety, but this is optional, you can remove the type annotations if you wish.)
mapWithDefault.ts:
export const DEFAULT = Symbol();
export class MapWithDefault<K, V> extends Map<K | typeof DEFAULT, V> {
get(key: K): V {
return super.has(key)
? super.get(key)
: super.get(DEFAULT);
}
}
Usage:
import { MapWithDefault, DEFAULT } from './mapWithDefault';
const map = new MapWithDefault<number, string>([
[404, 'Not found!'],
[403, 'Forbidden!'],
[DEFAULT, 'Unknown error'],
]);
console.log(map.get(404)); // "Not found!"
console.log(map.get(123)); // "Unknown error"

Implementing safe navigation in typescript

Recently I came across the following article in Medium about Using ES6's Proxy for safe Object property access from Gidi Meir Morris. I really liked it and wanted to give it a try in my Typescript project for optional nested objects without loosing type checking.
In order to turn optional nested objects into all-required, I'm using the following type:
export type DeepRequired<T> = {
[P in keyof T]-?: DeepRequired<T[P]>;
};
Gidi's code in typescript (including some hacks...):
export interface Dictionary {
[key: string]: any;
};
const isObject = (obj: any) => obj && typeof obj === 'object';
const hasKey = (obj: object, key: string) => key in obj;
const Undefined: object = new Proxy({}, {
get: function (target, name) {
return Undefined;
}
});
export const either = (val: any, fallback: any) => (val === Undefined ? fallback : val);
export function safe<T extends Dictionary>(obj: T): DeepRequired<T> {
return new Proxy(obj, {
get: function(target, name){
return hasKey(target, name as string) ?
(isObject(target[name]) ? safe(target[name]) : target[name]) : Undefined;
}
}) as DeepRequired<T>;
}
Usage example:
interface A {
a?: {
b?: {
c?: {
d?: string
}
}
},
b: boolean,
c?: {
d: {
e: number
}
},
d?: Array<{e: boolean}>
}
const obj: A = {b: false};
const saferObj = safe(obj);
Scenarios that it works without TS errors:
test('should work for nested optional objects', () => {
expect(either(saferObj.a.b.c.d, null)).toEqual(null);
expect(either(saferObj.a.b.c.d, undefined)).toEqual(undefined);
expect(either(saferObj.a.b.c.d, 322)).toEqual(322);
});
test('should work for required members', () => {
expect(either(saferObj.b, null)).toEqual(false);
});
test('should work for mixed optional/required tree', () => {
expect(either(saferObj.c.d.e, null)).toEqual(null);
});
As for arrays...
test('should work for arrays', () => {
expect(either(saferObj.d[0].e, null)).toEqual(null);
});
TS compiler throws the following error:
[ts] Element implicitly has an 'any' type because type 'DeepRequired<{ e: boolean; }[]>' has no index signature.
Any idea how can i make this work for Arrays?
Your code will work as is on Typescript 2.9 and above because in Typescript 2.9 the keyof operator includes numeric and symbol keys as well as string keys which were previously returned by keyof. Playground link
If you want to stick to 2.8 for some reasons, you can use the workaround of handling arrays explicitly in DeepRequired using a conditional type.
export type DeepRequired<T> = {
[P in keyof T]-?: T[P] extends Array<infer U>?Array<DeepRequired<U>>: DeepRequired<T[P]>;
};

Categories