Skip to content
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

refactor: pre-pano chores #539

Merged
merged 28 commits into from
Jul 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
7f98bec
chore: failing test scripts
usirin Jul 21, 2023
6b270cd
fix(@kampus/next-auth): use VERCEL_URL from env.ts & update turbo.json
usirin Jul 21, 2023
9f21bce
fix(@kampus/prisma): update seed script
usirin Jul 21, 2023
64baebb
feat(@kampus/prisma): remove slug from post model
usirin Jul 21, 2023
39af435
feat(@kampus-apps/gql): update codegen config & types
usirin Jul 22, 2023
fc233da
refactor: use return type of clients factory
usirin Jul 22, 2023
9061569
refactor: rename users to user
usirin Jul 22, 2023
cd82251
fix: update client import path
usirin Jul 22, 2023
eb32e8c
refactor: export DataLoaders from inder
usirin Jul 22, 2023
de4f35f
refactor: update codegen
usirin Jul 22, 2023
b6bd485
refactor: update codegen
usirin Jul 22, 2023
ece9fc1
chore: remove unused files
usirin Jul 22, 2023
ec593ca
test: update sozluk terms tests with correct types
usirin Jul 22, 2023
eede3d9
refactor: remove resolver files, move everything to index
usirin Jul 22, 2023
f5d2c14
feat: use string indexed resolver signatures
usirin Jul 22, 2023
6a1ef77
feat: add Date & DateTime scalars and their resolvers
usirin Jul 22, 2023
1ef3caa
chore: add Node interface to gql schema and generate its types
usirin Jul 24, 2023
1cf2eb4
chore: update codegen
usirin Jul 24, 2023
e922678
refactor: upgrade @graphql-codegen versions
usirin Jul 24, 2023
b0d8e4a
chore: update codegen script to handle interface types better
usirin Jul 24, 2023
c3c331a
feat: update user loaders
usirin Jul 25, 2023
03f4294
feat(gql-utils): add global-id utils
usirin Jul 25, 2023
0228b33
test(gql): update user tests
usirin Jul 25, 2023
4dd64b2
feat(gql-utils): add connection types
usirin Jul 26, 2023
7324148
feat(std): add dictionary type
usirin Jul 26, 2023
0673189
chore: update package jsons
usirin Jul 26, 2023
74b598f
feat(apps/gql): update loaders & resolvers
usirin Jul 26, 2023
080201b
fix: update loader creation
usirin Jul 26, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions apps/gql/clients/__mocks__/clients.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { type Clients } from "../types";
import { mockedPrisma } from "./prisma";

export const mockedClients: Clients = {
prisma: mockedPrisma
};
export const mockedClients = {
Ketcap marked this conversation as resolved.
Show resolved Hide resolved
prisma: mockedPrisma,
};
5 changes: 3 additions & 2 deletions apps/gql/clients/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { createPrismaClient } from "./prisma";
import { type Clients } from "./types";

export const createClients = (): Clients => {
export type Clients = ReturnType<typeof createClients>;

export const createClients = () => {
return {
prisma: createPrismaClient(),
};
Expand Down
5 changes: 0 additions & 5 deletions apps/gql/clients/types.ts

This file was deleted.

28 changes: 27 additions & 1 deletion apps/gql/codegen.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,39 @@
import type { CodegenConfig } from "@graphql-codegen/cli";

const scalars = { Date: "string", DateTime: "string" };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would "https://www.npmjs.com/package/graphql-scalars" help us with these ?


const config: CodegenConfig = {
schema: "schema/schema.graphql",
generates: {
"./schema/types.generated.ts": {
plugins: ["typescript", "typescript-resolvers"],
config: {
avoidOptionals: true,
// wraps the resolver types with following:
// type WithIndex<T> = T & Record<string, any>;
// this ensures the resolvers can only be dictionaries with string
// keys to avoid using number or symbol keys which are normally
// allowed in regular objects
useIndexSignature: true,

// Enforce __typename in return values of resolvers for types that
// implements an interface, or part of a union.
resolversNonOptionalTypename: true,
optionalResolveType: true,
//
// interfaces are only responsible for defining a __resolveType resolver
// and implementing types are responsible for returning the fields.
onlyResolveTypeForInterfaces: true,

// our custom context type so we have access to it in resolvers
contextType: "./types#KampusGQLContext",
avoidOptionals: true,
enumsAsTypes: true,
makeResolverTypeCallable: true,
optionalInfoArgument: true,
useTypeImports: true,

// scalars config
scalars,
},
},
},
Expand Down
13 changes: 7 additions & 6 deletions apps/gql/loaders/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { type Clients } from "~/clients/types";
import { type Clients } from "~/clients";
import { createSozlukLoaders } from "./sozluk";
import { type DataLoaders } from "./types";
import { createUsersLoader } from "./users";
import { createUserLoaders } from "./user";

export const createLoaders = (clients: Clients): DataLoaders => {
export type DataLoaders = ReturnType<typeof createLoaders>;

export const createLoaders = (clients: Clients) => {
return {
users: createUsersLoader(clients),
sozluk: createSozlukLoaders(clients),
user: createUserLoaders(clients),
sozluk: createSozlukLoaders(),
};
};
9 changes: 4 additions & 5 deletions apps/gql/loaders/sozluk.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { describe, expect, it, vi } from "vitest";

import { mockedClients } from "../clients/__mocks__/clients";
import { createSozlukLoaders } from "./sozluk";

// mock runs before everything so import needs to be handled here.
Expand All @@ -14,24 +13,24 @@ vi.mock("@kampus/sozluk-content", async () => {
describe("Sozluk Loader", () => {
describe("term loader", () => {
it("should load terms", async () => {
const loader = createSozlukLoaders(mockedClients);
const loader = createSozlukLoaders();

const result = await loader.term.load("1");

expect(result).toMatchObject({
body: {
code: "code",
html: "mdxHtml",
raw: "raw",
code: "code",
},
mdxHtml: "mdxHtml",
id: "1",
tags: ["tag1", "tag2"],
title: "title",
});
});

it("should return null if term not found", () => {
const loader = createSozlukLoaders(mockedClients);
const loader = createSozlukLoaders();

void expect(async () => {
await loader.term.load("2");
Expand Down
79 changes: 39 additions & 40 deletions apps/gql/loaders/sozluk.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,25 @@
import DataLoader from "dataloader";
import hash from "object-hash";

import { ConnectionArguments, type Connection } from "@kampus/gql-utils/connection";
import { allTerms, type Term } from "@kampus/sozluk-content";

import { applyPagination, generatePageInfo } from "~/features/relay/pagination";
import { type Clients } from "~/clients/types";
import {
type SozlukQueryTermsArgs,
type SozlukTerm,
type SozlukTermConnection,
} from "~/schema/types.generated";
import { type SozlukQueryTermsArgs, type SozlukTermConnection } from "~/schema/types.generated";

export const createSozlukLoaders = (clients: Clients) => {
export const createSozlukLoaders = () => {
return {
term: createTermLoader(clients),
terms: createTermsLoader(clients),
term: createTermLoader(),
terms: createTermsLoader(),
};
};

export type SozlukTermLoader = ReturnType<typeof createTermLoader>;
export type SozlukTermsLoader = ReturnType<typeof createTermsLoader>;

const transformTerm = (term: Term) => {
export const transformSozlukTerm = (term: Term) => {
return {
__typename: "SozlukTerm" as const,
id: term.id,
title: term.title,
tags: term.tags,
Expand All @@ -34,17 +31,22 @@ const transformTerm = (term: Term) => {
};
};

// eslint-disable-next-line @typescript-eslint/require-await
const loadTerm = async (id: string) => {
export const transformSozlukTermsConnection = (connection: Connection<Term>) => ({
...connection,
nodes: connection.nodes.map(transformSozlukTerm),
edges: connection.edges.map((edge) => ({ ...edge, node: transformSozlukTerm(edge.node) })),
});

const loadTerm = (id: string) => {
const term = allTerms.find((term) => term.id === id);
if (!term) {
return null;
}
return transformTerm(term);
return Promise.resolve(term);
};

const createTermLoader = (_: Clients) =>
new DataLoader<string, SozlukTerm>(async (keys) => {
const createTermLoader = () =>
new DataLoader<string, Term>(async (keys) => {
return await Promise.all(
keys.map(async (key) => {
const term = await loadTerm(key);
Expand All @@ -56,32 +58,29 @@ const createTermLoader = (_: Clients) =>
);
});

const createTermsLoader = (_: Clients) =>
new DataLoader<Partial<SozlukQueryTermsArgs>, SozlukTermConnection, string>(termsLoaderBatchFn, {
cacheKeyFn: (key) => hash(key),
});

const termsLoaderBatchFn = async (keys: readonly Partial<SozlukQueryTermsArgs>[]) => {
const results: SozlukTermConnection[] = [];

for (const key of keys) {
const { before, after, first, last } = key;

const terms = applyPagination<Term>({ data: allTerms, before, after, first, last });
const createTermsLoader = () =>
new DataLoader<ConnectionArguments, Connection<Term>, string>(
// eslint-disable-next-line @typescript-eslint/require-await
async (keys) => {
const results: Connection<Term>[] = [];

const edges = terms.map((term) => ({ cursor: term.id, node: transformTerm(term) }));
for (const key of keys) {
const nodes = applyPagination<Term>({ data: allTerms, ...key });
const edges = nodes.map((term) => ({ cursor: term.id, node: term }));

const totalCount = allTerms.length;
const result = {
nodes,
edges,
pageInfo: generatePageInfo({ data: allTerms, ...key }),
totalCount: allTerms.length,
};

const result = {
edges,
// need to pass the same args to generatePageInfo
pageInfo: generatePageInfo({ data: allTerms, before, after, first, last }),
totalCount,
};
results.push(result);
}

results.push(result);
}

return results;
};
return results;
},
{
cacheKeyFn: (key) => hash(key),
}
);
20 changes: 0 additions & 20 deletions apps/gql/loaders/types.test.ts

This file was deleted.

10 changes: 0 additions & 10 deletions apps/gql/loaders/types.ts

This file was deleted.

56 changes: 56 additions & 0 deletions apps/gql/loaders/user.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { beforeEach, describe, expect, it } from "vitest";

import { mockedClients } from "../clients/__mocks__/clients";
import { createUserLoaders } from "./user";

describe(createUserLoaders, () => {
beforeEach(() => {
mockedClients.prisma.user.findMany.mockResolvedValueOnce([
{
username: "test",
emailVerified: new Date(),
name: "test",
image: null,
id: "1",
createdAt: new Date(),
updatedAt: new Date(),
deletedAt: null,
email: "[email protected]",
},
]);
});

describe("byID", () => {
it("fetches users by id", async () => {
const { byID } = createUserLoaders(mockedClients);

const result = await byID.load("1");

expect(result.id).toBe("1");
expect(result.username).toBe("test");
expect(result.email).toBe("[email protected]");
});

it("should throw error if user not found with id", async () => {
const { byID } = createUserLoaders(mockedClients);
await expect(() => byID.load("2")).rejects.toThrowError(/not found/);
});
});

describe("byUsername", () => {
it("fetches users by username", async () => {
const { byUsername } = createUserLoaders(mockedClients);

const result = await byUsername.load("test");

expect(result.id).toBe("1");
expect(result.username).toBe("test");
expect(result.email).toBe("[email protected]");
});

it("should throw error if user not found with username", async () => {
const { byUsername } = createUserLoaders(mockedClients);
await expect(() => byUsername.load("mock")).rejects.toThrowError(/not found/);
});
});
});
34 changes: 34 additions & 0 deletions apps/gql/loaders/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { createPrismaLoader } from "@kampus/gql-utils";
import { type User } from "@kampus/prisma";

import { type Clients } from "~/clients";
import { LoaderKey } from "./utils/loader-key";

export class UserLoaderKey extends LoaderKey<"id" | "username", string> {}

export function createUserLoaders(clients: Clients) {
const byID = createPrismaLoader(clients.prisma.user, "id", (users) => {
users.forEach((user) => {
if (user.username) {
byUsername.prime(user.username, user);
}
});
});

const byUsername = createPrismaLoader(clients.prisma.user, "username", (users) => {
users.forEach((user) => {
byUsername.prime(user.id, user);
});
});
Ketcap marked this conversation as resolved.
Show resolved Hide resolved

return {
byID,
byUsername,
};
}

export const transformUser = (user: User) => ({
...user,
__typename: "User" as const,
username: user.username ?? "",
});
Loading