@eddeee888/gcg-typescript-resolver-files 0.0.0-pr6-20221024131652

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.eslintrc.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "extends": ["../../.eslintrc.json"],
3
+ "ignorePatterns": ["!**/*"],
4
+ "overrides": [
5
+ {
6
+ "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
7
+ "rules": {}
8
+ },
9
+ {
10
+ "files": ["*.ts", "*.tsx"],
11
+ "rules": {}
12
+ },
13
+ {
14
+ "files": ["*.js", "*.jsx"],
15
+ "rules": {}
16
+ }
17
+ ]
18
+ }
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # @eddeee888/gcg-typescript-resolver-files
2
+
3
+ ## 0.0.0-pr6-20221024131652
4
+
5
+ ### Patch Changes
6
+
7
+ - b7eb6e2: Initial publish
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # @eddeee888/gcg-typescript-resolver-files
2
+
3
+ This GraphQL Code Generator plugin creates resolvers given GraphQL schema.
package/jest.config.ts ADDED
@@ -0,0 +1,16 @@
1
+ /* eslint-disable */
2
+ export default {
3
+ displayName: 'typescript-resolver-files',
4
+ preset: '../../jest.preset.js',
5
+ globals: {
6
+ 'ts-jest': {
7
+ tsconfig: '<rootDir>/tsconfig.spec.json',
8
+ },
9
+ },
10
+ testEnvironment: 'node',
11
+ transform: {
12
+ '^.+\\.[tj]sx?$': 'ts-jest',
13
+ },
14
+ moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
15
+ coverageDirectory: '../../coverage/packages/typescript-resolver-files',
16
+ };
package/package.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "name": "@eddeee888/gcg-typescript-resolver-files",
3
+ "version": "0.0.0-pr6-20221024131652"
4
+ }
package/project.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "typescript-resolver-files",
3
+ "$schema": "../../node_modules/nx/schemas/project-schema.json",
4
+ "sourceRoot": "packages/typescript-resolver-files/src",
5
+ "projectType": "library",
6
+ "targets": {
7
+ "build": {
8
+ "executor": "@nrwl/js:tsc",
9
+ "outputs": ["{options.outputPath}"],
10
+ "options": {
11
+ "outputPath": "dist/packages/typescript-resolver-files",
12
+ "tsConfig": "packages/typescript-resolver-files/tsconfig.lib.json",
13
+ "packageJson": "packages/typescript-resolver-files/package.json",
14
+ "main": "packages/typescript-resolver-files/src/index.ts",
15
+ "assets": ["packages/typescript-resolver-files/*.md"]
16
+ }
17
+ },
18
+ "lint": {
19
+ "executor": "@nrwl/linter:eslint",
20
+ "outputs": ["{options.outputFile}"],
21
+ "options": {
22
+ "lintFilePatterns": ["packages/typescript-resolver-files/**/*.ts"]
23
+ }
24
+ },
25
+ "test": {
26
+ "executor": "@nrwl/jest:jest",
27
+ "outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
28
+ "options": {
29
+ "jestConfig": "packages/typescript-resolver-files/jest.config.ts",
30
+ "passWithNoTests": true
31
+ }
32
+ }
33
+ },
34
+ "tags": []
35
+ }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export { plugin } from './plugin';
package/src/plugin.ts ADDED
@@ -0,0 +1,46 @@
1
+ import * as path from 'path';
2
+ import { PluginFunction } from '@graphql-codegen/plugin-helpers';
3
+ import { mkdir, writeFile } from 'fs/promises';
4
+ import { run } from './run';
5
+ import { RunResult } from './types';
6
+
7
+ interface PluginConfig {
8
+ resolverTypesPath: string;
9
+ }
10
+
11
+ export const plugin: PluginFunction<PluginConfig> = async (
12
+ schema,
13
+ _documents,
14
+ config,
15
+ info
16
+ ) => {
17
+ // TODO: check if there's a better way to handle output dir
18
+ const baseOutputDir = info?.outputFile;
19
+ if (!baseOutputDir) {
20
+ throw new Error('Output folder is invalid');
21
+ }
22
+ const { resolverTypesPath: relativeResolverTypesPathFromBaseOutputDir } =
23
+ config;
24
+ const resolverTypesPath = path.join(
25
+ baseOutputDir,
26
+ relativeResolverTypesPathFromBaseOutputDir
27
+ );
28
+
29
+ const result: RunResult = {
30
+ dirs: [],
31
+ files: {},
32
+ };
33
+ run({ schema, baseOutputDir, resolverTypesPath }, result);
34
+
35
+ // Write dirs and files
36
+ await Promise.all(
37
+ result.dirs.map(async (dir) => await mkdir(dir, { recursive: true }))
38
+ );
39
+ await Promise.all(
40
+ Object.entries(result.files).map(
41
+ async ([filePath, file]) => await writeFile(filePath, file.content)
42
+ )
43
+ );
44
+
45
+ return { content: '' };
46
+ };
@@ -0,0 +1,98 @@
1
+ import * as path from 'path';
2
+ import type { RootObjectType, RunResult } from '../types';
3
+ import { printImportModule, relativeModulePath } from '../utils';
4
+
5
+ interface AddResolversIndexFileParams {
6
+ baseOutputDir: string;
7
+ resolverTypesPath: string;
8
+ }
9
+ export const addResolversIndexFile = (
10
+ { baseOutputDir, resolverTypesPath }: AddResolversIndexFileParams,
11
+ result: RunResult
12
+ ): void => {
13
+ const filename = path.join(baseOutputDir, 'index.ts');
14
+
15
+ const relativePathToResolverTypes = relativeModulePath(
16
+ baseOutputDir,
17
+ resolverTypesPath
18
+ );
19
+ const pathToResolverModule = printImportModule(relativePathToResolverTypes);
20
+
21
+ const resolversDetails = Object.entries(result.files).reduce<{
22
+ importLines: string[];
23
+ queryFields: string[];
24
+ mutationFields: string[];
25
+ subscriptionFields: string[];
26
+ objectTypes: string[];
27
+ }>(
28
+ (res, [filepath, file]) => {
29
+ if (file.__filetype === 'file') {
30
+ return res;
31
+ }
32
+
33
+ const pathToModule = printImportModule(
34
+ relativeModulePath(baseOutputDir, filepath)
35
+ );
36
+ res.importLines.push(
37
+ `import { ${file.mainImportIdentifier} } from '${pathToModule}'`
38
+ );
39
+
40
+ if (!file.meta.belongsToRootObject) {
41
+ res.objectTypes.push(file.mainImportIdentifier);
42
+ return res;
43
+ }
44
+ const rootObjectMap: Record<RootObjectType, () => void> = {
45
+ Query: () => res.queryFields.push(file.mainImportIdentifier),
46
+ Mutation: () => res.mutationFields.push(file.mainImportIdentifier),
47
+ Subscription: () =>
48
+ res.subscriptionFields.push(file.mainImportIdentifier),
49
+ };
50
+ rootObjectMap[file.meta.belongsToRootObject]();
51
+
52
+ return res;
53
+ },
54
+ {
55
+ importLines: [],
56
+ queryFields: [],
57
+ mutationFields: [],
58
+ subscriptionFields: [],
59
+ objectTypes: [],
60
+ }
61
+ );
62
+
63
+ const resolversIdentifier = 'resolvers';
64
+ const resolversTypeName = 'Resolvers'; // Generated type from typescript-resolvers plugin
65
+
66
+ const queries =
67
+ resolversDetails.queryFields.length > 0
68
+ ? `Query: { ${resolversDetails.queryFields
69
+ .map((field) => field)
70
+ .join(',\n')} },`
71
+ : '';
72
+ const mutations =
73
+ resolversDetails.mutationFields.length > 0
74
+ ? `Mutation: { ${resolversDetails.mutationFields
75
+ .map((field) => field)
76
+ .join(',\n')} },`
77
+ : '';
78
+ const suscriptions =
79
+ resolversDetails.subscriptionFields.length > 0
80
+ ? `Subscription: { ${resolversDetails.subscriptionFields
81
+ .map((field) => field)
82
+ .join(',\n')} },`
83
+ : '';
84
+
85
+ result.files[filename] = {
86
+ __filetype: 'file',
87
+ content: `/* This file was automatically generated. DO NOT UPDATE MANUALLY. */
88
+ import type { ${resolversTypeName} } from '${pathToResolverModule}';
89
+ ${resolversDetails.importLines.map((line) => line).join(';\n')}
90
+ export const ${resolversIdentifier}: ${resolversTypeName} = {
91
+ ${queries}
92
+ ${mutations}
93
+ ${suscriptions}
94
+ ${resolversDetails.objectTypes.map((type) => type).join(',\n')}
95
+ }`,
96
+ mainImportIdentifier: 'resolvers',
97
+ };
98
+ };
@@ -0,0 +1,84 @@
1
+ import { existsSync } from 'fs';
2
+ import { Project } from 'ts-morph';
3
+ import * as path from 'path';
4
+ import type { ResolverFile, RunResult } from '../types';
5
+
6
+ export const fixExistingResolvers = (result: RunResult) => {
7
+ const existingResolverFiles = Object.entries(result.files).reduce<
8
+ Record<string, ResolverFile>
9
+ >((res, [filePath, file]) => {
10
+ if (existsSync(filePath) && file.__filetype === 'resolver') {
11
+ res[filePath] = file;
12
+ }
13
+ return res;
14
+ }, {});
15
+
16
+ const project = new Project();
17
+ project.addSourceFilesAtPaths(Object.keys(existingResolverFiles));
18
+ const sourceFiles = project.getSourceFiles();
19
+ sourceFiles.forEach((sourceFile) => {
20
+ const normalisedRelativePath = path.relative(
21
+ process.cwd(),
22
+ sourceFile.getFilePath()
23
+ );
24
+ const file = existingResolverFiles[normalisedRelativePath];
25
+ if (!file) {
26
+ throw new Error(
27
+ `Unable to find resolver file: ${normalisedRelativePath}`
28
+ );
29
+ }
30
+
31
+ // TODO: Check missing import
32
+ // ...
33
+
34
+ // Check expected identifier
35
+ let isExpectedIdentifierExportedInVariableStatement = false;
36
+ const variableStatementWithExpectedIdentifier =
37
+ sourceFile.getVariableStatement((statement) => {
38
+ let hasExpectedIdentifier = false;
39
+ statement
40
+ .getDeclarationList()
41
+ .getDeclarations()
42
+ .forEach((declarationNode) => {
43
+ if (declarationNode.getName() === file.mainImportIdentifier) {
44
+ hasExpectedIdentifier = true;
45
+ if (statement.isExported()) {
46
+ isExpectedIdentifierExportedInVariableStatement = true;
47
+ }
48
+ }
49
+ });
50
+
51
+ if (!hasExpectedIdentifier) {
52
+ return false;
53
+ }
54
+ return true;
55
+ });
56
+
57
+ if (!variableStatementWithExpectedIdentifier) {
58
+ // Did not find variable statement with expected identifier, add it to the end with a warning
59
+ sourceFile.addStatements(
60
+ '/* WARNING: The following resolver was missing from this file. Make sure it is properly implemented or there could be runtime errors. */'
61
+ );
62
+ sourceFile.addStatements(file.meta.resolverVariableStatement);
63
+ } else if (
64
+ variableStatementWithExpectedIdentifier &&
65
+ !isExpectedIdentifierExportedInVariableStatement
66
+ ) {
67
+ // If has identifier but not exported
68
+ // Add export keyword to statement
69
+ const isExpectedIdentifierExported = Boolean(
70
+ sourceFile.getExportedDeclarations().get(file.mainImportIdentifier)
71
+ );
72
+ if (!isExpectedIdentifierExported) {
73
+ variableStatementWithExpectedIdentifier.setIsExported(true);
74
+ }
75
+ // else, if identifier's been exported do nothing
76
+ }
77
+
78
+ // Overwrite existing files with fixes
79
+ result.files[normalisedRelativePath] = {
80
+ ...file,
81
+ content: sourceFile.getText(),
82
+ };
83
+ });
84
+ };
@@ -0,0 +1,37 @@
1
+ import * as path from 'path';
2
+ import type { GraphQLObjectType } from 'graphql';
3
+ import type { HandleGraphQLType } from '../types';
4
+ import { printImportModule, relativeModulePath } from '../utils';
5
+
6
+ export const handleGraphQLObjectType: HandleGraphQLType<GraphQLObjectType> = (
7
+ type,
8
+ { baseOutputDir, resolverTypesPath },
9
+ result
10
+ ) => {
11
+ const fieldFilePath = path.join(baseOutputDir, `${type.name}.ts`);
12
+ if (result.files[fieldFilePath]) {
13
+ throw new Error(
14
+ `Unexpected duplication in field filename. Type: ${type.name}, file: ${fieldFilePath}`
15
+ );
16
+ }
17
+
18
+ const resolverTypeName = `${type.name}Resolvers`; // Generated type from typescript-resolvers plugin
19
+ const relativePathToResolverTypes = relativeModulePath(
20
+ baseOutputDir,
21
+ resolverTypesPath
22
+ );
23
+ const pathToResolverModule = printImportModule(relativePathToResolverTypes);
24
+ const resolverVariableStatement = `export const ${type.name}: ${resolverTypeName} = {
25
+ /* Implement ${type.name} resolver logic here */
26
+ };`;
27
+ result.files[fieldFilePath] = {
28
+ __filetype: 'resolver',
29
+ content: `import type { ${resolverTypeName} } from '${pathToResolverModule}';
30
+ ${resolverVariableStatement}`,
31
+ mainImportIdentifier: type.name,
32
+ meta: {
33
+ belongsToRootObject: null,
34
+ resolverVariableStatement,
35
+ },
36
+ };
37
+ };
@@ -0,0 +1,52 @@
1
+ import * as path from 'path';
2
+ import type { GraphQLObjectType } from 'graphql';
3
+ import type { HandleGraphQLType } from '../types';
4
+ import {
5
+ isRootObjectType,
6
+ printImportModule,
7
+ relativeModulePath,
8
+ } from '../utils';
9
+
10
+ export const handleGraphQLRootObjectType: HandleGraphQLType<
11
+ GraphQLObjectType
12
+ > = (type, { baseOutputDir, resolverTypesPath }, result) => {
13
+ const typeName = type.name;
14
+ if (!isRootObjectType(typeName)) {
15
+ return;
16
+ }
17
+
18
+ const fields = type.getFields();
19
+ const outputDir = path.join(baseOutputDir, typeName);
20
+
21
+ result.dirs.push(outputDir);
22
+
23
+ Object.keys(fields).forEach((fieldName) => {
24
+ const fieldFilePath = path.join(outputDir, `${fieldName}.ts`);
25
+ if (result.files[fieldFilePath]) {
26
+ throw new Error(
27
+ `Unexpected duplication in field filename. Type: ${typeName}, file: ${fieldFilePath}`
28
+ );
29
+ }
30
+
31
+ const resolverTypeName = `${typeName}Resolvers`; // Generated type from typescript-resolvers plugin
32
+ const relativePathToResolverTypes = relativeModulePath(
33
+ outputDir,
34
+ resolverTypesPath
35
+ );
36
+ const pathToResolverModule = printImportModule(relativePathToResolverTypes);
37
+ const resolverVariableStatement = `export const ${fieldName}: ${resolverTypeName}['${fieldName}'] = async (_parent, _arg, _ctx) => {
38
+ /* Implement ${typeName}.${fieldName} resolver logic here */
39
+ };`;
40
+
41
+ result.files[fieldFilePath] = {
42
+ __filetype: 'resolver',
43
+ content: `import type { ${resolverTypeName} } from '${pathToResolverModule}';
44
+ ${resolverVariableStatement}`,
45
+ mainImportIdentifier: fieldName,
46
+ meta: {
47
+ belongsToRootObject: typeName,
48
+ resolverVariableStatement,
49
+ },
50
+ };
51
+ });
52
+ };
@@ -0,0 +1,36 @@
1
+ import * as path from 'path';
2
+ import type { GraphQLUnionType } from 'graphql';
3
+ import type { HandleGraphQLType } from '../types';
4
+ import { printImportModule, relativeModulePath } from '../utils';
5
+
6
+ export const handleGraphQLUninionType: HandleGraphQLType<GraphQLUnionType> = (
7
+ type,
8
+ { baseOutputDir, resolverTypesPath },
9
+ result
10
+ ) => {
11
+ const typeName = type.name;
12
+ const fieldFilePath = path.join(baseOutputDir, `${typeName}.ts`);
13
+ if (result.files[fieldFilePath]) {
14
+ throw new Error(
15
+ `Unexpected duplication in field filename. Type: ${typeName}, file: ${fieldFilePath}`
16
+ );
17
+ }
18
+
19
+ const resolverTypeName = `${typeName}Resolvers`; // Generated type from typescript-resolvers plugin
20
+ const relativePathToResolverTypes = relativeModulePath(
21
+ baseOutputDir,
22
+ resolverTypesPath
23
+ );
24
+ const pathToResolverModule = printImportModule(relativePathToResolverTypes);
25
+ const resolverVariableStatement = `export const ${typeName}: ${resolverTypeName} = { __resolveType: (parent) => parent.__typename };`;
26
+ result.files[fieldFilePath] = {
27
+ __filetype: 'resolver',
28
+ content: `import type { ${resolverTypeName} } from '${pathToResolverModule}';
29
+ ${resolverVariableStatement}`,
30
+ mainImportIdentifier: typeName,
31
+ meta: {
32
+ belongsToRootObject: null,
33
+ resolverVariableStatement,
34
+ },
35
+ };
36
+ };
@@ -0,0 +1 @@
1
+ export { run } from './run';
package/src/run/run.ts ADDED
@@ -0,0 +1,28 @@
1
+ import { isObjectType, isUnionType } from 'graphql';
2
+ import type { RunConfig, RunResult } from '../types';
3
+ import { isRootObjectType } from '../utils';
4
+ import { handleGraphQLRootObjectType } from './handleGraphQLRootObjectType';
5
+ import { handleGraphQLObjectType } from './handleGraphQLObjectType';
6
+ import { handleGraphQLUninionType } from './handleGraphQLUninionType';
7
+ import { addResolversIndexFile } from './addResolversIndexFile';
8
+ import { fixExistingResolvers } from './fixExistingResolvers';
9
+
10
+ export const run = (config: RunConfig, result: RunResult): void => {
11
+ Object.entries(config.schema.getTypeMap())
12
+ .filter(([schemaType]) => !schemaType.startsWith('__')) // There are a few internal types with `__` prefixes. We don't want them.
13
+ .forEach(([schemaType, namedType]) => {
14
+ if (isObjectType(namedType) && isRootObjectType(schemaType)) {
15
+ handleGraphQLRootObjectType(namedType, config, result);
16
+ } else if (isObjectType(namedType) && !isRootObjectType(schemaType)) {
17
+ handleGraphQLObjectType(namedType, config, result);
18
+ } else if (isUnionType(namedType)) {
19
+ handleGraphQLUninionType(namedType, config, result);
20
+ }
21
+ });
22
+
23
+ // Check to see which resolver file exists already
24
+ fixExistingResolvers(result);
25
+
26
+ // Put all resolvers into a barrel file
27
+ addResolversIndexFile(config, result);
28
+ };
package/src/types.ts ADDED
@@ -0,0 +1,38 @@
1
+ import type { GraphQLSchema } from 'graphql';
2
+
3
+ interface BaseVirtualFile {
4
+ __filetype: string;
5
+ content: string;
6
+ mainImportIdentifier: string;
7
+ }
8
+
9
+ export interface StandardFile extends BaseVirtualFile {
10
+ __filetype: 'file';
11
+ }
12
+
13
+ export interface ResolverFile extends BaseVirtualFile {
14
+ __filetype: 'resolver';
15
+ meta: {
16
+ belongsToRootObject: RootObjectType | null;
17
+ resolverVariableStatement: string;
18
+ };
19
+ }
20
+
21
+ export interface RunConfig {
22
+ baseOutputDir: string;
23
+ resolverTypesPath: string;
24
+ schema: GraphQLSchema;
25
+ }
26
+
27
+ export interface RunResult {
28
+ dirs: string[];
29
+ files: Record<string, StandardFile | ResolverFile>;
30
+ }
31
+
32
+ export type RootObjectType = 'Query' | 'Mutation' | 'Subscription';
33
+
34
+ export type HandleGraphQLType<T> = (
35
+ type: T,
36
+ params: RunConfig,
37
+ result: RunResult
38
+ ) => void;
package/src/utils.ts ADDED
@@ -0,0 +1,26 @@
1
+ import * as path from 'path';
2
+ import type { RootObjectType } from './types';
3
+
4
+ export const printImportModule = (moduleName: string) => {
5
+ if (moduleName.endsWith('.ts')) {
6
+ return moduleName.split('.').slice(0, -1).join('.');
7
+ }
8
+ return moduleName;
9
+ };
10
+
11
+ export const isRootObjectType = (
12
+ typeName: string
13
+ ): typeName is RootObjectType =>
14
+ typeName === 'Query' ||
15
+ typeName === 'Mutation' ||
16
+ typeName === 'Subscription';
17
+
18
+ export const relativeModulePath = (from: string, to: string) => {
19
+ const rawPath = path.relative(from, to);
20
+
21
+ if (!rawPath.startsWith('../') || !rawPath.startsWith('./')) {
22
+ return `./${rawPath}`;
23
+ }
24
+
25
+ return rawPath;
26
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "files": [],
4
+ "include": [],
5
+ "references": [
6
+ {
7
+ "path": "./tsconfig.lib.json"
8
+ },
9
+ {
10
+ "path": "./tsconfig.spec.json"
11
+ }
12
+ ],
13
+ "compilerOptions": {
14
+ "forceConsistentCasingInFileNames": true,
15
+ "strict": true,
16
+ "noImplicitReturns": true,
17
+ "noFallthroughCasesInSwitch": true
18
+ }
19
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "module": "commonjs",
5
+ "outDir": "../../dist/out-tsc",
6
+ "declaration": true,
7
+ "types": ["node"]
8
+ },
9
+ "exclude": ["jest.config.ts", "**/*.spec.ts", "**/*.test.ts"],
10
+ "include": ["**/*.ts"]
11
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "../../dist/out-tsc",
5
+ "module": "commonjs",
6
+ "types": ["jest", "node"]
7
+ },
8
+ "include": [
9
+ "jest.config.ts",
10
+ "**/*.test.ts",
11
+ "**/*.spec.ts",
12
+ "**/*.test.tsx",
13
+ "**/*.spec.tsx",
14
+ "**/*.test.js",
15
+ "**/*.spec.js",
16
+ "**/*.test.jsx",
17
+ "**/*.spec.jsx",
18
+ "**/*.d.ts"
19
+ ]
20
+ }