Skip to content

Commit

Permalink
Merge pull request #17 from puzzmo-com/fixes
Browse files Browse the repository at this point in the history
Type narrowing improvements and graphql parent type support
  • Loading branch information
orta committed Aug 18, 2024
2 parents 97f7466 + a190894 commit 7c3be0e
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 19 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@

- Better handling of modern prettier versions

- Improvements to codegen when using GraphQL types and interfaces in parent or return positions

- When a resolver is simply a fn to a literal, narrow to that exact type in the codegen (instead of keeping the optional promise type)

### 1.0.2

- Better prettier detection (and fallback) for the generated files, re #14
Expand Down
28 changes: 18 additions & 10 deletions src/serviceFile.codefacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,26 +111,24 @@ const getResolverInformationForDeclaration = (initialiser: tsMorph.Expression |
}
}

let isObjLiteral = false
if (initialiser.isKind(tsMorph.SyntaxKind.ArrowFunction)) {
const isSingleLiner = initialiser.getStatements().length === 0
if (isSingleLiner) isObjLiteral = isLiteral(initialiser.getBody())
}

return {
funcArgCount: params.length,
isFunc: true,
isAsync: initialiser.isAsync(),
isUnknown: false,
isObjLiteral: false,
isObjLiteral,
infoParamType,
}
}

// resolver is a raw obj
if (
initialiser.isKind(tsMorph.SyntaxKind.ObjectLiteralExpression) ||
initialiser.isKind(tsMorph.SyntaxKind.StringLiteral) ||
initialiser.isKind(tsMorph.SyntaxKind.NumericLiteral) ||
initialiser.isKind(tsMorph.SyntaxKind.TrueKeyword) ||
initialiser.isKind(tsMorph.SyntaxKind.FalseKeyword) ||
initialiser.isKind(tsMorph.SyntaxKind.NullKeyword) ||
initialiser.isKind(tsMorph.SyntaxKind.UndefinedKeyword)
) {
if (isLiteral(initialiser)) {
return {
funcArgCount: 0,
isFunc: false,
Expand All @@ -149,3 +147,13 @@ const getResolverInformationForDeclaration = (initialiser: tsMorph.Expression |
isObjLiteral: false,
}
}

const isLiteral = (node: tsMorph.Node) =>
node.isKind(tsMorph.SyntaxKind.ObjectLiteralExpression) ||
node.isKind(tsMorph.SyntaxKind.StringLiteral) ||
node.isKind(tsMorph.SyntaxKind.TemplateExpression) ||
node.isKind(tsMorph.SyntaxKind.NumericLiteral) ||
node.isKind(tsMorph.SyntaxKind.TrueKeyword) ||
node.isKind(tsMorph.SyntaxKind.FalseKeyword) ||
node.isKind(tsMorph.SyntaxKind.NullKeyword) ||
node.isKind(tsMorph.SyntaxKind.UndefinedKeyword)
17 changes: 13 additions & 4 deletions src/serviceFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export const lookAtServiceFile = (file: string, context: AppContext) => {

// Tracks prospective prisma models which are used in the file
const extraPrismaReferences = new Set<string>()
const extraSharedFileImportReferences = new Set<{ import: string; name?: string }>()

// The file we'll be creating in-memory throughout this fn
const fileDTS = context.tsProject.createSourceFile(`source/${fileKey}.d.ts`, "", { overwrite: true })
Expand Down Expand Up @@ -119,11 +120,14 @@ export const lookAtServiceFile = (file: string, context: AppContext) => {
})
}

if (sharedInternalGraphQLObjectsReferenced.types.length) {
if (sharedInternalGraphQLObjectsReferenced.types.length || extraSharedFileImportReferences.size) {
fileDTS.addImportDeclaration({
isTypeOnly: true,
moduleSpecifier: `./${settings.sharedInternalFilename.replace(".d.ts", "")}`,
namedImports: sharedInternalGraphQLObjectsReferenced.types.map((t) => `${t} as RT${t}`),
namedImports: [
...sharedInternalGraphQLObjectsReferenced.types.map((t) => `${t} as RT${t}`),
...[...extraSharedFileImportReferences.values()].map((t) => ("name" in t && t.name ? `${t.import} as ${t.name}` : t.import)),
],
})
}

Expand Down Expand Up @@ -232,12 +236,16 @@ export const lookAtServiceFile = (file: string, context: AppContext) => {
isExported: true,
})

// Handle extending classes in the runtime which only exist in SDL
const parentIsPrisma = prisma.has(modelName)
if (!parentIsPrisma) extraSharedFileImportReferences.add({ name: `S${modelName}`, import: modelName })
const suffix = parentIsPrisma ? "P" : "S"

// The parent type for the resolvers
fileDTS.addTypeAlias({
name: `${modelName}AsParent`,
typeParameters: hasGenerics ? ["Extended"] : [],
type: `P${modelName} ${createParentAdditionallyDefinedFunctions()} ${hasGenerics ? " & Extended" : ""}`,
// docs: ["The prisma model, mixed with fns already defined inside the resolvers."],
type: `${suffix}${modelName} ${createParentAdditionallyDefinedFunctions()} ${hasGenerics ? " & Extended" : ""}`,
})

const modelFieldFacts = fieldFacts.get(modelName) ?? {}
Expand Down Expand Up @@ -311,6 +319,7 @@ function returnTypeForResolver(mapper: TypeMapper, field: graphql.GraphQLField<u
const all = `${tType} | Promise<${tType}> | (() => Promise<${tType}>)`

if (resolver.isFunc && resolver.isAsync) returnType = `Promise<${tType}>`
else if (resolver.isFunc && !resolver.isObjLiteral) returnType = tType
else if (resolver.isFunc) returnType = all
else if (resolver.isObjLiteral) returnType = tType
else if (resolver.isUnknown) returnType = all
Expand Down
42 changes: 42 additions & 0 deletions src/tests/bugs/parentCanBeGraphQLObject.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { expect, it } from "vitest"

import { getDTSFilesForRun, graphql, prisma } from "../testRunner.js"

it("Uses GraphQL objects when prisma objects are not available for resolver parents", () => {
const prismaSchema = prisma`
model Game {
id Int @id @default(autoincrement())
}
`

const sdl = graphql`
type Game {
id: Int!
}
type Puzzle {
id: Int!
}
`

const gamesService = `
import { db } from "src/lib/db";
export const Puzzle = {
id: "",
};
`

const { vfsMap } = getDTSFilesForRun({ sdl, gamesService, prismaSchema })
const dts = vfsMap.get("/types/games.d.ts")!
expect(dts.trim()).toMatchInlineSnapshot(`
"import type { Puzzle as SPuzzle } from \\"./shared-return-types\\";
export interface PuzzleTypeResolvers {
/** SDL: id: Int! */
id: number;
}
type PuzzleAsParent = SPuzzle & { id: () => number };"
`)
})
56 changes: 56 additions & 0 deletions src/tests/bugs/returnObjectCanBeGraphQLInterfaces.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { expect, it } from "vitest"

import { getDTSFilesForRun, graphql, prisma } from "../testRunner.js"

it("The retunr type can be a graphql interface", () => {
const prismaSchema = prisma`
model Game {
id Int @id @default(autoincrement())
}
`

const sdl = graphql`
type Game {
id: Int!
puzzle: Node!
}
interface Node {
id: ID!
}
`

const gamesService = `
import { db } from "src/lib/db";
export const Game = {
puzzle: () => {}
};
`

const { vfsMap } = getDTSFilesForRun({ sdl, gamesService, prismaSchema })
const dts = vfsMap.get("/types/games.d.ts")!
expect(dts.trim()).toMatchInlineSnapshot(`
"import type { Game as PGame } from \\"@prisma/client\\";
import type { GraphQLResolveInfo } from \\"graphql\\";
import type { RedwoodGraphQLContext } from \\"@redwoodjs/graphql-server/dist/types\\";
import type { Node as RTNode } from \\"./shared-return-types\\";
import type { Node } from \\"./shared-schema-types\\";
export interface GameTypeResolvers {
/** SDL: puzzle: Node! */
puzzle: (
args?: undefined,
obj?: {
root: GameAsParent;
context: RedwoodGraphQLContext;
info: GraphQLResolveInfo;
}
) => RTNode;
}
type GameAsParent = PGame & { puzzle: () => RTNode };"
`)
})
21 changes: 18 additions & 3 deletions src/tests/features/preferPromiseFnWhenKnown.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ model Game {
awayTeamID: Int!
summarySync: String!
summarySyncBlock: String!
summaryAsync: String!
summary: String!
}
Expand All @@ -43,6 +44,9 @@ export const gameObj = {}
export const Game = {
summary: "",
summarySync: () => "",
summarySyncBlock: () => {
return ""
},
summaryAsync: async () => ""
};
`
Expand All @@ -67,7 +71,7 @@ export const Game = {
context: RedwoodGraphQLContext;
info: GraphQLResolveInfo;
}
): RTGame | null | Promise<RTGame | null> | (() => Promise<RTGame | null>);
): RTGame | null;
}
/** SDL: gameAsync: Game */
Expand All @@ -91,7 +95,7 @@ export const Game = {
context: RedwoodGraphQLContext;
info: GraphQLResolveInfo;
}
): RTGame | null | Promise<RTGame | null> | (() => Promise<RTGame | null>);
): RTGame | null;
}
/** SDL: gameAsync2Arg: Game */
Expand All @@ -103,7 +107,7 @@ export const Game = {
context: RedwoodGraphQLContext;
info: GraphQLResolveInfo;
}
): RTGame | null | Promise<RTGame | null> | (() => Promise<RTGame | null>);
): RTGame | null;
}
/** SDL: gameObj: Game */
Expand Down Expand Up @@ -132,6 +136,16 @@ export const Game = {
}
) => string | Promise<string> | (() => Promise<string>);
/** SDL: summarySyncBlock: String! */
summarySyncBlock: (
args?: undefined,
obj?: {
root: GameAsParent<Extended>;
context: RedwoodGraphQLContext;
info: GraphQLResolveInfo;
}
) => string;
/** SDL: summaryAsync: String! */
summaryAsync: (
args?: undefined,
Expand All @@ -146,6 +160,7 @@ export const Game = {
type GameAsParent<Extended> = PGame & {
summary: () => string;
summarySync: () => string | Promise<string> | (() => Promise<string>);
summarySyncBlock: () => string;
summaryAsync: () => Promise<string>;
} & Extended;"
`)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export const Game = {
context: RedwoodGraphQLContext;
info: GraphQLResolveInfo;
}
): RTGame | null | Promise<RTGame | null> | (() => Promise<RTGame | null>);
): RTGame | null;
}
export interface GameTypeResolvers {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export const Game: GameResolvers = {};
context: RedwoodGraphQLContext;
info: GraphQLResolveInfo;
}
): RTGame[] | Promise<RTGame[]> | (() => Promise<RTGame[]>);
): RTGame[];
}
export interface GameTypeResolvers {}
Expand Down
1 change: 1 addition & 0 deletions src/typeMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ export const typeMapper = (context: AppContext, config: { preferPrismaModels?: t
}

if (graphql.isInterfaceType(type)) {
referencedGraphQLTypes.add(type.name)
return prefix + type.name
}

Expand Down

0 comments on commit 7c3be0e

Please sign in to comment.