|
| 1 | +/// The main schema for objects and inputs |
| 2 | + |
| 3 | +import * as graphql from "graphql" |
| 4 | +import * as tsMorph from "ts-morph" |
| 5 | + |
| 6 | +import { AppContext } from "./context.js" |
| 7 | +import { typeMapper } from "./typeMap.js" |
| 8 | + |
| 9 | +export function createSharedExternalSchemaFileViaStructure(context: AppContext) { |
| 10 | + const { gql, prisma, fieldFacts } = context |
| 11 | + const types = gql.getTypeMap() |
| 12 | + const mapper = typeMapper(context, { preferPrismaModels: true }) |
| 13 | + |
| 14 | + const typesToImport = [] as string[] |
| 15 | + const knownPrimitives = ["String", "Boolean", "Int"] |
| 16 | + |
| 17 | + const externalTSFile = context.tsProject.createSourceFile( |
| 18 | + `/source/a/${context.pathSettings.sharedInternalFilename}`, |
| 19 | + ` |
| 20 | +// You may very reasonably ask yourself, 'what is this file?' and why do I need it. |
| 21 | +
|
| 22 | +// Roughly, this file ensures that when a resolver wants to return a type - that |
| 23 | +// type will match a prisma model. This is useful because you can trivially extend |
| 24 | +// the type in the SDL and not have to worry about type mis-matches because the thing |
| 25 | +// you returned does not include those functions. |
| 26 | +
|
| 27 | +// This gets particularly valuable when you want to return a union type, an interface, |
| 28 | +// or a model where the prisma model is nested pretty deeply (GraphQL connections, for example.) |
| 29 | +
|
| 30 | +` |
| 31 | + ) |
| 32 | + |
| 33 | + const statements = [] as tsMorph.StatementStructures[] |
| 34 | + |
| 35 | + Object.keys(types).forEach((name) => { |
| 36 | + if (name.startsWith("__")) { |
| 37 | + return |
| 38 | + } |
| 39 | + |
| 40 | + if (knownPrimitives.includes(name)) { |
| 41 | + return |
| 42 | + } |
| 43 | + |
| 44 | + const type = types[name] |
| 45 | + const pType = prisma.get(name) |
| 46 | + |
| 47 | + if (graphql.isObjectType(type) || graphql.isInterfaceType(type) || graphql.isInputObjectType(type)) { |
| 48 | + // Return straight away if we have a matching type in the prisma schema |
| 49 | + // as we dont need it |
| 50 | + if (pType) { |
| 51 | + typesToImport.push(name) |
| 52 | + return |
| 53 | + } |
| 54 | + |
| 55 | + statements.push({ |
| 56 | + name: type.name, |
| 57 | + kind: tsMorph.StructureKind.Interface, |
| 58 | + isExported: true, |
| 59 | + docs: [], |
| 60 | + properties: [ |
| 61 | + { |
| 62 | + name: "__typename", |
| 63 | + type: `"${type.name}"`, |
| 64 | + hasQuestionToken: true, |
| 65 | + }, |
| 66 | + ...Object.entries(type.getFields()).map(([fieldName, obj]: [string, graphql.GraphQLField<object, object>]) => { |
| 67 | + const hasResolverImplementation = fieldFacts.get(name)?.[fieldName]?.hasResolverImplementation |
| 68 | + const isOptionalInSDL = !graphql.isNonNullType(obj.type) |
| 69 | + const doesNotExistInPrisma = false // !prismaField; |
| 70 | + |
| 71 | + const field: tsMorph.OptionalKind<tsMorph.PropertySignatureStructure> = { |
| 72 | + name: fieldName, |
| 73 | + type: mapper.map(obj.type, { preferNullOverUndefined: true }), |
| 74 | + hasQuestionToken: hasResolverImplementation ?? (isOptionalInSDL || doesNotExistInPrisma), |
| 75 | + } |
| 76 | + return field |
| 77 | + }), |
| 78 | + ], |
| 79 | + }) |
| 80 | + } |
| 81 | + |
| 82 | + if (graphql.isEnumType(type)) { |
| 83 | + statements.push({ |
| 84 | + name: type.name, |
| 85 | + isExported: true, |
| 86 | + kind: tsMorph.StructureKind.TypeAlias, |
| 87 | + type: |
| 88 | + '"' + |
| 89 | + type |
| 90 | + .getValues() |
| 91 | + .map((m) => (m as { value: string }).value) |
| 92 | + .join('" | "') + |
| 93 | + '"', |
| 94 | + }) |
| 95 | + } |
| 96 | + |
| 97 | + if (graphql.isUnionType(type)) { |
| 98 | + statements.push({ |
| 99 | + name: type.name, |
| 100 | + kind: tsMorph.StructureKind.TypeAlias, |
| 101 | + isExported: true, |
| 102 | + type: type |
| 103 | + .getTypes() |
| 104 | + .map((m) => m.name) |
| 105 | + .join(" | "), |
| 106 | + }) |
| 107 | + } |
| 108 | + }) |
| 109 | + |
| 110 | + const { scalars, prisma: prismaModels } = mapper.getReferencedGraphQLThingsInMapping() |
| 111 | + if (scalars.length) { |
| 112 | + statements.push( |
| 113 | + ...scalars.map( |
| 114 | + (s) => |
| 115 | + ({ |
| 116 | + kind: tsMorph.StructureKind.TypeAlias, |
| 117 | + name: s, |
| 118 | + type: "any", |
| 119 | + } as const) |
| 120 | + ) |
| 121 | + ) |
| 122 | + } |
| 123 | + |
| 124 | + const allPrismaModels = [...new Set([...prismaModels, ...typesToImport])].sort() |
| 125 | + if (allPrismaModels.length) { |
| 126 | + statements.push({ |
| 127 | + kind: tsMorph.StructureKind.ImportDeclaration, |
| 128 | + moduleSpecifier: `@prisma/client`, |
| 129 | + namedImports: allPrismaModels.map((p) => `${p} as P${p}`), |
| 130 | + }) |
| 131 | + |
| 132 | + statements.push( |
| 133 | + ...allPrismaModels.map( |
| 134 | + (p) => |
| 135 | + ({ |
| 136 | + kind: tsMorph.StructureKind.TypeAlias, |
| 137 | + name: p, |
| 138 | + type: `P${p}`, |
| 139 | + } as const) |
| 140 | + ) |
| 141 | + ) |
| 142 | + } |
| 143 | + |
| 144 | + const fullPath = context.join(context.pathSettings.typesFolderRoot, context.pathSettings.sharedInternalFilename) |
| 145 | + externalTSFile.set({ statements }) |
| 146 | + const text = externalTSFile.getText() |
| 147 | + |
| 148 | + // console.log(sourceFileStructure.statements) |
| 149 | + // console.log(text) |
| 150 | + // const formatted = await formatDTS(fullPath, externalTSFile) |
| 151 | + |
| 152 | + const prior = context.sys.readFile(fullPath) |
| 153 | + if (prior !== text) context.sys.writeFile(fullPath, text) |
| 154 | +} |
0 commit comments