Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 8 additions & 0 deletions examples/pothos-interop/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Grats + Yoga + node:http

Simple demo project integrating [Grats](https://grats.capt.dev/) and [Yoga](https://github.com/dotansimha/graphql-yoga) with Node's built-in HTTP server. This example also includes an working example of GraphQL subscriptions.

## Running the demo

- `$ pnpm install`
- `$ pnpm run start`
9 changes: 9 additions & 0 deletions examples/pothos-interop/Subscription.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Int } from "grats";

/** @gqlSubscriptionField */
export async function* countdown(args: { from: Int }): AsyncIterable<Int> {
for (let i = args.from; i >= 0; i--) {
await new Promise((resolve) => setTimeout(resolve, 1000));
yield i;
}
}
12 changes: 12 additions & 0 deletions examples/pothos-interop/interfaces/IPerson.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import User from "../models/User";

/** @gqlInterface */
export default interface IPerson {
/** @gqlField */
name(): string;
}

/** @gqlQueryField */
export function person(): IPerson {
return new User();
}
20 changes: 20 additions & 0 deletions examples/pothos-interop/models/Group.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import User from "./User";

/** @gqlType */
export default class Group {
/** @gqlField */
description: string;

constructor() {
this.description = "A group of people";
}

/** @gqlField */
name(): string {
return "Pal's Club";
}
/** @gqlField */
async members(): Promise<User[]> {
return [new User()];
}
}
22 changes: 22 additions & 0 deletions examples/pothos-interop/models/User.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import IPerson from "../interfaces/IPerson";
import Group from "./Group";

/** @gqlType User */
export default class User implements IPerson {
/** @gqlField */
name(): string {
return "Alice";
}
/** @gqlField */
groups(): Group[] {
return [new Group()];
}
/** @gqlQueryField */
static me(): User {
return new User();
}
/** @gqlQueryField */
static allUsers(): User[] {
return [new User(), new User()];
}
}
40 changes: 40 additions & 0 deletions examples/pothos-interop/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"name": "grats-example-yoga",
"version": "0.0.0",
"description": "Example server showcasing Grats used with Yoga",
"main": "index.js",
"scripts": {
"start": "tsc && node dist/server.js",
"grats": "grats",
"build": "tsc"
},
"repository": {
"type": "git",
"url": "git+https://github.com/captbaritone/grats.git"
},
"keywords": [
"graphql",
"grats",
"yoga",
"javascript",
"typescript",
"subscriptions"
],
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/captbaritone/grats/issues"
},
"homepage": "https://github.com/captbaritone/grats#readme",
"dependencies": {
"@pothos/core": "^3.41.1",
"@pothos/plugin-add-graphql": "^4.2.4",
"graphql": "16.8.1",
"graphql-yoga": "^5.0.0",
"typescript": "^5.5.4"
},
"devDependencies": {
"@types/node": "^20.8.10",
"grats": "workspace:*"
}
}
17 changes: 17 additions & 0 deletions examples/pothos-interop/pothosTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import IPersonInternal from "./interfaces/IPerson";
import GroupInternal from "./models/Group";
import UserInternal from "./models/User";

type EnsureSubtype<T extends Partial<PothosSchemaTypes.UserSchemaTypes>> = T;

export type PothosUserSchemaTypes = EnsureSubtype<{
Defaults: "v4";
Objects: {
Group: GroupInternal;
User: UserInternal;
};
Interface: {
IPerson: IPersonInternal;
};
DefaultFieldNullability: true;
}>;
27 changes: 27 additions & 0 deletions examples/pothos-interop/schema.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Schema generated by Grats (https://grats.capt.dev)
# Do not manually edit. Regenerate by running `npx grats`.

interface IPerson {
name: String
}

type Group {
description: String
members: [User!]
name: String
}

type Query {
allUsers: [User!]
me: User
person: IPerson
}

type Subscription {
countdown(from: Int!): Int
}

type User implements IPerson {
groups: [Group!]
name: String
}
135 changes: 135 additions & 0 deletions examples/pothos-interop/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/**
* Executable schema generated by Grats (https://grats.capt.dev)
* Do not manually edit. Regenerate by running `npx grats`.
*/

import type TUser from "./models/User";
import type TGroup from "./models/Group";
import UserClass from "./models/User";
import queryAllUsersResolver from "./models/User";
import queryMeResolver from "./models/User";
import { GraphQLSchema, GraphQLObjectType, GraphQLList, GraphQLNonNull, GraphQLString, GraphQLInterfaceType, GraphQLInt } from "graphql";
import { person as queryPersonResolver } from "./interfaces/IPerson";
import { countdown as subscriptionCountdownResolver } from "./Subscription";
export function getSchema(): GraphQLSchema {
const GroupType: GraphQLObjectType = new GraphQLObjectType<TGroup>({
name: "Group",
fields() {
return {
description: {
name: "description",
type: GraphQLString
},
members: {
name: "members",
type: new GraphQLList(new GraphQLNonNull(UserType))
},
name: {
name: "name",
type: GraphQLString
}
};
}
});
const IPersonType: GraphQLInterfaceType = new GraphQLInterfaceType({
name: "IPerson",
fields() {
return {
name: {
name: "name",
type: GraphQLString
}
};
},
resolveType
});
const UserType: GraphQLObjectType = new GraphQLObjectType<TUser>({
name: "User",
fields() {
return {
groups: {
name: "groups",
type: new GraphQLList(new GraphQLNonNull(GroupType))
},
name: {
name: "name",
type: GraphQLString
}
};
},
interfaces() {
return [IPersonType];
}
});
const QueryType: GraphQLObjectType = new GraphQLObjectType({
name: "Query",
fields() {
return {
allUsers: {
name: "allUsers",
type: new GraphQLList(new GraphQLNonNull(UserType)),
resolve() {
return queryAllUsersResolver.allUsers();
}
},
me: {
name: "me",
type: UserType,
resolve() {
return queryMeResolver.me();
}
},
person: {
name: "person",
type: IPersonType,
resolve() {
return queryPersonResolver();
}
}
};
}
});
const SubscriptionType: GraphQLObjectType = new GraphQLObjectType({
name: "Subscription",
fields() {
return {
countdown: {
name: "countdown",
type: GraphQLInt,
args: {
from: {
type: new GraphQLNonNull(GraphQLInt)
}
},
subscribe(_source, args) {
return subscriptionCountdownResolver(args);
},
resolve(payload) {
return payload;
}
}
};
}
});
return new GraphQLSchema({
query: QueryType,
subscription: SubscriptionType,
types: [IPersonType, GroupType, QueryType, SubscriptionType, UserType]
});
}
const typeNameMap = new Map();
typeNameMap.set(UserClass, "User");
function resolveType(obj: any): string {
if (typeof obj.__typename === "string") {
return obj.__typename;
}
let prototype = Object.getPrototypeOf(obj);
while (prototype) {
const name = typeNameMap.get(prototype.constructor);
if (name != null) {
return name;
}
prototype = Object.getPrototypeOf(prototype);
}
throw new Error("Cannot find type name.");
}
36 changes: 36 additions & 0 deletions examples/pothos-interop/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { createServer } from "node:http";
import { createYoga } from "graphql-yoga";
import { getSchema } from "./schema";
import SchemaBuilder from "@pothos/core";
import AddGraphQLPlugin from "@pothos/plugin-add-graphql";
import { PothosUserSchemaTypes } from "./pothosTypes";
import User from "./models/User";

const builder = new SchemaBuilder<PothosUserSchemaTypes>({
plugins: [AddGraphQLPlugin],
add: { schema: getSchema() },
// Pothos + Grats' generated PothosUserSchemaTypes ensure this matches
// the value in Grats config.
defaultFieldNullability: true,
});

builder.queryType({
fields: (t) => ({
pothosAllUsers: t.field({
type: ["User"],
resolve: () => {
return User.allUsers();
},
}),
}),
});

const yoga = createYoga({
schema: builder.toSchema(),
});

const server = createServer(yoga);

server.listen(4000, () => {
console.log("Running a GraphQL API server at http://localhost:4000/graphql");
});
15 changes: 15 additions & 0 deletions examples/pothos-interop/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"grats": {
"requireExportedTypes": true,
"EXPERIMENTAL__emitPothos": "./pothosTypes.ts"
},
"compilerOptions": {
"outDir": "dist",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"target": "esnext",
"lib": ["esnext"],
"strict": true,
"skipLibCheck": true
}
}
Loading