Skip to content
This repository was archived by the owner on Oct 26, 2025. It is now read-only.

Commit 24c50a2

Browse files
committed
USe structures api
1 parent 3fb669b commit 24c50a2

File tree

3 files changed

+161
-4
lines changed

3 files changed

+161
-4
lines changed

src/sharedSchema.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import * as tsMorph from "ts-morph"
55

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

1516
await step("Creating shared schema files", () => createSharedExternalSchemaFile(context))
1617
await step("Creating shared schema files via tsc", () => createSharedExternalSchemaFileViaTSC(context))
18+
await step("Creating shared schema files via structure", () => createSharedExternalSchemaFileViaStructure(context))
19+
1720
await step("Creating shared return position schema files", () => createSharedReturnPositionSchemaFile(context))
1821

1922
return [
@@ -22,7 +25,7 @@ export const createSharedSchemaFiles = async (context: AppContext, verbose: bool
2225
]
2326
}
2427

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

137140
const fullPath = context.join(context.pathSettings.typesFolderRoot, context.pathSettings.sharedFilename)
138-
const formatted = await formatDTS(fullPath, externalTSFile.getText())
141+
const text = externalTSFile.getText()
139142

140143
const prior = context.sys.readFile(fullPath)
141-
if (prior !== formatted) context.sys.writeFile(fullPath, formatted)
144+
if (prior !== text) context.sys.writeFile(fullPath, text)
142145
}
143146

144147
async function createSharedReturnPositionSchemaFile(context: AppContext) {

src/sharedSchemaStructures.ts

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
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+
}

src/sharedSchemaTSC.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { AppContext } from "./context.js"
77
import { formatDTS } from "./formatDTS.js"
88
import { typeMapper } from "./typeMap.js"
99

10-
export async function createSharedExternalSchemaFileViaTSC(context: AppContext) {
10+
export function createSharedExternalSchemaFileViaTSC(context: AppContext) {
1111
const gql = context.gql
1212
const types = gql.getTypeMap()
1313
const knownPrimitives = ["String", "Boolean", "Int"]

0 commit comments

Comments
 (0)