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

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

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!

Forward operators in haxe

I'm trying to write my own boolean "abstract" with some additional functions.
#forward
abstract MyBool(Bool) {
public inline function new(b:Bool) {
this = b;
}
#:from
public static inline function fromBool(b:Bool):MyBool {
return new MyBool(b);
}
#:to
public inline function toBool():Bool {
return this;
}
// some additional functions
}
In principal this works fine:
var t:T = true;
if(t) {
trace("1");
}
t.someStrangeMethod();
However #:forward does not forward basic boolean-operators like "!":
var f:T = false;
if(!f) { // fails here, because "!" is not defined as an operator for MyBool ...
trace("2");
}
The error message is "MyBool should be Bool", which I find quite strange because MyBool is an abstract of a Bool with #:forward annotation and there is a #:to-method.
Of course there are some easy workarounds. One could either use:
if(!f.toBool()) {
trace("2");
}
and/or add a function annotated with #:op(!A) to the abstract:
#:op(!A)
public inline function notOp():Bool {
return !this;
}
However I do not like both methods:
I dislike adding #:op(...) to MyBool, because creating a method for each possible operator would require much code (Maybe not with a boolean, but e.g. with an Int, Float, ...).
I dislike using !var.toBool(). If someone has already written quite some code (s)he does not want to go through all of it, when (s)he simply wants to change Bool to a MyBool ... I mean of course (s)he could also cast Bool to MyBool whenever adding new code, but that can be horrible too.
So I was wondering if anyone has a better idea? Is there maybe another "#:forward"-like compiling metadata, I do not know about yet?
There's an open feature request regarding this:
Can #:forward also forward underlying operator overloads? (#5035)
One way to make your code example work is to allow implicit conversions with to Bool. I'm not entirely sure why the equivalent #:to function doesn't work here, as the Haxe Manual states that "Class field casts have the same semantics".
abstract MyBool(Bool) to Bool {
Apart from that, I think the only options is to declare an #:op function for each operator you want to support. If declared without a body, the underlying type's operator will be forwarded:
#:op(!A) function notOp():MyBool;
If your main goal is to just add methods to the Bool type, then perhaps avoid the problem altogether by instead creating a class that adds methods to Bool via static extension (documented in the Haxe manual). This method would eliminate the need for operator forwarding.

How do I improve this object design in Typescript?

I have created a class in Typescript that implements a simple stream (FRP). Now I want to extend it with client side functionality (streams of events). To illustrate my problem, here is some pseudo-code:
class Stream<T> {
map<U>(f: (value: T) => U): Stream<U> {
// Creates a new Stream instance that maps the values.
}
// Quite a few other functions that return new instances.
}
This class can be used both on the server and on the client. For the client side, I created a class that extends this one:
class ClientStream<T> extends Stream<T> {
watch(events: string, selector: string): Stream<Event> {
// Creates a new ClientStream instance
}
}
Now the ClientStream class knows about map but the Stream class doesn't know about watch. To circumvent this, functions call a factory method.
protected create<U>(.....): Stream<U> {
return new Stream<U>(.....)
}
The ClientStream class overrides this function to return ClientStream instances. However, the compiler complains that ClientStream.map returns a Stream, not a ClientStream. That can be 'solved' using a cast, but besides being ugly it prevents chaining.
Example code that exhibits this problem:
class Stream {
protected create(): Stream {
return new Stream()
}
map() {
return this.create()
}
}
class ClientStream extends Stream {
protected create(): ClientStream {
return new ClientStream()
}
watch() {
return this.create()
}
}
let s = new ClientStream().map().watch()
This does not compile because according to the compiler, the stream returned from map is not a ClientStream: error TS2339: Property 'watch' does not exist on type 'Stream'.
I don't really like this pattern, but I have no other solution that is more elegant. Things I've thought about:
Use composition (decorator). Not really an option given the number of methods I would have to proxy through. And I want to be able to add methods to Stream later without having to worry about ClientStream.
Mix Stream into ClientStream. More or less the same problem, ClientStream has to know the signatures of the functions that are going to be mixed in (or not? Please tell).
Merge these classes into one. This is a last resort, the watch function has no business being on the server.
Do you have a better (more elegant) solution? If you have an idea that gets closer to a more functional style, I'd be happy to hear about it. Thanks!
What you're trying to do is called F-bounded polymorphism.
In TypeScript this is done via the this keyword. Take a look at Typescript's documentation for polymorphic this types. If you follow the documentation, you should be able to implement what you want :-)
Actually, just make sure that you're returning this in your member methods and you should be fine!

Optional arguments on interface and class can conflict

I have just come across an interesting gotcha where optional arguments on an interface and the implementing class can conflict.
I found this out the hard way (school boy error) whilst experimenting. You cannot spot it in the debugger and I assumed it was me messing up the dependency injection.
I'm guessing this is so an alternative interface can give a differing view on what default behaviour should be?
Is there a compiler warning or style cop rule to help point this out?
public interface MyInterface
{
MyStuff Get(bool eagerLoad = true); //this overrules the implementation.
}
public class MyClass : MyInterface
{
public MyStuff Get(bool eagerLoad = false) //will still be true
{
//stuff
}
}
Remember default arguments are a compile-time feature. The compiler picks up the default argument based on the static type of the reference in question and inserts the appropriate default argument. I.e. if you reference is of the interface type you get one behavior but if the reference is of the class type you get the other in your case.

Generics and Anonymous types

I have a function,
public static IPagedResponse<T> GetPagedResponse<T, TAnon>(
this IQueryable<TAnon> query,
QueryableRequestMessage request)
where T : class
{
//...
}
I'm trying to pass query as an IQueryable of an anonymous type.
var query = _repository.All.Select(
i => new //anon type
{
i.Id,
i.Name,
}
);
var result = query.GetPagedResponse<EftInterfaceDto, ??????>(request);
The issue is I don't know what to put in place of ??????? It can't seem to infer it. And any combinations using .GetType() or typeof() I have tried, failed.
I tried changing the function to be IQueryable<dynamic> but that resulted in other errors, about dynamic not being allowed in Expression trees.
HACK:
I can make it work if I change my function to this:
public static IPagedResponse<T> GetPagedResponse<T, TAnon>(
this IQueryable<TAnon> query,
QueryableRequestMessage request,
T typeSample)
where T : class
{
//...
}
And then pass in an instance of T
var result = query.GetPagedResponse(request, new SomeClassOfT());
This way, I can use type inference to determine the anonymous type TAnon, and don't need to be explicit in the call to the generic (no <types> required).
However, I don't want to do this, as it's clearly not clear what I'm doing.

Resources