@xyd-js/gql 0.1.0-xyd.12 → 0.1.0-xyd.15

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.
Files changed (88) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/TODO.md +8 -0
  3. package/__fixtures__/-1.opendocs.docs-nested/input.graphql +66 -0
  4. package/__fixtures__/-1.opendocs.docs-nested/output.json +554 -0
  5. package/__fixtures__/-1.opendocs.flat/input.graphql +19 -0
  6. package/__fixtures__/-1.opendocs.flat/output.json +243 -0
  7. package/__fixtures__/-1.opendocs.scopes/input.graphql +33 -0
  8. package/__fixtures__/-1.opendocs.scopes/output.json +378 -0
  9. package/__fixtures__/-1.opendocs.sidebar/input.graphql +44 -0
  10. package/__fixtures__/-1.opendocs.sort/input.graphql +92 -0
  11. package/__fixtures__/-1.opendocs.sort/output.json +1078 -0
  12. package/__fixtures__/-1.opendocs.sort+group/input.graphql +111 -0
  13. package/__fixtures__/-1.opendocs.sort+group/output.json +1114 -0
  14. package/__fixtures__/-1.opendocs.sort+group+path/input.graphql +118 -0
  15. package/__fixtures__/-1.opendocs.sort+group+path/output.json +1114 -0
  16. package/__fixtures__/-2.complex.github/input.graphql +69424 -0
  17. package/__fixtures__/-2.complex.github/output.json +269874 -0
  18. package/__fixtures__/-2.complex.livesession/input.graphql +23 -0
  19. package/__fixtures__/-2.complex.livesession/output.json +302 -0
  20. package/__fixtures__/-2.complex.monday/input.graphql +6089 -0
  21. package/__fixtures__/-2.complex.monday/output.json +1 -0
  22. package/__fixtures__/-3.array-non-null-return/input.graphql +9 -0
  23. package/__fixtures__/-3.array-non-null-return/output.json +151 -0
  24. package/__fixtures__/1.basic/input.graphql +118 -0
  25. package/__fixtures__/1.basic/output.json +630 -0
  26. package/__fixtures__/2.circular/input.graphql +17 -0
  27. package/__fixtures__/2.circular/output.json +248 -0
  28. package/__fixtures__/3.opendocs/input.graphql +27 -0
  29. package/__fixtures__/3.opendocs/output.json +338 -0
  30. package/__fixtures__/4.union/input.graphql +19 -0
  31. package/__fixtures__/4.union/output.json +344 -0
  32. package/__fixtures__/5.flat/input.graphql +27 -0
  33. package/__fixtures__/5.flat/output.json +383 -0
  34. package/__fixtures__/6.default-values/input.graphql +47 -0
  35. package/__fixtures__/6.default-values/output.json +655 -0
  36. package/__fixtures__/7.type-args/input.graphql +19 -0
  37. package/__fixtures__/7.type-args/output.json +301 -0
  38. package/__fixtures__/8.default-sort/input.graphql +60 -0
  39. package/__fixtures__/8.default-sort/output.json +1078 -0
  40. package/__tests__/gqlSchemaToReferences.test.ts +109 -0
  41. package/__tests__/utils.ts +45 -0
  42. package/declarations.d.ts +4 -0
  43. package/dist/index.d.ts +17 -1
  44. package/dist/index.js +1334 -19871
  45. package/dist/index.js.map +1 -1
  46. package/dist/opendocs.graphql +56 -0
  47. package/package.json +7 -6
  48. package/src/context.ts +17 -0
  49. package/src/converters/gql-arg.ts +51 -0
  50. package/src/converters/gql-enum.ts +27 -0
  51. package/src/converters/gql-field.ts +164 -0
  52. package/src/converters/gql-input.ts +34 -0
  53. package/src/converters/gql-interface.ts +35 -0
  54. package/src/converters/gql-mutation.ts +36 -0
  55. package/src/converters/gql-object.ts +83 -0
  56. package/src/converters/gql-operation.ts +128 -0
  57. package/src/converters/gql-query.ts +36 -0
  58. package/src/converters/gql-sample.ts +159 -0
  59. package/src/converters/gql-scalar.ts +16 -0
  60. package/src/converters/gql-subscription.ts +36 -0
  61. package/src/converters/gql-types.ts +195 -0
  62. package/src/converters/gql-union.ts +40 -0
  63. package/src/gql-core.ts +362 -0
  64. package/src/opendocs.graphql +56 -0
  65. package/src/opendocs.ts +253 -0
  66. package/src/schema.ts +253 -67
  67. package/src/types.ts +103 -0
  68. package/src/utils.ts +21 -96
  69. package/tsconfig.json +1 -1
  70. package/tsup.config.ts +15 -1
  71. package/vitest.config.ts +15 -1
  72. package/examples/basic/index.ts +0 -12
  73. package/examples/basic/schema.graphqls +0 -89
  74. package/examples/basic/todo-app.graphqls +0 -184
  75. package/examples/graphql-types/graphql-types.0.basic.graphqls +0 -28
  76. package/examples/nested/nested-arg.0.not-required.graphqls +0 -8
  77. package/examples/nested/nested-arg.0.required.graphqls +0 -8
  78. package/examples/nested/nested-arg.1.deep.graphqls +0 -12
  79. package/src/hydration/README.md +0 -1
  80. package/src/hydration/gql-arg.ts +0 -53
  81. package/src/hydration/gql-field.ts +0 -206
  82. package/src/hydration/gql-input.ts +0 -67
  83. package/src/hydration/gql-object.ts +0 -35
  84. package/src/hydration/gql-types.ts +0 -50
  85. package/src/samples/index.ts +0 -95
  86. package/test/graphql-types.0.test.ts +0 -125
  87. package/test/nested-arg.0.test.ts +0 -208
  88. package/test/nested-arg.1.test.ts +0 -19
@@ -0,0 +1,362 @@
1
+ import {
2
+ GraphQLArgument,
3
+ GraphQLEnumType,
4
+ GraphQLEnumValue,
5
+ GraphQLInputObjectType,
6
+ GraphQLInterfaceType,
7
+ GraphQLObjectType,
8
+ GraphQLScalarType,
9
+ GraphQLUnionType,
10
+ GraphQLField,
11
+ GraphQLInputField,
12
+ GraphQLNamedType,
13
+
14
+ isIntrospectionType,
15
+ isSpecifiedScalarType, GraphQLNonNull,
16
+ GraphQLList
17
+ } from "graphql";
18
+ import GithubSlugger from 'github-slugger';
19
+
20
+ import {
21
+ Definition,
22
+ DefinitionProperty,
23
+ DefinitionPropertyMeta,
24
+ ExampleGroup,
25
+ Reference,
26
+ ReferenceCategory,
27
+ ReferenceType
28
+ } from "@xyd-js/uniform";
29
+
30
+ import {GQLTypeInfo, GQLOperation} from "./types";
31
+ import {gqlFieldToUniformDefinitionProperty} from "./converters/gql-field";
32
+ import {Context} from "./context";
33
+ import {openDocsCanonical, openDocsToGroup} from "./opendocs";
34
+
35
+ export type GraphqlUniformReferenceType =
36
+ | GraphQLScalarType
37
+ | GraphQLObjectType
38
+ | GraphQLInterfaceType
39
+ | GraphQLUnionType
40
+ | GraphQLEnumType
41
+ | GraphQLInputObjectType
42
+ | GQLOperation
43
+ | GraphQLField<any, any> | GraphQLInputField | GraphQLArgument
44
+
45
+ export function extractScopesFromDocDirective(
46
+ ctx: Context,
47
+ gqlType: GraphqlUniformReferenceType,
48
+ ): string[] {
49
+ const scopes: string[] = []
50
+
51
+ const schema = ctx?.schema
52
+
53
+ if (gqlType.astNode?.directives) {
54
+ for (const directive of gqlType.astNode.directives) {
55
+ if (directive.name.value === "doc") {
56
+ const scopesArg = directive.arguments?.find(arg => arg.name.value === 'scopes')
57
+ if (scopesArg?.value.kind === 'ListValue') {
58
+ for (const scopeValue of scopesArg.value.values) {
59
+ if (scopeValue.kind === 'EnumValue') {
60
+ // For enum values, we need to find the enum type and its value with @scope directive
61
+ let enumType: GraphQLEnumType | undefined
62
+
63
+ // First check if current type is an enum
64
+ if (gqlType instanceof GraphQLEnumType) {
65
+ enumType = gqlType
66
+ }
67
+ // If not and we have a schema, try to find the enum type
68
+ else if (schema) {
69
+ const type = schema.getType('OpenDocsScope')
70
+ if (type instanceof GraphQLEnumType) {
71
+ enumType = type
72
+ }
73
+ }
74
+
75
+ if (enumType) {
76
+ const enumValue = enumType.getValue(scopeValue.value)
77
+ if (enumValue?.astNode?.directives) {
78
+ for (const enumDirective of enumValue.astNode.directives) {
79
+ if (enumDirective.name.value === 'scope') {
80
+ const valueArg = enumDirective.arguments?.find(arg => arg.name.value === 'value')
81
+ if (valueArg?.value.kind === 'StringValue') {
82
+ scopes.push(valueArg.value.value)
83
+ }
84
+ }
85
+ }
86
+ }
87
+ }
88
+ } else if (scopeValue.kind === 'StringValue') {
89
+ // Handle string literal (e.g. "user:write")
90
+ scopes.push(scopeValue.value)
91
+ }
92
+ }
93
+ }
94
+ }
95
+ }
96
+ }
97
+
98
+ return scopes
99
+ }
100
+
101
+ export function uniformify(
102
+ ctx: Context,
103
+ gqlType: GraphqlUniformReferenceType,
104
+ definitions: Definition[],
105
+ examples: ExampleGroup[],
106
+ ): Reference {
107
+ let canonicalPrefix = ""
108
+ let graphqlTypeShort = ""
109
+ let refType: ReferenceType | undefined = undefined
110
+
111
+ if (gqlType instanceof GraphQLScalarType) {
112
+ canonicalPrefix = "scalars"
113
+ graphqlTypeShort = "scalar"
114
+ refType = ReferenceType.GRAPHQL_SCALAR
115
+ } else if (gqlType instanceof GraphQLObjectType) {
116
+ canonicalPrefix = "objects"
117
+ graphqlTypeShort = "object"
118
+ refType = ReferenceType.GRAPHQL_OBJECT
119
+ } else if (gqlType instanceof GraphQLInterfaceType) {
120
+ canonicalPrefix = "interfaces"
121
+ graphqlTypeShort = "interface"
122
+ refType = ReferenceType.GRAPHQL_INTERFACE
123
+ } else if (gqlType instanceof GraphQLUnionType) {
124
+ canonicalPrefix = "unions"
125
+ graphqlTypeShort = "union"
126
+ refType = ReferenceType.GRAPHQL_UNION
127
+ } else if (gqlType instanceof GraphQLEnumType) {
128
+ canonicalPrefix = "enums"
129
+ graphqlTypeShort = "enum"
130
+ refType = ReferenceType.GRAPHQL_ENUM
131
+ } else if (gqlType instanceof GraphQLInputObjectType) {
132
+ canonicalPrefix = "inputs"
133
+ graphqlTypeShort = "input"
134
+ refType = ReferenceType.GRAPHQL_INPUT
135
+ } else if (gqlType instanceof GQLOperation) {
136
+ switch (gqlType._operationType) {
137
+ case "query": {
138
+ canonicalPrefix = "queries"
139
+ graphqlTypeShort = "query"
140
+ refType = ReferenceType.GRAPHQL_QUERY
141
+ break;
142
+ }
143
+ case "mutation": {
144
+ canonicalPrefix = "mutations"
145
+ graphqlTypeShort = "mutation"
146
+ refType = ReferenceType.GRAPHQL_MUTATION
147
+ break;
148
+ }
149
+ case "subscription": {
150
+ canonicalPrefix = "subscriptions"
151
+ graphqlTypeShort = "subscription"
152
+ refType = ReferenceType.GRAPHQL_SUBSCRIPTION
153
+ break;
154
+ }
155
+ }
156
+ } else {
157
+ const info = gqlFieldTypeInfo(gqlType)
158
+ if (info?.typeFlat && !isBuiltInType(info?.typeFlat)) {
159
+ if (info?.typeFlat) {
160
+ return uniformify(ctx, info.typeFlat, definitions, examples)
161
+ }
162
+ }
163
+ }
164
+
165
+ const slugger = new GithubSlugger();
166
+ // const slug = slugger.slug(gqlType.name);
167
+ const slug = gqlType.name;
168
+
169
+ const odCanonical = openDocsCanonical(ctx, gqlType);
170
+
171
+ let canonical = ""
172
+ if (odCanonical) {
173
+ canonical = odCanonical;
174
+ } else if (canonicalPrefix) {
175
+ canonical = [canonicalPrefix, slug].join("/")
176
+ }
177
+
178
+ const scopes = extractScopesFromDocDirective(ctx, gqlType) || []
179
+ return {
180
+ title: gqlType.name,
181
+ description: gqlType.description || "",
182
+ canonical,
183
+
184
+ category: ReferenceCategory.GRAPHQL,
185
+ type: refType,
186
+
187
+ context: {
188
+ graphqlTypeShort: graphqlTypeShort,
189
+ graphqlName: gqlType.name,
190
+ group: openDocsToGroup(ctx, gqlType),
191
+ scopes
192
+ },
193
+
194
+ examples: {
195
+ groups: examples || [],
196
+ },
197
+
198
+ definitions,
199
+ }
200
+ }
201
+
202
+ export function propsUniformify(
203
+ ctx: Context,
204
+ field: GraphQLField<any, any> | GraphQLInputField | GraphQLArgument,
205
+ properties?: DefinitionProperty[],
206
+ meta?: DefinitionPropertyMeta[]
207
+ ): DefinitionProperty {
208
+ const fieldInfo = gqlFieldTypeInfo(field);
209
+ const objRef = uniformify(ctx, field, [], []);
210
+
211
+ const builtInType = fieldInfo?.typeFlat ? isBuiltInType(fieldInfo?.typeFlat) : undefined
212
+
213
+ return {
214
+ name: field.name,
215
+ type: field.type.toJSON(),
216
+ description: field.description || "",
217
+ context: {
218
+ graphqlName: field.name,
219
+ graphqlTypeFlat: fieldInfo.typeFlat?.toJSON() || "",
220
+ graphqlBuiltInType: builtInType,
221
+ },
222
+ properties: properties || [],
223
+ meta: [
224
+ ...gqlFieldToUniformMeta(field),
225
+ ...(meta || []),
226
+ ],
227
+ symbolDef: {
228
+ canonical: objRef?.canonical,
229
+ },
230
+ }
231
+ }
232
+
233
+ export function gqlObjectPropsUniformify(
234
+ ctx: Context,
235
+ obj: GraphQLInputObjectType | GraphQLObjectType,
236
+ meta?: DefinitionPropertyMeta[]
237
+ ): DefinitionProperty {
238
+ const objRef = uniformify(ctx, obj, [], []);
239
+ const inputFields = obj.getFields?.()
240
+ const nestedProps: DefinitionProperty[] = []
241
+
242
+ const nestedDefinitionProperty: DefinitionProperty = {
243
+ name: obj.name,
244
+ type: obj.toJSON(),
245
+ description: obj.description || "",
246
+ context: objRef.context,
247
+ properties: nestedProps,
248
+ meta: [
249
+ ...(meta || []),
250
+ ],
251
+ symbolDef: {
252
+ canonical: objRef.canonical,
253
+ },
254
+ }
255
+
256
+ if (!ctx?.config?.flatArg) {
257
+ for (const [name, inputField] of Object.entries(inputFields)) {
258
+ const prop = gqlFieldToUniformDefinitionProperty(
259
+ ctx,
260
+ inputField,
261
+ )
262
+
263
+ if (prop) {
264
+ nestedProps.push(prop)
265
+ }
266
+ }
267
+ }
268
+
269
+ return nestedDefinitionProperty
270
+ }
271
+
272
+ export function gqlFieldToUniformMeta(
273
+ field: GraphQLField<any, any> | GraphQLInputField | GraphQLArgument
274
+ ): DefinitionPropertyMeta[] {
275
+ const meta: DefinitionPropertyMeta[] = []
276
+
277
+ // Check if field is required (non-null)
278
+ if (isNonNullField(field.type) || isListOfNonNullItems(field.type)) {
279
+ meta.push({
280
+ name: "required",
281
+ value: "true"
282
+ })
283
+ }
284
+
285
+ // Handle directives
286
+ if (field.astNode?.directives) {
287
+ for (const directive of field.astNode.directives) {
288
+ // Handle @deprecated directive
289
+ if (directive.name.value === "deprecated") {
290
+ let foundDeprecatedReason = false
291
+ for (const arg of directive.arguments || []) {
292
+ if (arg.name.value === "reason") {
293
+ foundDeprecatedReason = true
294
+ meta.push({
295
+ name: "deprecated",
296
+ value: arg.value.kind === 'StringValue' ? arg.value.value : "true"
297
+ })
298
+ }
299
+ }
300
+
301
+ if (!foundDeprecatedReason) {
302
+ meta.push({
303
+ name: "deprecated",
304
+ value: "true"
305
+ })
306
+ }
307
+ }
308
+ }
309
+ }
310
+
311
+ if ("defaultValue" in field && field.defaultValue !== undefined) {
312
+ meta.push({
313
+ name: "defaults",
314
+ value: field.defaultValue
315
+ })
316
+ }
317
+
318
+ return meta
319
+ }
320
+
321
+ export function gqlFieldTypeInfo(
322
+ field: GraphQLField<any, any> | GraphQLInputField
323
+ ): GQLTypeInfo {
324
+ const getTypeInfo = (type: any): GraphQLNamedType | undefined => {
325
+ if (!type) return undefined;
326
+
327
+ // Handle non-null types
328
+ if (type instanceof GraphQLNonNull) {
329
+ return getTypeInfo(type.ofType);
330
+ }
331
+
332
+
333
+ if (type instanceof GraphQLList) {
334
+ return getTypeInfo(type.ofType);
335
+ }
336
+
337
+ // Return the actual type
338
+ return type;
339
+ };
340
+
341
+ const graphqlTypeFlat = getTypeInfo(field.type);
342
+
343
+ return {
344
+ typeFlat: graphqlTypeFlat
345
+ }
346
+ }
347
+
348
+ export function isBuiltInType(gqlType: GraphQLNamedType): boolean {
349
+ return isSpecifiedScalarType(gqlType) || isIntrospectionType(gqlType)
350
+ }
351
+
352
+ // TODO: fix any
353
+ function isNonNullField(type: any): boolean {
354
+ return type.constructor.name === "GraphQLNonNull"
355
+ }
356
+
357
+ // TODO: fix any
358
+ function isListOfNonNullItems(type: any): boolean {
359
+ return "ofType" in type &&
360
+ type.constructor.name === "GraphQLList" &&
361
+ type.ofType.constructor.name === "GraphQLNonNull"
362
+ }
@@ -0,0 +1,56 @@
1
+ directive @scope(value: String!) on ENUM_VALUE
2
+
3
+ enum OpenDocsScope {
4
+ _PLACEHOLDER_
5
+ }
6
+
7
+ directive @docs(
8
+ route: String
9
+ sort: [OpenDocsSortInput!]
10
+ sortStack: [[String!]!]
11
+ flattenTypes: Boolean
12
+ sidebar: [OpenDocsPage]
13
+ group: [String]
14
+ ) repeatable on SCHEMA
15
+
16
+ input OpenDocsPage {
17
+ type: OpenDocsSidebarItemType!
18
+ key: String!
19
+ group: String
20
+ path: String
21
+ pages: [OpenDocsPage]
22
+ }
23
+
24
+ enum OpenDocsSidebarItemType {
25
+ OPERATION
26
+ TYPE
27
+ }
28
+
29
+ directive @doc(
30
+ group: [String!],
31
+ path: String,
32
+ example: OpenDocsExampleInput,
33
+ scopes: [OpenDocsScope],
34
+ ) on QUERY |
35
+ FIELD_DEFINITION |
36
+ MUTATION |
37
+ SUBSCRIPTION |
38
+ OBJECT |
39
+ INPUT_OBJECT |
40
+ ENUM |
41
+ SCALAR |
42
+ UNION
43
+
44
+ # TODO: finish
45
+ input OpenDocsExampleInput {
46
+ """
47
+ The name of the code
48
+ """
49
+ code: String!
50
+ }
51
+
52
+ input OpenDocsSortInput {
53
+ node: String
54
+ group: [String!]
55
+ stack: Int
56
+ }
@@ -0,0 +1,253 @@
1
+ import {
2
+ GraphQLSchema,
3
+ GraphQLArgument,
4
+ GraphQLEnumType,
5
+ GraphQLEnumValue,
6
+ GraphQLField,
7
+ GraphQLInputObjectType,
8
+ GraphQLInterfaceType,
9
+ GraphQLObjectType,
10
+ GraphQLScalarType,
11
+ GraphQLUnionType,
12
+ ConstValueNode,
13
+ StringValueNode,
14
+ } from "graphql";
15
+
16
+ import {GQLOperation, GQLSchemaToReferencesOptions, SortItem} from "./types";
17
+ import {GraphqlUniformReferenceType} from "./gql-core";
18
+ import {Context} from "./context";
19
+
20
+ export const OPEN_DOCS_SCHEMA_DIRECTIVE_NAME = "docs";
21
+ export const OPEN_DOCS_DIRECTIVE_NAME = "doc";
22
+
23
+ export function openDocsExtensionsToOptions(
24
+ schema: GraphQLSchema,
25
+ ) {
26
+ const options: GQLSchemaToReferencesOptions = {}
27
+
28
+ // Check for @docs directive in schema extensions
29
+ for (const extension of schema.extensionASTNodes || []) {
30
+ if (extension.kind === 'SchemaExtension') {
31
+ for (const directive of extension.directives || []) {
32
+ if (directive.name.value === OPEN_DOCS_SCHEMA_DIRECTIVE_NAME) {
33
+ for (const arg of directive.arguments || []) {
34
+ if (arg.name.value === 'flattenTypes' && arg.value.kind === 'BooleanValue') {
35
+ if (arg.value.value === true) {
36
+ options.flat = true
37
+ } else if (arg.value.value === false) {
38
+ options.flat = false
39
+ }
40
+ } else if (arg.name.value === 'sort' && arg.value.kind === 'ListValue') {
41
+ const sortItems: SortItem[] = [];
42
+ for (const item of arg.value.values) {
43
+ if (item.kind === 'ObjectValue') {
44
+ const sortItem: SortItem = {};
45
+ for (const field of item.fields) {
46
+ if (field.name.value === 'node' && field.value.kind === 'StringValue') {
47
+ sortItem.node = field.value.value;
48
+ } else if (field.name.value === 'group' && field.value.kind === 'ListValue') {
49
+ sortItem.group = field.value.values
50
+ .filter((v): v is StringValueNode => v.kind === 'StringValue')
51
+ .map(v => v.value);
52
+ } else if (field.name.value === 'stack' && field.value.kind === 'IntValue') {
53
+ sortItem.stack = parseInt(field.value.value);
54
+ }
55
+ }
56
+ sortItems.push(sortItem);
57
+ }
58
+ }
59
+ if (!options.sort) {
60
+ options.sort = {};
61
+ }
62
+ options.sort.sort = sortItems;
63
+ } else if (arg.name.value === 'sortStack' && arg.value.kind === 'ListValue') {
64
+ const sortStacks: string[][] = [];
65
+ for (const item of arg.value.values) {
66
+ if (item.kind === 'ListValue') {
67
+ const stack = item.values
68
+ .filter((v): v is StringValueNode => v.kind === 'StringValue')
69
+ .map(v => v.value);
70
+ sortStacks.push(stack);
71
+ }
72
+ }
73
+ if (!options.sort) {
74
+ options.sort = {};
75
+ }
76
+ options.sort.sortStack = sortStacks;
77
+ } else if (arg.name.value === 'route' && arg.value.kind === 'StringValue') {
78
+ options.route = arg.value.value;
79
+ }
80
+ }
81
+ }
82
+ }
83
+ }
84
+ }
85
+
86
+ return options;
87
+ }
88
+
89
+ type OpenDocsGQLNode =
90
+ | GQLOperation
91
+ | GraphQLScalarType
92
+ | GraphQLObjectType
93
+ | GraphQLField<any, any>
94
+ | GraphQLArgument
95
+ | GraphQLInterfaceType
96
+ | GraphQLUnionType
97
+ | GraphQLEnumType
98
+ | GraphQLEnumValue
99
+ | GraphQLInputObjectType
100
+
101
+ export function openDocsToGroup(
102
+ ctx: Context | undefined,
103
+ odGqlNode: OpenDocsGQLNode,
104
+ ): string[] {
105
+ let groups: string[] = []
106
+
107
+ const metadata = (ctx?.schema as any).__metadata;
108
+
109
+ if (metadata?.rootGroups) {
110
+ groups = [...metadata.rootGroups];
111
+ }
112
+
113
+ let directiveGroups = false
114
+
115
+ // Check schema metadata for field-specific groups (for operations)
116
+ if (ctx?.schema && 'name' in odGqlNode) {
117
+ const metadata = (ctx.schema as any).__metadata;
118
+ if (metadata?.fields) {
119
+ if ("_operationType" in odGqlNode) {
120
+ let fieldKey = ""
121
+
122
+ switch (odGqlNode._operationType) {
123
+ case "query": {
124
+ fieldKey = `Query.${odGqlNode.name}`;
125
+ break;
126
+ }
127
+ case "mutation": {
128
+ fieldKey = `Mutation.${odGqlNode.name}`;
129
+ break;
130
+ }
131
+ case "subscription": {
132
+ fieldKey = `Subscription.${odGqlNode.name}`;
133
+ break;
134
+ }
135
+ }
136
+
137
+ const fieldMetadata = metadata.fields.get(fieldKey);
138
+ if (fieldMetadata?.groups) {
139
+ directiveGroups = true
140
+ groups.push(...fieldMetadata.groups);
141
+ }
142
+ }
143
+ }
144
+ }
145
+
146
+ // If no groups from metadata, try getting groups from the node itself
147
+ if (!directiveGroups && odGqlNode.astNode?.directives) {
148
+ for (const directive of odGqlNode.astNode.directives) {
149
+ switch (directive.name.value) {
150
+ case OPEN_DOCS_DIRECTIVE_NAME: {
151
+ const groupArg = directive.arguments?.find((arg: {
152
+ name: { value: string }
153
+ }) => arg.name.value === 'group')
154
+ if (groupArg?.value.kind === 'ListValue') {
155
+ directiveGroups = true
156
+ groups.push(...groupArg.value.values
157
+ .filter((v: ConstValueNode): v is StringValueNode => v.kind === 'StringValue')
158
+ .map(v => v.value)
159
+ )
160
+ }
161
+ break
162
+ }
163
+ }
164
+ }
165
+ }
166
+
167
+ // If still no groups, use default based on type
168
+ if (!directiveGroups) {
169
+ if (odGqlNode instanceof GraphQLObjectType) {
170
+ groups.push("Objects")
171
+ } else if (odGqlNode instanceof GraphQLInterfaceType) {
172
+ groups.push("Interfaces")
173
+ } else if (odGqlNode instanceof GraphQLUnionType) {
174
+ groups.push("Unions")
175
+ } else if (odGqlNode instanceof GraphQLEnumType) {
176
+ groups.push("Enums")
177
+ } else if (odGqlNode instanceof GraphQLInputObjectType) {
178
+ groups.push("Inputs")
179
+ } else if (odGqlNode instanceof GraphQLScalarType) {
180
+ groups.push("Scalars")
181
+ } else if (odGqlNode instanceof GQLOperation) {
182
+ switch (odGqlNode._operationType) {
183
+ case "query": {
184
+ groups.push("Queries")
185
+ break;
186
+ }
187
+ case "mutation": {
188
+ groups.push("Mutations")
189
+ break;
190
+ }
191
+ case "subscription": {
192
+ groups.push("Subscriptions")
193
+ break;
194
+ }
195
+ }
196
+ }
197
+ }
198
+
199
+ return groups
200
+ }
201
+
202
+ export function openDocsCanonical(
203
+ ctx: Context,
204
+ gqlType: GraphqlUniformReferenceType,
205
+ ) {
206
+ let path = ""
207
+
208
+ // Get parent path if this is a field
209
+ if ('astNode' in gqlType && gqlType.astNode?.kind === 'FieldDefinition' && ctx?.schema) {
210
+ // Check schema metadata for field-specific path
211
+ const metadata = (ctx.schema as any).__metadata;
212
+ if (metadata?.fields && 'name' in gqlType) {
213
+ if ("_operationType" in gqlType) {
214
+ let fieldKey = ""
215
+
216
+ switch (gqlType._operationType) {
217
+ case "query": {
218
+ fieldKey = `Query.${gqlType.name}`;
219
+ break;
220
+ }
221
+ case "mutation": {
222
+ fieldKey = `Mutation.${gqlType.name}`;
223
+ break;
224
+ }
225
+ case "subscription": {
226
+ fieldKey = `Subscription.${gqlType.name}`;
227
+ break;
228
+ }
229
+ }
230
+ const fieldMetadata = metadata.fields.get(fieldKey);
231
+
232
+
233
+ if (fieldMetadata?.path) {
234
+ path = fieldMetadata.path;
235
+ }
236
+ }
237
+ }
238
+ }
239
+
240
+ // Extract path from @doc directive if present
241
+ if (!path && gqlType.astNode?.directives) {
242
+ for (const directive of gqlType.astNode.directives) {
243
+ if (directive.name.value === OPEN_DOCS_DIRECTIVE_NAME) {
244
+ const pathArg = directive.arguments?.find(arg => arg.name.value === 'path')
245
+ if (pathArg?.value.kind === 'StringValue') {
246
+ path = pathArg.value.value
247
+ }
248
+ }
249
+ }
250
+ }
251
+
252
+ return path
253
+ }