-
-
Notifications
You must be signed in to change notification settings - Fork 163
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Approach for circular references in prisma objects #641
Comments
This is something that there isn't a good for right now. I can think of 2 workarounds: export const User = builder.prismaObject('User', {
fields: (t) => ({
id: t.exposeID('id'),
// ...
})
})
// Move the field out into it's own field, this isn't optimized the same way, but can often still be very similar in efficiency
builder.objectField(User, 'posts', t => t.prismaField({
resolve: (query, user) => prisma.post.findMany({ ...query, where: { users: { userId: user.id } } })
})) // create a manually typed ref to break the circular dependency
const PostRef: ObjectRef<Prisma.Post> = Post
export const User = builder.prismaObject('User', {
fields: (t) => ({
id: t.exposeID('id'),
// ...
posts: t.field({
type: [PostRef],
select: (args, context, nestedSelection) => ({
posts: {
select: {
post: nestedSelection()
}
}
}),
resolve: user => user.posts.map(({ user }) => user)
}),
})
}) |
That works well, cheers! Any plans in the works for the prisma plugin to avoid this? |
There isn't a way to completely avoid this issue, but I will probably be adding a simpler workaround. I'll have to do some investigation, but it would likely be something like adding a The underlying issue of typescript reverting to |
Edit: DetailsI did solve this with a complex workaround using RxJS until something simpler shows up... Just sharing if this helps anyone 👀 // getProducer.ts
import { Observable, shareReplay, take } from "rxjs";
/**
* an observable that must happen in sync, that will be shared between everyone that uses a common key.
* once emmited, it will be shared between everyone that uses that key.
* if didn't emit, it will wait for the same event to happen, and then share it.
*/
export const getProducer = <Fn extends (args: any) => any | Observable<any>, Args extends Parameters<Fn>[0]>(
fn: Fn,
getKeyFn: (args: Args) => any
) => {
const producersMap = new Map<string, Observable<any>>();
return (args: Args) => {
const key = getKeyFn(args);
const prevObservable = producersMap.get(key);
if (prevObservable) {
return prevObservable;
}
const observable = new Observable<any>((subscriber) => {
const result = fn(args);
if (result instanceof Observable) {
result.subscribe(subscriber);
} else {
subscriber.next(result);
}
}).pipe(shareReplay(1), take(1));
producersMap.set(key, observable);
return observable;
};
}; then everything I want to be shared by a common key (the operation name, for example), thus not provoking any circular dependency or other problem, I use this for example: import type { MainBuilder } from "../setupBuilder/getBuilder";
import { prismaWhereForObjectFromArgs } from "./whereForPrismaObject";
import { map } from "rxjs";
import {getProducer} from "../getProducer";
type BuildListRelationFilterArg = {
builder: MainBuilder;
modelName: string;
};
export const listRelationFilterFromArgs = getProducer(({ builder, modelName }: BuildListRelationFilterArg) => {
const name = modelName + "ListRelationFilter";
const where$ = prismaWhereForObjectFromArgs({
builder: builder,
modelName: modelName
});
return where$.pipe(map(WhereForObject => builder.prismaListFilter(WhereForObject, {
ops: ["every", "some", "none"] as const,
name: name
})))
}, ({ modelName }) => modelName + "ListRelationFilter"); and to make this observable sync (nothing async running inside all these) import type { Observable } from "rxjs";
/**
* if a observable can be produced in sync mode, it will. Otherwise it will fail.
* @param observable
*/
export const syncObservable = <T>(observable: Observable<T>): T => {
let emitted = false;
let value: T = undefined;
const subscription = observable.subscribe((v) => {
emitted = true;
value = v;
});
subscription.unsubscribe();
if (!emitted) {
throw new Error("observable did not emit");
}
return value;
}; not proud of it, but it is helping me with a relatively large schema |
I am working on some improvements for the prisma plugin in #671, I'll try to add a way of creating prismaRefs in that PR before it gets merged |
Add builder.prismaObjectField(s) methods that can be used to definition je circular references without type issues. |
What is the intended approach for addressing circular references for nested relations in prisma objects?
For example, suppose there is a many-to-many relationship between posts and users. The declaration of the
Post
prisma object andUser
prisma object reference each other.This causes typescript to complain about circular declarations
The circular references section of the guide resolves this by first declaring the object and then implementing the fields that would cause the circular reference in a call to
.implement()
. However, it seems that there is not an equivalent to.implement()
forbuilder.prismaObject
.Any help or advice would be greatly appreciated :)
The text was updated successfully, but these errors were encountered: