Skip to content

Commit

Permalink
USe structures api
Browse files Browse the repository at this point in the history
  • Loading branch information
orta committed Oct 29, 2024
1 parent 3fb669b commit 24c50a2
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 4 deletions.
9 changes: 6 additions & 3 deletions src/sharedSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as tsMorph from "ts-morph"

import { AppContext } from "./context.js"
import { formatDTS } from "./formatDTS.js"
import { createSharedExternalSchemaFileViaStructure } from "./sharedSchemaStructures.js"
import { createSharedExternalSchemaFileViaTSC } from "./sharedSchemaTSC.js"
import { typeMapper } from "./typeMap.js"
import { makeStep } from "./utils.js"
Expand All @@ -14,6 +15,8 @@ export const createSharedSchemaFiles = async (context: AppContext, verbose: bool

await step("Creating shared schema files", () => createSharedExternalSchemaFile(context))
await step("Creating shared schema files via tsc", () => createSharedExternalSchemaFileViaTSC(context))
await step("Creating shared schema files via structure", () => createSharedExternalSchemaFileViaStructure(context))

await step("Creating shared return position schema files", () => createSharedReturnPositionSchemaFile(context))

return [
Expand All @@ -22,7 +25,7 @@ export const createSharedSchemaFiles = async (context: AppContext, verbose: bool
]
}

async function createSharedExternalSchemaFile(context: AppContext) {
function createSharedExternalSchemaFile(context: AppContext) {
const gql = context.gql
const types = gql.getTypeMap()
const knownPrimitives = ["String", "Boolean", "Int"]
Expand Down Expand Up @@ -135,10 +138,10 @@ async function createSharedExternalSchemaFile(context: AppContext) {
if (scalars.length) externalTSFile.addTypeAliases(scalars.map((s) => ({ name: s, type: "any" })))

const fullPath = context.join(context.pathSettings.typesFolderRoot, context.pathSettings.sharedFilename)
const formatted = await formatDTS(fullPath, externalTSFile.getText())
const text = externalTSFile.getText()

const prior = context.sys.readFile(fullPath)
if (prior !== formatted) context.sys.writeFile(fullPath, formatted)
if (prior !== text) context.sys.writeFile(fullPath, text)
}

async function createSharedReturnPositionSchemaFile(context: AppContext) {
Expand Down
154 changes: 154 additions & 0 deletions src/sharedSchemaStructures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/// The main schema for objects and inputs

import * as graphql from "graphql"
import * as tsMorph from "ts-morph"

import { AppContext } from "./context.js"
import { typeMapper } from "./typeMap.js"

export function createSharedExternalSchemaFileViaStructure(context: AppContext) {
const { gql, prisma, fieldFacts } = context
const types = gql.getTypeMap()
const mapper = typeMapper(context, { preferPrismaModels: true })

const typesToImport = [] as string[]
const knownPrimitives = ["String", "Boolean", "Int"]

const externalTSFile = context.tsProject.createSourceFile(
`/source/a/${context.pathSettings.sharedInternalFilename}`,
`
// You may very reasonably ask yourself, 'what is this file?' and why do I need it.
// Roughly, this file ensures that when a resolver wants to return a type - that
// type will match a prisma model. This is useful because you can trivially extend
// the type in the SDL and not have to worry about type mis-matches because the thing
// you returned does not include those functions.
// This gets particularly valuable when you want to return a union type, an interface,
// or a model where the prisma model is nested pretty deeply (GraphQL connections, for example.)
`
)

const statements = [] as tsMorph.StatementStructures[]

Object.keys(types).forEach((name) => {
if (name.startsWith("__")) {
return
}

if (knownPrimitives.includes(name)) {
return
}

const type = types[name]
const pType = prisma.get(name)

if (graphql.isObjectType(type) || graphql.isInterfaceType(type) || graphql.isInputObjectType(type)) {
// Return straight away if we have a matching type in the prisma schema
// as we dont need it
if (pType) {
typesToImport.push(name)
return
}

statements.push({
name: type.name,
kind: tsMorph.StructureKind.Interface,
isExported: true,
docs: [],
properties: [
{
name: "__typename",
type: `"${type.name}"`,
hasQuestionToken: true,
},
...Object.entries(type.getFields()).map(([fieldName, obj]: [string, graphql.GraphQLField<object, object>]) => {
const hasResolverImplementation = fieldFacts.get(name)?.[fieldName]?.hasResolverImplementation
const isOptionalInSDL = !graphql.isNonNullType(obj.type)
const doesNotExistInPrisma = false // !prismaField;

const field: tsMorph.OptionalKind<tsMorph.PropertySignatureStructure> = {
name: fieldName,
type: mapper.map(obj.type, { preferNullOverUndefined: true }),
hasQuestionToken: hasResolverImplementation ?? (isOptionalInSDL || doesNotExistInPrisma),
}
return field
}),
],
})
}

if (graphql.isEnumType(type)) {
statements.push({
name: type.name,
isExported: true,
kind: tsMorph.StructureKind.TypeAlias,
type:
'"' +
type
.getValues()
.map((m) => (m as { value: string }).value)
.join('" | "') +
'"',
})
}

if (graphql.isUnionType(type)) {
statements.push({
name: type.name,
kind: tsMorph.StructureKind.TypeAlias,
isExported: true,
type: type
.getTypes()
.map((m) => m.name)
.join(" | "),
})
}
})

const { scalars, prisma: prismaModels } = mapper.getReferencedGraphQLThingsInMapping()
if (scalars.length) {
statements.push(
...scalars.map(
(s) =>
({
kind: tsMorph.StructureKind.TypeAlias,
name: s,
type: "any",
} as const)
)
)
}

const allPrismaModels = [...new Set([...prismaModels, ...typesToImport])].sort()
if (allPrismaModels.length) {
statements.push({
kind: tsMorph.StructureKind.ImportDeclaration,
moduleSpecifier: `@prisma/client`,
namedImports: allPrismaModels.map((p) => `${p} as P${p}`),
})

statements.push(
...allPrismaModels.map(
(p) =>
({
kind: tsMorph.StructureKind.TypeAlias,
name: p,
type: `P${p}`,
} as const)
)
)
}

const fullPath = context.join(context.pathSettings.typesFolderRoot, context.pathSettings.sharedInternalFilename)
externalTSFile.set({ statements })
const text = externalTSFile.getText()

// console.log(sourceFileStructure.statements)
// console.log(text)
// const formatted = await formatDTS(fullPath, externalTSFile)

const prior = context.sys.readFile(fullPath)
if (prior !== text) context.sys.writeFile(fullPath, text)
}
2 changes: 1 addition & 1 deletion src/sharedSchemaTSC.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { AppContext } from "./context.js"
import { formatDTS } from "./formatDTS.js"

Check warning on line 7 in src/sharedSchemaTSC.ts

View workflow job for this annotation

GitHub Actions / lint

'formatDTS' is defined but never used
import { typeMapper } from "./typeMap.js"

export async function createSharedExternalSchemaFileViaTSC(context: AppContext) {
export function createSharedExternalSchemaFileViaTSC(context: AppContext) {
const gql = context.gql
const types = gql.getTypeMap()
const knownPrimitives = ["String", "Boolean", "Int"]
Expand Down

0 comments on commit 24c50a2

Please sign in to comment.