@graphcommerce/graphql-codegen-near-operation-file 2.102.2 → 2.103.2
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/fragment-resolver.js +136 -0
- package/dist/index.js +118 -0
- package/dist/injectable.js +124 -0
- package/dist/resolve-document-imports.js +134 -0
- package/dist/utils.js +51 -0
- package/package.json +8 -6
- package/src/fragment-resolver.ts +191 -0
- package/src/index.ts +266 -0
- package/src/injectable.graphqls +21 -0
- package/src/injectable.ts +126 -0
- package/src/resolve-document-imports.ts +173 -0
- package/src/test/InjectableFragment.graphql +3 -0
- package/src/test/InjectingFragment.graphql +3 -0
- package/src/test/QueryWithInjectable.graphql +5 -0
- package/src/utils.ts +61 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/* eslint-disable import/no-cycle */
|
|
2
|
+
import { resolve } from 'path'
|
|
3
|
+
import { isUsingTypes, Types, DetailedError } from '@graphql-codegen/plugin-helpers'
|
|
4
|
+
import {
|
|
5
|
+
generateImportStatement,
|
|
6
|
+
ImportSource,
|
|
7
|
+
resolveImportSource,
|
|
8
|
+
FragmentImport,
|
|
9
|
+
ImportDeclaration,
|
|
10
|
+
LoadedFragment,
|
|
11
|
+
} from '@graphql-codegen/visitor-plugin-common'
|
|
12
|
+
import { Source } from '@graphql-tools/utils'
|
|
13
|
+
import { FragmentDefinitionNode, GraphQLSchema, visit } from 'graphql'
|
|
14
|
+
import buildFragmentResolver, { buildFragmentRegistry } from './fragment-resolver'
|
|
15
|
+
import { extractExternalFragmentsInUse } from './utils'
|
|
16
|
+
|
|
17
|
+
export type FragmentRegistry = {
|
|
18
|
+
[fragmentName: string]: {
|
|
19
|
+
location: string
|
|
20
|
+
importNames: string[]
|
|
21
|
+
onType: string
|
|
22
|
+
node: FragmentDefinitionNode
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type DocumentImportResolverOptions = {
|
|
27
|
+
baseDir: string
|
|
28
|
+
/** Generates a target file path from the source `document.location` */
|
|
29
|
+
generateFilePath: (location: string) => string
|
|
30
|
+
/** Schema base types source */
|
|
31
|
+
schemaTypesSource: string | ImportSource
|
|
32
|
+
/** Should `import type` be used */
|
|
33
|
+
typesImport: boolean
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface ResolveDocumentImportResult {
|
|
37
|
+
filename: string
|
|
38
|
+
documents: Source[]
|
|
39
|
+
importStatements: string[]
|
|
40
|
+
fragmentImports: ImportDeclaration<FragmentImport>[]
|
|
41
|
+
externalFragments: LoadedFragment<{
|
|
42
|
+
level: number
|
|
43
|
+
}>[]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getFragmentName(documentFile: Types.DocumentFile) {
|
|
47
|
+
let name: string | undefined
|
|
48
|
+
visit(documentFile.document!, {
|
|
49
|
+
enter: {
|
|
50
|
+
FragmentDefinition: (node: FragmentDefinitionNode) => {
|
|
51
|
+
name = node.name.value
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
})
|
|
55
|
+
return name
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Transform the preset's provided documents into single-file generator sources, while resolving
|
|
60
|
+
* fragment and user-defined imports
|
|
61
|
+
*
|
|
62
|
+
* Resolves user provided imports and fragment imports using the `DocumentImportResolverOptions`.
|
|
63
|
+
* Does not define specific plugins, but rather returns a string[] of `importStatements` for the
|
|
64
|
+
* calling plugin to make use of
|
|
65
|
+
*/
|
|
66
|
+
export function resolveDocumentImports<T>(
|
|
67
|
+
presetOptions: Types.PresetFnArgs<T>,
|
|
68
|
+
schemaObject: GraphQLSchema,
|
|
69
|
+
importResolverOptions: DocumentImportResolverOptions,
|
|
70
|
+
): Array<ResolveDocumentImportResult> {
|
|
71
|
+
const { baseOutputDir, documents, pluginMap } = presetOptions
|
|
72
|
+
const { generateFilePath, schemaTypesSource, baseDir, typesImport } = importResolverOptions
|
|
73
|
+
|
|
74
|
+
const resolveFragments = buildFragmentResolver(importResolverOptions, presetOptions, schemaObject)
|
|
75
|
+
const fragmentRegistry = buildFragmentRegistry(importResolverOptions, presetOptions, schemaObject)
|
|
76
|
+
|
|
77
|
+
const isRelayOptimizer = !!Object.keys(pluginMap).find((plugin) =>
|
|
78
|
+
plugin.includes('relay-optimizer-plugin'),
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
const resDocuments = documents.map((documentFile) => {
|
|
82
|
+
try {
|
|
83
|
+
const isFragment = typeof getFragmentName(documentFile) !== 'undefined'
|
|
84
|
+
|
|
85
|
+
if (!isFragment && isRelayOptimizer) {
|
|
86
|
+
const generatedFilePath = generateFilePath(documentFile.location!)
|
|
87
|
+
|
|
88
|
+
let externalFragments = extractExternalFragmentsInUse(
|
|
89
|
+
documentFile.document!,
|
|
90
|
+
fragmentRegistry,
|
|
91
|
+
)
|
|
92
|
+
// Sort the entries in the right order so fragments are defined when using
|
|
93
|
+
externalFragments = Object.fromEntries(
|
|
94
|
+
Object.entries(externalFragments).sort(([, levelA], [, levelB]) => levelB - levelA),
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
const fragments = documents.filter(
|
|
98
|
+
(d) => typeof externalFragments[getFragmentName(d) ?? ''] !== 'undefined',
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
const importStatements: string[] = []
|
|
102
|
+
|
|
103
|
+
if (isUsingTypes(documentFile.document!, [], schemaObject)) {
|
|
104
|
+
const schemaTypesImportStatement = generateImportStatement({
|
|
105
|
+
baseDir,
|
|
106
|
+
importSource: resolveImportSource(schemaTypesSource),
|
|
107
|
+
baseOutputDir,
|
|
108
|
+
outputPath: generatedFilePath,
|
|
109
|
+
typesImport,
|
|
110
|
+
})
|
|
111
|
+
importStatements.unshift(schemaTypesImportStatement)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// const newDocument = [...fragments.map((f) => f.rawSDL), documentFile.rawSDL].join('\n')
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
filename: generatedFilePath,
|
|
118
|
+
documents: [...fragments, documentFile],
|
|
119
|
+
importStatements,
|
|
120
|
+
fragmentImports: [],
|
|
121
|
+
externalFragments: [],
|
|
122
|
+
} as ResolveDocumentImportResult
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const generatedFilePath = generateFilePath(documentFile.location!)
|
|
126
|
+
const importStatements: string[] = []
|
|
127
|
+
const { externalFragments, fragmentImports } = resolveFragments(
|
|
128
|
+
generatedFilePath,
|
|
129
|
+
documentFile.document!,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
if (
|
|
133
|
+
isRelayOptimizer ||
|
|
134
|
+
isUsingTypes(
|
|
135
|
+
documentFile.document!,
|
|
136
|
+
externalFragments.map((m) => m.name),
|
|
137
|
+
schemaObject,
|
|
138
|
+
)
|
|
139
|
+
) {
|
|
140
|
+
const schemaTypesImportStatement = generateImportStatement({
|
|
141
|
+
baseDir,
|
|
142
|
+
importSource: resolveImportSource(schemaTypesSource),
|
|
143
|
+
baseOutputDir,
|
|
144
|
+
outputPath: generatedFilePath,
|
|
145
|
+
typesImport,
|
|
146
|
+
})
|
|
147
|
+
importStatements.unshift(schemaTypesImportStatement)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
filename: generatedFilePath,
|
|
152
|
+
documents: [documentFile],
|
|
153
|
+
importStatements,
|
|
154
|
+
fragmentImports,
|
|
155
|
+
externalFragments,
|
|
156
|
+
}
|
|
157
|
+
} catch (e) {
|
|
158
|
+
if (e instanceof Error) {
|
|
159
|
+
throw new DetailedError(
|
|
160
|
+
`Unable to validate GraphQL document!`,
|
|
161
|
+
`File ${documentFile.location} caused error: ${e.message || e.toString()}`,
|
|
162
|
+
documentFile.location,
|
|
163
|
+
)
|
|
164
|
+
} else {
|
|
165
|
+
throw e
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
return resDocuments.filter((result) =>
|
|
171
|
+
result.filename.startsWith(resolve(baseDir, baseOutputDir)),
|
|
172
|
+
)
|
|
173
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/* eslint-disable import/no-cycle */
|
|
2
|
+
import { join } from 'path'
|
|
3
|
+
import { DocumentNode, visit, FragmentSpreadNode, FragmentDefinitionNode } from 'graphql'
|
|
4
|
+
import parsePath from 'parse-filepath'
|
|
5
|
+
import { FragmentRegistry } from './fragment-resolver'
|
|
6
|
+
|
|
7
|
+
export function defineFilepathSubfolder(baseFilePath: string, folder: string) {
|
|
8
|
+
const parsedPath = parsePath(baseFilePath)
|
|
9
|
+
return join(parsedPath.dir, folder, parsedPath.base).replace(/\\/g, '/')
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function appendExtensionToFilePath(baseFilePath: string, extension: string) {
|
|
13
|
+
const parsedPath = parsePath(baseFilePath)
|
|
14
|
+
|
|
15
|
+
return join(parsedPath.dir, parsedPath.name + extension).replace(/\\/g, '/')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function extractExternalFragmentsInUse(
|
|
19
|
+
documentNode: DocumentNode | FragmentDefinitionNode,
|
|
20
|
+
fragmentNameToFile: FragmentRegistry,
|
|
21
|
+
result: { [fragmentName: string]: number } = {},
|
|
22
|
+
level = 0,
|
|
23
|
+
): { [fragmentName: string]: number } {
|
|
24
|
+
const ignoreList: Set<string> = new Set()
|
|
25
|
+
|
|
26
|
+
// First, take all fragments definition from the current file, and mark them as ignored
|
|
27
|
+
visit(documentNode, {
|
|
28
|
+
enter: {
|
|
29
|
+
FragmentDefinition: (node: FragmentDefinitionNode) => {
|
|
30
|
+
ignoreList.add(node.name.value)
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
// Then, look for all used fragments in this document
|
|
36
|
+
visit(documentNode, {
|
|
37
|
+
enter: {
|
|
38
|
+
FragmentSpread: (node: FragmentSpreadNode) => {
|
|
39
|
+
if (!ignoreList.has(node.name.value)) {
|
|
40
|
+
if (
|
|
41
|
+
result[node.name.value] === undefined ||
|
|
42
|
+
(result[node.name.value] !== undefined && level < result[node.name.value])
|
|
43
|
+
) {
|
|
44
|
+
result[node.name.value] = level
|
|
45
|
+
|
|
46
|
+
if (fragmentNameToFile[node.name.value]) {
|
|
47
|
+
extractExternalFragmentsInUse(
|
|
48
|
+
fragmentNameToFile[node.name.value].node,
|
|
49
|
+
fragmentNameToFile,
|
|
50
|
+
result,
|
|
51
|
+
level + 1,
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
return result
|
|
61
|
+
}
|