@eddeee888/gcg-typescript-resolver-files 0.18.4 → 0.19.0
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/dist/generateResolverFiles/addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented.d.ts +3 -2
- package/dist/generateResolverFiles/addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented.js +19 -50
- package/dist/generateResolverFiles/postProcessFiles.js +11 -5
- package/dist/getGraphQLObjectTypeResolversToGenerate/cache.d.ts +15 -0
- package/dist/getGraphQLObjectTypeResolversToGenerate/cache.js +83 -0
- package/dist/getGraphQLObjectTypeResolversToGenerate/getGraphQLObjectTypeResolversToGenerate.js +18 -2
- package/dist/preset.js +14 -0
- package/package.json +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type PropertyAssignment, type SourceFile } from 'ts-morph';
|
|
1
|
+
import { type PropertyAssignment, type SourceFile, type VariableStatement } from 'ts-morph';
|
|
2
2
|
import type { ObjectTypeFile } from './types.js';
|
|
3
3
|
export type AddedPropertyAssignmentNodes = Record<string, // SourceFile's filename
|
|
4
4
|
Record<number, // Line number
|
|
@@ -10,9 +10,10 @@ Record<number, // Line number
|
|
|
10
10
|
/**
|
|
11
11
|
* Ensure objectTypeResolver files have all the resolvers due to mismatched types
|
|
12
12
|
*/
|
|
13
|
-
export declare const addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented: ({ addedPropertyAssignmentNodes, sourceFile, resolverFile, mode, }: {
|
|
13
|
+
export declare const addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented: ({ addedPropertyAssignmentNodes, sourceFile, variableStatement, resolverFile, mode, }: {
|
|
14
14
|
addedPropertyAssignmentNodes: AddedPropertyAssignmentNodes;
|
|
15
15
|
sourceFile: SourceFile;
|
|
16
|
+
variableStatement: VariableStatement | undefined;
|
|
16
17
|
resolverFile: ObjectTypeFile;
|
|
17
18
|
mode: "smart" | "fast";
|
|
18
19
|
}) => void;
|
package/dist/generateResolverFiles/addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { SyntaxKind } from 'ts-morph';
|
|
2
|
-
import { getVariableStatementWithExpectedIdentifier } from './getVariableStatementWithExpectedIdentifier.js';
|
|
1
|
+
import { SyntaxKind, } from 'ts-morph';
|
|
3
2
|
/**
|
|
4
3
|
* Ensure objectTypeResolver files have all the resolvers due to mismatched types
|
|
5
4
|
*/
|
|
6
|
-
export const addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented = ({ addedPropertyAssignmentNodes, sourceFile, resolverFile, mode, }) => {
|
|
5
|
+
export const addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented = ({ addedPropertyAssignmentNodes, sourceFile, variableStatement, resolverFile, mode, }) => {
|
|
7
6
|
const sourceFilePath = sourceFile.getFilePath().toString();
|
|
8
7
|
addedPropertyAssignmentNodes[sourceFilePath] =
|
|
9
8
|
addedPropertyAssignmentNodes[sourceFilePath] || {};
|
|
@@ -11,71 +10,41 @@ export const addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented = ({
|
|
|
11
10
|
if (!Object.keys(resolversToGenerate).length) {
|
|
12
11
|
return;
|
|
13
12
|
}
|
|
14
|
-
const { variableStatement } = getVariableStatementWithExpectedIdentifier(sourceFile, resolverFile);
|
|
15
13
|
if (!variableStatement) {
|
|
16
14
|
throw new Error('Missing variableStatement in addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented.');
|
|
17
15
|
}
|
|
18
16
|
// 1. Check to see which generated to-be-generated resolvers are implemented
|
|
19
17
|
const resolversData = { ...resolversToGenerate };
|
|
20
18
|
/**
|
|
21
|
-
*
|
|
19
|
+
* Mark a resolver as implemented if the object literal already has a member
|
|
20
|
+
* with that name, in any of these forms (single traversal over the statement):
|
|
22
21
|
* ```
|
|
23
22
|
* const name = () => {};
|
|
24
23
|
* const OutputType = {
|
|
25
|
-
* id: () => {},
|
|
26
|
-
*
|
|
24
|
+
* id: () => {}, // PropertyAssignment
|
|
25
|
+
* greet(){}, // MethodDeclaration
|
|
26
|
+
* name, // ShorthandPropertyAssignment
|
|
27
27
|
* }
|
|
28
28
|
* ```
|
|
29
29
|
*/
|
|
30
|
-
variableStatement
|
|
31
|
-
.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
resolversData[resolverName]
|
|
30
|
+
variableStatement.forEachDescendant((node) => {
|
|
31
|
+
if (node.isKind(SyntaxKind.PropertyAssignment) ||
|
|
32
|
+
node.isKind(SyntaxKind.MethodDeclaration) ||
|
|
33
|
+
node.isKind(SyntaxKind.ShorthandPropertyAssignment)) {
|
|
34
|
+
const resolverName = node.getName();
|
|
35
|
+
if (resolversData[resolverName]) {
|
|
36
|
+
resolversData[resolverName].implemented = true;
|
|
37
|
+
}
|
|
36
38
|
}
|
|
37
39
|
});
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
* const OutputType = {
|
|
42
|
-
* id(){},
|
|
43
|
-
* }
|
|
44
|
-
* ```
|
|
45
|
-
*/
|
|
46
|
-
variableStatement
|
|
47
|
-
.getDescendantsOfKind(SyntaxKind.MethodDeclaration)
|
|
48
|
-
.forEach((methodDeclaration) => {
|
|
49
|
-
const resolverName = methodDeclaration.getName();
|
|
50
|
-
if (resolversData[resolverName]) {
|
|
51
|
-
resolversData[resolverName].implemented = true;
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
/**
|
|
55
|
-
* ShorthandPropertyAssignment example:
|
|
56
|
-
* ```
|
|
57
|
-
* const id = () => {};
|
|
58
|
-
* const OutputType = {
|
|
59
|
-
* id,
|
|
60
|
-
* }
|
|
61
|
-
* ```
|
|
62
|
-
*/
|
|
63
|
-
variableStatement
|
|
64
|
-
.getDescendantsOfKind(SyntaxKind.ShorthandPropertyAssignment)
|
|
65
|
-
.forEach((propertyAssignment) => {
|
|
66
|
-
const resolverName = propertyAssignment.getName();
|
|
67
|
-
if (resolversData[resolverName]) {
|
|
68
|
-
resolversData[resolverName].implemented = true;
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
// 2. Add missing resolver properties if they haven't been implemented
|
|
40
|
+
// 2. Add missing resolver properties if they haven't been implemented.
|
|
41
|
+
// Resolve the object literal once, not once per added property.
|
|
42
|
+
const objectLiteralExpression = variableStatement.getDescendantsOfKind(SyntaxKind.ObjectLiteralExpression)[0];
|
|
72
43
|
Object.values(resolversData).forEach(({ resolverName, resolverDeclaration, implemented }) => {
|
|
73
44
|
if (implemented) {
|
|
74
45
|
return;
|
|
75
46
|
}
|
|
76
|
-
const addedNode =
|
|
77
|
-
.getDescendantsOfKind(SyntaxKind.ObjectLiteralExpression)[0]
|
|
78
|
-
.addPropertyAssignment({
|
|
47
|
+
const addedNode = objectLiteralExpression.addPropertyAssignment({
|
|
79
48
|
name: resolverName,
|
|
80
49
|
initializer: resolverDeclaration,
|
|
81
50
|
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SyntaxKind } from 'ts-morph';
|
|
1
2
|
import * as path from 'path';
|
|
2
3
|
import { cwd } from '../utils/index.js';
|
|
3
4
|
import { getVariableStatementWithExpectedIdentifier } from './getVariableStatementWithExpectedIdentifier.js';
|
|
@@ -43,7 +44,7 @@ export const postProcessFiles = ({ config: { tsMorph: { project }, fixObjectType
|
|
|
43
44
|
const addedPropertyAssignmentNodes = {};
|
|
44
45
|
sourceFilesToProcess.forEach(({ sourceFile, resolverFile }) => {
|
|
45
46
|
const normalizedRelativePath = path.posix.relative(cwd(), sourceFile.getFilePath());
|
|
46
|
-
const { addedVariableStatement } = ensureExportedResolver(sourceFile, resolverFile);
|
|
47
|
+
const { addedVariableStatement, variableStatement } = ensureExportedResolver(sourceFile, resolverFile);
|
|
47
48
|
if (resolverFile.__filetype !== 'scalarResolver' ||
|
|
48
49
|
// For scalarResolver, only add `import { GraphQLScalarType } ...` if variable statement was added.
|
|
49
50
|
// This is because user could have used custom scalar. If so, we don't want to add unnecessary import
|
|
@@ -56,6 +57,7 @@ export const postProcessFiles = ({ config: { tsMorph: { project }, fixObjectType
|
|
|
56
57
|
addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented({
|
|
57
58
|
addedPropertyAssignmentNodes,
|
|
58
59
|
sourceFile,
|
|
60
|
+
variableStatement,
|
|
59
61
|
resolverFile,
|
|
60
62
|
mode: fixObjectTypeResolvers.object,
|
|
61
63
|
});
|
|
@@ -169,9 +171,13 @@ const ensureExportedResolver = (sourceFile, resolverFile) => {
|
|
|
169
171
|
}
|
|
170
172
|
if (!variableStatement) {
|
|
171
173
|
// Did not find variable statement with expected identifier, add it to the end with a warning
|
|
172
|
-
sourceFile.addStatements(resolverFile.meta.variableStatement);
|
|
174
|
+
const addedStatements = sourceFile.addStatements(resolverFile.meta.variableStatement);
|
|
173
175
|
resolverFile.filesystem.contentUpdated = true;
|
|
174
|
-
|
|
176
|
+
const addedVariableStatementNode = addedStatements.find((statement) => statement.isKind(SyntaxKind.VariableStatement));
|
|
177
|
+
return {
|
|
178
|
+
addedVariableStatement: true,
|
|
179
|
+
variableStatement: addedVariableStatementNode,
|
|
180
|
+
};
|
|
175
181
|
}
|
|
176
182
|
else if (variableStatement && !isExported) {
|
|
177
183
|
// If has identifier but not exported
|
|
@@ -184,9 +190,9 @@ const ensureExportedResolver = (sourceFile, resolverFile) => {
|
|
|
184
190
|
resolverFile.filesystem.contentUpdated = true;
|
|
185
191
|
}
|
|
186
192
|
// else, if identifier's been exported do nothing
|
|
187
|
-
return { addedVariableStatement: false };
|
|
193
|
+
return { addedVariableStatement: false, variableStatement };
|
|
188
194
|
}
|
|
189
|
-
return { addedVariableStatement: false };
|
|
195
|
+
return { addedVariableStatement: false, variableStatement };
|
|
190
196
|
};
|
|
191
197
|
/**
|
|
192
198
|
* Ensure correctly imported resolves type generated by typescript-resolvers plugin
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Project, SourceFile } from 'ts-morph';
|
|
2
|
+
import type { TypeMappersMap } from '../parseTypeMappers/index.js';
|
|
3
|
+
import type { GraphQLObjectTypeResolversToGenerate } from './getGraphQLObjectTypeResolversToGenerate.js';
|
|
4
|
+
type Cache = {
|
|
5
|
+
get(key: string): GraphQLObjectTypeResolversToGenerate | undefined;
|
|
6
|
+
set(key: string, value: GraphQLObjectTypeResolversToGenerate): GraphQLObjectTypeResolversToGenerate;
|
|
7
|
+
createCacheKey({ mode, typesSourceFile, typeMappersMap, tsMorphProject, }: {
|
|
8
|
+
mode: string;
|
|
9
|
+
typesSourceFile: SourceFile;
|
|
10
|
+
typeMappersMap: TypeMappersMap;
|
|
11
|
+
tsMorphProject: Project;
|
|
12
|
+
}): string;
|
|
13
|
+
};
|
|
14
|
+
export declare const createCache: () => Cache;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
2
|
+
/**
|
|
3
|
+
* Maximum number of entries kept in the cache. Each entry corresponds to one
|
|
4
|
+
* distinct `(mode + types file + mappers)` state. A long watch session can visit
|
|
5
|
+
* many such states, and separate `generates` targets each contribute their own
|
|
6
|
+
* key, so we keep the N most-recently-used entries and evict the rest to bound
|
|
7
|
+
* memory. N is comfortably larger than the number of concurrent targets a single
|
|
8
|
+
* codegen run has.
|
|
9
|
+
*/
|
|
10
|
+
const MAX_CACHE_ENTRIES = 50;
|
|
11
|
+
export const createCache = () => {
|
|
12
|
+
/**
|
|
13
|
+
* Cache of previous runs' results, so the ts-morph type-checker work is skipped
|
|
14
|
+
* when nothing it depends on has changed (e.g. a watch re-run where only a
|
|
15
|
+
* resolver implementation file was edited). The result is a pure function of
|
|
16
|
+
* - `mode`
|
|
17
|
+
* - the generated types file
|
|
18
|
+
* - the mapper file contents
|
|
19
|
+
*
|
|
20
|
+
* so the key is a hash of exactly those. Reused only on an exact key match, so
|
|
21
|
+
* it can never go stale. A `Map` is used because its insertion order gives a
|
|
22
|
+
* cheap LRU: the first key is the least-recently-used.
|
|
23
|
+
*/
|
|
24
|
+
const resultCache = new Map();
|
|
25
|
+
return {
|
|
26
|
+
get(key) {
|
|
27
|
+
const result = resultCache.get(key);
|
|
28
|
+
if (result === undefined) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
// Mark as most-recently-used by re-inserting at the end.
|
|
32
|
+
resultCache.delete(key);
|
|
33
|
+
resultCache.set(key, result);
|
|
34
|
+
return structuredClone(result);
|
|
35
|
+
},
|
|
36
|
+
/**
|
|
37
|
+
* set
|
|
38
|
+
* Create a structured clone in the cache
|
|
39
|
+
* because downstream can update the value object
|
|
40
|
+
*/
|
|
41
|
+
set(key, value) {
|
|
42
|
+
// Re-insert so this key becomes the most-recently-used entry.
|
|
43
|
+
resultCache.delete(key);
|
|
44
|
+
resultCache.set(key, structuredClone(value));
|
|
45
|
+
// Evict least-recently-used entries beyond the cap.
|
|
46
|
+
while (resultCache.size > MAX_CACHE_ENTRIES) {
|
|
47
|
+
const lruKey = resultCache.keys().next().value;
|
|
48
|
+
if (lruKey === undefined) {
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
resultCache.delete(lruKey);
|
|
52
|
+
}
|
|
53
|
+
return value;
|
|
54
|
+
},
|
|
55
|
+
/**
|
|
56
|
+
* Hash of everything the result depends on: the mode, the generated types file
|
|
57
|
+
* text (captures all schema-derived inputs), and each mapper file's contents.
|
|
58
|
+
* Same key => same result.
|
|
59
|
+
*/
|
|
60
|
+
createCacheKey({ mode, typesSourceFile, typeMappersMap, tsMorphProject }) {
|
|
61
|
+
const hash = createHash('sha1');
|
|
62
|
+
hash.update(mode);
|
|
63
|
+
hash.update(' ');
|
|
64
|
+
hash.update(typesSourceFile.getFullText());
|
|
65
|
+
// The parsed mapper map (which schema type -> which mapper declaration) depends
|
|
66
|
+
// on config such as mappersSuffix, so hash it too: mapper file text alone would
|
|
67
|
+
// not distinguish two configs that read the same files differently.
|
|
68
|
+
hash.update(' ');
|
|
69
|
+
hash.update(JSON.stringify(Object.entries(typeMappersMap).sort(([a], [b]) => (a < b ? -1 : 1))));
|
|
70
|
+
const mapperFilenames = [
|
|
71
|
+
...new Set(Object.values(typeMappersMap).map((m) => m.mapper.filename)),
|
|
72
|
+
].sort();
|
|
73
|
+
for (const filename of mapperFilenames) {
|
|
74
|
+
const sourceFile = tsMorphProject.getSourceFile(filename);
|
|
75
|
+
hash.update(' ');
|
|
76
|
+
hash.update(filename);
|
|
77
|
+
hash.update(' ');
|
|
78
|
+
hash.update(sourceFile ? sourceFile.getFullText() : '');
|
|
79
|
+
}
|
|
80
|
+
return hash.digest('hex');
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
};
|
package/dist/getGraphQLObjectTypeResolversToGenerate/getGraphQLObjectTypeResolversToGenerate.js
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
import { SyntaxKind, Node, } from 'ts-morph';
|
|
2
2
|
import { getNodePropertyMap, } from './getNodePropertyMap.js';
|
|
3
|
+
import { createCache } from './cache.js';
|
|
4
|
+
const cache = createCache();
|
|
3
5
|
export const getGraphQLObjectTypeResolversToGenerate = ({ mode, tsMorphProject, typesSourceFile, typeMappersMap, userDefinedSchemaObjectTypeMap, generatedTypesFileMeta, }) => {
|
|
4
6
|
const typeMappersEntries = Object.entries(typeMappersMap);
|
|
5
7
|
if (typeMappersEntries.length === 0) {
|
|
6
8
|
return {};
|
|
7
9
|
}
|
|
10
|
+
const cacheKey = cache.createCacheKey({
|
|
11
|
+
mode,
|
|
12
|
+
typesSourceFile,
|
|
13
|
+
typeMappersMap,
|
|
14
|
+
tsMorphProject,
|
|
15
|
+
});
|
|
16
|
+
const cachedResult = cache.get(cacheKey);
|
|
17
|
+
if (cachedResult) {
|
|
18
|
+
return cachedResult;
|
|
19
|
+
}
|
|
8
20
|
/**
|
|
9
21
|
* `generatedTypesFileMeta.generatedResolverTypes.userDefined` is `schemaType` -> `generatedResolverTypes`
|
|
10
22
|
* We will be parsing `types.generated.ts` file for the `generatedResolverTypes`
|
|
@@ -27,6 +39,7 @@ export const getGraphQLObjectTypeResolversToGenerate = ({ mode, tsMorphProject,
|
|
|
27
39
|
res[generatedSchemaTypeName.name] = schemaType;
|
|
28
40
|
return res;
|
|
29
41
|
}, {});
|
|
42
|
+
// "Fast" mode
|
|
30
43
|
if (mode === 'fast') {
|
|
31
44
|
// 1. Get property map of all schema types
|
|
32
45
|
const resolverTypesMap = {};
|
|
@@ -105,8 +118,10 @@ export const getGraphQLObjectTypeResolversToGenerate = ({ mode, tsMorphProject,
|
|
|
105
118
|
};
|
|
106
119
|
});
|
|
107
120
|
});
|
|
108
|
-
|
|
121
|
+
const newResult = cache.set(cacheKey, result);
|
|
122
|
+
return newResult;
|
|
109
123
|
}
|
|
124
|
+
// "Smart" mode
|
|
110
125
|
// 1. Get property map of all schema types
|
|
111
126
|
const schemaResolversTypePropertyMap = {};
|
|
112
127
|
const populateSchemaTypeResolversPropertyMap = (node) => {
|
|
@@ -181,7 +196,8 @@ export const getGraphQLObjectTypeResolversToGenerate = ({ mode, tsMorphProject,
|
|
|
181
196
|
return;
|
|
182
197
|
});
|
|
183
198
|
});
|
|
184
|
-
|
|
199
|
+
const newResult = cache.set(cacheKey, result);
|
|
200
|
+
return newResult;
|
|
185
201
|
};
|
|
186
202
|
const mustGetMapperOriginalDeclarationNode = ({ tsMorphProject, mapper, }) => {
|
|
187
203
|
const typeMapperFile = tsMorphProject.getSourceFile(mapper.filename);
|
package/dist/preset.js
CHANGED
|
@@ -151,6 +151,10 @@ export const preset = {
|
|
|
151
151
|
plugins: [{ add: { content: meta.content } }],
|
|
152
152
|
config: {},
|
|
153
153
|
schema,
|
|
154
|
+
// Pass schemaAst so codegen-core reuses the prebuilt schema instead of
|
|
155
|
+
// rebuilding one per output (O(types) each -> O(types^2) overall). This
|
|
156
|
+
// is an `add`-only output, so the schema doesn't affect its content.
|
|
157
|
+
schemaAst,
|
|
154
158
|
documents: [],
|
|
155
159
|
};
|
|
156
160
|
generatesSection.push(typeDefsFile);
|
|
@@ -214,7 +218,17 @@ export const preset = {
|
|
|
214
218
|
plugins: [{ add: { content } }],
|
|
215
219
|
config: {},
|
|
216
220
|
schema,
|
|
221
|
+
// Pass schemaAst so codegen-core reuses the prebuilt schema instead of
|
|
222
|
+
// rebuilding one per output (O(types) each -> O(types^2) overall). This
|
|
223
|
+
// is an `add`-only output, so the schema doesn't affect its content.
|
|
224
|
+
schemaAst,
|
|
217
225
|
documents: [],
|
|
226
|
+
// Forces Codegen to compare generated content vs on disk, instead of in-memory cache
|
|
227
|
+
// This avoids situation where resolver files are content on disk are updated,
|
|
228
|
+
// and goes out of sync with the in-memory cache version, and can lead to generated
|
|
229
|
+
// content not getting written to disk
|
|
230
|
+
// https://github.com/dotansimha/graphql-code-generator/pull/10928
|
|
231
|
+
contentComparison: 'disk',
|
|
218
232
|
};
|
|
219
233
|
});
|
|
220
234
|
logger.debug(`Applying changes to ${resolverFilesGenerateOptions.length}/${resultFilesArray.length} files. (${resultFilesArray.length - resolverFilesGenerateOptions.length} skipped files because they are already on filesystem and are not updated)`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eddeee888/gcg-typescript-resolver-files",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/eddeee888/graphql-code-generator-plugins.git",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@graphql-codegen/add": "^7.1.0",
|
|
44
|
-
"@graphql-codegen/plugin-helpers": "^7.
|
|
44
|
+
"@graphql-codegen/plugin-helpers": "^7.3.0",
|
|
45
45
|
"@graphql-codegen/schema-ast": "^6.1.0",
|
|
46
46
|
"@graphql-codegen/typescript": "^6.1.0",
|
|
47
47
|
"@graphql-codegen/typescript-resolvers": "^6.1.0",
|