Nested object type resolvers giving type error in GraphQL Nexus while works without any type system - next.js

I am building a classroom app. While the resolver and schema works as expected without any type system, but with using nexus TS is complaining about the type errors.
I am using NextJs, Apollo Server, and graphql nexus
Currently I am using local db.ts file as database.
Should I not use nested object types? Or am I doing something wrong here?
Is there any other way of using relationships in graphql?
typescript
export const Class = objectType({
name: "Class",
definition(t) {
t.nonNull.id("id"),
t.nonNull.string("title"),
t.nonNull.url("imageUrl"),
t.nonNull.dateTime("createdAt"),
t.nonNull.string("instructorUserId");
t.field('instructor',{
type:UserProfile,
resolve(root,_args,ctx){
return ctx.db.UserProfile.find(user => user.id === root.instructorUserId)
}
})
},
});
Resolve method is giving me type errors.
Type '(root: { createdAt: any; id: string; imageUrl: any; instructorUserId: string; title: string; }, _args: {}, ctx: Context) => { id: string; name: string; email: string; phoneNumber: number; joinedAt: string; lastOnlineAt: string; profileImg: string; role: string; } | undefined' is not assignable to type 'FieldResolver<"Class", "instructor">'.
Type '{ id: string; name: string; email: string; phoneNumber: number; joinedAt: string; lastOnlineAt: string; profileImg: string; role: string; } | undefined' is not assignable to type 'MaybePromise<{ email: string; id: string; joinedAt: any; lastOnlineAt: any; name: string; phoneNumber?: number | null | undefined; profileImg: any; role: "BASIC" | "STUDENT" | "TEACHER" | "SUPERADMIN"; } | null>'.
Type 'undefined' is not assignable to type 'MaybePromise<{ email: string; id: string; joinedAt: any; lastOnlineAt: any; name: string; phoneNumber?: number | null | undefined; profileImg: any; role: "BASIC" | "STUDENT" | "TEACHER" | "SUPERADMIN"; } | null>'.
UserProfile
typescript
export const RoleEnum = enumType({
name: "Role",
members: ["BASIC", "STUDENT", "TEACHER", "SUPERADMIN"],
description: "Role of user",
});
export const UserProfile = objectType({
name: "UserProfile",
definition(t) {
t.nonNull.id("id"),
t.nonNull.string("name"),
t.nonNull.string("email"),
t.int("phoneNumber"),
t.nonNull.dateTime("joinedAt"),
t.nonNull.dateTime("lastOnlineAt"),
t.nonNull.url("profileImg"),
t.nonNull.field("role", {
type: "Role"
});
},
});
UserProfile Object in db
ts
{
id: "e9276605-aecd-460a-b5f1-f470d115ab60",
name: "Dahlia Grant",
email: "dahlia#gmail.com",
phoneNumber: 4665943805,
joinedAt: "2023-12-03T10:15:30Z",
lastOnlineAt: "2023-12-03T10:15:30Z",
profileImg: "http://unsplash.it/300/300?gravity=center",
role: "TEACHER",
},
Meanwhile plain graphql code is working along with nested object types
schema.graphql
type UserProfile {
id: String!
name: String!
email: String!
phoneNumber: Int
joinedAt: DateTime!
lastOnlineAt: DateTime!
profileImg: URL!
role: Role!
instructorInClasses: [Class!]!
posts: [Post!]!
likes: [PostLike!]!
classesEnrolled: [Class]!
}
type Class {
id: String!
title: String!
imageUrl: String
createdAt: DateTime!
instructor: UserProfile!
instructorUserId: String!
studentsEnrolled: [UserProfile!]!
posts: [Post!]!
}
Corresponding resolvers
ts
Class:{
instructor:(parent:Class,args:any,ctx:any) => {
const {instructorUserId} = parent
return db.UserProfile.find(user => user.id === instructorUserId)
},
studentsEnrolled:(parent:Class,args:any,ctx:any) => {
const {id} = parent
return db.StudentsEnrolled.filter(item => item.classId === id)
},
posts:(parent:Class,args:any,ctx:any) => {
const {id} = parent
return db.Post.filter(post => post.classId === id)
},
},
I tried removing nonNull but still the type error persisted.
Underlying Prisma schema that I am trying to implement
model UserProfile {
id String #id #default(uuid())
name String
email String #unique
phoneNumber Int? #unique
joinedAt DateTime #default(now())
lastOnlineAt DateTime #default(now())
role Role #default(BASIC)
instructorInClasses Class[] #relation(name: "ClassInstructor")
posts Post[] #relation(name: "PostedOwner")
likes PostLike[] #relation(name: "PostLiked")
classesEnrolled Class[]
}
model Class {
id String #id #default(uuid())
title String
imageUrl String #default("")
createdAt DateTime #default(now())
instructor UserProfile #relation(name: "ClassInstructor", fields: [instructorUserId], references: [id])
instructorUserId String
studentsEnrolled UserProfile[]
posts Post[] #relation(name: "ClassPost")
}
model Post {
id String #id #default(uuid())
title String
text String
views Int #default(0)
postType PostType #default(PUBLIC)
belongsToClass Class? #relation(name: "ClassPost", fields: [classId], references: [id])
classId String
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
postedBy UserProfile #relation(name: "PostedOwner", fields: [postedByUserId], references: [id])
postedByUserId String
postLikes PostLike[] #relation(name: "LikePostId")
}
model PostLike {
id String #id #default(uuid())
createdAt DateTime #default(now())
likedOnPost Post #relation(name: "LikePostId", fields: [postId], references: [id])
postId String
likedBy UserProfile #relation(name: "PostLiked", fields: [likedByUserId], references: [id])
likedByUserId String
}

Related

next auth not passing all user info to the client

I am trying to have a role for the user in the session
This is what I get from session.user on the client :
{ "email": "test value" }
what I want to get :
{
"email": "test value",
"role": "user"
}
For some reason I can access the role on the server side but not on the client
[...nextauth].ts :
//..
const authOptions: NextAuthOptions = {
session: {
strategy: "jwt",
},
providers: [
CredentialsProvider({
type: "credentials",
credentials: {},
async authorize(credentials, req) {
const { email, password } = credentials as {
email: string;
password: string;
};
const saltRounds = 10;
const db = path.join(process.cwd(), "db");
const users = JSON.parse(fs.readFileSync(db + "/users.json", "utf-8"));
type User = {
id: string;
email: string;
name: string;
role: "user" | "admin";
password: string;
};
for (let i = 0; i < users.length; i++) {
const e = users[i] as User;
const emailMatch = e.email === email;
if (emailMatch) {
const passwordMatch = bcrypt.compareSync(password, e.password);
if (passwordMatch) {
console.log("user loggedin", e);
return {
id: e.id,
email: e.email,
name: e.name,
role: e.role,
};
}
}
}
throw new Error("Invalid email or password");
},
}),
],
pages: {
signIn: "/auth/signin",
},
callbacks: {
jwt(params) {
if (params.user?.role) {
params.token.role = params.user.role;
}
console.log("jwt", params);
return params.token;
},
},
};
export default NextAuth(authOptions);
I have tried searching for how to do it and I dont see what's wrong with my code.
Here you are not setting the session you have to use the session callback to update the session from the returned token:
async jwt(params) {
if (params.user?.role) {
params.token.role = params.user.role;
}
if (params.user?.email) {
params.token.email = params.user.email;
}
return params.token;
},
async session({ session, token }) {
session.role = token.role;
session.email = token.email;
return session;
},
For some reason I can access the role on the server side but not on the client
that's the role from the token because you have added the property role to it now you have to add properties to your session

MikroORM Can't map join-table that uses a composite primary-key that includes a composite foreign-key

I get an error when mapping a join table that has a composite primary-key that includes a foreign-key to a composite primary-key. I believe the problem is with UserRole.user.
Stack trace
TypeError: Cannot read property 'id' of undefined
at /Users/emurphy/projects/aperture/aap-api/node_modules/#mikro-orm/core/utils/Utils.js:369:29
at Array.map (<anonymous>)
at Function.getOrderedPrimaryKeys (/Users/emurphy/projects/aperture/aap-api/node_modules/#mikro-orm/core/utils/Utils.js:367:33)
at /Users/emurphy/projects/aperture/aap-api/node_modules/#mikro-orm/core/utils/Utils.js:371:37
at Array.map (<anonymous>)
at Function.getOrderedPrimaryKeys (/Users/emurphy/projects/aperture/aap-api/node_modules/#mikro-orm/core/utils/Utils.js:367:33)
at /Users/emurphy/projects/aperture/aap-api/node_modules/#mikro-orm/core/utils/Utils.js:371:37
at Array.map (<anonymous>)
at Function.getOrderedPrimaryKeys (/Users/emurphy/projects/aperture/aap-api/node_modules/#mikro-orm/core/utils/Utils.js:367:33)
at EntityFactory.findEntity (/Users/emurphy/projects/aperture/aap-api/node_modules/#mikro-orm/core/entity/EntityFactory.js:95:35)
To Reproduce
Create the following entities:
#Entity()
export class Organization {
#PrimaryKey({ type: 'uuid', defaultRaw: 'uuid_generate_v4()' })
id: string = v4();
#Unique()
#Property({ columnType: 'varchar' })
name: string;
#OneToMany({ entity: () => User, mappedBy: (user) => user.organization, cascade: [] })
users = new Collection<User>(this);
constructor(value: Partial<Organization> = {}) {
Object.assign(this, value);
this.users = this.users || new Collection<User>(this);
}
}
#Entity()
export class User {
#PrimaryKey({ columnType: 'varchar' })
id: string;
#Index()
#ManyToOne({
entity: () => Organization,
primary: true,
wrappedReference: true,
index: true,
cascade: [],
onDelete: 'no action',
})
organization: IdentifiedReference<Organization>;
#Property({ columnType: 'varchar' })
firstName: string;
#Property({ columnType: 'varchar' })
lastName: string;
#Property({ columnType: 'varchar' })
email: string;
#OneToMany({ entity: () => UserRole, mappedBy: (userRole) => userRole.user })
userRoles = new Collection<UserRole>(this);
[PrimaryKeyType]: [string, string];
constructor(value: Partial<User> = {}) {
Object.assign(this, value);
this.userRoles = this.userRoles || new Collection<UserRole>(this);
}
}
#Entity()
export class Role {
#PrimaryKey({ columnType: 'varchar' })
id: string;
#Property({ columnType: 'varchar' })
name: string;
#OneToMany({ entity: () => UserRole, mappedBy: (userRole) => userRole.role })
userRoles = new Collection<UserRole>(this);
constructor(value: Partial<Role> = {}) {
Object.assign(this, value);
this.userRoles = this.userRoles || new Collection<UserRole>(this);
}
}
#Entity()
export class UserRole {
#ManyToOne({
entity: () => User,
inversedBy: (x) => x.userRoles,
primary: true,
wrappedReference: true,
cascade: [],
onDelete: 'cascade',
})
user: Reference<User>;
#ManyToOne({
entity: () => Role,
inversedBy: (x) => x.userRoles,
primary: true,
wrappedReference: true,
cascade: [],
onDelete: 'no action',
})
role: IdentifiedReference<Role>;
[PrimaryKeyType]: [string, string, string];
constructor(value: Partial<UserRole> = {}) {
Object.assign(this, value);
}
}
Run either of the following queries:
// query and map just the join table
await this.em.find(UserRole, { user: { $eq: [userId, orgId] } })
// or try to populate it
await this.em.findOne(
User,
{ id: { $eq: userId }, organization: { $eq: orgId } },
{ populate: { userRoles: LoadStrategy.JOINED } },
);
Versions
Dependency
Version
node
14.15.3
typescript
4.1.5
mikro-orm
4.4.4
mikro-orm/postgresql
4.4.4
I have tried removing the UserRole.role property and the error still occurs. I have tried querying the table directly and populating it as part of finding another entity (User). I have tried using the query builder. I have tried using normal entities instead of wrapped entities. I have tried using em.map(...) on the results of a raw execute.
Am I setting it up wrong or is this just a bug in the framework? I couldn't find examples for this specific scenario.
Update
The issue was fixed here: https://github.com/mikro-orm/mikro-orm/issues/1624

loopback 4 how to avoid creating more than 5 items in a shopping cart

I am new to loopback 4, and almost all the documentation I found is for lower versions. I have a shopping cart, but I need to avoid it to have more than 5 items... how can I made this constraint?
These are my models:
import {Entity, hasMany, model, property} from '#loopback/repository';
import {Item} from './item.model';
#model()
export class Shoppingcar extends Entity {
#property({
type: 'string',
id: true,
generated: true,
})
id?: string;
#property({
type: 'string',
required: true,
})
desc: string;
#property({
type: 'string',
required: true,
}
})
shopper: string;
#hasMany(() => Item)
items: Item[];
constructor(data?: Partial<Gateway>) {
super(data);
}
}
export interface ShoppingcarRelations {
// describe navigational properties here
}
export type ShoppingcarWithRelations = Shoppingcar & ShoppingcarRelations;
and
import {Entity, model, property, belongsTo} from '#loopback/repository';
import {Shoppingcar} from './shoppingcar.model';
#model()
export class Item extends Entity {
#property({
type: 'string',
id: true,
generated: true,
})
id?: string;
#property({
type: 'string',
required: true,
})
name: string;
#property({
type: 'string',
required: true,
})
date: string;
#belongsTo(() => Shoppingcar)
shoppingcarId: string;
constructor(data?: Partial<Item>) {
super(data);
}
}
export interface ItemRelations {
// describe navigational properties here
}
export type ItemWithRelations = Item & ItemRelations;
Create one interceptor,
check for the item count in the interceptor, if the count is greater the 5 throw an error else continue with next()

How to validate optional fields with `$Diff`?

I want my type to validate "default props" passed to the React component but $Diff (which is designed for this) is not doing so. How can I get this behavior?
/* #flow */
type Props = { name: string, age: number };
type DefaultProps = { age: number };
type RequiredProps = $Diff<Props, DefaultProps>;
const a1: RequiredProps = { name: 'foo' };
const a2: RequiredProps = { name: 'foo', age: 1 };
const a3: RequiredProps = { name: 'foo', age: '1' }; // why no error?
$Diff<A, B> returns an object type that, by default, will accept additional properties. This means, that any properties that are on A and B can be on an object of type $Diff<A, B> with any type. In your case, RequiredProps is equivalent to { name: string }. Thus, an object { name: '', age: <whatever> }: RequiredProps is completely valid for any value of <whatever>.
It seems like what you really want is an object type that requires all of the properties of RequiredProps and requires that any property from DefaultProps matches that type definition. This can be achieved with the type,
type RequiredAndDefaultProps = { ...$Exact<RequiredProps>, ...DefaultProps };
which can be verified
type Props = { name: string, age: number };
type DefaultProps = { age: number };
type RequiredProps = $Diff<Props, DefaultProps>;
type RequiredAndDefaultProps = { ...$Exact<RequiredProps>, ...DefaultProps };
({ name: 'foo' }: RequiredAndDefaultProps);
({ name: 'foo', age: 42 }: RequiredAndDefaultProps);
({ name: 'foo', age: '42' }: RequiredAndDefaultProps); // Error: age should be number
({ age: 42 }: RequiredAndDefaultProps); // Error: missing name
({ name: 'foo', bar: 'bar' }: RequiredAndDefaultProps);
Try Flow.

Action declaration doesn't work properly

I activated flow in my project with redux, but the Action declarations doesn't work as I expected.
Declarations are:
type PostRequest = {
type: string;
};
type PostPayload = {
posts: Object;
offset: number;
hasMore: boolean;
};
type PostSuccess = {
type: string;
payload: PostPayload;
};
type PostError = {
type: string;
};
type PostSelected = {
type: string;
postId: string;
};
export type Action = PostSuccess | PostError | PostSelected | PostRequest;
In actionCreators I don't see any errors, while reducer I get this error in the use of property "payload": property 'payload' (Property not found in object type).
This is my reducer:
import type { Action } from "../types";
// other import...
export default function(state: State = initialState, action: Action):
State {
switch (action.type) {
// ...
case POST_SUCCESS: {
const { posts, offset, hasMore } = action.payload;
return {
...state,
isFetching: false,
posts: _.merge(state.posts, posts),
offset: state.offset + offset,
hasMore,
};
}
// ...
What is the correct way to declare Actions?
You can define Disjoint Unions
type ActionA = {
type: 'A',
a: number
};
type ActionB = {
type: 'B',
b: string
};
type Action = ActionA | ActionB;
type State = number;
function reducer(state: State, action: Action): State {
switch(action.type) {
case 'A' :
return action.a
case 'B' :
return action.b.length
default :
(action: null) // check for exhaustivity
throw `unknown action`
}
}

Resources