Skip to content

Commit ecffc9b

Browse files
committed
find dataloader and very basic example
1 parent 8ce85fb commit ecffc9b

20 files changed

+3453
-38
lines changed

examples/graphql/package.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"name": "graphql-example",
3+
"private": true,
4+
"version": "1.0.0",
5+
"main": "lib/index.js",
6+
"license": "MIT",
7+
"mikro-orm": {
8+
"useTsNode": true,
9+
"configPaths": [
10+
"./src/mikro-orm-config.ts",
11+
"./lib/mikro-orm-config.js"
12+
]
13+
},
14+
"scripts": {
15+
"start": "NODE_ENV=development yarn run -T nodemon -w src -x ts-node src/index.ts",
16+
"test": "yarn run build"
17+
},
18+
"dependencies": {
19+
"@mikro-orm/cli": "6.0.0-dev.187",
20+
"@mikro-orm/core": "6.0.0-dev.187",
21+
"@mikro-orm/entity-generator": "6.0.0-dev.187",
22+
"@mikro-orm/knex": "6.0.0-dev.187",
23+
"@mikro-orm/migrations": "6.0.0-dev.187",
24+
"@mikro-orm/reflection": "6.0.0-dev.187",
25+
"@mikro-orm/sqlite": "6.0.0-dev.187",
26+
"graphql": "16.8.1",
27+
"graphql-yoga": "5.0.0",
28+
"tslib": "2.6.2"
29+
}
30+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { Collection, Entity, ManyToMany, OneToMany, PrimaryKey, Property } from "@mikro-orm/core";
2+
import { Book } from "./Book";
3+
import { Chat } from "./Chat";
4+
5+
@Entity()
6+
export class Author {
7+
@PrimaryKey()
8+
id!: number;
9+
10+
@Property()
11+
name: string;
12+
13+
@Property()
14+
email: string;
15+
16+
@OneToMany(() => Book, (book) => book.author)
17+
books = new Collection<Book>(this);
18+
19+
// No inverse side exists
20+
@ManyToMany(() => Author)
21+
friends = new Collection<Author>(this);
22+
23+
// Inverse side exists
24+
@ManyToMany(() => Author)
25+
buddies = new Collection<Author>(this);
26+
27+
@ManyToMany(() => Author, (author) => author.buddies)
28+
buddiesInverse = new Collection<Author>(this);
29+
30+
@OneToMany(() => Chat, (chat) => chat.owner)
31+
ownedChats: Collection<Chat> = new Collection<Chat>(this);
32+
33+
constructor({ id, name, email }: { id?: number; name: string; email: string }) {
34+
if (id != null) {
35+
this.id = id;
36+
}
37+
this.name = name;
38+
this.email = email;
39+
}
40+
}

examples/graphql/src/entities/Book.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { Entity, ManyToOne, PrimaryKey, Property, Ref, ref } from "@mikro-orm/core";
2+
import { Author } from "./Author";
3+
import { Publisher } from "./Publisher";
4+
5+
@Entity()
6+
export class Book {
7+
@PrimaryKey()
8+
id!: number;
9+
10+
@Property()
11+
title: string;
12+
13+
@ManyToOne(() => Author, { ref: true })
14+
author: Ref<Author>;
15+
16+
@ManyToOne(() => Publisher, { ref: true, nullable: true })
17+
publisher!: Ref<Publisher> | null;
18+
19+
constructor({ id, title, author }: { id?: number; title: string; author: Author | Ref<Author> }) {
20+
if (id != null) {
21+
this.id = id;
22+
}
23+
this.title = title;
24+
this.author = ref(author);
25+
}
26+
}

examples/graphql/src/entities/Chat.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { Collection, Entity, ManyToOne, OneToMany, PrimaryKeyProp, Ref, ref } from "@mikro-orm/core";
2+
import { Author } from "./Author";
3+
import { Message } from "./Message";
4+
5+
@Entity()
6+
export class Chat {
7+
@ManyToOne(() => Author, { ref: true, primary: true })
8+
owner: Ref<Author>;
9+
10+
@ManyToOne(() => Author, { ref: true, primary: true })
11+
recipient: Ref<Author>;
12+
13+
[PrimaryKeyProp]?: ["owner", "recipient"];
14+
15+
@OneToMany(() => Message, (message) => message.chat)
16+
messages: Collection<Message> = new Collection<Message>(this);
17+
18+
constructor({ owner, recipient }: { owner: Author | Ref<Author>; recipient: Author | Ref<Author> }) {
19+
this.owner = ref(owner);
20+
this.recipient = ref(recipient);
21+
}
22+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Entity, ManyToOne, PrimaryKey, Property, Ref, ref } from "@mikro-orm/core";
2+
import { Chat } from "./Chat";
3+
4+
@Entity()
5+
export class Message {
6+
@PrimaryKey()
7+
id!: number;
8+
9+
@ManyToOne(() => Chat, { ref: true })
10+
chat!: Ref<Chat>;
11+
12+
@Property()
13+
content: string;
14+
15+
constructor({ id, chat, content }: { id?: number; chat?: Chat | Ref<Chat>; content: string }) {
16+
if (id != null) {
17+
this.id = id;
18+
}
19+
if (chat != null) {
20+
this.chat = ref(chat);
21+
}
22+
this.content = content;
23+
}
24+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { Collection, Entity, Enum, OneToMany, PrimaryKey, Property } from "@mikro-orm/core";
2+
import { Book } from "./Book";
3+
4+
export enum PublisherType {
5+
LOCAL = "local",
6+
GLOBAL = "global",
7+
}
8+
9+
@Entity()
10+
export class Publisher {
11+
@PrimaryKey()
12+
id!: number;
13+
14+
@Property()
15+
name: string;
16+
17+
@OneToMany(() => Book, (book) => book.publisher)
18+
books = new Collection<Book, Publisher>(this);
19+
20+
@Enum(() => PublisherType)
21+
type = PublisherType.LOCAL;
22+
23+
constructor({ id, name = "asd", type = PublisherType.LOCAL }: { id?: number; name?: string; type?: PublisherType }) {
24+
if (id != null) {
25+
this.id = id;
26+
}
27+
this.name = name;
28+
this.type = type;
29+
}
30+
}

examples/graphql/src/index.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/* eslint-disable @typescript-eslint/no-misused-promises */
2+
import { createSchema, createYoga } from "graphql-yoga";
3+
import { MikroORM } from "@mikro-orm/sqlite";
4+
import config from "./mikro-orm-config";
5+
import { populateDatabase } from "./utils/populateDatabase";
6+
import { assertSingleValue, executeOperation } from "./utils/yoga";
7+
import gql from "graphql-tag";
8+
import { Book } from "./entities/Book";
9+
import { Author } from "./entities/Author";
10+
import { EntityDataLoader } from "mikro-orm-find-dataloader";
11+
import { type EntityManager } from "@mikro-orm/core";
12+
13+
const getAuthorsQuery = gql`
14+
{
15+
authors {
16+
id
17+
name
18+
books {
19+
id
20+
title
21+
}
22+
}
23+
}
24+
`;
25+
26+
void (async () => {
27+
const orm = await MikroORM.init(config);
28+
let em: EntityManager;
29+
em = orm.em.fork();
30+
try {
31+
await orm.schema.clearDatabase();
32+
} catch (e) {
33+
console.log("Couldn't clear databse");
34+
}
35+
try {
36+
const generator = orm.getSchemaGenerator();
37+
await generator.createSchema({ wrap: true });
38+
} catch {
39+
console.log("Schema has already been created");
40+
}
41+
await populateDatabase(em);
42+
em = orm.em.fork();
43+
44+
const entityDataLoader = new EntityDataLoader(em);
45+
46+
const schema = createSchema({
47+
typeDefs: gql`
48+
type Query {
49+
authors: [Author!]!
50+
}
51+
type Author {
52+
id: ID!
53+
name: String!
54+
books: [Book!]!
55+
}
56+
type Book {
57+
id: ID!
58+
title: String!
59+
author: Author!
60+
}
61+
`,
62+
resolvers: {
63+
Query: {
64+
authors: async () => await em.find(Author, {}),
65+
},
66+
Author: {
67+
books: async (author: Author) => {
68+
// return await author.books.load();
69+
// return await author.books.load({ dataloader: true });
70+
// return await em.find(Book, { author: author.id });
71+
return entityDataLoader.find(Book, { author: author.id });
72+
},
73+
},
74+
},
75+
});
76+
77+
const yoga = createYoga({ schema });
78+
const res = await executeOperation(yoga, getAuthorsQuery);
79+
assertSingleValue(res);
80+
console.log(res.data.authors);
81+
})();
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Author } from "./entities/Author";
2+
import { Book } from "./entities/Book";
3+
import { Chat } from "./entities/Chat";
4+
import { Message } from "./entities/Message";
5+
import { Publisher } from "./entities/Publisher";
6+
7+
export default {
8+
entities: [Author, Book, Chat, Message, Publisher],
9+
dbName: ":memory:",
10+
debug: true,
11+
};
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/* eslint-disable @typescript-eslint/no-non-null-assertion */
2+
import { ref, type MikroORM } from "@mikro-orm/core";
3+
import { Author } from "../entities/Author";
4+
import { Chat } from "../entities/Chat";
5+
import { Message } from "../entities/Message";
6+
import { Publisher } from "../entities/Publisher";
7+
import { Book } from "../entities/Book";
8+
9+
export async function populateDatabase(em: MikroORM["em"]): Promise<void> {
10+
const authors = [
11+
new Author({ id: 1, name: "a", email: "[email protected]" }),
12+
new Author({ id: 2, name: "b", email: "[email protected]" }),
13+
new Author({ id: 3, name: "c", email: "[email protected]" }),
14+
new Author({ id: 4, name: "d", email: "[email protected]" }),
15+
new Author({ id: 5, name: "e", email: "[email protected]" }),
16+
];
17+
authors[0]!.friends.add([authors[1]!, authors[3]!, authors[4]!]);
18+
authors[0]!.friends.add([authors[1]!, authors[3]!, authors[4]!]);
19+
authors[1]!.friends.add([authors[0]!]);
20+
authors[2]!.friends.add([authors[3]!]);
21+
authors[3]!.friends.add([authors[0]!, authors[2]!]);
22+
authors[4]!.friends.add([authors[0]!]);
23+
authors[0]!.buddies.add([authors[1]!, authors[3]!, authors[4]!]);
24+
authors[0]!.buddies.add([authors[1]!, authors[3]!, authors[4]!]);
25+
authors[1]!.buddies.add([authors[0]!]);
26+
authors[2]!.buddies.add([authors[3]!]);
27+
authors[3]!.buddies.add([authors[0]!, authors[2]!]);
28+
authors[4]!.buddies.add([authors[0]!]);
29+
em.persist(authors);
30+
31+
const chats = [
32+
new Chat({ owner: authors[0]!, recipient: authors[1]! }),
33+
new Chat({ owner: authors[0]!, recipient: authors[2]! }),
34+
new Chat({ owner: authors[0]!, recipient: authors[4]! }),
35+
new Chat({ owner: authors[2]!, recipient: authors[0]! }),
36+
];
37+
chats[0]!.messages.add([new Message({ content: "A1" }), new Message({ content: "A2" })]);
38+
chats[1]!.messages.add([new Message({ content: "B1" }), new Message({ content: "B2" })]);
39+
chats[3]!.messages.add([new Message({ content: "C1" })]);
40+
em.persist(chats);
41+
42+
const publishers = [new Publisher({ id: 1, name: "AAA" }), new Publisher({ id: 2, name: "BBB" })];
43+
em.persist(publishers);
44+
45+
const books = [
46+
new Book({ id: 1, title: "One", author: authors[0]! }),
47+
new Book({ id: 2, title: "Two", author: authors[0]! }),
48+
new Book({ id: 3, title: "Three", author: authors[1]! }),
49+
new Book({ id: 4, title: "Four", author: authors[2]! }),
50+
new Book({ id: 5, title: "Five", author: authors[2]! }),
51+
new Book({ id: 6, title: "Six", author: authors[2]! }),
52+
];
53+
books[0]!.publisher = ref(publishers[0]!);
54+
books[1]!.publisher = ref(publishers[1]!);
55+
books[2]!.publisher = ref(publishers[1]!);
56+
books[3]!.publisher = ref(publishers[1]!);
57+
books[4]!.publisher = ref(publishers[1]!);
58+
books[5]!.publisher = ref(publishers[1]!);
59+
em.persist(books);
60+
61+
await em.flush();
62+
em.clear();
63+
}

examples/graphql/src/utils/yoga.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { buildHTTPExecutor } from "@graphql-tools/executor-http";
2+
import { type YogaServerInstance } from "graphql-yoga";
3+
import { type DocumentNode, type ExecutionResult } from "graphql";
4+
5+
export function assertSingleValue<TValue extends object>(
6+
value: TValue | AsyncIterable<TValue>,
7+
): asserts value is TValue {
8+
if (Symbol.asyncIterator in value) {
9+
throw new Error("Expected single value");
10+
}
11+
}
12+
13+
type MaybeAsyncIterable<T> = AsyncIterable<T> | T;
14+
15+
export async function executeOperation(
16+
yoga: YogaServerInstance<any, any>,
17+
document: DocumentNode,
18+
): Promise<MaybeAsyncIterable<ExecutionResult<any, any>>> {
19+
const executor = buildHTTPExecutor({
20+
// eslint-disable-next-line @typescript-eslint/unbound-method
21+
fetch: yoga.fetch,
22+
});
23+
24+
return await executor({
25+
document,
26+
});
27+
}

0 commit comments

Comments
 (0)