diff --git a/package.json b/package.json index 7930978..7c4e62e 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,7 @@ "release-it": "^15.6.0", "sentences-per-line": "^0.2.1", "should-semantic-release": "^0.1.0", - "typescript": "^5.0.0", + "typescript": "^5.6.3", "vitest": "^0.31.1", "yaml-eslint-parser": "^1.2.0" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index da24773..3e63d59 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -44,10 +44,10 @@ importers: version: 16.18.24 '@typescript-eslint/eslint-plugin': specifier: ^5.48.2 - version: 5.48.2(@typescript-eslint/parser@5.48.2(eslint@8.32.0)(typescript@5.0.2))(eslint@8.32.0)(typescript@5.0.2) + version: 5.48.2(@typescript-eslint/parser@5.48.2(eslint@8.32.0)(typescript@5.6.3))(eslint@8.32.0)(typescript@5.6.3) '@typescript-eslint/parser': specifier: ^5.48.2 - version: 5.48.2(eslint@8.32.0)(typescript@5.0.2) + version: 5.48.2(eslint@8.32.0)(typescript@5.6.3) '@typescript/vfs': specifier: 1.4.0 version: 1.4.0 @@ -65,13 +65,13 @@ importers: version: 8.6.0(eslint@8.32.0) eslint-plugin-deprecation: specifier: ^1.4.1 - version: 1.4.1(eslint@8.32.0)(typescript@5.0.2) + version: 1.4.1(eslint@8.32.0)(typescript@5.6.3) eslint-plugin-eslint-comments: specifier: ^3.2.0 version: 3.2.0(eslint@8.32.0) eslint-plugin-import: specifier: ^2.27.5 - version: 2.27.5(@typescript-eslint/parser@5.48.2(eslint@8.32.0)(typescript@5.0.2))(eslint@8.32.0) + version: 2.27.5(@typescript-eslint/parser@5.48.2(eslint@8.32.0)(typescript@5.6.3))(eslint@8.32.0) eslint-plugin-jsonc: specifier: ^2.6.0 version: 2.6.0(eslint@8.32.0) @@ -89,7 +89,7 @@ importers: version: 10.0.0(eslint@8.32.0) eslint-plugin-typescript-sort-keys: specifier: ^2.3.0 - version: 2.3.0(@typescript-eslint/parser@5.48.2(eslint@8.32.0)(typescript@5.0.2))(eslint@8.32.0)(typescript@5.0.2) + version: 2.3.0(@typescript-eslint/parser@5.48.2(eslint@8.32.0)(typescript@5.6.3))(eslint@8.32.0)(typescript@5.6.3) eslint-plugin-yml: specifier: ^1.5.0 version: 1.5.0(eslint@8.32.0) @@ -139,8 +139,8 @@ importers: specifier: ^0.1.0 version: 0.1.1 typescript: - specifier: ^5.0.0 - version: 5.0.2 + specifier: ^5.6.3 + version: 5.6.3 vitest: specifier: ^0.31.1 version: 0.31.1 @@ -3620,9 +3620,9 @@ packages: typedarray-to-buffer@3.1.5: resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} - typescript@5.0.2: - resolution: {integrity: sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==} - engines: {node: '>=12.20'} + typescript@5.6.3: + resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} + engines: {node: '>=14.17'} hasBin: true uc.micro@1.0.6: @@ -4635,41 +4635,41 @@ snapshots: '@types/unist@2.0.6': {} - '@typescript-eslint/eslint-plugin@5.48.2(@typescript-eslint/parser@5.48.2(eslint@8.32.0)(typescript@5.0.2))(eslint@8.32.0)(typescript@5.0.2)': + '@typescript-eslint/eslint-plugin@5.48.2(@typescript-eslint/parser@5.48.2(eslint@8.32.0)(typescript@5.6.3))(eslint@8.32.0)(typescript@5.6.3)': dependencies: - '@typescript-eslint/parser': 5.48.2(eslint@8.32.0)(typescript@5.0.2) + '@typescript-eslint/parser': 5.48.2(eslint@8.32.0)(typescript@5.6.3) '@typescript-eslint/scope-manager': 5.48.2 - '@typescript-eslint/type-utils': 5.48.2(eslint@8.32.0)(typescript@5.0.2) - '@typescript-eslint/utils': 5.48.2(eslint@8.32.0)(typescript@5.0.2) + '@typescript-eslint/type-utils': 5.48.2(eslint@8.32.0)(typescript@5.6.3) + '@typescript-eslint/utils': 5.48.2(eslint@8.32.0)(typescript@5.6.3) debug: 4.3.4 eslint: 8.32.0 ignore: 5.2.4 natural-compare-lite: 1.4.0 regexpp: 3.2.0 semver: 7.3.8 - tsutils: 3.21.0(typescript@5.0.2) + tsutils: 3.21.0(typescript@5.6.3) optionalDependencies: - typescript: 5.0.2 + typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/experimental-utils@5.48.0(eslint@8.32.0)(typescript@5.0.2)': + '@typescript-eslint/experimental-utils@5.48.0(eslint@8.32.0)(typescript@5.6.3)': dependencies: - '@typescript-eslint/utils': 5.48.0(eslint@8.32.0)(typescript@5.0.2) + '@typescript-eslint/utils': 5.48.0(eslint@8.32.0)(typescript@5.6.3) eslint: 8.32.0 transitivePeerDependencies: - supports-color - typescript - '@typescript-eslint/parser@5.48.2(eslint@8.32.0)(typescript@5.0.2)': + '@typescript-eslint/parser@5.48.2(eslint@8.32.0)(typescript@5.6.3)': dependencies: '@typescript-eslint/scope-manager': 5.48.2 '@typescript-eslint/types': 5.48.2 - '@typescript-eslint/typescript-estree': 5.48.2(typescript@5.0.2) + '@typescript-eslint/typescript-estree': 5.48.2(typescript@5.6.3) debug: 4.3.4 eslint: 8.32.0 optionalDependencies: - typescript: 5.0.2 + typescript: 5.6.3 transitivePeerDependencies: - supports-color @@ -4688,15 +4688,15 @@ snapshots: '@typescript-eslint/types': 5.59.0 '@typescript-eslint/visitor-keys': 5.59.0 - '@typescript-eslint/type-utils@5.48.2(eslint@8.32.0)(typescript@5.0.2)': + '@typescript-eslint/type-utils@5.48.2(eslint@8.32.0)(typescript@5.6.3)': dependencies: - '@typescript-eslint/typescript-estree': 5.48.2(typescript@5.0.2) - '@typescript-eslint/utils': 5.48.2(eslint@8.32.0)(typescript@5.0.2) + '@typescript-eslint/typescript-estree': 5.48.2(typescript@5.6.3) + '@typescript-eslint/utils': 5.48.2(eslint@8.32.0)(typescript@5.6.3) debug: 4.3.4 eslint: 8.32.0 - tsutils: 3.21.0(typescript@5.0.2) + tsutils: 3.21.0(typescript@5.6.3) optionalDependencies: - typescript: 5.0.2 + typescript: 5.6.3 transitivePeerDependencies: - supports-color @@ -4706,7 +4706,7 @@ snapshots: '@typescript-eslint/types@5.59.0': {} - '@typescript-eslint/typescript-estree@5.48.0(typescript@5.0.2)': + '@typescript-eslint/typescript-estree@5.48.0(typescript@5.6.3)': dependencies: '@typescript-eslint/types': 5.48.0 '@typescript-eslint/visitor-keys': 5.48.0 @@ -4714,13 +4714,13 @@ snapshots: globby: 11.1.0 is-glob: 4.0.3 semver: 7.3.8 - tsutils: 3.21.0(typescript@5.0.2) + tsutils: 3.21.0(typescript@5.6.3) optionalDependencies: - typescript: 5.0.2 + typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@5.48.2(typescript@5.0.2)': + '@typescript-eslint/typescript-estree@5.48.2(typescript@5.6.3)': dependencies: '@typescript-eslint/types': 5.48.2 '@typescript-eslint/visitor-keys': 5.48.2 @@ -4728,13 +4728,13 @@ snapshots: globby: 11.1.0 is-glob: 4.0.3 semver: 7.3.8 - tsutils: 3.21.0(typescript@5.0.2) + tsutils: 3.21.0(typescript@5.6.3) optionalDependencies: - typescript: 5.0.2 + typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@5.59.0(typescript@5.0.2)': + '@typescript-eslint/typescript-estree@5.59.0(typescript@5.6.3)': dependencies: '@typescript-eslint/types': 5.59.0 '@typescript-eslint/visitor-keys': 5.59.0 @@ -4742,19 +4742,19 @@ snapshots: globby: 11.1.0 is-glob: 4.0.3 semver: 7.3.8 - tsutils: 3.21.0(typescript@5.0.2) + tsutils: 3.21.0(typescript@5.6.3) optionalDependencies: - typescript: 5.0.2 + typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@5.48.0(eslint@8.32.0)(typescript@5.0.2)': + '@typescript-eslint/utils@5.48.0(eslint@8.32.0)(typescript@5.6.3)': dependencies: '@types/json-schema': 7.0.11 '@types/semver': 7.3.13 '@typescript-eslint/scope-manager': 5.48.0 '@typescript-eslint/types': 5.48.0 - '@typescript-eslint/typescript-estree': 5.48.0(typescript@5.0.2) + '@typescript-eslint/typescript-estree': 5.48.0(typescript@5.6.3) eslint: 8.32.0 eslint-scope: 5.1.1 eslint-utils: 3.0.0(eslint@8.32.0) @@ -4763,13 +4763,13 @@ snapshots: - supports-color - typescript - '@typescript-eslint/utils@5.48.2(eslint@8.32.0)(typescript@5.0.2)': + '@typescript-eslint/utils@5.48.2(eslint@8.32.0)(typescript@5.6.3)': dependencies: '@types/json-schema': 7.0.11 '@types/semver': 7.3.13 '@typescript-eslint/scope-manager': 5.48.2 '@typescript-eslint/types': 5.48.2 - '@typescript-eslint/typescript-estree': 5.48.2(typescript@5.0.2) + '@typescript-eslint/typescript-estree': 5.48.2(typescript@5.6.3) eslint: 8.32.0 eslint-scope: 5.1.1 eslint-utils: 3.0.0(eslint@8.32.0) @@ -4778,14 +4778,14 @@ snapshots: - supports-color - typescript - '@typescript-eslint/utils@5.59.0(eslint@8.32.0)(typescript@5.0.2)': + '@typescript-eslint/utils@5.59.0(eslint@8.32.0)(typescript@5.6.3)': dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.32.0) '@types/json-schema': 7.0.11 '@types/semver': 7.3.13 '@typescript-eslint/scope-manager': 5.59.0 '@typescript-eslint/types': 5.59.0 - '@typescript-eslint/typescript-estree': 5.59.0(typescript@5.0.2) + '@typescript-eslint/typescript-estree': 5.59.0(typescript@5.6.3) eslint: 8.32.0 eslint-scope: 5.1.1 semver: 7.3.8 @@ -5610,23 +5610,23 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.7.4(@typescript-eslint/parser@5.48.2(eslint@8.32.0)(typescript@5.0.2))(eslint-import-resolver-node@0.3.7)(eslint@8.32.0): + eslint-module-utils@2.7.4(@typescript-eslint/parser@5.48.2(eslint@8.32.0)(typescript@5.6.3))(eslint-import-resolver-node@0.3.7)(eslint@8.32.0): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 5.48.2(eslint@8.32.0)(typescript@5.0.2) + '@typescript-eslint/parser': 5.48.2(eslint@8.32.0)(typescript@5.6.3) eslint: 8.32.0 eslint-import-resolver-node: 0.3.7 transitivePeerDependencies: - supports-color - eslint-plugin-deprecation@1.4.1(eslint@8.32.0)(typescript@5.0.2): + eslint-plugin-deprecation@1.4.1(eslint@8.32.0)(typescript@5.6.3): dependencies: - '@typescript-eslint/utils': 5.59.0(eslint@8.32.0)(typescript@5.0.2) + '@typescript-eslint/utils': 5.59.0(eslint@8.32.0)(typescript@5.6.3) eslint: 8.32.0 tslib: 2.5.0 - tsutils: 3.21.0(typescript@5.0.2) - typescript: 5.0.2 + tsutils: 3.21.0(typescript@5.6.3) + typescript: 5.6.3 transitivePeerDependencies: - supports-color @@ -5636,7 +5636,7 @@ snapshots: eslint: 8.32.0 ignore: 5.2.4 - eslint-plugin-import@2.27.5(@typescript-eslint/parser@5.48.2(eslint@8.32.0)(typescript@5.0.2))(eslint@8.32.0): + eslint-plugin-import@2.27.5(@typescript-eslint/parser@5.48.2(eslint@8.32.0)(typescript@5.6.3))(eslint@8.32.0): dependencies: array-includes: 3.1.6 array.prototype.flat: 1.3.1 @@ -5645,7 +5645,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.32.0 eslint-import-resolver-node: 0.3.7 - eslint-module-utils: 2.7.4(@typescript-eslint/parser@5.48.2(eslint@8.32.0)(typescript@5.0.2))(eslint-import-resolver-node@0.3.7)(eslint@8.32.0) + eslint-module-utils: 2.7.4(@typescript-eslint/parser@5.48.2(eslint@8.32.0)(typescript@5.6.3))(eslint-import-resolver-node@0.3.7)(eslint@8.32.0) has: 1.0.3 is-core-module: 2.11.0 is-glob: 4.0.3 @@ -5655,7 +5655,7 @@ snapshots: semver: 6.3.0 tsconfig-paths: 3.14.1 optionalDependencies: - '@typescript-eslint/parser': 5.48.2(eslint@8.32.0)(typescript@5.0.2) + '@typescript-eslint/parser': 5.48.2(eslint@8.32.0)(typescript@5.6.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -5693,14 +5693,14 @@ snapshots: dependencies: eslint: 8.32.0 - eslint-plugin-typescript-sort-keys@2.3.0(@typescript-eslint/parser@5.48.2(eslint@8.32.0)(typescript@5.0.2))(eslint@8.32.0)(typescript@5.0.2): + eslint-plugin-typescript-sort-keys@2.3.0(@typescript-eslint/parser@5.48.2(eslint@8.32.0)(typescript@5.6.3))(eslint@8.32.0)(typescript@5.6.3): dependencies: - '@typescript-eslint/experimental-utils': 5.48.0(eslint@8.32.0)(typescript@5.0.2) - '@typescript-eslint/parser': 5.48.2(eslint@8.32.0)(typescript@5.0.2) + '@typescript-eslint/experimental-utils': 5.48.0(eslint@8.32.0)(typescript@5.6.3) + '@typescript-eslint/parser': 5.48.2(eslint@8.32.0)(typescript@5.6.3) eslint: 8.32.0 json-schema: 0.4.0 natural-compare-lite: 1.4.0 - typescript: 5.0.2 + typescript: 5.6.3 transitivePeerDependencies: - supports-color @@ -6486,7 +6486,7 @@ snapshots: pretty-ms: 8.0.0 strip-json-comments: 5.0.0 summary: 2.1.0 - typescript: 5.0.2 + typescript: 5.6.3 zod: 3.21.4 zod-validation-error: 1.2.1(zod@3.21.4) @@ -7672,10 +7672,10 @@ snapshots: tslib@2.5.0: {} - tsutils@3.21.0(typescript@5.0.2): + tsutils@3.21.0(typescript@5.6.3): dependencies: tslib: 1.14.1 - typescript: 5.0.2 + typescript: 5.6.3 type-check@0.3.2: dependencies: @@ -7713,7 +7713,7 @@ snapshots: dependencies: is-typedarray: 1.0.0 - typescript@5.0.2: {} + typescript@5.6.3: {} uc.micro@1.0.6: {} diff --git a/src/formatDTS.ts b/src/formatDTS.ts deleted file mode 100644 index a8863d1..0000000 --- a/src/formatDTS.ts +++ /dev/null @@ -1,19 +0,0 @@ -// https://prettier.io/docs/en/api.html - -let hasPrettierInstalled = false -let prettier = null -try { - hasPrettierInstalled = !!require.resolve("prettier") - prettier = require("prettier") -} catch (error) {} - -export const formatDTS = async (path: string, content: string): Promise => { - if (!hasPrettierInstalled) return content - - try { - if (!prettier) return content - return prettier.format(content, { filepath: path }) - } catch (error) { - return content - } -} diff --git a/src/index.ts b/src/index.ts index 20fd082..ae08066 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,7 +5,7 @@ import typescript from "typescript" import { AppContext } from "./context.js" import { PrismaMap, prismaModeller } from "./prismaModeller.js" -import { lookAtServiceFile } from "./serviceFile2.js" +import { lookAtServiceFile } from "./serviceFile.js" import { createSharedSchemaFiles } from "./sharedSchema.js" import { CodeFacts, FieldFacts } from "./typeFacts.js" import { RedwoodPaths } from "./types.js" diff --git a/src/serviceFile.ts b/src/serviceFile.ts index 44958c2..156cfa9 100644 --- a/src/serviceFile.ts +++ b/src/serviceFile.ts @@ -1,10 +1,11 @@ /* eslint-disable @typescript-eslint/no-unnecessary-condition */ +import t, { tsTypeAnnotation } from "@babel/types" import * as graphql from "graphql" import { AppContext } from "./context.js" -import { formatDTS } from "./formatDTS.js" import { getCodeFactsForJSTSFileAtPath } from "./serviceFile.codefacts.js" +import { builder, TSBuilder } from "./tsBuilder.js" import { CodeFacts, ModelResolverFacts, ResolverFuncFact } from "./typeFacts.js" import { TypeMapper, typeMapper } from "./typeMap.js" import { capitalizeFirstLetter, createAndReferOrInlineArgsForField, inlineArgsForField } from "./utils.js" @@ -21,6 +22,7 @@ export const lookAtServiceFile = async (file: string, context: AppContext) => { const thisFact: CodeFacts = {} const filename = context.basename(file) + const dts = builder("", {}) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const queryType = gql.getQueryType()! @@ -41,9 +43,6 @@ export const lookAtServiceFile = async (file: string, context: AppContext) => { const extraPrismaReferences = new Set() 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 }) - // Basically if a top level resolver reference Query or Mutation const knownSpecialCasesForGraphQL = new Set() @@ -55,10 +54,10 @@ export const lookAtServiceFile = async (file: string, context: AppContext) => { const isMutation = v.name in mutationType.getFields() const parentName = isQuery ? queryType.name : isMutation ? mutationType.name : undefined if (parentName) { - addDefinitionsForTopLevelResolvers(parentName, v) + addDefinitionsForTopLevelResolvers(parentName, v, dts) } else { // Add warning about unused resolver - fileDTS.addStatements(`\n// ${v.name} does not exist on Query or Mutation`) + dts.rootScope.addInterface(v.name, [], { exported: true, docs: "This resolver does not exist on Query or Mutation" }) } }) @@ -77,13 +76,9 @@ export const lookAtServiceFile = async (file: string, context: AppContext) => { const sharedInternalGraphQLObjectsReferenced = returnTypeMapper.getReferencedGraphQLThingsInMapping() const aliases = [...new Set([...sharedGraphQLObjectsReferenced.scalars, ...sharedInternalGraphQLObjectsReferenced.scalars])] - if (aliases.length) { - fileDTS.addTypeAliases( - aliases.map((s) => ({ - name: s, - type: "any", - })) - ) + + for (const alias of aliases) { + dts.rootScope.addTypeAlias(alias, "any") } const prismases = [ @@ -96,34 +91,22 @@ export const lookAtServiceFile = async (file: string, context: AppContext) => { const validPrismaObjs = prismases.filter((p) => prisma.has(p)) if (validPrismaObjs.length) { - fileDTS.addImportDeclaration({ - isTypeOnly: true, - moduleSpecifier: "@prisma/client", - namedImports: validPrismaObjs.map((p) => `${p} as P${p}`), - }) + dts.setImport("@prisma/client", { subImports: validPrismaObjs.map((p) => `${p} as P${p}`) }) } - if (fileDTS.getText().includes("GraphQLResolveInfo")) { - fileDTS.addImportDeclaration({ - isTypeOnly: true, - moduleSpecifier: "graphql", - namedImports: ["GraphQLResolveInfo"], - }) + const initialResult = dts.getResult() + if (initialResult.includes("GraphQLResolveInfo")) { + dts.setImport("graphql", { subImports: ["GraphQLResolveInfo"] }) } - if (fileDTS.getText().includes("RedwoodGraphQLContext")) { - fileDTS.addImportDeclaration({ - isTypeOnly: true, - moduleSpecifier: "@redwoodjs/graphql-server/dist/types", - namedImports: ["RedwoodGraphQLContext"], - }) + if (initialResult.includes("RedwoodGraphQLContext")) { + dts.setImport("@redwoodjs/graphql-server/dist/types", { subImports: ["RedwoodGraphQLContext"] }) } if (sharedInternalGraphQLObjectsReferenced.types.length || extraSharedFileImportReferences.size) { - fileDTS.addImportDeclaration({ - isTypeOnly: true, - moduleSpecifier: `./${settings.sharedInternalFilename.replace(".d.ts", "")}`, - namedImports: [ + const source = `./${settings.sharedInternalFilename.replace(".d.ts", "")}` + dts.setImport(source, { + subImports: [ ...sharedInternalGraphQLObjectsReferenced.types.map((t) => `${t} as RT${t}`), ...[...extraSharedFileImportReferences.values()].map((t) => ("name" in t && t.name ? `${t.import} as ${t.name}` : t.import)), ], @@ -131,11 +114,8 @@ export const lookAtServiceFile = async (file: string, context: AppContext) => { } if (sharedGraphQLObjectsReferencedTypes.length) { - fileDTS.addImportDeclaration({ - isTypeOnly: true, - moduleSpecifier: `./${settings.sharedFilename.replace(".d.ts", "")}`, - namedImports: sharedGraphQLObjectsReferencedTypes, - }) + const source = `./${settings.sharedFilename.replace(".d.ts", "")}` + dts.setImport(source, { subImports: sharedGraphQLObjectsReferencedTypes }) } serviceFacts.set(fileKey, thisFact) @@ -144,15 +124,12 @@ export const lookAtServiceFile = async (file: string, context: AppContext) => { const dtsFilepath = context.join(context.pathSettings.typesFolderRoot, dtsFilename) // Some manual formatting tweaks so we align with Redwood's setup more - const dts = fileDTS - .getText() - .replace(`from "graphql";`, `from "graphql";\n`) - .replace(`from "@redwoodjs/graphql-server/dist/types";`, `from "@redwoodjs/graphql-server/dist/types";\n`) + const final = dts.getResult() - const shouldWriteDTS = !!dts.trim().length + const shouldWriteDTS = !!final.trim().length if (!shouldWriteDTS) return - const formatted = await formatDTS(dtsFilepath, dts) + const formatted = final // await formatDTS(dtsFilepath, dts) // Don't make a file write if the content is the same const priorContent = context.sys.readFile(dtsFilename) @@ -161,24 +138,21 @@ export const lookAtServiceFile = async (file: string, context: AppContext) => { context.sys.writeFile(dtsFilepath, formatted) return dtsFilepath - function addDefinitionsForTopLevelResolvers(parentName: string, config: ResolverFuncFact) { + function addDefinitionsForTopLevelResolvers(parentName: string, config: ResolverFuncFact, dts: TSBuilder) { const { name } = config let field = queryType.getFields()[name] if (!field) { field = mutationType.getFields()[name] } - const interfaceDeclaration = fileDTS.addInterface({ - name: `${capitalizeFirstLetter(config.name)}Resolver`, - isExported: true, - docs: field.astNode - ? ["SDL: " + graphql.print(field.astNode)] - : ["@deprecated: Could not find this field in the schema for Mutation or Query"], - }) + const nodeDocs = field.astNode + ? ["SDL: " + graphql.print(field.astNode)] + : ["@deprecated: Could not find this field in the schema for Mutation or Query"] + const interfaceName = `${capitalizeFirstLetter(config.name)}Resolver` const args = createAndReferOrInlineArgsForField(field, { - name: interfaceDeclaration.getName(), - file: fileDTS, + name: interfaceName, + dts, mapper: externalMapper.map, }) @@ -189,19 +163,28 @@ export const lookAtServiceFile = async (file: string, context: AppContext) => { const qForInfos = config.infoParamType === "just_root_destructured" ? "?" : "" const returnType = returnTypeForResolver(returnTypeMapper, field, config) - interfaceDeclaration.addCallSignature({ - parameters: [ - { name: "args", type: argsParam, hasQuestionToken: config.funcArgCount < 1 }, + dts.rootScope.addInterface( + interfaceName, + [ { - name: "obj", - type: `{ root: ${parentName}, context${qForInfos}: RedwoodGraphQLContext, info${qForInfos}: GraphQLResolveInfo }`, - hasQuestionToken: config.funcArgCount < 2, + type: "call-signature", + optional: config.funcArgCount < 1, + returnType, + params: [ + { name: "args", type: argsParam, optional: config.funcArgCount < 1 }, + { + name: "obj", + type: `{ root: ${parentName}, context${qForInfos}: RedwoodGraphQLContext, info${qForInfos}: GraphQLResolveInfo }`, + optional: config.funcArgCount < 2, + }, + ], }, ], - returnType, - }) - - interfaceDeclaration.forget() + { + exported: true, + docs: nodeDocs.join(" "), + } + ) } /** Ideally, we want to be able to write the type for just the object */ @@ -214,7 +197,7 @@ export const lookAtServiceFile = async (file: string, context: AppContext) => { const gqlType = gql.getType(modelName) if (!gqlType) { // throw new Error(`Could not find a GraphQL type named ${d.getName()}`); - fileDTS.addStatements(`\n// ${modelName} does not exist in the schema`) + // fileDTS.addStatements(`\n// ${modelName} does not exist in the schema`) return } @@ -229,11 +212,9 @@ export const lookAtServiceFile = async (file: string, context: AppContext) => { const hasGenerics = modelFacts.hasGenericArg - // This is what they would have to write - const resolverInterface = fileDTS.addInterface({ - name: `${modelName}TypeResolvers`, - typeParameters: hasGenerics ? ["Extended"] : [], - isExported: true, + const resolverInterface = dts.rootScope.addInterface(`${modelName}TypeResolvers`, [], { + exported: true, + generics: hasGenerics ? [{ name: "Extended" }] : [], }) // Handle extending classes in the runtime which only exist in SDL @@ -241,13 +222,19 @@ export const lookAtServiceFile = async (file: string, context: AppContext) => { 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: `${suffix}${modelName} ${createParentAdditionallyDefinedFunctions()} ${hasGenerics ? " & Extended" : ""}`, - }) + const parentTypeString = `${suffix}${modelName} ${createParentAdditionallyDefinedFunctions()} ${hasGenerics ? " & Extended" : ""}` + /** + type CurrentUserAccountAsParent = SCurrentUserAccount & { + users: () => PUser[] | Promise | (() => Promise); + registeredPublishingPartner: () => Promise; + subIsViaGift: () => boolean | Promise | (() => Promise); + } + */ + + dts.rootScope.addTypeAlias(`${modelName}AsParent`, t.tsTypeReference(t.identifier(parentTypeString)), { + generics: hasGenerics ? [{ name: "Extended" }] : [], + }) const modelFieldFacts = fieldFacts.get(modelName) ?? {} // Loop through the resolvers, adding the fields which have resolvers implemented in the source file @@ -266,21 +253,18 @@ export const lookAtServiceFile = async (file: string, context: AppContext) => { const qForInfos = resolver.infoParamType === "just_root_destructured" ? "?" : "" const innerArgs = `args${firstQ}: ${argsType}, obj${secondQ}: { root: ${modelName}AsParent${param}, context${qForInfos}: RedwoodGraphQLContext, info${qForInfos}: GraphQLResolveInfo }` - const returnType = returnTypeForResolver(returnTypeMapper, field, resolver) + const args = resolver.isFunc || resolver.isUnknown ? `(${innerArgs}) => ${returnType ?? "any"}` : returnType + + const docs = field.astNode ? `SDL: ${graphql.print(field.astNode)}` : "" + const property = t.tsPropertySignature(t.identifier(fieldName), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(args)))) + t.addComment(property, "leading", " " + docs) - const docs = field.astNode ? [`SDL: ${graphql.print(field.astNode)}`] : [] - // For speed we should switch this out to addProperties eventually - resolverInterface.addProperty({ - name: fieldName, - leadingTrivia: "\n", - docs, - type: resolver.isFunc || resolver.isUnknown ? `(${innerArgs}) => ${returnType ?? "any"}` : returnType, - }) + resolverInterface.body.body.push(property) } else { - resolverInterface.addCallSignature({ - docs: [` @deprecated: SDL ${modelName}.${resolver.name} does not exist in your schema`], - }) + resolverInterface.body.body.push( + t.tsPropertySignature(t.identifier(resolver.name), t.tsTypeAnnotation(t.tsTypeReference(t.identifier("void")))) + ) } }) diff --git a/src/serviceFile2.ts b/src/serviceFile2.ts deleted file mode 100644 index 012fbd5..0000000 --- a/src/serviceFile2.ts +++ /dev/null @@ -1,311 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unnecessary-condition */ - -import t, { tsTypeAnnotation } from "@babel/types" -import * as graphql from "graphql" - -import { AppContext } from "./context.js" -import { getCodeFactsForJSTSFileAtPath } from "./serviceFile.codefacts.js" -import { builder, TSBuilder } from "./tsBuilder.js" -import { CodeFacts, ModelResolverFacts, ResolverFuncFact } from "./typeFacts.js" -import { TypeMapper, typeMapper } from "./typeMap.js" -import { capitalizeFirstLetter, createAndReferOrInlineArgsForField, inlineArgsForField } from "./utils.js" - -export const lookAtServiceFile = async (file: string, context: AppContext) => { - const { gql, prisma, pathSettings: settings, codeFacts: serviceFacts, fieldFacts } = context - - if (!gql) throw new Error(`No schema when wanting to look at service file: ${file}`) - if (!prisma) throw new Error(`No prisma schema when wanting to look at service file: ${file}`) - - // This isn't good enough, needs to be relative to api/src/services - const fileKey = file.replace(settings.apiServicesPath, "") - - const thisFact: CodeFacts = {} - - const filename = context.basename(file) - const dts = builder("", {}) - - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const queryType = gql.getQueryType()! - if (!queryType) throw new Error("No query type") - - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const mutationType = gql.getMutationType()! - if (!mutationType) throw new Error("No mutation type") - - const externalMapper = typeMapper(context, { preferPrismaModels: true }) - const returnTypeMapper = typeMapper(context, {}) - - // The description of the source file - const fileFacts = getCodeFactsForJSTSFileAtPath(file, context) - if (Object.keys(fileFacts).length === 0) return - - // Tracks prospective prisma models which are used in the file - const extraPrismaReferences = new Set() - const extraSharedFileImportReferences = new Set<{ import: string; name?: string }>() - - // Basically if a top level resolver reference Query or Mutation - const knownSpecialCasesForGraphQL = new Set() - - // Add the root function declarations - const rootResolvers = fileFacts.maybe_query_mutation?.resolvers - if (rootResolvers) - rootResolvers.forEach((v) => { - const isQuery = v.name in queryType.getFields() - const isMutation = v.name in mutationType.getFields() - const parentName = isQuery ? queryType.name : isMutation ? mutationType.name : undefined - if (parentName) { - addDefinitionsForTopLevelResolvers(parentName, v, dts) - } else { - // Add warning about unused resolver - dts.rootScope.addInterface(v.name, [], { exported: true, docs: "This resolver does not exist on Query or Mutation" }) - } - }) - - // Add the root function declarations - Object.values(fileFacts).forEach((model) => { - if (!model) return - const skip = ["maybe_query_mutation", queryType.name, mutationType.name] - if (skip.includes(model.typeName)) return - - addCustomTypeModel(model) - }) - - // Set up the module imports at the top - const sharedGraphQLObjectsReferenced = externalMapper.getReferencedGraphQLThingsInMapping() - const sharedGraphQLObjectsReferencedTypes = [...sharedGraphQLObjectsReferenced.types, ...knownSpecialCasesForGraphQL] - const sharedInternalGraphQLObjectsReferenced = returnTypeMapper.getReferencedGraphQLThingsInMapping() - - const aliases = [...new Set([...sharedGraphQLObjectsReferenced.scalars, ...sharedInternalGraphQLObjectsReferenced.scalars])] - - for (const alias of aliases) { - dts.rootScope.addTypeAlias(alias, "any") - } - - const prismases = [ - ...new Set([ - ...sharedGraphQLObjectsReferenced.prisma, - ...sharedInternalGraphQLObjectsReferenced.prisma, - ...extraPrismaReferences.values(), - ]), - ] - - const validPrismaObjs = prismases.filter((p) => prisma.has(p)) - if (validPrismaObjs.length) { - dts.setImport("@prisma/client", { subImports: validPrismaObjs.map((p) => `${p} as P${p}`) }) - } - - const initialResult = dts.getResult() - if (initialResult.includes("GraphQLResolveInfo")) { - dts.setImport("graphql", { subImports: ["GraphQLResolveInfo"] }) - } - - if (initialResult.includes("RedwoodGraphQLContext")) { - dts.setImport("@redwoodjs/graphql-server/dist/types", { subImports: ["RedwoodGraphQLContext"] }) - } - - if (sharedInternalGraphQLObjectsReferenced.types.length || extraSharedFileImportReferences.size) { - const source = `./${settings.sharedInternalFilename.replace(".d.ts", "")}` - dts.setImport(source, { - subImports: [ - ...sharedInternalGraphQLObjectsReferenced.types.map((t) => `${t} as RT${t}`), - ...[...extraSharedFileImportReferences.values()].map((t) => ("name" in t && t.name ? `${t.import} as ${t.name}` : t.import)), - ], - }) - } - - if (sharedGraphQLObjectsReferencedTypes.length) { - const source = `./${settings.sharedFilename.replace(".d.ts", "")}` - dts.setImport(source, { subImports: sharedGraphQLObjectsReferencedTypes }) - } - - serviceFacts.set(fileKey, thisFact) - - const dtsFilename = filename.endsWith(".ts") ? filename.replace(".ts", ".d.ts") : filename.replace(".js", ".d.ts") - const dtsFilepath = context.join(context.pathSettings.typesFolderRoot, dtsFilename) - - // Some manual formatting tweaks so we align with Redwood's setup more - const final = dts.getResult() - - const shouldWriteDTS = !!final.trim().length - if (!shouldWriteDTS) return - - const formatted = final // await formatDTS(dtsFilepath, dts) - - // Don't make a file write if the content is the same - const priorContent = context.sys.readFile(dtsFilename) - if (priorContent === formatted) return - - context.sys.writeFile(dtsFilepath, formatted) - return dtsFilepath - - function addDefinitionsForTopLevelResolvers(parentName: string, config: ResolverFuncFact, dts: TSBuilder) { - const { name } = config - let field = queryType.getFields()[name] - if (!field) { - field = mutationType.getFields()[name] - } - - const nodeDocs = field.astNode - ? ["SDL: " + graphql.print(field.astNode)] - : ["@deprecated: Could not find this field in the schema for Mutation or Query"] - const interfaceName = `${capitalizeFirstLetter(config.name)}Resolver` - - const args = createAndReferOrInlineArgsForField(field, { - name: interfaceName, - dts, - mapper: externalMapper.map, - }) - - if (parentName === queryType.name) knownSpecialCasesForGraphQL.add(queryType.name) - if (parentName === mutationType.name) knownSpecialCasesForGraphQL.add(mutationType.name) - - const argsParam = args ?? "object" - const qForInfos = config.infoParamType === "just_root_destructured" ? "?" : "" - const returnType = returnTypeForResolver(returnTypeMapper, field, config) - - dts.rootScope.addInterface( - interfaceName, - [ - { - type: "call-signature", - optional: config.funcArgCount < 1, - returnType, - params: [ - { name: "args", type: argsParam, optional: config.funcArgCount < 1 }, - { - name: "obj", - type: `{ root: ${parentName}, context${qForInfos}: RedwoodGraphQLContext, info${qForInfos}: GraphQLResolveInfo }`, - optional: config.funcArgCount < 2, - }, - ], - }, - ], - { - exported: true, - docs: nodeDocs.join(" "), - } - ) - } - - /** Ideally, we want to be able to write the type for just the object */ - function addCustomTypeModel(modelFacts: ModelResolverFacts) { - const modelName = modelFacts.typeName - extraPrismaReferences.add(modelName) - - // Make an interface, this is the version we are replacing from graphql-codegen: - // Account: MergePrismaWithSdlTypes, AllMappedModels>; - const gqlType = gql.getType(modelName) - if (!gqlType) { - // throw new Error(`Could not find a GraphQL type named ${d.getName()}`); - // fileDTS.addStatements(`\n// ${modelName} does not exist in the schema`) - return - } - - if (!graphql.isObjectType(gqlType)) { - throw new Error(`In your schema ${modelName} is not an object, which we can only make resolver types for`) - } - - const fields = gqlType.getFields() - - // See: https://github.com/redwoodjs/redwood/pull/6228#issue-1342966511 - // For more ideas - - const hasGenerics = modelFacts.hasGenericArg - - const resolverInterface = dts.rootScope.addInterface(`${modelName}TypeResolvers`, [], { - exported: true, - generics: hasGenerics ? [{ name: "Extended" }] : [], - }) - - // 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" - - const parentTypeString = `${suffix}${modelName} ${createParentAdditionallyDefinedFunctions()} ${hasGenerics ? " & Extended" : ""}` - - /** - type CurrentUserAccountAsParent = SCurrentUserAccount & { - users: () => PUser[] | Promise | (() => Promise); - registeredPublishingPartner: () => Promise; - subIsViaGift: () => boolean | Promise | (() => Promise); - } - */ - - dts.rootScope.addTypeAlias(`${modelName}AsParent`, t.tsTypeReference(t.identifier(parentTypeString)), { - generics: hasGenerics ? [{ name: "Extended" }] : [], - }) - const modelFieldFacts = fieldFacts.get(modelName) ?? {} - - // Loop through the resolvers, adding the fields which have resolvers implemented in the source file - modelFacts.resolvers.forEach((resolver) => { - const field = fields[resolver.name] - if (field) { - const fieldName = resolver.name - if (modelFieldFacts[fieldName]) modelFieldFacts[fieldName].hasResolverImplementation = true - else modelFieldFacts[fieldName] = { hasResolverImplementation: true } - - const argsType = inlineArgsForField(field, { mapper: externalMapper.map }) ?? "undefined" - const param = hasGenerics ? "" : "" - - const firstQ = resolver.funcArgCount < 1 ? "?" : "" - const secondQ = resolver.funcArgCount < 2 ? "?" : "" - const qForInfos = resolver.infoParamType === "just_root_destructured" ? "?" : "" - - const innerArgs = `args${firstQ}: ${argsType}, obj${secondQ}: { root: ${modelName}AsParent${param}, context${qForInfos}: RedwoodGraphQLContext, info${qForInfos}: GraphQLResolveInfo }` - const returnType = returnTypeForResolver(returnTypeMapper, field, resolver) - const args = resolver.isFunc || resolver.isUnknown ? `(${innerArgs}) => ${returnType ?? "any"}` : returnType - - const docs = field.astNode ? `SDL: ${graphql.print(field.astNode)}` : "" - const property = t.tsPropertySignature(t.identifier(fieldName), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(args)))) - t.addComment(property, "leading", docs) - - resolverInterface.body.body.push(property) - } else { - resolverInterface.body.body.push( - t.tsPropertySignature(t.identifier(resolver.name), t.tsTypeAnnotation(t.tsTypeReference(t.identifier("void")))) - ) - } - }) - - function createParentAdditionallyDefinedFunctions() { - const fns: string[] = [] - modelFacts.resolvers.forEach((resolver) => { - const existsInGraphQLSchema = fields[resolver.name] - if (!existsInGraphQLSchema) { - console.warn( - `The service file ${filename} has a field ${resolver.name} on ${modelName} that does not exist in the generated schema.graphql` - ) - } - - const prefix = !existsInGraphQLSchema ? "\n// This field does not exist in the generated schema.graphql\n" : "" - const returnType = returnTypeForResolver(externalMapper, existsInGraphQLSchema, resolver) - // fns.push(`${prefix}${resolver.name}: () => Promise<${externalMapper.map(type, {})}>`) - fns.push(`${prefix}${resolver.name}: () => ${returnType}`) - }) - - if (fns.length < 1) return "" - return "& {" + fns.join(", \n") + "}" - } - - fieldFacts.set(modelName, modelFieldFacts) - } -} - -function returnTypeForResolver(mapper: TypeMapper, field: graphql.GraphQLField | undefined, resolver: ResolverFuncFact) { - if (!field) return "void" - - const tType = mapper.map(field.type, { preferNullOverUndefined: true, typenamePrefix: "RT" }) ?? "void" - - let returnType = tType - 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 - - return returnType -} -/* eslint-enable @typescript-eslint/no-unnecessary-condition */ diff --git a/src/sharedSchema.ts b/src/sharedSchema.ts index 28c2d59..abb6aaf 100644 --- a/src/sharedSchema.ts +++ b/src/sharedSchema.ts @@ -1,12 +1,9 @@ /// The main schema for objects and inputs -import { BlockStatement } from "@babel/types" import t from "@babel/types" import * as graphql from "graphql" -import * as tsMorph from "ts-morph" import { AppContext } from "./context.js" -import { formatDTS } from "./formatDTS.js" import { builder } from "./tsBuilder.js" import { typeMapper } from "./typeMap.js" import { makeStep } from "./utils.js" @@ -117,7 +114,7 @@ function createSharedExternalSchemaFile(context: AppContext) { if (prior !== text) context.sys.writeFile(fullPath, text) } -async function createSharedReturnPositionSchemaFile(context: AppContext) { +function createSharedReturnPositionSchemaFile(context: AppContext) { const { gql, prisma, fieldFacts } = context const types = gql.getTypeMap() const mapper = typeMapper(context, { preferPrismaModels: true }) diff --git a/src/tests/bugs/parentCanBeGraphQLObject.test.ts b/src/tests/bugs/parentCanBeGraphQLObject.test.ts index 2323445..f4e7ed9 100644 --- a/src/tests/bugs/parentCanBeGraphQLObject.test.ts +++ b/src/tests/bugs/parentCanBeGraphQLObject.test.ts @@ -30,8 +30,8 @@ export const Puzzle = { const { vfsMap } = await getDTSFilesForRun({ sdl, gamesService, prismaSchema }) const dts = vfsMap.get("/types/games.d.ts")! expect(dts.trim()).toMatchInlineSnapshot(` - "interface PuzzleTypeResolvers { - /*SDL: id: Int!*/ + "export interface PuzzleTypeResolvers { + /* SDL: id: Int!*/ id: number; } type PuzzleAsParent = SPuzzle & {id: () => number} ; diff --git a/src/tests/bugs/returnObjectCanBeGraphQLInterfaces.test.ts b/src/tests/bugs/returnObjectCanBeGraphQLInterfaces.test.ts index 42fc270..6c391e5 100644 --- a/src/tests/bugs/returnObjectCanBeGraphQLInterfaces.test.ts +++ b/src/tests/bugs/returnObjectCanBeGraphQLInterfaces.test.ts @@ -31,8 +31,8 @@ export const Game = { const { vfsMap } = await getDTSFilesForRun({ sdl, gamesService, prismaSchema }) const dts = vfsMap.get("/types/games.d.ts")! expect(dts.trim()).toMatchInlineSnapshot(` - "interface GameTypeResolvers { - /*SDL: puzzle: Node!*/ + "export interface GameTypeResolvers { + /* SDL: puzzle: Node!*/ puzzle: (args?: undefined, obj?: { root: GameAsParent, context: RedwoodGraphQLContext, info: GraphQLResolveInfo }) => RTNode | Promise | (() => Promise); } type GameAsParent = PGame & {puzzle: () => RTNode | Promise | (() => Promise)} ; diff --git a/src/tests/features/generatesTypesForUnions.test.ts b/src/tests/features/generatesTypesForUnions.test.ts index 7762f7d..508e3da 100644 --- a/src/tests/features/generatesTypesForUnions.test.ts +++ b/src/tests/features/generatesTypesForUnions.test.ts @@ -38,21 +38,21 @@ export const Game = { const { vfsMap } = await getDTSFilesForRun({ sdl, gamesService, prismaSchema, generateShared: true }) const dts = vfsMap.get("/types/shared-schema-types.d.ts")! expect(dts.trim()).toMatchInlineSnapshot(` - "interface Game { + "export interface Game { __typename?: \\"Game\\"; id?: number; } - interface Puzzle { + export interface Puzzle { __typename?: \\"Puzzle\\"; id: number; } - type Gameish = Game | Puzzle; - interface Query { + export type Gameish = Game | Puzzle; + export interface Query { __typename?: \\"Query\\"; gameObj?: Game| null | Puzzle| null| null; gameArr: (Game | Puzzle)[]; } - interface Mutation { + export interface Mutation { __typename?: \\"Mutation\\"; __?: string| null; }" diff --git a/src/tests/features/preferPromiseFnWhenKnown.test.ts b/src/tests/features/preferPromiseFnWhenKnown.test.ts index 18e57c2..388fb1e 100644 --- a/src/tests/features/preferPromiseFnWhenKnown.test.ts +++ b/src/tests/features/preferPromiseFnWhenKnown.test.ts @@ -54,29 +54,34 @@ export const Game = { const { vfsMap } = await getDTSFilesForRun({ sdl, gamesService, prismaSchema }) const dts = vfsMap.get("/types/games.d.ts")! expect(dts.trim()).toMatchInlineSnapshot(` - "interface GameSyncResolver { - (args?: RTGame| null | Promise | (() => Promise), obj?: RTGame| null | Promise | (() => Promise)): RTGame| null | Promise | (() => Promise); + "/*SDL: gameSync: Game*/ + export interface GameSyncResolver { + (args?: object, obj?: { root: Query, context: RedwoodGraphQLContext, info: GraphQLResolveInfo }): RTGame| null | Promise | (() => Promise); } - interface GameAsyncResolver { - (args?: Promise, obj?: Promise): Promise; + /*SDL: gameAsync: Game*/ + export interface GameAsyncResolver { + (args?: object, obj?: { root: Query, context: RedwoodGraphQLContext, info: GraphQLResolveInfo }): Promise; } - interface GameAsync1ArgResolver { - (args: RTGame| null | Promise | (() => Promise), obj?: RTGame| null | Promise | (() => Promise)): RTGame| null | Promise | (() => Promise); + /*SDL: gameAsync1Arg: Game*/ + export interface GameAsync1ArgResolver { + (args: object, obj?: { root: Query, context: RedwoodGraphQLContext, info: GraphQLResolveInfo }): RTGame| null | Promise | (() => Promise); } - interface GameAsync2ArgResolver { - (args: RTGame| null | Promise | (() => Promise), obj: RTGame| null | Promise | (() => Promise)): RTGame| null | Promise | (() => Promise); + /*SDL: gameAsync2Arg: Game*/ + export interface GameAsync2ArgResolver { + (args: object, obj: { root: Query, context: RedwoodGraphQLContext, info: GraphQLResolveInfo }): RTGame| null | Promise | (() => Promise); } - interface GameObjResolver { - (args?: RTGame| null, obj?: RTGame| null): RTGame| null; + /*SDL: gameObj: Game*/ + export interface GameObjResolver { + (args?: object, obj?: { root: Query, context: RedwoodGraphQLContext, info: GraphQLResolveInfo }): RTGame| null; } - interface GameTypeResolvers { - /*SDL: summary: String!*/ + export interface GameTypeResolvers { + /* SDL: summary: String!*/ summary: string; - /*SDL: summarySync: String!*/ + /* SDL: summarySync: String!*/ summarySync: (args?: undefined, obj?: { root: GameAsParent, context: RedwoodGraphQLContext, info: GraphQLResolveInfo }) => string; - /*SDL: summarySyncBlock: String!*/ + /* SDL: summarySyncBlock: String!*/ summarySyncBlock: (args?: undefined, obj?: { root: GameAsParent, context: RedwoodGraphQLContext, info: GraphQLResolveInfo }) => string | Promise | (() => Promise); - /*SDL: summaryAsync: String!*/ + /* SDL: summaryAsync: String!*/ summaryAsync: (args?: undefined, obj?: { root: GameAsParent, context: RedwoodGraphQLContext, info: GraphQLResolveInfo }) => Promise; } type GameAsParent = PGame & {summary: () => string, diff --git a/src/tests/features/returnTypePositionsWhichPreferPrisma.test.ts b/src/tests/features/returnTypePositionsWhichPreferPrisma.test.ts index 0ffa49a..7f1a32b 100644 --- a/src/tests/features/returnTypePositionsWhichPreferPrisma.test.ts +++ b/src/tests/features/returnTypePositionsWhichPreferPrisma.test.ts @@ -41,11 +41,12 @@ export const Game = { expect(dts.trimStart()).toMatchInlineSnapshot( ` - "interface GameResolver { - (args?: RTGame| null | Promise | (() => Promise), obj?: RTGame| null | Promise | (() => Promise)): RTGame| null | Promise | (() => Promise); + "/*SDL: game: Game*/ + export interface GameResolver { + (args?: object, obj?: { root: Query, context: RedwoodGraphQLContext, info: GraphQLResolveInfo }): RTGame| null | Promise | (() => Promise); } - interface GameTypeResolvers { - /*SDL: summary: String!*/ + export interface GameTypeResolvers { + /* SDL: summary: String!*/ summary: (args: undefined, obj: { root: GameAsParent, context?: RedwoodGraphQLContext, info?: GraphQLResolveInfo }) => string; } type GameAsParent = PGame & {summary: () => string} ; diff --git a/src/tests/features/supportGenericExtension.test.ts b/src/tests/features/supportGenericExtension.test.ts index c8018a0..c5b375d 100644 --- a/src/tests/features/supportGenericExtension.test.ts +++ b/src/tests/features/supportGenericExtension.test.ts @@ -29,14 +29,11 @@ export const Game: GameResolvers<{ type: string }> = {}; expect(vfsMap.get("/types/games.d.ts")!).toContain("interface GameTypeResolvers") - expect(vfsMap.get("/types/games.d.ts")!).toContain("GameAsParent = PGame & Extended") + expect(vfsMap.get("/types/games.d.ts")!).toContain("GameAsParent = PGame & Extended") expect(vfsMap.get("/types/games.d.ts"))!.toMatchInlineSnapshot(` - "import type { Game as PGame } from \\"@prisma/client\\"; - - export interface GameTypeResolvers {} - - type GameAsParent = PGame & Extended; - " + "export interface GameTypeResolvers {} + type GameAsParent = PGame & Extended; + import { Game as PGame } from \\"@prisma/client\\";" `) }) diff --git a/src/tests/features/supportReferringToEnumsOnlyInSDL.test.ts b/src/tests/features/supportReferringToEnumsOnlyInSDL.test.ts index aa8132a..8f903d3 100644 --- a/src/tests/features/supportReferringToEnumsOnlyInSDL.test.ts +++ b/src/tests/features/supportReferringToEnumsOnlyInSDL.test.ts @@ -38,28 +38,31 @@ export const Game: GameResolvers = {}; // We are expecting to see import type GameType from "./shared-schema-types" expect(vfsMap.get("/types/games.d.ts")).toMatchInlineSnapshot(` - "interface AllGamesResolver { - (args?: RTGame[] | Promise | (() => Promise), obj?: RTGame[] | Promise | (() => Promise)): RTGame[] | Promise | (() => Promise); + "/*SDL: allGames(type: GameType!): [Game!]!*/ + export interface AllGamesResolver { + (args?: {type: GameType}, obj?: { root: Query, context: RedwoodGraphQLContext, info: GraphQLResolveInfo }): RTGame[] | Promise | (() => Promise); } - interface GameTypeResolvers {} + export interface GameTypeResolvers {} type GameAsParent = PGame ; import { Game as PGame } from \\"@prisma/client\\"; + import { GraphQLResolveInfo } from \\"graphql\\"; + import { RedwoodGraphQLContext } from \\"@redwoodjs/graphql-server/dist/types\\"; import { Game as RTGame } from \\"./shared-return-types\\"; import { GameType, Query } from \\"./shared-schema-types\\";" `) expect(vfsMap.get("/types/shared-schema-types.d.ts"))!.toMatchInlineSnapshot(` - "interface Game { + "export interface Game { __typename?: \\"Game\\"; id: number; games: Game[]; } - interface Query { + export interface Query { __typename?: \\"Query\\"; allGames: Game[]; } - type GameType = \\"FOOTBALL\\" | \\"BASKETBALL\\"; - interface Mutation { + export type GameType = \\"FOOTBALL\\" | \\"BASKETBALL\\"; + export interface Mutation { __typename?: \\"Mutation\\"; __?: string| null; }" diff --git a/src/tests/testRunner.ts b/src/tests/testRunner.ts index 0c519ac..3d4b4de 100644 --- a/src/tests/testRunner.ts +++ b/src/tests/testRunner.ts @@ -6,7 +6,7 @@ import { Project } from "ts-morph" import { AppContext } from "../context.js" import { prismaModeller } from "../prismaModeller.js" -import { lookAtServiceFile } from "../serviceFile2.js" +import { lookAtServiceFile } from "../serviceFile.js" import { createSharedSchemaFiles } from "../sharedSchema.js" import type { CodeFacts, FieldFacts } from "../typeFacts.js" @@ -56,7 +56,7 @@ export async function getDTSFilesForRun(run: Run) { } if (run.generateShared) { - await createSharedSchemaFiles(appContext) + await createSharedSchemaFiles(appContext, false) } return { diff --git a/src/tsBuilder.ts b/src/tsBuilder.ts index 9fb3934..17943df 100644 --- a/src/tsBuilder.ts +++ b/src/tsBuilder.ts @@ -1,5 +1,3 @@ -// @eslint-disable-file - import generator from "@babel/generator" import parser from "@babel/parser" import traverse from "@babel/traverse" @@ -113,7 +111,9 @@ export const builder = (priorSource: string, opts: {}) => { return } - throw new Error(`Unsupported type annotation: ${newAnnotion.type} - ${generator(newAnnotion).code}`) + // @ts-expect-error - ts/js babel interop issue + const code = generator(newAnnotion).code + throw new Error(`Unsupported type annotation: ${newAnnotion.type} - ${code}`) } /** An internal API for describing a new area for inputting template info */ @@ -121,7 +121,7 @@ export const builder = (priorSource: string, opts: {}) => { const addFunction = (name: string) => { let functionNode = statements.find( (s) => t.isVariableDeclaration(s) && t.isIdentifier(s.declarations[0].id) && s.declarations[0].id.name === name - ) + ) as t.VariableDeclaration | undefined if (!functionNode) { functionNode = t.variableDeclaration("const", [ @@ -209,7 +209,7 @@ export const builder = (priorSource: string, opts: {}) => { null, // generics f.params.map((p) => { const i = t.identifier(p.name) - i.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier(f.returnType))) + i.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier(p.type))) if (p.optional) i.optional = true return i }), @@ -219,7 +219,7 @@ export const builder = (priorSource: string, opts: {}) => { } else { const prop = t.tsPropertySignature(t.identifier(f.name), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(f.type)))) prop.optional = f.optional - if (f.docs?.length) t.addComment(prop, "leading", f.docs) + if (f.docs?.length) t.addComment(prop, "leading", " " + f.docs) return prop } }) @@ -253,15 +253,17 @@ export const builder = (priorSource: string, opts: {}) => { /** Experimental function for parsing out a graphql template tag, and ensuring certain fields have been called */ const updateGraphQLTemplateTag = (expression: t.Expression, path: string, modelFields: string[]) => { if (path !== ".") throw new Error("Only support updating the root of the graphql tag ATM") + // @ts-expect-error - ts/js babel interop issue traverse( expression, { - TaggedTemplateExpression(path) { + TaggedTemplateExpression(path: traverse.NodePath) { const { tag, quasi } = path.node if (t.isIdentifier(tag) && tag.name === "graphql") { // This is the graphql query const query = quasi.quasis[0].value.raw const inner = query.match(/\{(.*)\}/)?.[1] + if (inner === undefined) throw new Error("Could not find inner query") path.replaceWithSourceString(`graphql\`${query.replace(inner, `${inner}, ${modelFields.join(", ")}`)}\``) path.stop() @@ -277,6 +279,7 @@ export const builder = (priorSource: string, opts: {}) => { const parseStatement = (code: string) => parser.parse(code, { sourceType: "module", plugins: ["jsx", "typescript"] }).program.body[0] as ExpressionStatement + // @ts-expect-error - ts/js babel interop issue const getResult = () => generator(sourceFile.program, {}).code const rootScope = createScope("root", sourceFile, sourceFile.program.body) @@ -286,7 +289,7 @@ export const builder = (priorSource: string, opts: {}) => { /** Parses something as though it is in type-space and extracts the subset of the AST that the string represents */ const getTypeLevelAST = (type: string) => { const typeAST = parser.parse(`type A = ${type}`, { sourceType: "module", plugins: ["jsx", "typescript"] }) - const typeDeclaration = typeAST.program.body.find((s) => s.type === "TSTypeAliasDeclaration") + const typeDeclaration = typeAST.program.body.find((s) => t.isTSTypeAliasDeclaration(s)) if (!typeDeclaration) throw new Error("No type declaration found in template: " + type) return typeDeclaration.typeAnnotation } @@ -304,5 +307,5 @@ const nodeFromNodeConfig = t.tsTypeParameter(null, null, g.name))) } - return node + return statement }