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

Commit 3fb669b

Browse files
committed
Use the typescript factory ap
1 parent a1213c9 commit 3fb669b

File tree

7 files changed

+170
-35
lines changed

7 files changed

+170
-35
lines changed

src/formatDTS.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
// https://prettier.io/docs/en/api.html
22

33
let hasPrettierInstalled = false
4+
let prettier = null
45
try {
56
hasPrettierInstalled = !!require.resolve("prettier")
7+
prettier = require("prettier")
68
} catch (error) {}
79

810
export const formatDTS = async (path: string, content: string): Promise<string> => {
911
if (!hasPrettierInstalled) return content
1012

1113
try {
12-
const prettier = await import("prettier")
1314
if (!prettier) return content
1415
return prettier.format(content, { filepath: path })
1516
} catch (error) {

src/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,15 @@ export async function runFullCodegen(preset: string, config: unknown): Promise<S
8989

9090
// Create the two shared schema files
9191
await step("Create shared schema files", async () => {
92-
const sharedDTSes = await createSharedSchemaFiles(appContext)
92+
const sharedDTSes = await createSharedSchemaFiles(appContext, verbose)
9393
filepaths.push(...sharedDTSes)
9494
})
9595

9696
let knownServiceFiles: string[] = []
9797
const createDTSFilesForAllServices = async () => {
98-
// TODO: Maybe Redwood has an API for this? Its grabbing all the services
9998
const serviceFiles = appContext.sys.readDirectory(appContext.pathSettings.apiServicesPath)
10099
knownServiceFiles = serviceFiles.filter(isRedwoodServiceFile)
100+
101101
for (const path of knownServiceFiles) {
102102
const dts = await lookAtServiceFile(path, appContext)
103103
if (dts) filepaths.push(dts)
@@ -123,7 +123,7 @@ export async function runFullCodegen(preset: string, config: unknown): Promise<S
123123

124124
if (verbose) console.log("[sdl-codegen] SDL Schema changed")
125125
await step("GraphQL schema changed", () => getGraphQLSDLFromFile(appContext.pathSettings))
126-
await step("Create all shared schema files", () => createSharedSchemaFiles(appContext))
126+
await step("Create all shared schema files", () => createSharedSchemaFiles(appContext, verbose))
127127
await step("Create all service files", createDTSFilesForAllServices)
128128
} else if (path === appContext.pathSettings.prismaDSLPath) {
129129
await step("Prisma schema changed", () => getPrismaSchemaFromFile(appContext.pathSettings))

src/serviceFile.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ export const lookAtServiceFile = async (file: string, context: AppContext) => {
2828

2929
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
3030
const mutationType = gql.getMutationType()!
31-
3231
if (!mutationType) throw new Error("No mutation type")
3332

3433
const externalMapper = typeMapper(context, { preferPrismaModels: true })
@@ -201,6 +200,8 @@ export const lookAtServiceFile = async (file: string, context: AppContext) => {
201200
],
202201
returnType,
203202
})
203+
204+
interfaceDeclaration.forget()
204205
}
205206

206207
/** Ideally, we want to be able to write the type for just the object */

src/sharedSchema.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@ import * as tsMorph from "ts-morph"
55

66
import { AppContext } from "./context.js"
77
import { formatDTS } from "./formatDTS.js"
8+
import { createSharedExternalSchemaFileViaTSC } from "./sharedSchemaTSC.js"
89
import { typeMapper } from "./typeMap.js"
910
import { makeStep } from "./utils.js"
1011

11-
export const createSharedSchemaFiles = async (context: AppContext) => {
12-
const verbose = !!(context as { verbose?: true }).verbose
12+
export const createSharedSchemaFiles = async (context: AppContext, verbose: boolean) => {
1313
const step = makeStep(verbose)
1414

1515
await step("Creating shared schema files", () => createSharedExternalSchemaFile(context))
16+
await step("Creating shared schema files via tsc", () => createSharedExternalSchemaFileViaTSC(context))
1617
await step("Creating shared return position schema files", () => createSharedReturnPositionSchemaFile(context))
1718

1819
return [
@@ -122,8 +123,13 @@ async function createSharedExternalSchemaFile(context: AppContext) {
122123
}
123124
})
124125

125-
externalTSFile.addInterfaces(interfaces)
126-
externalTSFile.addTypeAliases(typeAliases)
126+
context.tsProject.forgetNodesCreatedInBlock(() => {
127+
externalTSFile.addInterfaces(interfaces)
128+
})
129+
130+
context.tsProject.forgetNodesCreatedInBlock(() => {
131+
externalTSFile.addTypeAliases(typeAliases)
132+
})
127133

128134
const { scalars } = mapper.getReferencedGraphQLThingsInMapping()
129135
if (scalars.length) externalTSFile.addTypeAliases(scalars.map((s) => ({ name: s, type: "any" })))

src/sharedSchemaTSC.ts

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/// The main schema for objects and inputs
2+
3+
import * as graphql from "graphql"
4+
import ts from "typescript"
5+
6+
import { AppContext } from "./context.js"
7+
import { formatDTS } from "./formatDTS.js"
8+
import { typeMapper } from "./typeMap.js"
9+
10+
export async function createSharedExternalSchemaFileViaTSC(context: AppContext) {
11+
const gql = context.gql
12+
const types = gql.getTypeMap()
13+
const knownPrimitives = ["String", "Boolean", "Int"]
14+
15+
const { prisma, fieldFacts } = context
16+
const mapper = typeMapper(context, {})
17+
18+
const statements = [] as ts.Statement[]
19+
20+
console.time("")
21+
22+
Object.keys(types).forEach((name) => {
23+
if (name.startsWith("__")) {
24+
return
25+
}
26+
27+
if (knownPrimitives.includes(name)) {
28+
return
29+
}
30+
31+
const type = types[name]
32+
const pType = prisma.get(name)
33+
34+
if (graphql.isObjectType(type) || graphql.isInterfaceType(type) || graphql.isInputObjectType(type)) {
35+
// This is slower than it could be, use the add many at once api
36+
const docs = []
37+
if (pType?.leadingComments) {
38+
docs.push(pType.leadingComments)
39+
}
40+
41+
if (type.description) {
42+
docs.push(type.description)
43+
}
44+
45+
const properties = [
46+
ts.factory.createPropertySignature(
47+
undefined,
48+
"__typename",
49+
ts.factory.createToken(ts.SyntaxKind.QuestionToken),
50+
ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral(type.name))
51+
),
52+
]
53+
54+
Object.entries(type.getFields()).forEach(([fieldName, obj]: [string, graphql.GraphQLField<object, object>]) => {
55+
const docs = []
56+
const prismaField = pType?.properties.get(fieldName)
57+
const type = obj.type as graphql.GraphQLType
58+
59+
if (prismaField?.leadingComments.length) {
60+
docs.push(prismaField.leadingComments.trim())
61+
}
62+
63+
// if (obj.description) docs.push(obj.description);
64+
const hasResolverImplementation = fieldFacts.get(name)?.[fieldName]?.hasResolverImplementation
65+
const isOptionalInSDL = !graphql.isNonNullType(type)
66+
const doesNotExistInPrisma = false // !prismaField;
67+
68+
const hasQuestionToken = hasResolverImplementation ?? (isOptionalInSDL || doesNotExistInPrisma)
69+
const mappedType = mapper.map(type, { preferNullOverUndefined: true })
70+
if (mappedType) {
71+
properties.push(
72+
ts.factory.createPropertySignature(
73+
undefined,
74+
fieldName,
75+
hasQuestionToken ? ts.factory.createToken(ts.SyntaxKind.QuestionToken) : undefined,
76+
ts.factory.createTypeReferenceNode(mappedType)
77+
)
78+
)
79+
}
80+
})
81+
82+
const interfaceD = ts.factory.createInterfaceDeclaration(
83+
[ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
84+
ts.factory.createIdentifier(name),
85+
undefined,
86+
undefined,
87+
properties
88+
)
89+
90+
statements.push(interfaceD)
91+
}
92+
93+
if (graphql.isEnumType(type)) {
94+
const values = type.getValues().map((m) => (m as { value: string }).value)
95+
const typeKind = `"${values.join('" | "')}"`
96+
97+
statements.push(ts.factory.createTypeAliasDeclaration(undefined, type.name, [], ts.factory.createTypeReferenceNode(typeKind)))
98+
}
99+
100+
if (graphql.isUnionType(type)) {
101+
const types = type.getTypes().map((t) => t.name)
102+
const typeKind = types.join(" | ")
103+
statements.push(
104+
ts.factory.createTypeAliasDeclaration(
105+
[ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
106+
type.name,
107+
[],
108+
ts.factory.createTypeReferenceNode(typeKind)
109+
)
110+
)
111+
}
112+
})
113+
114+
const { scalars } = mapper.getReferencedGraphQLThingsInMapping()
115+
if (scalars.length) {
116+
statements.push(
117+
ts.factory.createTypeAliasDeclaration(
118+
[ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
119+
"Scalars",
120+
[],
121+
ts.factory.createTypeReferenceNode(`{ ${scalars.join(", ")} }`)
122+
)
123+
)
124+
}
125+
126+
const sourceFile = ts.factory.createSourceFile(statements, ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None)
127+
const printer = ts.createPrinter({})
128+
const result = printer.printNode(ts.EmitHint.Unspecified, sourceFile, sourceFile)
129+
130+
const fullPath = context.join(context.pathSettings.typesFolderRoot, context.pathSettings.sharedFilename)
131+
132+
const prior = context.sys.readFile(fullPath)
133+
if (prior !== result) context.sys.writeFile(fullPath, result)
134+
}

src/tests/features/generatesTypesForUnions.test.ts

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,26 +39,22 @@ export const Game = {
3939
const dts = vfsMap.get("/types/shared-schema-types.d.ts")!
4040
expect(dts.trim()).toMatchInlineSnapshot(`
4141
"export interface Game {
42-
__typename?: \\"Game\\";
43-
id?: number;
42+
__typename?: \\"Game\\";
43+
id?: number;
4444
}
45-
4645
export interface Puzzle {
47-
__typename?: \\"Puzzle\\";
48-
id: number;
46+
__typename?: \\"Puzzle\\";
47+
id: number;
4948
}
50-
49+
export type Gameish = Game | Puzzle;
5150
export interface Query {
52-
__typename?: \\"Query\\";
53-
gameObj?: Game | null | Puzzle | null | null;
54-
gameArr: (Game | Puzzle)[];
51+
__typename?: \\"Query\\";
52+
gameObj?: Game| null | Puzzle| null| null;
53+
gameArr: (Game | Puzzle)[];
5554
}
56-
5755
export interface Mutation {
58-
__typename?: \\"Mutation\\";
59-
__?: string | null;
60-
}
61-
62-
export type Gameish = Game | Puzzle;"
56+
__typename?: \\"Mutation\\";
57+
__?: string| null;
58+
}"
6359
`)
6460
})

src/tests/features/supportRefferingToEnumsOnlyInSDL.test.ts

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -66,22 +66,19 @@ export const Game: GameResolvers = {};
6666

6767
expect(vfsMap.get("/types/shared-schema-types.d.ts"))!.toMatchInlineSnapshot(`
6868
"export interface Game {
69-
__typename?: \\"Game\\";
70-
id: number;
71-
games: Game[];
69+
__typename?: \\"Game\\";
70+
id: number;
71+
games: Game[];
7272
}
73-
7473
export interface Query {
75-
__typename?: \\"Query\\";
76-
allGames: Game[];
74+
__typename?: \\"Query\\";
75+
allGames: Game[];
7776
}
78-
77+
type GameType = \\"FOOTBALL\\" | \\"BASKETBALL\\";
7978
export interface Mutation {
80-
__typename?: \\"Mutation\\";
81-
__?: string | null;
79+
__typename?: \\"Mutation\\";
80+
__?: string| null;
8281
}
83-
84-
export type GameType = \\"FOOTBALL\\" | \\"BASKETBALL\\";
8582
"
8683
`)
8784
})

0 commit comments

Comments
 (0)