I was trying to use streams with grpc.
But when I use the below-published code, it says that subscribe is not a function.
It seems like nestjs/microservice does not create an observable from a stream.
The method used in the docs seems to be outdated.
My code - proto file:
message Rating {
string id = 1;
uint32 value = 2;
string comment = 3;
string type = 4;
}
service RatingsRpcService {
rpc Test (stream GetRatingRequest) returns (stream Rating);
}
message GetRatingRequest {
string id = 1;
}
message Ratings {
repeated Rating items = 1;
}
And the controller file:
#GrpcStreamMethod('RatingsRpcService')
test(msg: Observable<any>, metadata: any): Observable<any> {
const subject = new Subject();
msg.subscribe({
next: (item: any) => {
subject.next({whatever: 'value'});
},
error: (err: any) => console.log(err),
complete: () => subject.complete()
});
return subject.asObservable();
}
And the error I get: TypeError: msg.subscribe is not a function
Did I miss something?
Okay, I guess I found the issue.
It's kinda strange, but changing the type from Observable fixed the error.
I mean this one: messages: any instead of messages: Observable<any> or messages: Observable<RatingInterface>.
Please find the code presented below:
#GrpcStreamMethod('RatingsRpcService')
async findByIdStream(messages: any, metadata: any): Promise<Observable<RatingInterface>> {
const subject = new Subject<RatingInterface>();
messages.subscribe({
next: async (dto: GetRatingDto) => {
const item = await this.ratingsService.findById(dto.id);
subject.next(item);
},
error: (err: any) => {
throw new RpcException('Could not process stream.')
},
complete: () => subject.complete()
});
return subject.asObservable();
}
Related
In node.js I'm getting the error below. Any ideas why?
Conversion of type 'Promise' to type 'Member[]' may be a mistake
because neither type sufficiently overlaps with the other. If this was
intentional, convert the expression to 'unknown' first. Type
'Promise' is missing the following properties from type
'Member[]': length, pop, push, concat, and 26 more.
export async function getFamilyMembers(tenantId: string, familyCode: string): Promise<Member[]> {
return db.collection(`tenants/${tenantId}/members`)
.where('familyCode', '==', familyCode)
.get()
.then(snaps => {
snaps.docs.forEach(doc => {
return { id: doc.id, ...doc.data()}
});
}) as Member[];
}
EDIT:
If I remove the types and change it to
export async function getFamilyMembers(tenantId: string, familyCode: string) {
return db.collection(`tenants/${tenantId}/members`)
.where('familyCode', '==', familyCode)
.get()
.then(snaps => {
snaps.docs.forEach(doc => {
return { id: doc.id, ...doc.data()}
});
});
}
I just have to deal with the problem later.
I get the error
Property 'length' does not exist on type 'void'.
const familyMembers: Member[] | void = await getFamilyMembers(tenantId, familyCode);
if (familyMembers === null) {
isVerified = false;
verificationFailMessage = `Sorry we can't find this code. Please check it is correct.`;
} else if (familyMembers.length === 0) {
I needed to add Promise in front of Member.
export async function getFamilyMembers(tenantId: string, familyCode: string): Promise<Member[]> {
return db.collection(`tenants/${tenantId}/members`)
.where('familyCode', '==', familyCode)
.get()
.then(snaps => {
snaps.docs.forEach(doc => {
return { id: doc.id, ...doc.data()}
});
}) as Promise<Member[]>;
}
I have this:
LocationController.ts
import {GenericController} from './_genericController';
interface Response {
id : number,
code: string,
name: string,
type: string,
long: number,
lat: number
}
const fields = ['code','name','type','long','lat'];
class LocationController extends GenericController{
tableName:string = 'location';
fields:Array<any> = fields;
}
const locationController = new LocationController();
const get = async (req, res) => {
await locationController._get(req, res);
}
export {get};
GenericController.ts
interface Response {
id : number
}
export class GenericController{
tableName:string = '';
fields:Array<any> = [];
_get = async (req, res) => {
try{
const id = req.body['id'];
const send = async () => {
const resp : Array<Response> = await db(this.tableName).select(this.fields).where('id', id)
if (resp[0] === undefined) {
// some error handling
}
res.status(status.success).json(resp[0]);
}
await send();
}catch (error){
// some error handling
}
}
}
What I want to do is to pass the Response interface from LocationController to the GenericController parent, so that the response is typed accurately depending on how the child class has defined it. Clearly it doesn't work like this since the interface is defined outside of the class so the parent has no idea about the Response interface in the LocationController.ts file.
I've tried passing interface as an argument in the constructor, that doesn't work. So is there a way I can make this happen? I feel like I'm missing something really simple.
Typically, generics are used in a situation like this. Here's how I'd do it:
interface Response {
id: number;
}
// Note the generic parameter <R extends Response>
export class GenericController<R extends Response> {
tableName: string = "";
fields: Array<any> = [];
_get = async (req, res) => {
try {
const id = req.body["id"];
const send = async () => {
// The array is now properly typed. You don't know the exact type,
// but you do know the constraint - R is some type of `Response`
let resp: Array<R> = await db(this.tableName).select(this.fields).where("id", id);
if (resp[0] === undefined) {
// some error handling
}
res.status(status.success).json(resp[0]);
};
await send();
} catch (error) {
// some error handling
}
};
}
import { GenericController } from "./_genericController";
interface Response {
id: number;
code: string;
name: string;
type: string;
long: number;
lat: number;
}
const fields = ["code", "name", "type", "long", "lat"];
// Here we tell the GenericController exactly what type of Response it's going to get
class LocationController extends GenericController<Response> {
tableName: string = "location";
fields: Array<any> = fields;
}
const locationController = new LocationController();
const get = async (req, res) => {
await locationController._get(req, res);
};
export { get };
If this is not enough and you wish to somehow know the exact response type you're going to get, I believe the only way is a manual check. For example:
import { LocationResponse } from './locationController';
// ... stuff
// Manual runtime type check
if (this.tableName === 'location') {
// Manual cast
resp = resp as Array<LocationResponse>
}
// ...
You could also check the form of resp[0] (if (resp[0].hasOwnProperty('code')) { ... }) and cast accordingly. There are also nicer ways to write this, but the basic idea remains the same.
Generally, a properly written class should be unaware of any classes that inherit from it. Putting child-class-specific logic into your generic controller is a code smell. Though as always, it all depends on a particular situation.
I'm just getting into Fastify with Typescript and really enjoying it.
However, I'm trying to figure out if I can type the response payload. I have the response schema for serialization working and that may be sufficient, but I have internally typed objects (such as IUser) that it would be nice to have Typescript check against.
The following works great, but I'd like to return an TUser for example and have typescript if I return something different. Using schema merely discludes fields.
interface IUser {
firstname: string,
lastname: string
} // Not in use in example
interface IUserRequest extends RequestGenericInterface {
Params: { username: string };
}
const getUserHandler = async (
req: FastifyRequest<IUserRequest, RawServerBase, IncomingMessage | Http2ServerRequest>
) => {
const { username } = req.params;
return { ... }; // Would like to return instance of IUser
};
app.get<IUserRequest>('/:username', { schema }, helloWorldHandler);
Is there an equivalent of RequestGenericInterface I can extend for the response?
Small Update: It seems that the reply.send() can be used to add the type, but it would be nice for self-documentation sake to provide T higher up.
From the documentation:
Using the two interfaces, define a new API route and pass them as generics. The shorthand route methods (i.e. .get) accept a generic object RouteGenericInterface containing five named properties: Body, Querystring, Params, Headers and Reply.
You can use the Reply type.
interface MiscIPAddressRes {
ipv4: string
}
server.get<{
Reply: MiscIPAddressRes
}>('/misc/ip-address', async (req, res) => {
res
.status(_200_OKAY)
.send({
ipv4: req.ip // this will be typechecked
})
})
After looking at the type definitions, I found out that there is also an alternative way to only type-check the handler (like in Julien TASSIN's answer), like this:
import { FastifyReply, FastifyRequest, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerDefault } from "fastify";
import { RouteGenericInterface } from "fastify/types/route";
interface IUser {
firstname: string;
lastname: string;
}
interface IUserRequest extends RouteGenericInterface {
Params: { username: string };
Reply: IUser; // put the response payload interface here
}
function getUserHandler(
request: FastifyRequest<IUserRequest>,
reply: FastifyReply<
RawServerDefault,
RawRequestDefaultExpression,
RawReplyDefaultExpression,
IUserRequest // put the request interface here
>
) {
const { username } = request.params;
// do something
// the send() parameter is now type-checked
return reply.send({
firstname: "James",
lastname: "Bond",
});
}
You can also create your own interface with generic to save writing repeating lines, like this:
import { FastifyReply, FastifyRequest, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerDefault } from "fastify";
import { RouteGenericInterface } from "fastify/types/route";
export interface FastifyReplyWithPayload<Payload extends RouteGenericInterface>
extends FastifyReply<
RawServerDefault,
RawRequestDefaultExpression,
RawReplyDefaultExpression,
Payload
> {}
then use the interface like this:
function getUserHandler(
request: FastifyRequest<IUserRequest>,
reply: FastifyReplyWithPayload<IUserRequest>
) {
const { username } = request.params;
// do something
// the send() parameter is also type-checked like above
return reply.send({
firstname: "James",
lastname: "Bond",
});
}
If you want to type the handler only, you can perform it this way
import { RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerDefault, RouteHandler, RouteHandlerMethod } from "fastify";
const getUserHandler: RouteHandlerMethod<
RawServerDefault,
RawRequestDefaultExpression,
RawReplyDefaultExpression,
{ Reply: IUser; Params: { username: string } }
> = async (
req: FastifyRequest<IUserRequest, RawServerBase, IncomingMessage | Http2ServerRequest>
) => {
const { username } = req.params;
return { ... }; // Would like to return instance of IUser
};
Trying to type these was a truely awful experience. Thanks to the other answers, this is where I ended up. Bit of a code dump to make life easier for others.
request-types.ts
With this I am standardising my response to optionally have data and message.
import {
FastifyReply,
FastifyRequest,
RawReplyDefaultExpression,
RawRequestDefaultExpression,
RawServerDefault,
} from 'fastify';
type ById = {
id: string;
};
type ApiRequest<Body = void, Params = void, Reply = void> = {
Body: Body;
Params: Params;
Reply: { data?: Reply & ById; message?: string };
};
type ApiResponse<Body = void, Params = void, Reply = {}> = FastifyReply<
RawServerDefault,
RawRequestDefaultExpression,
RawReplyDefaultExpression,
ApiRequest<Body, Params, Reply>
>;
type RouteHandlerMethod<Body = void, Params = void, Reply = void> = (
request: FastifyRequest<ApiRequest<Body, Params, Reply>>,
response: ApiResponse<Body, Params, Reply>
) => void;
export type DeleteRequestHandler<ReplyPayload = ById> = RouteHandlerMethod<void, ById, ReplyPayload>;
export type GetRequestHandler<ReplyPayload> = RouteHandlerMethod<void, ById, ReplyPayload>;
export type PostRequestHandler<Payload, ReplyPayload> = RouteHandlerMethod<Payload, void, ReplyPayload>;
export type PatchRequestHandler<Payload, ReplyPayload> = RouteHandlerMethod<Payload, ById, ReplyPayload>;
export type PutRequestHandler<Payload, ReplyPayload> = RouteHandlerMethod<Payload, ById, ReplyPayload>;
Usage
get-account.ts - GetRequestHandler
export const getAccount: GetRequestHandler<AccountResponseDto> = async (request, reply) => {
const { id } = request.params;
...
const account = await Account.findOne....
...
if (account) {
return reply.status(200).send({ data: account });
}
return reply.status(404).send({ message: 'Account not found' });
};
delete-entity.ts - DeleteRequestHandler
export const deleteEntity: DeleteRequestHandler = async (request, reply) => {
const { id } = request.params;
...
// Indicate success by 200 and returning the id of the deleted entity
return reply.status(200).send({ data: { id } });
};
update-account.ts - PatchRequestHandler
export const updateAccount: PatchRequestHandler<
UpdateAccountRequestDto,
AccountResponseDto
> = async (request, reply) => {
const { id } = request.params;
...
return reply.status(200).send({ data: account });
};
register-account-routes.ts - No errors with provided handler.
export const registerAccountRoutes = (app: FastifyInstance) => {
app.get(EndPoints.ACCOUNT_BY_ID, getAccount);
app.patch(EndPoints.ACCOUNT_BY_ID, updateAccount);
app.post(EndPoints.ACCOUNTS_AUTHENTICATE, authenticate);
app.put(EndPoints.ACCOUNTS, createAccount);
};
I'm writing decorators for the following class method:
export default class API {
...
public async request(url_stub: string, options: any = {}): Promise<any> {
console.log(this)
const url = this.join_url(url_stub);
...
}
}
The functions run as expected when no decorators are applied, but when I apply one of the following decorators:
export function log_func(_target: any,
name: string,
descriptor: PropertyDescriptor): PropertyDescriptor {
const original_function = descriptor.value;
descriptor.value = (... args: any[]) => {
const parameters = args.map((a) => JSON.stringify(a)).join();
const result = original_function.apply(this, args);
const result_str = JSON.stringify(result);
console.log(`Call: ${name}(${parameters}) => ${result_str}`);
return result;
}
return descriptor;
}
export function uri_encode(parameter_index?: number) {
return (_target: any,
name: string,
descriptor: PropertyDescriptor): PropertyDescriptor => {
const original_function = descriptor.value;
descriptor.value = (... args: any[]) => {
args = args.map((arg, index) => {
if (parameter_index === undefined || index === parameter_index) {
arg = encodeURI(arg);
}
return arg;
});
const result = original_function.apply(this, args);
return result;
}
return descriptor;
}
}
as such:
#uri_encode(0)
#log_func
public async request(url_stub: string, options: any = {}): Promise<any> {
this inside the class method is now undefined. I'm guessing this is because the method is technically being called from outside the context of the class.
Is there a flaw in my design, or is this what I should expect? If so is there a way for me to retain the context while still using decorators?
The problem was in my decorator. Apparently modifying the original descriptor value with a () => {} function was the problem. Changing it to function () {} made it work.
God knows why.
I have a function in which I'm calling an instance of Manager's onSpecificData() to which I'm subscribing in order to update my application's state (I'm managing a state on the server-side as well).
The problem is that in the SomeManager's implementation of onSpecificData() I'm merging 3 different Observables using merge() operator, which for some reason triggers the invocation of all the underlying Observable's operators even though only 1 of the sources is the one that's emitting a value
SomeManager.ts
export class DerivedManager implements Manager {
private driver: SomeDriver;
constructor(...) {
this.driver = new SomeDriver(...);
}
public onSpecificData(): Observable<DataType> {
return merge(
this.driver.onSpecificData(Sources.Source1).map((value) => {
return {source1: value};
}),
this.driver.onSpecificData(Sources.Source2).map((value) => {
return {source2: value};
}),
this.driver.onSpecificData(Sources.Source3).map((value) => {
return {source3: value};
})
);
}
Manager.ts
export type DataType = Partial<{value1: number, value2: number, value3: number}>;
export interface Manager {
onSpecificData(): Observable<DataType>;
}
SomeDriver.ts
export const enum Sources {
Source1,
Source2,
Source3,
}
export class SomeDriver extends Driver {
private static specificDataId = 1337; // some number
private handler: Handler;
constructor(...) {
super(...);
this.handler = new Handler(this.connection, ...);
// ...
}
// ...
onSpecificData(source: Sources): Observable<number> {
return this.handler
.listenToData<SpecificDataType>(
SomeDriver.specificDataId,
(data) => data.source === source)
).map((data) => data.value);
}
}
Driver.ts
export abstract class Driver {
protected connection: Duplex;
constructor(...) {
// init connection, etc...
}
public abstract onSpecificData(source: number);
// some implementations and more abstract stuff...
}
Handler.ts
export class Handler {
private data$: Observable<Buffer>;
constructor(private connection: Duplex, ...) {
this.data$ = Observable.fromEvent<Buffer>(connection as any, 'data');
}
listenToData<T>(dataId: number, filter?: (data: T) => boolean) {
return this.data$
.map((data) => {
// decode and transform
})
.filter((decodedData) => !decodedData.error && decodedData.value.id)
.do((decodedData) => {
console.log(`Got ${decodedData.value.id}`);
})
.map((decodedData) => decodedData.value.value as T)
.filter(filter || () => true);
}
}
And finally, subscribe()-ing:
export default function(store: Store<State>, manager: Manager) {
// ...
manager.onSpecificData()
.subscribe((data) => {
// update state according to returned data
});
}
As you can see, there is only 1 underlying Observable (data$) but apparently the operator chain in listenToData<T>() is invoked 3 times for each value emitted by it. I already know this is because of SomeManager#onSpecificData()'s merge of those 3 Observables, but I don't know why this happens. I want it to be invoked once for each value.
Help will be much appreciated.
I solved this in a "hacky" way, in my opinion. I replaced data$ with a Subject, created an observable from stream's 'data' event, moving all the shared logic to that observable and emit a value from the subject, like so:
export class Handler {
private dataSrc = new Subject<DecodedData>();
constructor(private connection: Duplex, ...) {
Observable.fromEvent<Buffer>(connection as any, 'data')
.map((data) => {
// decode and transform
})
.filter((decodedData) => !decodedData.error)
.do((decodedData) => {
console.log(`Got ${decodedData.value.id}`);
})
.subscribe((decodedData) => {
this.dataSrc.next(decodedData);
});
}
listenToData<T>(dataId: number, filter?: (data: T) => boolean) {
return this.dataSrc
.filter((decodedData) => decodedData.value.id === dataId)
.map((decodedData) => decodedData.value.value as T)
.filter(filter || () => true);
}
}
Not exactly the solution I was looking for, but it works. If anyone has a better solution, which better suits the "Rx way" to do stuff, I'd love to hear it.