NestJS GRPC Error: call.sendMetadata is not a function - nestjs

I'm trying to send a metadata from the server side gprc using NestJS framework. On the official NestJS guide here: it shows a example of doing the server side grpc metadata:
#Controller()
export class HeroesService {
#GrpcMethod()
findOne(data: HeroById, metadata: Metadata, call: ServerUnaryCall<any>): Hero {
const serverMetadata = new Metadata();
const items = [
{ id: 1, name: 'John' },
{ id: 2, name: 'Doe' },
];
serverMetadata.add('Set-Cookie', 'yummy_cookie=choco');
call.sendMetadata(serverMetadata);
return items.find(({ id }) => id === data.id);
}
}
On my code, I wrote a similar code:
#GrpcMethod('ClinicService', 'GetCustomerClinicNames')
async GetCustomerClinicNames_GRPC(data: CustomerClinicID, call:ServerUnaryCall<Any,Any>) {
const metadata = new Metadata();
const result = await this.clinicService.GetClinicName(Number(data.customer_id));
console.log(result);
metadata.add('context', "Jello")
call.sendMetadata(metadata)
return { response: result};
}
However, it gives me an error says:
[Nest] 53188 - 04/06/2022, 5:17:50 PM ERROR [RpcExceptionsHandler] call.sendMetadata is not a function
TypeError: call.sendMetadata is not a function
at ClinicController.GetCustomerClinicNames_GRPC (C:\Users\Vibrant\Desktop\core-samples\src\clinic\clinic.controller.ts:118:10)
at runMicrotasks (<anonymous>)
at processTicksAndRejections (internal/process/task_queues.js:95:5)
at C:\Users\Vibrant\Desktop\core-samples\node_modules\#nestjs\microservices\context\rpc-proxy.js:11:32
But I think the .sendMetadata is indeed a function. Where am I wrong about this?
For the sendMetadata Function, it is defined as:
export declare type ServerSurfaceCall = {
cancelled: boolean;
readonly metadata: Metadata;
getPeer(): string;
sendMetadata(responseMetadata: Metadata): void;
getDeadline(): Deadline;
} & EventEmitter;
export declare type ServerUnaryCall<RequestType, ResponseType> = ServerSurfaceCall & {
request: RequestType;
};

So apparently you need to keep the metadata parameter in your async GetCustomerClinicNames_GRPC. I tried with and without and it only worked when I had it.
so for example, I tried:
#GrpcMethod('HeroesService', 'FindOne')
findOne(data: any, call: ServerUnaryCall<any, any>): any
and that didn't work, then I tried:
#GrpcMethod('HeroesService', 'FindOne')
findOne(data: any, _metadata: Metadata, call: ServerUnaryCall<any, any>): any
and it worked by doing something like:
#GrpcMethod('HeroesService', 'FindOne')
findOne(data: any, _metadata: Metadata, call: ServerUnaryCall<any, any>): any {
const items = [
{ id: 1, name: 'John' },
{ id: 2, name: 'Doe' },
];
const metadata = new Metadata();
metadata.add('context', 'Jello');
call.sendMetadata(metadata);
return items.find(({ id }) => id === data.id);
}
I'm not sure why though, since we are using named arguments.

Related

How to use data from DB as initial state with Zustand

I'm using the T3 stack (TypeScript, tRPC, Prisma, Next, etc) and I want to either load data from my database using a tRPC query, or use an empty array in my Zustand store. I keep getting an error saying:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app See https://reactjs.org/link/invalid-hook-call for tips about how to debug
and fix this problem. error - TypeError: Cannot read properties of
null (reading 'useContext')
(node:internal/process/task_queues:96:5) { page: '/' }
here's the code generating the error:
type Image = {
url: string;
prompt: string;
};
interface UserState {
images: Image[] | [];
isLoading: boolean;
addImage: (url: string, prompt: string) => void;
removeImage: (url: string, prompt: string) => void;
setLoading: () => void;
}
export const useStore = create<UserState>((set) => {
const { data: sessionData } = useSession();
const dbImages = trpc.images.list.useQuery({
limit: 20,
userId: sessionData?.user?.id ?? "",
}).data?.items;
return {
// initial state
images: dbImages ? dbImages : [],
isLoading: false,
// methods for manipulating state
addImage: (url, prompt) => {
set((state) => ({
images: [
...state.images,
{
url: url,
prompt: prompt,
} as Image,
],
}));
},
removeImage: (url: string) => {
set((state) => ({
images: state.images?.filter((x) => x.url !== url),
}));
},
setLoading: () => {
set((state) => ({
isLoading: !state.isLoading,
}));
},
};
});
What am I doing wrong here? I'm still in the learning phases and would appreciate best practices, etc.

How to assign argument of type promise

I'm trying to work on some unit test with jest to test, I have two function in separate file called generator in which I create fake data :
generator.ts
export async function generateReportData(overide = {}) {
return {
clientId: faker.datatype.number(),
incidentDesc: faker.lorem.sentence(15),
city: faker.address.city(),
country: faker.address.country(),
createdAt: new Date(),
...overide,
};
}
export async function generateReportsData(n: number = 1, overide = {}) {
return Array.from(
{
length: n,
},
(_, i) => {
return generateReportData({ id: i, ...overide });
}
);
}
when I try to create unit test with jest like that :
test("should return report list", async () => {
const reportsData = generateReportsData(4);
const spy = jest
.spyOn(ReportRepository, "getReports")
.mockResolvedValueOnce(reportsData);
const controller = new ReportController();
const reports = await controller.getReports();
expect(reports).toEqual(reportsData);
expect(spy).toHaveBeenCalledWith();
expect(spy).toHaveBeenCalledTimes(1);
});
I got this error :
Argument of type 'Promise<{ clientId: number; incidentDesc: string;
city: string; country: string; createdAt: Date; }>[]' is not
assignable to parameter of type 'Report[] | Promise<Report[]>'.
Here is my repository :
import { getRepository } from "typeorm";
import { Report } from "../models";
export interface IReportPayload {
incidentDesc: string;
city: string;
country: string;
}
export const getReports = async (): Promise<Array<Report>> => {
const reportRepository = getRepository(Report);
return reportRepository.find();
};
The problem is most likely that you did not type the return value from the generateReportData function, so typescript doesn't know that the type is actually Report (also, why is it async?). Just add the type and you should be fine:
export function generateReportData(overide = {}): Report

How to test Validation pipe is throwing the expect error for improperly shaped request on NestJS

I'm using NestJS 7.0.2 and have globally enabled validation pipes via app.useGlobalPipes(new ValidationPipe());.
I'd like to be able to have a unit test that verifies that errors are being thrown if the improperly shaped object is provided, however the test as written still passes. I've seen that one solution is to do this testing in e2e via this post, but I'm wondering if there is anything I'm missing that would allow me to do this in unit testing.
I have a very simple controller with a very simple DTO.
Controller
async myApi(#Body() myInput: myDto): Promise<myDto | any> {
return {};
}
DTO
export class myDto {
#IsNotEmpty()
a: string;
#IsNotEmpty()
b: string | Array<string>
}
Spec file
describe('generate', () => {
it('should require the proper type', async () => {
const result = await controller.generate(<myDto>{});
// TODO: I expect a validation error to occur here so I can test against it.
expect(result).toEqual({})
})
})
It also fails if I do not coerce the type of myDto and just do a ts-ignore on a generic object.
Just test your DTO with ValidationPipe:
it('validate DTO', async() => {
let target: ValidationPipe = new ValidationPipe({ transform: true, whitelist: true });
const metadata: ArgumentMetadata = {
type: 'body',
metatype: myDto,
data: ''
};
await target.transform(<myDto>{}, metadata)
.catch(err => {
expect(err.getResponse().message).toEqual(["your validation error"])
})
});
You can find here complete test examples for ValidationPipe in Nestjs code repository
To test custom ValidationPipe:
let target = new ValidationDob();
const metadata: ArgumentMetadata = {
type: 'query',
metatype: GetAgeDto,
data: '',
};
it('should throw error when dob is invalid', async () => {
try {
await target.transform(<GetAgeDto>{ dob: 'null' }, metadata);
expect(true).toBe(false);
} catch (err) {
expect(err.getResponse().message).toEqual('Invalid dob timestamp');
}
});

GraphQL error Cannot perform update query

I'm working on a ToDo list application in NodeJS, Koa, and GraphQL.
I wrote an update card mutation but when I run the query to update I get the following error:
Cannot perform update query because update values are not defined. Call "qb.set(...)" method to specify updated values.
The mutation:
import { getRepository } from 'typeorm';
import { Card } from '../../entities/card';
export const updateCardMutation = {
async updateCard(_, { id, patch }): Promise<Card> {
const repository = getRepository(Card);
const card = await repository.findOne({ id });
const result = await repository.update(id, patch);
return {
...card,
...patch,
};
},
};
I would like to know what I'm doing wrong and if something more is needed it please notify me so I will edit the question accordingly
card entity:
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
#Entity('cards')
export class Card {
#PrimaryGeneratedColumn('uuid')
id: string;
#CreateDateColumn()
created_at: Date;
#UpdateDateColumn()
updated_at: Date;
#Column('text')
title: string;
#Column('text', {
nullable: true,
})
description: string;
#Column('boolean', {
default: 'false',
})
done: boolean;
}
You need to spread the update Object.
export const updateCardMutation = {
async updateCard(_, { id, patch }): Promise<Card> {
const repository = getRepository(Card);
const card = await repository.findOne({ id });
const result = await repository.update(id, {...patch}); // here
return {
...card,
...patch,
};
},
};
The issue was when I was calling the updateMutation, it was creating the patch object of anonymous type. So it just needed to be clean before going to the DB engine
I resolved my issues by adding the following code:
{ ...patch }
Inside the next script:
export const updateCardMutation = {
async updateCard(_, { id, patch }): Promise<Card> {
const repository = getRepository(Card);
const card = await repository.findOne({ id });
const result = await repository.update(id, { ...patch }); // Added here
return {
...card,
...patch,
};
},
};
In this way, I was able to update my card.
https://github.com/typeorm/typeorm/blob/master/docs/update-query-builder.md
As an error Call qb.set() , typeorm query builder are different with other orm
await getRepository().createQueryBuilder().update(Card).set(patch)
.where("id = :id", { id })
.execute();
some how patch object may stringify [object], so you can spread it like set({...patch})
I have had this error before with my update query is nestjs & graqhql
Cannot perform update query because update values are not defined
I have fixed it by using the save() function from the repository on the same id, so I have changed from this
async update(
id: number,
updateEmployeeInput: UpdateEmployeeInput,
): Promise<Employee> {
await this.employeeRepository.update(id, updateEmployeeInput);
return this.employeeRepository.findOneOrFail(id);
}
to this
async update(
id: number,
updateEmployeeInput: UpdateEmployeeInput,
): Promise<Employee> {
await this.employeeRepository.save(updateEmployeeInput)
return this.employeeRepository.findOneOrFail(id);
}

How do I make typescript work with promises?

So, I am using typescript on a node/express/mongoose application and I am trying to have my code typecheck without errors.
I define this mongoose model:
import * as mongoose from 'mongoose';
const City = new mongoose.Schema({
name: String
});
interface ICity extends mongoose.Document {
name: string
}
export default mongoose.model<ICity>('City', City);
and this controller:
import * as Promise from 'bluebird';
import CityModel from '../models/city';
export type City = {
name: string,
id: string
};
export function getCityById(id : string) : Promise<City>{
return CityModel.findById(id).lean().exec()
.then((city) => {
if (!city) {
return Promise.reject('No Cities found with given ID');
} else {
return {
name: city.name,
id: String(city._id)
};
}
});
}
The problem is that for some reason, typescript resolves my 'getCityById' function as returning a Promise<{}> rather than a Promise<City> as it should.
Failed attempts:
I tried to wrap the return object in a Promise.resolve
I tried to use new Promise and rely on mongoose's callback API as opposed to their promise API
typescript resolves my 'getCityById' function as returning a Promise<{}> rather than a Promise as it should.
This is because of multiple return paths.
if (!city) {
return Promise.reject('No Cities found with given ID');
} else {
return {
name: city.name,
id: String(city._id)
};
}
Specifically Promise.reject is untyped.
Quick fix
Assert:
if (!city) {
return Promise.reject('No Cities found with given ID') as Promise<any>;
} else {
return {
name: city.name,
id: String(city._id)
};
}

Resources