Aurelia Fetch usage broken after NPM update - node.js

I just recently did an npm update on my Aurelia CLI project using TypeScript in Visual Studio 2015. I'm using aurelia-fetch-client for making calls to my Web API (.NET Core) backend.
This is an example of the code that was previously compiling and running fine:
import { autoinject } from "aurelia-framework";
import { HttpClient, json } from "aurelia-fetch-client";
import { SupportedCultureInfo } from "../../model/Resources/SupportedCultureInfo";
#autoinject()
export class ResourceService {
constructor(private http: HttpClient) {
}
getSupportedCultures(): Promise<SupportedCultureInfo[]> {
return this.http.fetch("resource/cultures").then(response => response.json());
}
}
Neither Visual Studio nor ReSharper provides any indication in the code editor UI that this will not compile, however after the recent update my build is now broken with this error:
TS2322: Type 'Promise<Response>' is not assignable to type 'Promise<SupportedCultureInfo[]>'
The only workaround I've found so far is to return Promise<any> instead. What I really want to do here though is return classes mapped from the JSON result and have the return type of the method be a strongly-typed Promise as well.
Does anyone know what has changed recently that could cause this? It's very frustrating.
UPDATE:
This is the code that I was able to get working:
import { autoinject } from "aurelia-framework";
import { HttpClient, json } from "aurelia-fetch-client";
import { SupportedCultureInfo } from "../../model/resources/SupportedCultureInfo";
#autoinject()
export class ResourceService {
constructor(private http: HttpClient) {
}
getSupportedCultures(): Promise<SupportedCultureInfo[]> {
return this.http.fetch("resource/cultures")
.then(response => response.json())
.then<SupportedCultureInfo[]>((data: any) => {
const result = new Array<SupportedCultureInfo>();
for (let i of data) {
const info = new SupportedCultureInfo();
info.cultureCode = i.cultureCode;
info.name = i.name;
info.isCurrentCulture = i.isCurrentCulture;
result.push(info);
}
return result;
});
}
}
I had to do two things that weren't immediately obvious:
Use the generic overload of then() to specify the return type I want to use
Explicitly declare any type for the JSON parameter in the second callback, otherwise the gulp transpiler thinks it's still typed as Response.

UPDATE:
I've accidentally hit Enter causing the comment to be posted unfinished. I'm unable to edit the comment so updating my answer as per the updated question.
Chaining Callbacks:
If the API resource/cultures returns the response as SupportedCultureInfo[] and getSupportedCultures() just need to return the response as it is, then there's no need for that second callback. The answer I've posted previously would be sufficient.
I'm guessing a second callback is most likely required in either of these two cases (or for some other reason)
The API returns a different type that has to be mapped to SupportedCultureInfo[]
The API response requires any further processing before sending it back from getSupportedCultures()
If you require a second callback to process the response further then you should call the generic then<TResult> method while you're reading the response as json instead of at a later part in the callback chain.
Reason for reported error:
In the updated code, the reason gulp transpiler treats data as type of Response is due to the fact that a non-generic then(response => response.json()) is being used which returns Promise<Response>.
Instead use then<SupportedCultureInfo[]>(response => response.json()) which would return Promise<SupportedCultureInfo[]> or then<any>(response => response.json()) which would return Promise<any>.
Using either then<SupportedCultureInfo[]>(response => response.json()) or then<any>(response => response.json()) would give you the data in the second callback as SupportedCultureInfo[] or any respectively.
getSupportedCultures(): Promise<SupportedCultureInfo[]> {
return this.http.fetch("resource/cultures")
.then<SupportedCultureInfo[]>(response => response.json())
.then(data => {
// data will be of type SupportedCultureInfo[]
});
}
As the method signature for then<TResult> is intact it should give a strongly typed data varaible.
Solution
The changes to typescript implies that we specify the return type as a type parameter to then<TResult> callback as shown below.
getSupportedCultures(): Promise<SupportedCultureInfo[]> {
return this.http.fetch("resource/cultures").then<SupportedCultureInfo[]>(response => response.json());
}
Details:
I ran into the same situation when I did an npm update. Although my initial thoughts were to blame aurelia-fetch-client as well, I did some digging into the source code to contribute a fix for this issue. But, in my quest I found that typescript is the real culprit here.
The interface Promise<T> had some changes in the way then callback handles the return types. Now, the desired return type needs to be passed in as a type parameter TResult in then<TResult> callback.

Related

Having trouble importing an async function into another file

I've been working on a Guilded Bot that automatically runs a function after x amount of MS. My goal is to automate this function to check a website for new posts. The issue I'm encountering is when trying to import the function and call on it within another file. None of the recommended methods I've found seem to work. Below is my code.
//relay.ts under ./automations/
async function patchNotes(message:Message) {
}
export { patchNotes }
//The main file in src its called index.ts
import path from "path";
import { BotClient, Client, Message } from "#guildedjs/gil";
const { token, token2 } = require('./config.json');
import { patchNotes } from './automations/relay';
const client = new BotClient({
token: token,
prefix: "/",
});
client.once('ready', () => console.log('Ready! Shut down using "ctrl+c"'));
client.login();
process.on("unhandledRejection", console.log)
//setTimeout(() => console.log(client.commands), 600);
// Automations
patchNotes
setInterval(() => patchNotes, 6000);
Currently, this method doesn't return console errors for both Types and other stuff. But it also doesn't run the code at all? I've tried other methods too but none have worked so far. Below are what packages I'm using.
ts-node "10.8.1"
typescript "4.7.4"
It's running Node.js and all files are written in TS. If you need any more details, I'd be happy to give them. Really hoping to get past this issue instead of just putting the function in my main file.
So I've actually just found the answer. So it seems I can use setInterval with async tasks. Below is the code I use to achieve this.
setInterval(async () => {
await function();
}, delay)
As for my other issue. I've figured out that I could just write client.messages.send instead of putting message. in front of it. Reason I didn't follow the advice of the recent comment is because this function shouldn't have any values returning. The reason I added message: Message is because there is a line in my code that uses "message". Which is the one mentioned above. Shoulda added that to this thread. Thanks for the response though. Resolved.

NestJS API calls and reading response data within the API

I'm building a NestJS api and need to make a call to an outside API and parse the responding data. This is a healthCheck to see if another API is up and running. Here is the basic call:
#Get('healthCheck')
public async healthCheck(
#Req() req: Request,
#Res() res: Response,
)
{
const obj = this.test(req);
console.log(obj)
}
test(req) {
const testingURL = 'https://google.com';
return this.http.get(testingURL).pipe(
map((obj: AxiosResponse) => { return obj.data } ),
tap(console.log)
);
}
I've worked extensively with angular in the past and doing any sort of ".toPromise()" or "subscribe()" on the returned data causes a "Converting circular structure to JSON".
Currently the "console.log(obj)" in the first section is printing out an observable object that cannot be parsed:
Observable {
source: Observable {
source: Observable { _subscribe: [Function (anonymous)] },
operator: [Function (anonymous)]
},
operator: [Function (anonymous)]
}
Any advice or helpful hints would be helpful. Most other posts that discuss this issue say that simple mapping the response or adding a promise fixes the issue, but it does not explain further on how to parse that observable once it's been fetched.
Edit: solution posted below.
Thanks to https://stackoverflow.com/users/9576186/jay-mcdoniel for help on this and this project reference https://github.com/jmcdo29/heart-watch/blob/feat/nest-commander/src/character-reader/character-reader.api.service.ts
Here was my working solution:
#Get('healthCheck')
public async healthCheck() {
const obj = await this.getEndpoint('https://google.com');
console.log(obj);
}
private getEndpoint(url): Promise<any> {
return lastValueFrom(
this.http.get<any>(url).pipe(
map((res) => {
return res.data;
}),
),
);
}
This should parse any get (or post) endpoint, as long as parameters/auth isn't required, and return the observable parsed into a promise in nestjs using axios.
Edit: as Jay suggested, lastValueFrom used instead of depricated toPromise()
unless you have a reason, don't inject #Res() to the route handler, just return your data and let Nest handle it rather than having to call res.send(). If you need access to response headers or setting cookies, use #Res({ passthrough: true })
You can return an observable in Nest directly and Nest will handle reading the data and sending it back for you.
if you need to read a value inside the observable, you can use the tap operator from RxJS and pipe(tap(console.log)) to read the data
EDIT 10/15/2021
Now that I've also read that you're wanting to use this data in another API call, you have two options:
use lastValueFrom to convert the RxJS Observable into a Promise so that it can be awaited. Straightforward and easy to work with
use operators like mergeMap or switchMap and chain together your Observables, still returning a single observable in the end. RxJS is like really powerful callbacks with extra options on top, but they can also get complex so this option usually requires a bit more nuance and dedication to ensure the operators are chained correctly.
You should inject HttpService from HttpModule (#nestjs/axios) and get the axios reference like this this.httpService.axiosRef.get(...)
See official documentation for more details.

How to use axios HttpService from Nest.js to make a POST request

I am trying to make a POST request using #nestjs/axios and return the response.
This is my code:
verifyResponse(captcha_response: String): Observable<AxiosResponse<any>> {
return this.httpService.post('<url of rest endpoint to verify captcha>', {
captcha_response
});
}
However, Visual Studio Code says Cannot find name 'AxiosResponse'. Where can I import this from? This type was used in the Nest.js docs as Observable<AxiosResponse<Cat[]>>. I decided to remove this and just use the type Observable<any>.
This works. However, if I use console.log() to view the response I get this:
Observable { _subscribe: [Function (anonymous)] }
Most answers I found on StackOverflow suggest to use some variation of this:
return this.httpService.post('<url of rest endpoint to verify captcha>', {
captcha_response
}).pipe(map(response => response.data));
...but console.log() gives me this:
Observable {
source: Observable { _subscribe: [Function (anonymous)] },
operator: [Function (anonymous)]
}
Some other answers on StackOverflow suggest to use .toPromise(), but apparently this is deprecated.
All in all, I'm new to Nest.js and I'm super confused. Since I can't find one, I would be really grateful for a complete and up-to-date example of how to make a simple POST request and return the response as JSON.
If you want to make the HttpService use a promise instead of on RxJS Observable you can use lastValueFrom wrapping around the this.httpService.post() call. This will transform the Observable into a promise and you can await it as normal. Otherwise, if you just return the observable, Nest will handle waiting for the response for you. Either way, you'll need to make sure to use that map((resp) => resp.data) function you have so you don't end up with circular data in the response object (as Axios's response object is circular by design).
If you're trying to console.log() the data, you'll want to use tap along with map in the form of
this.httpService.post(url, data, options).pipe(
tap((resp) => console.log(resp)),
map((resp) => resp.data),
tap((data) => console.log(data)),
);
tap is essentially a spy method to tap into the observable, look at the data, and let it pass through. It can mutate the data, but generally it's a good idea to leave that to map or other rxjs operators.
For just the simple await ability, something like this is all that's needed
const data = await lastValueFrom(
this.httpService.post(url, data, options).pipe(
map(resp => res.data)
)
);
Example with AxiosRequestConfig object type (TypeScript)
const requestConfig: AxiosRequestConfig = {
headers: {
'Content-Type': 'YOUR_CONTENT_TYPE_HEADER',
},
params: {
param1: 'YOUR_VALUE_HERE'
},
};
const responseData = await lastValueFrom(
this.httpService.post(requestUrl, null, requestConfig).pipe(
map((response) => {
return response.data;
}),
),
);
AxiosResponse should be imported from axios:
import { AxiosResponse } from 'axios'
You can make use of import suggestions by pressing ctrl+space (or options + esc on mac) or by using ctrl+.

Trying to retrieve Data from MongoDB using Typescript

Context: Am not too experienced with TypeScript, as we don't use it at work, am attempting to just build a little portfolio piece for personal exposure.
So to start with this is my code:
import { request, Request, Response } from 'express';
import { Neighborhood as NeighborhoodType } from '../interfaces/neighborhood.interface';
import Neighborhood from '../models/neighborhood';
const fetchNeighborhoods = async (request: Request, response: Response): Promise<void> => {
try {
const neighborhoods: NeighborhoodType[] = await Neighborhood.paginate();
response.status(200).send(neighborhoods);
} catch (error) {
throw error;
}
};
Am attempting to fetch the neighborhoods from the DB, and am receiving the error Type 'PaginateResult<Neighborhood>' is missing the following properties from type 'Neighborhood[]': length, pop, push, concat, and 26 more. on this line const neighborhoods: NeighborhoodType[] = await Neighborhood.paginate();
If I remove the NeighborhoodType[] then the method will work fine. The neighborhood interface is literally an object with a string.
export interface Neighborhood extends Document {
name: string,
}
Is it an issue with MY code or is it an issue with one of the dependencies?
For anyone who encounters this issue:
The problem stems from trying to set the return type. As Mongoose will always return one document, an array of documents or an empty array (unless using onFail()) the return type can be inferred so there is no need to add NeighborhoodType[].
The PaginateResult Type is essentially the type of Array if I'm not mistaken and is expecting the Neighborhood type to have all of the array methods which it will not.

Jest with fetch-mock generating error: TypeError: Cannot read property 'prototype' of undefined when using on nodejs

I'm almost sure it's my mistake, but I spent one entire day trying to solve and I've being failed 😞.
I'm trying to configure the fetch-mock on node to use with jest. I tried a lot of things and the best result that I have is this:
https://user-images.githubusercontent.com/824198/50566568-7b49e400-0d22-11e9-884f-89720899de3a.png
I'm sure my mock is working because if I pass it through parameter to the "Myclass.query" it works perfectly.
I'm also sure that the mock is arriving in my test file, because the mock function is present in the fetch module.
But... all together aren't working 😭.
I created a very simple and small project to see this problem happening:
https://github.com/cesarjr/test-node-fetch
Can anyone help me?
Jest uses the mock at __mocks__/node-fetch.js any time node-fetch is required during the test.
The problem is that the first thing fetch-mock does is require node-fetch.
This means that Request is not defined when it is set on the config here, so calling prototype on the undefined Request causes an error here.
Someone smarter than me might know how to force Jest to require the actual node-fetch when fetch-mock requires node-fetch in the mock for node-fetch, but from what I can see it doesn't look like it is possible.
Looks like you will have to delete the mock at __mocks__/node-fetch.js and pass fetch to your code, something like this:
myclass.js
class MyClass {
static query(fetch, sessionId, query) {
const url = 'https://api.foobar.com';
const body = {
sessionId,
query
};
return fetch(url, {
method: 'post',
body: JSON.stringify(body)
})
.then(res => res.json());
}
}
module.exports = MyClass;
...then create the sandbox in your test and pass it to your code, something like this:
myclass.test.js
const fetch = require('fetch-mock').sandbox();
const MyClass = require('./myclass');
describe('MyClass', () => {
describe('.query', () => {
it('returns the response', () => {
fetch.mock('*', {'result': {'fulfillment': {'speech': 'The answer'}}});
expect.assertions(1);
return MyClass.query(fetch, '123', 'the question').then((data) => {
expect(data.result.fulfillment.speech).toBe('The answer'); // SUCCESS
});
});
});
});
I've now found a reliable way to combine fetch-mock and jest http://www.wheresrhys.co.uk/fetch-mock/#usageusage-with-jest

Resources