MongoDB aggregation with typescript - node.js

I have a User model and I want to perform generic aggregation. mean any array of object I pass to this function it executes.
this is the sample of getUser function
public async getUser(aggregate: object): Promise<ResponseDTO> {
let response = {} as ResponseDTO;
const [err, user] = await To(User.aggregate(aggregate));
if (user) {
response = { success: true, data: user, message: "User fround" };
}
else
response = { success: false, message: "User not fround" };
return response;
}
and I pass this as a Parameter
const query = [
{
$match: {
name:"Jon"
}
},
{
$project:{
_id:1
}
}
]
const userRes = await getUser(query);
But I'm not able to run the program it's giving me syntax error on getUser function
*(method) Model<any, any, any>.aggregate(pipeline?: any[] | undefined): Aggregate<any[]> (+1 overload)
Argument of type 'Aggregate<any[]>' is not assignable to parameter of type 'Promise'.
Type 'Aggregate<any[]>' is missing the following properties from type 'Promise': [Symbol.toStringTag], finally*
I tried to change object to any, Array or Array in getUser parameter
here is the SS of the Error
PS: I'm using node with typescript and IDE is VSCode

Mongoose queries have their own types, and you should use those types to avoid such errors.
You can import those types for anything you need directly from Mongoose package.
I strongly recommend using the Typegoose package, which helps to create fully typed MongoDB schemas. and by that allow you to use those types as a response, find and update queries and much more!
reference:
https://typegoose.github.io/typegoose/docs/guides/quick-start-guide
Example for your usage with correct type:
import { FilterQuery } from 'mongoose';
public async getUser(aggregate: FilterQuery<ResponseDTO>): Promise<ResponseDTO> {
let response = {} as ResponseDTO;
const [err, user] = await To(User.aggregate(aggregate));
if (user) {
response = { success: true, data: user, message: "User fround" };
}
else
response = { success: false, message: "User not fround" };
return response;
}

Mongoose provides PipelineStage interface for pipes We can use that right away
import { PipelineStage } from "mongoose";
public async getUser(aggregate: PipelineStage[]):
Promise<ResponseDTO> {
let response = {} as ResponseDTO;
const [err, user] = await User.aggregate(aggregate);
if (user) {
response = { success: true, data: user, message: "User found" };
}
else
response = { success: false, message: "User not found" };
return response;
}

Related

How to dynamically assign a name and type to unknown object property in Typescript

I'm using Shopify's rest client for node and its request and response look something like this:
request
client.get({
path: 'orders/count.json',
query: { fulfillment_status: 'unfulfilled' }
})
If there's an error:
{
"errors": "[API] Invalid API key or access...",
"code": 2342,
"statusText": "Authentication Error",
"Headers": "..."
}
If there's no error:
{
"body": { "count": 8 },
"code": 2342,
"statusText": "Authentication Error",
"Headers": "..."
}
I'd like to add some boilerplate over this client library so that I can get the typings of the response. This is what I'm trying to do but it's not working too well:
const customClient = {
get: async <T, K extends string>(params: GetRequestParams) => {
const response = (await client.get(params));
if (response.body.errors) return { errors: response.body.errors };
// somehow index it. obviously not with the type declaration???
return { [K]: response.body[K] as T };
},
}
With the hopes that I can use it as.
const { count, error } = customClient.get<number, "count">({ ... });
Any help would be appreciated. I have an entire file of the Shopify API types that I would like to leverage. A solution to this would be perfect!
A possible implementation can look like this:
const customClient = {
get: async <T, K extends string>(params: GetRequestParams):
Promise<Partial<Record<K, T> & { errors: string }>> =>
{
const response = (await client.get(params));
if (response.body.errors) return { errors: response.body.errors } as any;
return {
[Object.keys(response)[0]]: response[Object.keys(response)[0]]
} as any
},
}
As you correctly noted, we can't use the TypeScript generic types when constructing the returning object. We need to use JavaScript features instead. In this case I just took the first key of the response and used it for the key of the returning object as well as to index the response object.
The return type of the function is a Promise consisting of both a Record with K and T as keys and values and the error type. I used Partial here since they are not both required.
Destructing the response leads to the correct types now:
async function main(){
const { count, errors } = await customClient.get<number, "count">({} as any);
// count: number | undefined
// errors: string | undefined
}
Playground

GraphQL: Creating and Returning an Object in a Resolver?

I've got a mutation resolver that I call directly from the server like this:
import {graphql} from "graphql";
import {CRON_JOB_TO_FIND_USERS_WHO_HAVE_GONE_OFFLINE_MUTATION} from "../../query-library";
import AllResolvers from "../../resolvers";
import AllSchema from "../../schema";
import {makeExecutableSchema} from "graphql-tools";
const typeDefs = [AllSchema];
const resolvers = [AllResolvers];
const schema = makeExecutableSchema({
typeDefs,
resolvers
});
const {data, errors} = await graphql(
schema,
CRON_JOB_TO_FIND_USERS_WHO_HAVE_GONE_OFFLINE_MUTATION,
{},
{caller: 'synced-cron'},
{timeStarted: new Date().toISOString().slice(0, 19).replace('T', ' ')}
)
The mutation resolver is called and runs correctly. I don't need it to return anything, but GraphQL throws a warning if it doesn't, so I'd like it to return an object, any object.
So I'm trying it like this:
SCHEMA
cronJobToFindUsersWhoHaveGoneOffline(timeStarted: String): myUserData
QUERY
// note -- no gql. This string is passed directly to function graphql()
// where it gets gql applied to it.
const CRON_JOB_TO_FIND_USERS_WHO_HAVE_GONE_OFFLINE_MUTATION = `
mutation ($timeStarted: String){
cronJobToFindUsersWhoHaveGoneOffline(timeStarted: $timeStarted){
id,
},
}
`;
RESOLVER
cronJobToFindUsersWhoHaveGoneOffline(parent, args, context) {
return Promise.resolve()
.then(() => {
// there is code here that finds users who went offline if any
return usersWhoWentOffline;
})
.then((usersWhoWentOffline) => {
// HERE'S WHERE I HAVE TO RETURN SOMETHING FROM THE RESOLVER
let myUserDataPrototype = {
__typename: 'myUserData',
id: 'not_a_real_id'
}
const dataToReturn = Object.create(myUserDataPrototype);
dataToReturn.__typename = 'myUserData';
dataToReturn.id = 'not_a_real_id';
return dataToReturn; <==GRAPHQL IS NOT HAPPY HERE
})
.catch((err) => {
console.log(err);
});
},
}
GraphQL throws this warning:
data [Object: null prototype] {
cronJobToFindUsersWhoHaveGoneOffline: [Object: null prototype] { id: 'not_a_real_id' }
}
errors undefined
I have tried all kinds of different ways to fix this, but I haven't figured out the correct syntax yet.
What is a good way to handle this?
That doesn't appear to be a warning. That looks like you're writing the result to the console somewhere.

Got TS 2739 error while returning value from promise. Type 'Promise<any>' is missing the following properties from type

Currently I'm writing code for request login to server and receive it's session data and send to global context.
Basic principle is 1)Request and get Promise 2)Validate fetch result itself and response status. 3)Provide value to external component. And I'm working on 1) and 2).
But I got an error about data typing Type 'Promise<any>' is missing the following properties from type 'SessionInfo': userEmail, userName, sessionToken, duets(2739) at code that returns result data to external components. Despite of strict data typing(Maybe I think), I'm not sure why linter says Promise not Promise>. I think TS fails to assert it's type.
When I run very similar code with Javascript(without typing), it works in past. I'm not sure why this happens and I don't know what's wrong. Can you check my code?
Codes are below, there's 4 files -- interface definition file related to User, interface definition for handling response json, Actual request fetch, Response validation and evaluation.
When I checked linting at return res.data at actionHandler.ts, linter succeed to predict it's type. res.data is ResponseSet<SessionInfo>.data?: userTypes.SessionInfo as linter said.
In userTypes.ts
export interface SessionInfo {
userEmail: string,
userName: string,
sessionToken: string,
due: number,
}
In commonTypes.ts
export interface ResponseSet<T> { // Response wrapper when it returns with code 200
statusCode: ResponseStatusCode, // ResponseStatusCode is custom typed status code not for request it self.
data?: T,
description?: string,
};
In userReq.ts
const login = async (email: string, password: string): Promise<commonTypes.ResponseSet<userTypes.SessionInfo>> => {
try {
const request: Request = new Request(
composeUri(`user/login`, { email, password }),
{
method: 'GET',
headers: { 'Content-type': 'application/json' },
mode: 'cors',
}
);
const response: Response = await fetch(request);
if (response.status != 200) throw response.status;
return await response.json();
} catch {
return {
statusCode: 1,
};
}
}
In actionHandler.ts
export const doLogin = (email: string, password: string): userTypes.SessionInfo => {
const result: userTypes.SessionInfo = userReq.login(email, password)
.then(res => {
if (res.statusCode != 0) throw new Error(res.description || 'UnknownError');
return res.data;
})
.catch(err => {
return null;
});
return result;
}
Where I got an error is const result:.... I got Type 'Promise<any>' is missing the following properties from type 'SessionInfo': userEmail, userName, sessionToken, due ts(2739). I'm not sure why it is recognized as 'Promise` despite of strict type definition of my code.
the issue is that result isn't SessionInfo. It is a Promise of it.
const result: Promisse<userTypes.SessionInfo | null>;
doLogin is async due to used promise, you should follow async await inside and it can't return userTypes.SessionInfo, result will be a promise.
export const doLogin = async (email: string, password: string): Promise<userTypes.SessionInfo | null> => {
try {
const result: commonTypes.ResponseSet<userTypes.SessionInfo> = await userReq.login(email, password);
if (res.statusCode != 0) throw new Error(res.description || 'UnknownError');
} catch (e) {
return null;
}
return res.data;
}
// somewhere in the code (async env)
await doLogin(email, password);

Mock multiple api call inside one function using Moxios

I am writing a test case for my service class. I want to mock multiple calls inside one function as I am making two API calls from one function. I tried following but it is not working
it('should get store info', async done => {
const store: any = DealersAPIFixture.generateStoreInfo();
moxios.wait(() => {
const request = moxios.requests.mostRecent();
request.respondWith({
status: 200,
response: store
});
const nextRequest = moxios.requests.at(1);
nextRequest.respondWith({
status: 200,
response: DealersAPIFixture.generateLocation()
});
});
const params = {
dealerId: store.dealerId,
storeId: store.storeId,
uid: 'h0pw1p20'
};
return DealerServices.retrieveStoreInfo(params).then((data: IStore) => {
const expectedOutput = DealersFixture.generateStoreInfo(data);
expect(data).toMatchObject(expectedOutput);
});
});
const nextRequest is always undefined
it throw error TypeError: Cannot read property 'respondWith' of undefined
here is my service class
static async retrieveStoreInfo(
queryParam: IStoreQueryString
): Promise<IStore> {
const res = await request(getDealerStoreParams(queryParam));
try {
const locationResponse = await graphQlRequest({
query: locationQuery,
variables: { storeId: res.data.storeId }
});
res.data['inventoryLocationCode'] =
locationResponse.data?.location?.inventoryLocationCode;
} catch (e) {
res.data['inventoryLocationCode'] = 'N/A';
}
return res.data;
}
Late for the party, but I had to resolve this same problem just today.
My (not ideal) solution is to use moxios.stubRequest for each request except for the last one. This solution is based on the fact that moxios.stubRequest pushes requests to moxios.requests, so, you'll be able to analyze all requests after responding to the last call.
The code will look something like this (considering you have 3 requests to do):
moxios.stubRequest("get-dealer-store-params", {
status: 200,
response: {
name: "Audi",
location: "Berlin",
}
});
moxios.stubRequest("graph-ql-request", {
status: 204,
});
moxios.wait(() => {
const lastRequest = moxios.requests.mostRecent();
lastRequest.respondWith({
status: 200,
response: {
isEverythingWentFine: true,
},
});
// Here you can analyze any request you want
// Assert getDealerStoreParams's request
const dealerStoreParamsRequest = moxios.requests.first();
expect(dealerStoreParamsRequest.config.headers.Accept).toBe("application/x-www-form-urlencoded");
// Assert graphQlRequest
const graphQlRequest = moxios.requests.get("POST", "graph-ql-request");
...
// Assert last request
expect(lastRequest.config.url).toBe("status");
});

getting undefined from async await in typescript node

Use case:
I am trying to insert a record inside the amazon QLDB using Node and typescript.
I am able to insert the record/document successfully and it returns me documentID in return.
there are 2 controllers: EntityController and CommonController
-EntityController extends CommonController
-EntityController has the code for getting req object converting it into the model object and the calling insert() function that has been extended from the CommonController.
problem
I am trying to propagate that documentID to all the way to my API call, but somehow I am getting undefined in the EntityController.
whereas I am able to print the documentID in CommonController.
I am not sure why I am getting undefined when I am clearly returning a value.
const CommonController = require("../template/controller");
import { Request, Response } from 'express';
const tableName:string = "entities";
const EntityModel = require("./model")
class EntityController extends CommonController {
async insertEntitiy(req:Request,res:Response) {
async insertEntitiy(req:any,res:any) {
console.log(req);
console.log("===========");
console.log(req.body);
let entity = new EntityModel();
entity.balance = req.body.balance;
entity.firstName = req.body.firstName;
entity.lastName = req.body.lastName;
entity.email = req.body.email;
try {
let documentIds = await this.insert(tableName,entity);
console.log("--------- inside insertEntity fiunction()---------");
console.log(documentIds);
console.log("------------------");
res.status(200).send(documentIds[0]);
} catch (error) {
console.error(`error in creating Entity: ${error}`);
res.status(500).send({ errMsg: `error in creating Entity: ${error}` });
}
}
}
module.exports = new EntityController();
import { createQldbWriter, QldbSession, QldbWriter, Result, TransactionExecutor } from "amazon-qldb-driver-nodejs";
import { Reader } from "ion-js";
import { error, log } from "../qldb/LogUtil";
import { getFieldValue, writeValueAsIon } from "../qldb/Util";
import { closeQldbSession, createQldbSession } from "../qldb/ConnectToLedger";
module.exports = class Conroller {
async insert(tablename:string, object:any): Promise<Array<string>> {
let session: QldbSession;
let result:Array<string>;
try {
session = await createQldbSession();
await session.executeLambda(async (txn) => {
result = await this.insertDocument(txn,tablename,object);
console.log("---------result inside insert fiunction()---------");
console.log(result);
console.log("------------------");
return (Promise.resolve(result));
})
} catch (e) {
error(`Unable to insert documents: ${e}`);
return(["Error"]);
} finally {
closeQldbSession(session);
}
}
/**
* Insert the given list of documents into a table in a single transaction.
* #param txn The {#linkcode TransactionExecutor} for lambda execute.
* #param tableName Name of the table to insert documents into.
* #param documents List of documents to insert.
* #returns Promise which fulfills with a {#linkcode Result} object.
*/
async insertDocument(
txn: TransactionExecutor,
tableName: string,
documents: object
): Promise<Array<string>> {
const statement: string = `INSERT INTO ${tableName} ?`;
const documentsWriter: QldbWriter = createQldbWriter();
let documentIds: Array<string> = [];
writeValueAsIon(documents, documentsWriter);
let result: Result = await txn.executeInline(statement, [documentsWriter]);
const listOfDocumentIds: Reader[] = result.getResultList();
listOfDocumentIds.forEach((reader: Reader, i: number) => {
documentIds.push(getFieldValue(reader, ["documentId"]));
});
console.log("---------documentIds---------");
console.log(documentIds);
console.log("------------------");
return (documentIds);
}
}
ouptut :
---------documentIds---------
[ '4o5UZjMqEdgENqbP9l7Uhz' ]
---------result inside insert fiunction()---------
[ '4o5UZjMqEdgENqbP9l7Uhz' ]
--------- inside insertEntity fiunction()---------
undefined
As #daniel-w-strimpel pointed out in the comments, your insert method returns only in the catch part.
Try this:
insert(tablename:string, object:any): Promise<Array<string>> {
let session: QldbSession;
let result: Array<string>;
try {
session = await createQldbSession();
return session.executeLambda(async (txn) => {
result = await this.insertDocument(txn,tablename,object);
console.log("---------result inside insert fiunction()---------");
console.log(result);
console.log("------------------");
return result;
})
} catch (e) {
error(`Unable to insert documents: ${e}`);
return(["Error"]);
} finally {
closeQldbSession(session);
}
}
...
In return session.executeLambda you return the Promise.
In return result; you return the actual value.
More on promises here: https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html

Resources