Property does not exist on a function's return value of multiple types - node.js

I'm using typescript to write NodeJS program.
In this program, I import a node module called ts-md5, in which there is a function hashStr(), it could return a value of string or Int32Array.
I need to do things like this in my program:
Md5.hashStr(str).toUpperCase();
However, the compiler complains error:
error TS2339: Property 'toUpperCase' does not exist on type 'string | Int32Array'.
The program runs successfully. Because it always returns string during runtime. But I want to know if there is a way to get rid of this annoying error?

You can use a type guard, or a type assertion.
type guard
let hash = Md5.hashStr(str);
if (typeof hash === 'string') {
hash = hash.toUpperCase();
}
type assertion
let hash = (<string>Md5.hashStr(str)).toUpperCase();
The benefit of the type guard is that it technically safer - because if you ever did get something that wasn't a string at runtime, it would still work. The type assertion is simply you overriding the compiler, so it isn't technically as safe, but it is entirely erased and therefore results in the same runtime code you have at the point you have the error.

hashStr is declared in ts-md5 typings as
static hashStr(str: string, raw?: boolean): string | Int32Array;
Looking at the implementation, is seems that it returns Int32Array when raw is true, and returns string otherwise.
Given that declaration, you can't do much better than use type assertion:
let hash = (Md5.hashStr(str) as string).toUpperCase()
The proper way to express that return type is dependent on the parameter in TypeScript is via overload declarations. Something like this should work:
static hashStr(str: string): string;
static hashStr(str: string, raw: false): string;
static hashStr(str: string, raw: true): Int32Array;
static hashStr(str: string, raw: boolean): Int32Array | string;
static hashStr(str: string, raw?: boolean): string | Int32Array {
// implementation goes here...
}
I'd suggest posting an issue with ts-md5 about this.

Related

A strange mismatch not noticed by Typescript. How is this possible?

Apparently, Typescript doesn't seem to recognize the difference between an object like {} and a generic array [] by accepting last one as input of a function that requires an object with {}'s structure.
To resume my problem, this is a simplified example to replicate it:
type test = { [key: string]: any };
let x: test = ["x", "y", "z"];
Actually, Typescript seems to accept this. How is this possible?
Note: The situation I ran into is more similar to this:
type fooType = { [key: string]: any };
const fooFunction<T extends fooType>(input: T) => // code...
fooFunction([]); // No red underline
But you can consider the first example. It's the same.
The main idea is to create a function that accepts only objects with a key (type string) and a value of any type.
Thank you in advance for the answers!
Differentiating between plain objects and other things (like arrays, or even functions) can be frustrating in JavaScript (and therefore Typescript).
Since an array is an object, you need a type that excludes arrays. For completeness, you may also want to exclude other non-plain objects, like functions, dates, regexes, etc, but I'll just focus on arrays.
Using your example, here are some approaches:
1. Exclude objects with numeric indexes
function fooFunction<T extends {
[key: string]: any,
[index: number]: never
}>(input: T) { }
fooFunction(['']); // Will have red underline!
fooFunction([]); // This will NOT have an underline!
In the above case, we're saying that T cannot have any numeric indexes. There is an edge case, though: an empty array has type never[], which also has no numeric indexes!
2. Exclude array-specific fields
Another approach is to identify some property common to arrays that won't be in any of the objects you plan to pass through your function:
function fooFunction<T extends {
map?: never,
}>(input: T) { }
fooFunction(['']); // Will have red underline!
fooFunction([]); // So will this!
3. Narrow the parameter type
The cleanest approach is to narrow your generic at the parameter to exclude arrays. The following example uses a utility type that returns never for lots of non-plain-object inputs (but not all of them):
type FancyObject = any[]|Function|Date|RegExp|Error
type PlainObject<T> = T extends FancyObject
? never
: T extends { [key: string]: any }
? T
: never;
function fooFunction<T>(input: PlainObject<T>) {}
fooFunction(['']); // Will have red underline!
fooFunction([]); // So will this!
fooFunction({ hello: 'world' }) // This is fine!

Remove propertis not present in interface with nodejs typescript

I'm using node.js with typescript and I have a question
It is possible to remove properties that are not present in an interface when casting an object.
interface IFooReal {
prop1: string;
}
const objTMP = {prop1: 'foo001', prop2: 'foo002', prop3: 'foo003'};
cont barr = objTMP as IFooReal; //or do someting to remove properties not presente in interface
console.log(barr);
When execute console.log(barr); the result is {prop1: 'foo001'}
Using a type assertion like objTMP as IFooReal doesn't change anything about objTMP, it only tells TypeScript "please treat this as an IFooReal".
TypeScript's purpose is just annotating your code in a way that lets it check it for type safety. It doesn't modify your code, or add any execution of its own (with limited exceptions, like enums). When TypeScript is compiled into JavaScript, the process is mostly just removing those annotations.
If you want to remove all but a subset of properties from an object, then you will need to iterate through its properties and remove any that don't match the properties you want to keep. Something like this, for example:
interface IFooReal {
prop1: string;
}
const objTMP = {
prop1: 'foo001',
prop2: 'foo002',
prop3: 'foo003'
};
// You may want to get this by calling Object.keys() on another object?
const propertiesToKeep = ['prop1'];
for (let prop in objTMP) {
if (propertiesToKeep.includes(prop) === false) {
delete (objTMP as {[key:string]: any})[prop];
}
}
console.log(objTMP);
TypeScript playground
I've had to use the type assertion objTMP as {[key: string]: any}) here because otherwise TypeScript would complain that you're attempting to access a property on objTMP using a variable with type string, when TypeScript has implicitly typed it as { prop1: string, prop2: string, prop3: string } so only values of type 'prop1'|'prop2'|'prop3' can be used to access its properties.
That type assertion basically just tells TypeScript "let me try to access properties on this object using any string as the key, and don't worry about what type that property has".

Type 'string | undefined' is not assignable to type 'string'. Type 'undefined' is not assignable to type 'string'

I am declaring the following variables using TypeScript:
const BOT_PREFIX: string = process.env.PREFIX;
const BOT_TOKEN: string = process.env.TOKEN;
I get the following error:
Type 'string | undefined' is not assignable to type 'string'.
Type 'undefined' is not assignable to type 'string'.ts(2322)
I can fix it by changing the data type to any, but i don't want to do that.
Node's process.env has the following type definition (from this declaration file which uses a type defined here):
interface ProcessEnv {
[key: string]: string | undefined
}
So, as far as TypeScript's compiler is concerned, any property you access under process.env will be of type string | undefined. That's a union, meaning that process.env.RANDOMKEY (for example) will either be a string or it will be undefined. Generally speaking this is the right type; the compiler has no idea which environment variables are actually set.
And so this is a problem:
const BOT_PREFIX: string = process.env.PREFIX // error!
and the compiler warns you that process.env.PREFIX might be undefined, so it's not safe to treat it as a string.
The way to deal with this depends on whether you want convenience or type safety, and what you want to see happen if your assumptions about process.env.PREFIX and process.env.TOKEN are incorrect.
For pure convenience, you probably can't beat the non-null assertion operator (!):
const BOT_PREFIX: string = process.env.PREFIX!; // okay
const BOT_TOKEN: string = process.env.TOKEN!; // okay
Here you are just telling the compiler that, even though it cannot verify this, you are sure process.env.PREFIX and process.env.TOKEN will be defined. This essentially just suppresses the compiler warning; it's still doing the same thing at runtime as your original code. And that means if you turn out to be wrong about your assertion, then you might run into problems at runtime that the compiler can't help you with:
BOT_PREFIX.toUpperCase(); // runtime error if BOT_PREFIX is undefined after all
So be careful.
On the other hand, you can try to make the code safer by handling the situation in which the environment variables you expect are not set. For example:
function getEnvVar(v: string): string {
const ret = process.env[v];
if (ret === undefined) {
throw new Error("process.env." + v + " is undefined!");
}
return ret;
}
const BOT_PREFIX: string = getEnvVar("PREFIX");
const BOT_TOKEN: string = getEnvVar("TOKEN");
Here we've written a function called getEnvVar() which takes the name of the environment variable and returns its value, as long as that value is a string. If the environment variable is not defined, then a runtime error will be thrown. TypeScript understands via control flow analysis that the return type of getEnvVar() is string (and not string | undefined; the undefined possibility has been eliminated by the throw statement), and so you can safely assign the returned value to a variable of type string.
This code is obviously less convenient since it requires extra runtime code, but now instead of possibly lying to the compiler and having bizarre runtime behavior, you will get some immediate feedback at runtime if your assumptions are invalid.
Playground link to code
Since those are environment variables which makes it pretty sure that those value won't be empty or undefined.
You may use non-null assertion operator
should look like this:
const BOT_PREFIX: string = process.env.PREFIX!;
const BOT_TOKEN: string = process.env.TOKEN!;
Just make sure your environment variables are filled.
you can also do this:
const BOT_PREFIX: string = process.env.PREFIX || '';
const BOT_TOKEN: string = process.env.TOKEN || '';
You can also use ES6 template literals
const BOT_PREFIX: string = `${process.env.PREFIX}`;
const BOT_TOKEN: string = `${process.env.TOKEN}`;

TS: Cannot invoke an expression whose type lacks a call signature when defined dynamically, but it works

I'm still quite new to typescript, so please be gentle with me if I'm doing something with no sense for this technology!
The problem that I'm trying to solve is having a dynamic way to define how my application errors should be structured, but leaving to the users the faculty to enrich the messages.
So I tried to create this logic in a module that could be extended easily from the application, but I'm currently facing the problem:
Error:(35, 18) TS2349: Cannot invoke an expression whose type lacks a call signature. Type 'ErrorMessage' has no compatible call signatures.
What I thought it was a good idea (but please tell me if I'm wrong), was to use a register and a map to have the possibility to extend this mapping every time I want. So I created my ErrorMessage interface to be like the following:
export interface ErrorMessage {
actionMessage: string;
actionSubject: string;
originalErrorMessage?: string;
toString: () => string;
}
and a register for these, called ErrorResponseRegister, as it follows:
export enum defaultErrors {
ExceptionA = 'ExceptionA',
ExceptionB = 'ExceptionB',
}
export class ErrorResponseRegister {
private mapping: Map<string, ErrorMessage>;
constructor() {
this.mapping = new Map()
.set(defaultErrors.ExceptionA, exceptionAErrorMessage)
.set(defaultErrors.ExceptionB, exceptionBErrorMessage);
}
}
So at the end, every ErrorMessage function should look like:
export function exceptionAErrorMessage(originalErrorMessage?: string): ErrorMessage {
return {
enrichment1: "Something happened",
enrichment2: "in the application core",
originalErrorMessage: originalErrorMessage,
toString(): string {
return `${this.enrichment1} ${this.enrichment2}. Original error message: ${originalErrorMessage}`;
},
};
}
Please note I haven't used classes for this ones, as it doesn't really need to be instantiated
and I can have a bunch of them where the toString() method can vary. I just want to enforce the errors should have an enrichment1 and enrichment2 that highlight the problem in a better way for not-technical people.
So, now, back to code. When I'm trying to use the exceptionAErrorMessage statically, I can't see any problem:
console.log(exceptionAErrorMessage(originalErrorMessage).toString())
But when I try dynamically, using the map defined in the ErrorResponseRegister, something weird happens:
// In ErrorResponseRegister
public buildFor(errorType: string, originalErrorMessage?: string): Error {
const errorMessageBuilder = this.mapping.get(errorType);
if (errorMessageBuilder) {
return errorMessageBuilder(originalErrorMessage).toString();
}
return "undefined - do something else";
}
The code works as expected, the error returned is in the right format, so the toString function is executed correctly.
BUT, the following error appears in the IDE:
Error:(32, 18) TS2349: Cannot invoke an expression whose type lacks a call signature. Type 'ErrorMessage' has no compatible call signatures.
The line that causes the problem is
errorMessageBuilder(originalPosErrorMessage).toString()
Can someone help me to understand what I'm doing wrong?
It looks like your problem is you've mistyped mapping... it doesn't hold ErrorMessage values; it holds (x?: string)=>ErrorMessage values:
private mapping: Map<string, (x?: string) => ErrorMessage>;
What's unfortunate is that you initialize this variable via new Map().set(...) instead of the using an iterable constructor argument.
The former returns a Map<any, any> which is trivially assignable to mapping despite the mistyping. That is, you ran smack into this known issue where the standard library's typings for the no-argument Map constructor signature produces Map<any, any> which suppresses all kinds of otherwise useful error messages. Perhaps that will be fixed one day, but for now I'd suggest instead that you use the iterable constructor argument, whose type signature declaration will infer reasonable types for the keys/values:
constructor() {
this.mapping = new Map([
[defaultErrors.ExceptionA, exceptionAErrorMessage],
[defaultErrors.ExceptionB, exceptionBErrorMessage]
]); // inferred as Map<defaultErrors, (orig?: string)=>ErrorMessage>
}
If you had done so, it would have flagged the assignment as an error with your original typing for mapping (e.g., Type 'Map<defaultErrors, (originalErrorMessage?: string | undefined) => ErrorMessage>' is not assignable to type 'Map<string, ErrorMessage>'.) Oh well!
Once you make those changes, things should behave more reasonably for you. Hope that helps; good luck!
Link to code

Where a function is required, typescript allows me to pass an object with an incompatible `apply` property

Currently using typescript 3.4.5 with strict mode enabled...
Backstory
I just ran into a situation where typescript failed to protect me from my own mistakes, unfortunately. And I'm trying to figure out why typescript failed to catch this error.
I was writing a type declaration for a function like this:
function acceptVisitors (visitor) {
visitor.apply(value);
}
Astute observers may point out that visitor's type could be defined in one of two ways — as a function, or as an object with an apply property:
type visitorType = (this: IValue) => void;
// or
type visitorType = {
apply: (value: IValue) => void;
};
It turns out, in my case it was the latter. After adding the type declaration, I proceeded to write this incorrect code:
// This is incorrect because it doesn't pass it as an argument.
// Rather, the `this` context is set to the value.
acceptVisitors((value: IValue) => { ... });
Now, the puzzling thing is that Typescript did not show an error when I passed a function whose type was incompatible with visitorType.
Simplified example
Let's change the parameter type to a string, and walk through it.
I'm defining a type called func that is a function that requires a string argument.
type func = (param1: string) => void;
Functions by nature are callable objects that also have an apply method.
declare let f: func;
f.apply(undefined, ['str']);
// all good
Now here's the other type — an object with an apply property.
type objectWithApplyProp = {
apply: (param1: string) => void;
};
We can call the apply property, but not in the same way...
declare let o: objectWithApplyProp;
o.apply(undefined, ['str']); // Error: Expected 1 arguments, but got 2.
And objectWithApplyProp has a call signature that doesn't work with func:
o.apply('str'); // ok
f.apply('str'); // Error: The 'this' context of type 'func' is not assignable to
// method's 'this' of type '(this: string) => void'
And further tests show that f is assignable to o, but not the other way around, which makes sense... all functions are objects but not all objects are callable.
But why is f considered assignable to o? The type of objectWithApplyProp requires an apply value that matches a certain type, and func doesn't match it
A function's apply signature should be inferrable from its parameters, but typescript doesn't seem to be inferring it.
So, any feedback is welcome. Am I wrong, or is there a limitation in Typescript? Is it a known issue? Thanks
So this is a technical reason of why it's happening, and a workaround:
Typescript's built-in lib/es5.d.ts declaration file defines Function.apply with parameters of type any. Also it defines Function.prototype as any.
interface Function {
apply(this: Function, thisArg: any, argArray?: any): any;
call(this: Function, thisArg: any, ...argArray: any[]): any;
bind(this: Function, thisArg: any, ...argArray: any[]): any;
toString(): string;
prototype: any;
readonly length: number;
// Non-standard extensions
arguments: any;
caller: Function;
}
And I guess all function expressions are given the Function type by default.
So the function was allowed to be assigned to the object with the incompatible apply property because the function did not have a strongly typed apply method, based on the built-in Function types. Therefore typescript could not determine that the apply signatures were different.
Typescript 3.2 introduces CallableFunction which has generic arguments on its apply declaration. But I haven't figured out how to make it fix this problem.
A workaround is to define a stronger function type and manually assign it to the function. The workaround is a bit tedious, but it works.
interface func extends Function {
(param1: string): void;
// manually define `apply
apply<T, R> (thisArg: T, args: [string]): R;
}
interface objectWithApplyProp { // unchanged
apply: (param1: string) => void;
}
// Now we have proper errors here:
o = f;
// Type 'func' is not assignable to type 'objectWithApplyProp'.
// Types of property 'apply' are incompatible.
// Type '<T, R>(thisArg: T, args: [string]) => R' is not assignable to type '(param1: string) => void'. ts(2322)
f = o;
// Type 'objectWithApplyProp' is missing the following properties from type 'func': call, bind, prototype, length, and 2 more. ts(2740)

Resources