diff --git a/backend/package.json b/backend/package.json index ee7697e..a0910d3 100644 --- a/backend/package.json +++ b/backend/package.json @@ -19,6 +19,7 @@ "license": "ISC", "dependencies": { "@apollo/server": "^5.5.1", + "dataloader": "^2.2.3", "dotenv": "^18.0.5", "graphql": "^17.0.2", "pg": "^8.23.1" diff --git a/backend/src/index.ts b/backend/src/index.ts index 514ffc7..f175d59 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -3,6 +3,7 @@ import { startStandaloneServer } from '@apollo/server/standalone'; import { pool } from './db.js'; import { typeDefs } from './schema.js'; import { resolvers } from './resolvers.js'; +import { Context, createLoaders } from './loaders.js'; // apollo-server port number [default port 3001] const port = Number(process.env.PORT ?? 3001); @@ -11,11 +12,12 @@ const port = Number(process.env.PORT ?? 3001); await pool.query('SELECT 1'); // apollo server instance -const server = new ApolloServer({ typeDefs, resolvers }); +const server = new ApolloServer({ typeDefs, resolvers }); // pass apolloserver to standaloneserver const { url } = await startStandaloneServer(server, { listen: { port }, + context: async () => ({ loaders: createLoaders() }), }); console.log(`Server ready at: ${url}`); diff --git a/backend/src/loaders.ts b/backend/src/loaders.ts new file mode 100644 index 0000000..fd0f960 --- /dev/null +++ b/backend/src/loaders.ts @@ -0,0 +1,37 @@ +import DataLoader from 'dataloader'; +import { pool } from './db.js'; + +type Facet = { id: number; name: string }; + +function relationLoader(table: string, junc: string, fk: string) { + const sql = ` + SELECT j.game_id::text AS "gameId", t.id, t.name + FROM ${junc} j + JOIN ${table} t ON t.id = j.${fk} + WHERE j.game_id = ANY($1::bigint[]) + ORDER BY t.name + `; + return new DataLoader(async (ids) => { + const res = await pool.query(sql, [[...ids]]); + const byGame = new Map(); + for (const { gameId, id, name } of res.rows) { + const list = byGame.get(gameId) ?? []; + list.push({ id, name }); + byGame.set(gameId, list); + } + return ids.map((id) => byGame.get(id) ?? []); + }); +} + +export function createLoaders() { + return { + genres: relationLoader('genres', 'game_genres', 'genre_id'), + platforms: relationLoader('platforms', 'game_platforms', 'platform_id'), + stores: relationLoader('stores', 'game_stores', 'store_id'), + developers: relationLoader('developers', 'game_developers', 'developer_id'), + publishers: relationLoader('publishers', 'game_publishers', 'publisher_id'), + tags: relationLoader('tags', 'game_tags', 'tag_id'), + }; +} + +export type Context = { loaders: ReturnType }; \ No newline at end of file diff --git a/backend/src/resolvers.ts b/backend/src/resolvers.ts index 08e0122..3cb40a5 100644 --- a/backend/src/resolvers.ts +++ b/backend/src/resolvers.ts @@ -1,6 +1,7 @@ import { GraphQLError } from 'graphql/error'; import { decodeCursor, encodeCursor } from './cursor.js'; import { pool } from './db.js'; +import type { Context } from './loaders.js'; // type definitions type GamesArgs = { @@ -149,6 +150,7 @@ function filterValues(args: GamesArgs) { ]; } // resolver for koblings-tabellene i databasen +/* function relation(table: string, junc: string, fk: string) { const sql = ` SELECT t.id, t.name @@ -162,6 +164,12 @@ function relation(table: string, junc: string, fk: string) { return res.rows; }; } + */ + +function relation(key: keyof Context['loaders']) { + return (game: { id: string }, _args: unknown, ctx: Context) => + ctx.loaders[key].load(game.id); +} // resolver-funksjoner async function game(_parent: unknown, args: { id: string }) { @@ -250,12 +258,12 @@ export const resolvers = { Query: { game, games, genres, platforms }, Game: { - genres: relation('genres', 'game_genres', 'genre_id'), - platforms: relation('platforms', 'game_platforms', 'platform_id'), - stores: relation('stores', 'game_stores', 'store_id'), - developers: relation('developers', 'game_developers', 'developer_id'), - publishers: relation('publishers', 'game_publishers', 'publisher_id'), - tags: relation('tags', 'game_tags', 'tag_id'), + genres: relation('genres'), + platforms: relation('platforms'), + stores: relation('stores'), + developers: relation('developers'), + publishers: relation('publishers'), + tags: relation('tags'), }, GameConnection: { totalCount }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 91b5994..9e1bfca 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,6 +18,9 @@ importers: '@apollo/server': specifier: ^5.5.1 version: 5.5.1(graphql@17.0.2) + dataloader: + specifier: ^2.2.3 + version: 2.2.3 dotenv: specifier: ^18.0.5 version: 18.0.5 @@ -947,6 +950,9 @@ packages: csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + dataloader@2.2.3: + resolution: {integrity: sha512-y2krtASINtPFS1rSDjacrFgn1dcUuoREVabwlOGOe4SdxenREqwjwjElAdwvbGM7kgZz9a3KVicWR7vcz8rnzA==} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -2541,6 +2547,8 @@ snapshots: csstype@3.2.3: {} + dataloader@2.2.3: {} + debug@4.4.3: dependencies: ms: 2.1.3