Typescript : Create custom type axios instance - javascript

I want to create a complex type name api with an axios instance so that I can use the basic instance like this:
api.get("url")...
But also to be able to have new dynamic bodies such as:
api.myApi.get("url")
And finally, be able to read the list of instances like this:
api.list
Must be return
myApi
When I want to extend AxiosInstance or inject properties to the main instance, I always get a type error.
Any idea?
My test :
type apiInstances = Record<string, AxiosInstance>
type apiList = Record<'list', string[]>
let api: apiInstances | apiList
const instance = (baseURL: string) =>
axios.create({
baseURL
})
const instances = { instance('myApi') }
api = { ...instances, ...instance(''), list }
Error when I write api.myApi.get("...")
Property 'myApi' does not exist on type 'apiInstances | apiList'

I think you're on the good way.
The best thing you can do is to abstract axios client as you would do with another http client and hide axios implementation.
For this, you can make a class for instead
export class HttpClient extends HttpMethods {
_http: AxiosInstance;
constructor() {
super();
this._http = axios.create({
...this._options,
validateStatus: status => status >= 200 && status < 400,
});
}
setAdditionnalHeaders(headers: object, override?: boolean): void {
this._options = _.merge({}, override ? {} : this._options, { headers });
}
public async get<T>(path: string, params?: any, headers?: object): Promise<Result<T>> {
if (headers) {
this.setAdditionnalHeaders(headers, true);
}
const result = await this._http({
method: 'GET',
url: path,
params,
headers: this._options,
...this.hydrateConfig(this._config),
});
return result;
}
}
export abstract class HttpMethods {
public _http: any;
protected _options: object;
public abstract get<T>(path: string, params?: any, headers?: object): Promise<Result<T>>;
}
And then play with chainable functions where you will inject your class which hide that axios is use in this.
export function httpBuilder<I extends HttpMethods>(client: I): IHttpBuilder {
return {
...httpRequestMethods(client),
};
}
function httpRequestMethods(instance: HttpMethods): BuilderMethod {
const { config } = instance;
return {
get<T>(path: string, params?: any, headers?: object): ChainableHttp & HttpExecutableCommand<T> {
return {
...executableCommand<T>(path, instance, 'GET', requests, null, params, headers),
};
},
}
function executableCommand<T>(
path: string,
instance: HttpMethods,
commandType: CommandType,
requests: RequestType[],
data?: any,
params?: any,
headers?: object,
): HttpExecutableCommand<T> {
return {
async execute(): Promise<Result<T>> {
const result = await getResolvedResponse<T>(path, instance, commandType, data, params, headers);
return result;
},
};
}
async function getResolvedResponse<T>(
path: string,
instance: HttpMethods,
commandType: CommandType,
data?: any,
params?: any,
headers?: object,
): Promise<Result<T>> {
let result: Result<T>;
if (commandType === 'GET') {
result = await instance.get<T>(path, params, headers);
}
return result;
}
This is an example for help if you want to perform extra feature on your http client whether is axios, fetch or whatever you want :-)

https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeystype
type ApiKeys = "myApi" | "yourApi";
type Api = Partial<Record<ApiKeys, AxiosInstance>>;

I have found solution :
type Api = {
[key: string]: AxiosInstance
list: never
}
let api: Api
That work like a charm

Related

I'm new in Nest JS. I want to serialize custom response

I want to return this object on every response:
class Response<T> {
success: boolean
message: string
data: T
}
but built in serializer can't process it because it waits for object which is under serialization. This is my solution. I created a custom response serializer and inherit built in one and map response. It works excellent for now! Offered me better way please.
export interface PlainLiteralObject {
[key: string]: any;
}
#Injectable()
export class ResponseSerializerInterceptor extends ClassSerializerInterceptor {
public intercept(context: ExecutionContext, next: CallHandler<any>): Observable<any> {
const contextOptions = super.getContextOptions(context);
const options = {
...super.defaultOptions,
...contextOptions,
};
return next.handle().pipe(map((res: Response<PlainLiteralObject> | Response<Array<PlainLiteralObject>>) => {// data property holds object which is under serialization
const data = super.serialize(res.data, options);
res.data = data;
return res;
}));
}
}

NestJS setMetadata not working from authGuard

I have a few decorators that when called, I want to setMetadata to use it in my logging,
In my controller, I have these:
#Post("somePath")
#Permission("somePermission")
#UseGuards(JwtAuthGuard)
#HttpCode(200)
#Grafana(
"endpoint",
"functionalmap"
)
async getSubscriptionFromPwebFormFilter(
#Body(ValidationPipe) someDto: someDtoType
): Promise<ISuccessResponse> {
// some logic
}
In my decorators I want to set some data into the metadata to use in my logging inteceptor,
Grafana decorator:
export const Grafana = (functionalMap: string, endpoint: string) =>
applyDecorators(
SetMetadata("endpoint", endpoint),
SetMetadata("functionalMap", functionalMap)
);
AuthGuard decorator:
#Injectable()
export class JwtAuthGuard extends AuthGuard("jwt") {
constructor(
private readonly reflector: Reflector,
private readonly someService: SomeService
) {
super();
}
public async canActivate(context: ExecutionContext): Promise<boolean> {
const role = this.reflector.get<string>("permission", context.getHandler());
const request = context.switchToHttp().getRequest();
const { valueToLog } = request.body;
const jwtToken = request.headers.authorization;
console.log("check value exist", valueToLog);
SetMetadata("valueToLog", valueToLog);
}
Now, in my logging interceptor, I am getting all the values of the metadata this way:
#Injectable()
export default class LoggingInterceptor {
constructor(private readonly reflector: Reflector) {}
intercept(context: ExecutionContext, next: CallHandler) {
const executionStart = Date.now();
return next.handle().pipe(
tap((responseData) => {
const response = context.switchToHttp().getResponse<ServerResponse>();
const { statusCode } = response;
const valueToLog = this.reflector.get(
"valueToLog",
context.getHandler()
); // this is undefined
const endpoint = this.reflector.get("endpoint", context.getHandler()); // this have value
const functionalMap = this.reflector.get(
"functionalMap",
context.getHandler()
); // this have value
...
// some logic
})
);
}
}
In my case, the value of endpoint and functionalMap can be retrieved from the reflector, however, valueToLog is appearing as undefined,
Is setting of metadata not working for auth guard decorator?
I will be using the term "Decorator" in this explanation so I will first define it.
Decorators are functions that accept information about the decorated declaration.
Ex:
function ClassDecorator(target: typeof DecoratedClass) {
// do stuff
}
// it can then be used like this
#ClassDecorator
class DecoratedClass {}
when calling #SetMetadata(key, value) it returns a decorator. Functions like SetMetadata are referred to as Decorator Factories.
Decorators use the form #expression, where expression must evaluate to a function that will be called at runtime with information about the decorated declaration.
In your example you called SetMetadata("valueToLog", valueToLog). This returns a decorator. Decorators must be called with information about the decorated declaration. To actually attach the metadata you can do something like this:
SetMetadata("valueToLog", valueToLog)(context.getHandler());

Typescript overloads ignored after the first overload

Problem
When creating an overload method for a specific method, now call to it only works for the first defined overload.
If the call works with the second overload, It raises an error because the typing are not corresponding to the first overload definition.
Example
I have an AxiosWrapper Class that I created so I could add some overloads method.
The generic function prototype of Axios is the last one.
export interface MeiliAxiosWrapperInterface {
post(
url: string,
data: IndexRequest,
): Promise<IndexResponse>
post<T = any, R = AxiosResponse<EnqueuedUpdate>>(
url: string,
data?: T,
): Promise<R>
}
// axios-wrapper.ts
import * as Types from './types'
class AxiosWrapper implements Types.MeiliAxiosWrapper {
post(
url: string,
data: Types.IndexRequest,
): Promise<Types.IndexResponse>
post<T = any, R = AxiosResponse<Types.EnqueuedUpdate>>(
url: string,
data?: T,
): Promise<R> {
return this.instance.post(url, data, config) // this.instance is an axios instance
}
}
Success
This implementations works great with this method (the class in which this method is present extends the AxiosWrapper, so this.post uses the AxiosWrapper):
class Client extends AxiosWrapper {
//...
async createIndex(data: Types.IndexRequest): Promise<Indexes> {
const url = `/indexes`
const index = await this.post(url, data);
return new Indexes(this.config, index.uid)
}
}
Fails
On this method it used to work when i was only using the default axios prototype. But now that I have added an overload it fails:
class Indexes extends AxiosWrapper implements Types.Indexes {
//...
addDocuments(
documents: Types.Document[],
): Promise<Types.EnqueuedUpdate> {
const url = `/indexes/${this.indexUid}/documents`
return this.post(url, documents)
}
}
with this error being raised:
Argument of type 'Document<any>[]' is not assignable to parameter of type 'IndexRequest'.
Property 'uid' is missing in type 'Document<any>[]' but required in type 'IndexRequest'.
My error was that I didn't add the final overload which is not supposed to be used for comparison but just for code.
export interface MeiliAxiosWrapperInterface {
post(
url: string,
data: IndexRequest,
): Promise<IndexResponse>
post<T = any, R = AxiosResponse<EnqueuedUpdate>>(
url: string,
data?: T,
): Promise<R>
}
// axios-wrapper.ts
import * as Types from './types'
class AxiosWrapper implements Types.MeiliAxiosWrapper {
post(
url: string,
data: Types.IndexRequest,
): Promise<Types.IndexResponse>
post<T = any, R = AxiosResponse<Types.EnqueuedUpdate>>(
url: string,
data?: T,
): Promise<R>
post(
url: string,
data?: any,
): Promise<any> {
return this.instance.post(url, data, config) // this.instance is an axios instance
}
}

ERROR Error: Uncaught (in promise): Error: Required parameter participantId was null or undefined when calling getActive

I am using a angular 8 application with swagger for the api calls. And I have this service:
#Injectable({
providedIn: 'root'
})
export class ObjectiveService {
constructor(private http: HttpClient, private challengeService: ChallengeService) {}
getChallenges(patientUUID: string): Observable<ChallengeDTO[]> {
return this.challengeService.getActive(patientUUID);
}
}
And the swagger api call looks like this:
public getActive(participantId: string, observe?: 'body', reportProgress?: boolean): Observable<Array<ChallengeDTO>>;
public getActive(participantId: string, observe?: 'response', reportProgress?: boolean): Observable<HttpResponse<Array<ChallengeDTO>>>;
public getActive(participantId: string, observe?: 'events', reportProgress?: boolean): Observable<HttpEvent<Array<ChallengeDTO>>>;
public getActive(participantId: string, observe: any = 'body', reportProgress: boolean = false ): Observable<any> {
if (participantId === null || participantId === undefined) {
throw new Error('Required parameter participantId was null or undefined when calling getActive.');
}
let headers = this.defaultHeaders;
// authentication (Bearer) required
if (this.configuration.accessToken) {
const accessToken = typeof this.configuration.accessToken === 'function'
? this.configuration.accessToken()
: this.configuration.accessToken;
headers = headers.set('Authorization', 'Bearer ' + accessToken);
}
// to determine the Accept header
const httpHeaderAccepts: string[] = [
'text/plain',
'application/json',
'text/json'
];
const httpHeaderAcceptSelected: string | undefined = this.configuration.selectHeaderAccept(httpHeaderAccepts);
if (httpHeaderAcceptSelected !== undefined) {
headers = headers.set('Accept', httpHeaderAcceptSelected);
}
// to determine the Content-Type header
const consumes: string[] = [
];
return this.httpClient.get<Array<ChallengeDTO>>(`${this.configuration.basePath}/api/patient/${encodeURIComponent(String(participantId))}/Challenge`,
{
withCredentials: this.configuration.withCredentials,
headers: headers,
observe: observe,
reportProgress: reportProgress
}
);
}
So I pass an id with the method. But I still get this error:
core.js:7376 ERROR Error: Uncaught (in promise): Error: Required parameter participantId was null or undefined when calling getActive.
Error: Required parameter participantId was null or undefined when calling getActive.
at ChallengeService.push../src/app/generated/api/challenge.service.ts.ChallengeService.getActive (challenge.service.ts:76)
But If I hardcoded the participant id, for example like this:
getChallenges(patientUUID: string): Observable<ChallengeDTO[]> {
return this.challengeService.getActive('0d584905-fc20-4723-870a-0f0214419507');
}
Then it works.
So what I have to change?
Thank you

is not assignable to type error with Typescript 2.4.1

I'm developping a NodeJS app with typescript. I've updated typescript to 2.4.1 and I have a "is not assignable to type" error with Generics class. I've read that the new version has improved checking for generics (https://blogs.msdn.microsoft.com/typescript/2017/06/27/announcing-typescript-2-4/). But I still can't see what's wrong with my code...
I have a factory :
export abstract class EntityClass {
public abstract getTable (): string
}
export class FactoryClass {
public getRepository<T extends EntityClass> (target): Repository<T> {
return new Repository<T>(target)
}
}
My Repository class :
export interface NxtDbColumnValuePair {
column: string
value: any
}
export class Db {
public insert (): Db {
return this
}
public setTable (table: string) {
return this
}
public setColumnsValuesPair (colValPairs: NxtDbColumnValuePair[]) {
return this
}
public execute (): Promise<any> {
return Promise.resolve({ inserId: 1 })
}
}
export class Repository<T extends EntityClass> {
private db: Db = new Db()
constructor (private entity) {}
public insert (entityInstance: T): Promise<T> {
const propertiesNameEntity = Object.getOwnPropertyNames(entityInstance)
const colValPairs: NxtDbColumnValuePair[] = propertiesNameEntity
.map((prop: string) => {
return {
column: prop,
value: entityInstance[prop],
}
})
return this.db.insert() // Insert value in SQL table
.setTable(entityInstance.getTable())
.setColumnsValuesPair(colValPairs)
.execute()
.then((results) => results.inserId) // Get new id inserted
.then((id) => this.find(id)) // get entity by id and return it
}
public find (id?: number): Promise<T> { // I didn't put the code for this method but this is the definition
return Promise.resolve(new this.entity())
}
}
And this is where the code doesn't work :
export class User {}
export class Client {}
export class Token extends EntityClass {
public user: User
public client: Client
public token: string
public getTable (): string {
return 'token'
}
}
export class MyClass {
public generateNewToken (user: User, client: Client): Promise<Token> {
const factory: FactoryClass = new FactoryClass()
const token: string = '123456789' // Generated randomly
const tokenClass: Token = new Token() // this class extends EntityClass
tokenClass.user = user
tokenClass.client = client
tokenClass.token = token
return factory.getRepository(Token).insert(tokenClass) // Error here !
}
}
And the error is : Type 'Promise< EntityClass >' is not assignable to type 'Promise< Token >'. It works with typescript 2.2.1.
Thank you

Categories

Resources