So I have this little function that takes care of updating a todo document in the database, it looks like so
async function update({id, ...todoInfo }: ITodo) { // this line here
const db = await makeDb()
const foundTodo = await db.collection('todos').updateOne({ _id: transformId(id) }, { $set: { ...todoInfo } })
return foundTodo.modifiedCount > 0 ? { _id: id, ...todoInfo } : null
}
The id property is meant to come from the req.params object, and the ...todoInfo comes from the req.body object. But typescript throws an error that property id does not exist on interface ITodo. How do I overcome this issue? The interface ITodo looks like this currently,
export interface ITodo {
todo_name: string
start_time: Date
end_time: Date
description: string
priority: Priority
}
I tried this method, but it led to object nesting like so.. {todoInfo: {todo_name...}}
async function update({id, ...todoInfo }: {id: string, todoInfo: ITodo}) {
const db = await makeDb()
const foundTodo = await db.collection('todos').updateOne({ _id: transformId(id) }, { $set: { ...todoInfo } })
return foundTodo.modifiedCount > 0 ? { _id: id, ...todoInfo } : null
}
I don't want to add the property id to the interface because it's either I keep using it everywhere or I could make it optional which will mess up other function calls because the id property cannot be undefined. Thank you very much.
You can use an intersection type:
async function update({ id, ...todoInfo }: { id: string } & ITodo) {
//...
}
Playground Link
Related
I have a page creatin function wuth this:
createPage({
context: { productId: String(productId) },
As you see, I even forced the ID to be a string, but the page template still receives a number
const Template = ({ pageContext, data }) => {
console.log(pageContext.productId) // number
And the following query that expects a string fails:
export const query = graphql`
myStuff(filter: {productIdentifier: {eq: $productId}}) {
nodes {
id
}
}
}
`
Any ideas how I can force the productId to be a string?
Have you tried toString()?
createPage({
context: { productId: productId.toString() },
Even the productId is not being casted, you can also try defining it as a String or Int:
export const query = graphql`
query($productId: String!) {
myStuff(filter: {productIdentifier: {eq: $productId}}) {
nodes {
id
}
}
}
}
Use $productId: Int! otherwise but I don't know if this will match the filter though.
Another alternative is casting a new string variable before passing it to the context:
let productIdStringified = productId.toString()
createPage({
context: { productId: productIdStringified },
I have a data layer that reads and writes to a MongoDB instance. I only want to deal with MongoDB documents at that layer and not expose that implementation to my services.
Right now I am doing something like:
// users.repository.ts
...
async getUserById(id: string): Promise<UserDto> {
const user = await this.model.findOne({ _id: id }).exec();
return this.transformToDto(user);
}
private transformToDto(user: UserDocument): UserDto {
return {
id: user._id,
...etc
}
}
...
This seems overly verbose and there must be a simpler way to achieve this without adding a helper to every repository.
Is there a cleaner way to achieve this?
You can use class-transformer for that and you don't need to use extra helper methods it can be returned instantly.
import { plainToClass } from 'class-transformer';
class UserDto {
id: string;
email: string;
role: string;
}
class Service {
async getUserById(id: string): Promise<UserDto> {
const user = await this.model.findOne({ _id: id }).exec();
return plainToClass(UserDto, user);
}
}
It will return transformed value which is UserDto
UserDto { id: 'U-111', email: 'U-111#email', role: 'user' }
// callback functions
const fetchData = (userId:number, cb:any) => {
setTimeout(() => {
const fakeData = {
name: "bob",
id: userId,
pass: 1234
};
cb(fakeData);
}, 500)
}
interface Idata {
name: string,
id: number
}
const afterFetchData = <T extends Idata>(data: T) => {
console.log("your data is", data.pass)
}
How can I put any type of value inside the fakeData and accept it without using any in afterFetchData function ?
now I have this error : Property 'pass' does not exist on type 'T'.ts(2339)
If you know that there will be always some properties in there like id and name, but you are not sure about pass, you can do this :
console.log("your data is", data['pass'])
Otherwise you should leave the fakeData untyped or use any type
I am using Apollo Server v2 for my project
I have added auth like given here https://www.apollographql.com/docs/apollo-server/features/authentication/
I wanted to include nested resolver in my Query and Mutation so I did as per https://stackoverflow.com/a/40916089/7584077
The thing is my resolver is a lil bit more complex than the one shown above
// typeDefs/typeDefs.js
import { gql } from "apollo-server-express";
import issueTracker from "./issueTracker";
const base = gql`
scalar Timestamp
type Query {
ping: String!
}
type Mutation {
ping: String!
}
`;
export default [base, issueTracker];
// typeDefs/issuetracker.js
import { gql } from "apollo-server-express";
export default gql`
type Comment {
id: ID
message: String
commentBy: User
createdAt: Timestamp
updatedAt: Timestamp
version: Int
}
type Issue {
id: ID
requestId: ID
title: String
issueNumber: Int
status: Int
tags: [String]
assignees: [User]
createdBy: User
comments: [Comment]
createdAt: Timestamp
updatedAt: Timestamp
version: Int
}
input CreateIssueRequest {
requestId: ID!
title: String!
createdBy: ID!
message: String!
assignees: [ID]!
}
type IssueTrackerQuery {
ping: String!
}
type IssueTrackerMutation {
createIssue(request: CreateIssueRequest!): Issue
}
extend type Query {
IssueTracker: IssueTrackerQuery
}
extend type Mutation {
IssueTracker: IssueTrackerMutation
}
`;
And a lil modified version of the stackoverflow answer above.
Here is my combined resolver.
// resolvers/resolvers.js
import IssueTracker from "./issueTracker";
export default {
Query: {
ping: () => "ping!",
IssueTracker: () => ({
ping: IssueTracker.ping,
}),
},
Mutation: {
ping: () => "ping!",
IssueTracker: () => ({
createIssue: IssueTracker.createIssue,
}),
},
};
This is because I wanted Query & Mutation to be completely separate.
Here is my IssueTracker resolver
// resolvers/issueTracker.js
export default {
ping: () => "ping",
createIssue: async (parent, args, context) => {
console.log(parent);
console.log(args);
console.log(context);
// create issue as per request and return
}
The thing is here is that, parent actually is the args field!
And I need userId from context to make sensible data.
Hmm, the SDL first approach can be a bit tricky. It's not easy to explain what is wrong here but I will do my best. First let me tell you what to need to do to make this work and then I will explain what goes wrong.
Create a IssueTrackerMutation field in the resolver map:
export default {
Query: {
ping: () => "ping!",
IssueTracker: () => ({ // same here but I will just do the mutation for you
ping: IssueTracker.ping,
}),
},
Mutation: {
ping: () => "ping!",
IssueTracker: () => null, // or whatever you want as a root here
},
IssueTrackerMutation: {
createIssue: IssueTracker.createIssue
}
};
Note the difference between creating a "pure" resolver for the IssueTracker and returning an object for IssueTracker with a createIssue method.
Now the function should be called with the expected parameters. The reason why the parent argument seems to be missing is the very special implementation of the default resolver. The resolver is intended to work with an object oriented style where fields can be fields or methods. You can imagine the resolver to work like this:
defaultResolver(fieldName, parent, args, context, info) {
if (typeof parent !== 'object') {
throw "Need object to default resolve";
}
if (typeof parent[fieldName] === 'function') {
return parent[fieldName](args, context, info);
}
return parent[fieldName];
}
This would allow you to write your data access objects in the following manner:
class IssueTracker {
issues = []
async createIssue(args, ctx, info) {
const issue = await createIssue(args.request);
this.issues.push(issue);
return issue;
}
}
const issueTracker = new IssueTracker();
export default {
// ...
Mutation: {
// ...
IssueTracker: () => issueTracker,
},
IssueTrackerMutation: {
createIssue: IssueTracker.createIssue
}
};
This is not talked about that much but is probably relatively close how GraphQL at Facebook works. The community seems to move more logic into the resolvers.
I have a class for SearchFilter
class SearchFilter {
constructor(bucket: string,
pin: number,
qty: number,
category: string) {
}
}
When user hits search I'm filling in the filter to matrix params.
router.navigate(['filter', searchFilter]); //searchFilter is an instance
The URL looks like
filter;bucket=MPC123;category=JOINT;qty=90;pin=9087
This is being used in another component where the param is mapped back to an Object of type SearchFilter. But here the data types need to be set explicitly as far as I know.
const params = this.activatedRoute.snapshot.params;
const filter = this.getFilterFromParams(params);
getFilterFromParams(params) :SearchFilter {
const filter = new SearchFilter();
Object.keys(params).forEach((key) => {
switch(key) {
case 'pin':
case 'qty': filter[key] = Number(params[key]); break;
default: filter[key] = params[key];
}
});
return filter;
}
As seen from above code, to map the params back to Object a custom mapping function is written, the question is if there is any other obvious way this can be done or is this a common pattern?
I will have to depend on URL as users should be able to share URLs of different filters.
Adding a static factory method would work:
class SearchFilter {
constructor(
bucket: string,
pin: number,
qty: number,
category: string) {
}
public static fromParams({ bucket, pin, qty, category }) {
// TODO Add validation to ensure pin & qty are integers
return new SearchFilter(
bucket,
parseInt(pin),
parseInt(qty),
category);
}
}
Then we can use it like this:
const demoParams = {
bucket: "MPC123",
category: "JOINT",
qty: "90",
pin: "9087"
};
const filter = SearchFilter.fromParams(demoParams);
You could maybe go for the capabilities of Object.assign and the spread syntax:
getFilterFromParams(params) :SearchFilter {
return Object.assign(new SearchFilter(), params,
...['pin', 'qty'].map(key => key in params && { [key]: Number(params[key]) })
);
}