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

Generics #132

Merged
merged 40 commits into from
Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
0080cf5
Initial implementation of generics
captbaritone Mar 16, 2024
133db68
Avoid materializing templates more than once
captbaritone Mar 16, 2024
9e0811a
Refactor/cleanup
captbaritone Mar 16, 2024
69a95e0
Remove fluke
captbaritone Mar 16, 2024
357977f
Save some progress
captbaritone Mar 16, 2024
36ad1ef
Generics referenced inside templates
captbaritone Mar 16, 2024
ba9da0b
Templates as generics
captbaritone Mar 16, 2024
81cd516
Cleanup
captbaritone Mar 16, 2024
9334ec4
More cleanup
captbaritone Mar 16, 2024
b9733ec
Start reporting errors
captbaritone Mar 16, 2024
61a92e6
Small notes
captbaritone Mar 17, 2024
265315f
Hack to get rest of tests passing again
captbaritone Mar 17, 2024
f330a9a
Ensure it's okay to qualify generics
captbaritone Mar 17, 2024
ca22e6b
Generics in abstract fields as well as multi param validation
captbaritone Mar 17, 2024
280dede
Investigate defining fields on generics
captbaritone Mar 17, 2024
4ff2467
Refactor toward documented algorithm
captbaritone Mar 18, 2024
f1317f1
Implement new algorithm
captbaritone Mar 18, 2024
f09a15b
Additional tests for generics
captbaritone Mar 18, 2024
4f132b7
Don't use identity for tracking connection between ts nodes and graph…
captbaritone Mar 19, 2024
91e3141
Use generics in production app
captbaritone Mar 19, 2024
8cc7f49
More docs, more tests
captbaritone Mar 19, 2024
0353a9f
Progress on type context refactor
captbaritone Mar 19, 2024
6fac977
Add test passing output type to input type generic
captbaritone Mar 20, 2024
2e01000
TODO update
captbaritone Mar 20, 2024
9c047ec
Refactor TypeContext progress checkpoint
captbaritone Mar 20, 2024
dc07f90
More TypeContext refactor progress
captbaritone Mar 20, 2024
a697694
Refactor of TypeContext: Starting to get there!
captbaritone Mar 20, 2024
d0352ee
TypeContext refactor complete
captbaritone Mar 20, 2024
f874b94
Improve error messages
captbaritone Mar 21, 2024
0ec8f6a
Fix docs link
captbaritone Mar 21, 2024
5698faf
Clean up and support for parameterize interfaces
captbaritone Mar 21, 2024
7350a8b
Update error message
captbaritone Mar 21, 2024
9feec25
Mark tests relating to generics and __typename as todo
captbaritone Mar 21, 2024
7c8c4e6
Clean up todo.md (moved to PR)
captbaritone Mar 21, 2024
a28dc7b
Update @stream docs
captbaritone Mar 21, 2024
b6b7b74
Verify more invalid generics uses
captbaritone Mar 21, 2024
958a618
Improve error messages for generics used in abstract types
captbaritone Mar 22, 2024
65d084f
Update test fixtures
captbaritone Mar 22, 2024
8eee29d
Avoid needing special declaration node in favor of augmenting graphql…
captbaritone Mar 22, 2024
e894602
Document limitaitons
captbaritone Mar 22, 2024
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
27 changes: 27 additions & 0 deletions examples/production-app/graphql/Connection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/** @gqlType */
export type Connection<T> = {
/** @gqlField */
edges: Edge<T>[];
/** @gqlField */
pageInfo: PageInfo;
};

/** @gqlType */
export type Edge<T> = {
/** @gqlField */
node: T;
/** @gqlField */
cursor: string;
};

/** @gqlType */
export type PageInfo = {
/** @gqlField */
startCursor: string | null;
/** @gqlField */
endCursor: string | null;
/** @gqlField */
hasNextPage: boolean;
/** @gqlField */
hasPreviousPage: boolean;
};
2 changes: 1 addition & 1 deletion examples/production-app/models/LikeConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as DB from "../Database";
import { Ctx } from "../ViewerContext";
import { Query, Subscription } from "../graphql/Roots";
import { Like } from "./Like";
import { PageInfo } from "./PageInfo";
import { PageInfo } from "../graphql/Connection";
import { connectionFromArray } from "graphql-relay";
import { Post } from "./Post";
import { PubSub } from "../PubSub";
Expand Down
11 changes: 0 additions & 11 deletions examples/production-app/models/PageInfo.ts

This file was deleted.

22 changes: 3 additions & 19 deletions examples/production-app/models/PostConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,16 @@ import * as DB from "../Database";
import { Ctx } from "../ViewerContext";
import { Query } from "../graphql/Roots";
import { Post } from "./Post";
import { PageInfo } from "./PageInfo";
import { Connection } from "../graphql/Connection";
import { connectionFromArray } from "graphql-relay";

/** @gqlType */
export type PostConnection = {
/** @gqlField */
edges: PostEdge[];
/** @gqlField */
pageInfo: PageInfo;
};

/**
* Convenience field to get the nodes from a connection.
* @gqlField */
export function nodes(userConnection: PostConnection): Post[] {
export function nodes(userConnection: Connection<Post>): Post[] {
return userConnection.edges.map((edge) => edge.node);
}

/** @gqlType */
type PostEdge = {
/** @gqlField */
node: Post;
/** @gqlField */
cursor: string;
};

// --- Root Fields ---

/**
Expand All @@ -43,7 +27,7 @@ export async function posts(
before?: string | null;
},
ctx: Ctx,
): Promise<PostConnection> {
): Promise<Connection<Post>> {
const rows = await DB.selectPosts(ctx.vc);
const posts = rows.map((row) => new Post(row));
return connectionFromArray(posts, args);
Expand Down
4 changes: 2 additions & 2 deletions examples/production-app/models/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { Ctx } from "../ViewerContext";
import { GraphQLNode } from "../graphql/Node";
import { Model } from "./Model";
import { Post } from "./Post";
import { PostConnection } from "./PostConnection";
import { Mutation } from "../graphql/Roots";
import { Connection } from "../graphql/Connection";

/** @gqlType */
export class User extends Model<DB.UserRow> implements GraphQLNode {
Expand All @@ -21,7 +21,7 @@ export class User extends Model<DB.UserRow> implements GraphQLNode {
/**
* All posts written by this user. Note that there is no guarantee of order.
* @gqlField */
async posts(_: unknown, ctx: Ctx): Promise<PostConnection> {
async posts(_: unknown, ctx: Ctx): Promise<Connection<Post>> {
const rows = await DB.selectPostsWhereAuthor(ctx.vc, this.row.id);
const posts = rows.map((row) => new Post(row));
return connectionFromArray(posts, {});
Expand Down
22 changes: 3 additions & 19 deletions examples/production-app/models/UserConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,16 @@ import * as DB from "../Database";
import { Ctx } from "../ViewerContext";
import { Query } from "../graphql/Roots";
import { User } from "./User";
import { PageInfo } from "./PageInfo";
import { Connection } from "../graphql/Connection";
import { connectionFromArray } from "graphql-relay";

/** @gqlType */
export type UserConnection = {
/** @gqlField */
edges: UserEdge[];
/** @gqlField */
pageInfo: PageInfo;
};

/**
* Convenience field to get the nodes from a connection.
* @gqlField */
export function nodes(userConnection: UserConnection): User[] {
export function nodes(userConnection: Connection<User>): User[] {
return userConnection.edges.map((edge) => edge.node);
}

/** @gqlType */
type UserEdge = {
/** @gqlField */
node: User;
/** @gqlField */
cursor: string;
};

// --- Root Fields ---

/**
Expand All @@ -43,7 +27,7 @@ export async function users(
before?: string | null;
},
ctx: Ctx,
): Promise<UserConnection> {
): Promise<Connection<User>> {
const rows = await DB.selectUsers(ctx.vc);
const users = rows.map((row) => new User(row));
return connectionFromArray(users, args);
Expand Down
97 changes: 37 additions & 60 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions src/Errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -412,3 +412,28 @@ export function missingSpecifiedByUrl() {
export function specifiedByOnWrongNode() {
return `Unexpected \`@${SPECIFIED_BY_TAG}\` tag on non-scalar declaration. \`@${SPECIFIED_BY_TAG}\` can only be used on custom scalar declarations. Are you missing a \`@${SCALAR_TAG}\` tag?`;
}

export function missingGenericType(
templateName: string,
paramName: string,
): string {
return `Missing type argument for generic GraphQL type. Expected \`${templateName}\` to be passed a GraphQL type argument for type parameter \`${paramName}\`.`;
}

export function nonGraphQLGenericType(
templateName: string,
paramName: string,
): string {
return `Expected \`${templateName}\` to be passed a GraphQL type argument for type parameter \`${paramName}\`.`;
}

export function genericTypeUsedAsUnionMember(): string {
return `Unexpected generic type used sa union member. Generic type may not currently be used as members of a union. Grats requires that all union members define a \`__typename\` field typed as a string literal matching the type's name. Since generic types are synthesized into multiple types with different names, Grats cannot ensure they have a correct \`__typename\` property and thus cannot be used as members of a union.`;
}
export function genericTypeImplementsInterface(): string {
return `Unexpected \`implements\` on generic \`${TYPE_TAG}\`. Generic types may not currently declare themselves as implementing interfaces. Grats requires that all types which implement an interface define a \`__typename\` field typed as a string literal matching the type's name. Since generic types are synthesized into multiple types with different names, Grats cannot ensure they have a correct \`__typename\` property and thus declare themselves as interface implementors.`;
}

export function concreteTypeMissingTypename(implementor: string): string {
return `Missing \`__typename\` on \`${implementor}\`. The type \`${implementor}\` is used in a union or interface, so it must have a \`__typename\` field.`;
}
Loading
Loading