@xyd-js/gql 0.0.0-build

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 (76) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/LICENSE +21 -0
  3. package/README.md +3 -0
  4. package/TODO.md +8 -0
  5. package/__fixtures__/-1.opendocs.docs-nested/input.graphql +66 -0
  6. package/__fixtures__/-1.opendocs.docs-nested/output.json +554 -0
  7. package/__fixtures__/-1.opendocs.flat/input.graphql +19 -0
  8. package/__fixtures__/-1.opendocs.flat/output.json +243 -0
  9. package/__fixtures__/-1.opendocs.scopes/input.graphql +33 -0
  10. package/__fixtures__/-1.opendocs.scopes/output.json +378 -0
  11. package/__fixtures__/-1.opendocs.sidebar/input.graphql +44 -0
  12. package/__fixtures__/-1.opendocs.sort/input.graphql +92 -0
  13. package/__fixtures__/-1.opendocs.sort/output.json +1078 -0
  14. package/__fixtures__/-1.opendocs.sort+group/input.graphql +111 -0
  15. package/__fixtures__/-1.opendocs.sort+group/output.json +1114 -0
  16. package/__fixtures__/-1.opendocs.sort+group+path/input.graphql +118 -0
  17. package/__fixtures__/-1.opendocs.sort+group+path/output.json +1114 -0
  18. package/__fixtures__/-2.complex.github/input.graphql +69424 -0
  19. package/__fixtures__/-2.complex.github/output.json +269874 -0
  20. package/__fixtures__/-2.complex.livesession/input.graphql +23 -0
  21. package/__fixtures__/-2.complex.livesession/output.json +302 -0
  22. package/__fixtures__/-2.complex.monday/input.graphql +6089 -0
  23. package/__fixtures__/-2.complex.monday/output.json +1 -0
  24. package/__fixtures__/-3.array-non-null-return/input.graphql +9 -0
  25. package/__fixtures__/-3.array-non-null-return/output.json +151 -0
  26. package/__fixtures__/1.basic/input.graphql +118 -0
  27. package/__fixtures__/1.basic/output.json +630 -0
  28. package/__fixtures__/2.circular/input.graphql +17 -0
  29. package/__fixtures__/2.circular/output.json +248 -0
  30. package/__fixtures__/3.opendocs/input.graphql +27 -0
  31. package/__fixtures__/3.opendocs/output.json +338 -0
  32. package/__fixtures__/4.union/input.graphql +19 -0
  33. package/__fixtures__/4.union/output.json +344 -0
  34. package/__fixtures__/5.flat/input.graphql +27 -0
  35. package/__fixtures__/5.flat/output.json +383 -0
  36. package/__fixtures__/6.default-values/input.graphql +47 -0
  37. package/__fixtures__/6.default-values/output.json +655 -0
  38. package/__fixtures__/7.type-args/input.graphql +19 -0
  39. package/__fixtures__/7.type-args/output.json +301 -0
  40. package/__fixtures__/8.default-sort/input.graphql +60 -0
  41. package/__fixtures__/8.default-sort/output.json +1078 -0
  42. package/__tests__/gqlSchemaToReferences.test.ts +109 -0
  43. package/__tests__/utils.ts +45 -0
  44. package/declarations.d.ts +4 -0
  45. package/dist/index.d.ts +21 -0
  46. package/dist/index.js +1503 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/opendocs.graphql +56 -0
  49. package/index.ts +3 -0
  50. package/package.json +29 -0
  51. package/src/context.ts +17 -0
  52. package/src/converters/gql-arg.ts +51 -0
  53. package/src/converters/gql-enum.ts +27 -0
  54. package/src/converters/gql-field.ts +164 -0
  55. package/src/converters/gql-input.ts +34 -0
  56. package/src/converters/gql-interface.ts +35 -0
  57. package/src/converters/gql-mutation.ts +36 -0
  58. package/src/converters/gql-object.ts +83 -0
  59. package/src/converters/gql-operation.ts +128 -0
  60. package/src/converters/gql-query.ts +36 -0
  61. package/src/converters/gql-sample.ts +159 -0
  62. package/src/converters/gql-scalar.ts +16 -0
  63. package/src/converters/gql-subscription.ts +36 -0
  64. package/src/converters/gql-types.ts +195 -0
  65. package/src/converters/gql-union.ts +40 -0
  66. package/src/gql-core.ts +362 -0
  67. package/src/index.ts +3 -0
  68. package/src/opendocs.graphql +56 -0
  69. package/src/opendocs.ts +253 -0
  70. package/src/schema.ts +293 -0
  71. package/src/types.ts +103 -0
  72. package/src/utils.ts +25 -0
  73. package/tsconfig.json +18 -0
  74. package/tsup.config.ts +33 -0
  75. package/tsup.examples-config.ts +30 -0
  76. package/vitest.config.ts +21 -0
@@ -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
+ }
package/src/schema.ts ADDED
@@ -0,0 +1,293 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import {fileURLToPath} from "node:url";
4
+
5
+ import {
6
+ buildSchema,
7
+ DirectiveNode,
8
+ print,
9
+ visit,
10
+ parse,
11
+ ObjectTypeExtensionNode,
12
+ GraphQLSchema,
13
+ StringValueNode
14
+ } from "graphql";
15
+ import {mergeTypeDefs} from '@graphql-tools/merge';
16
+
17
+ import type {Reference} from "@xyd-js/uniform"
18
+ import {ReferenceType} from "@xyd-js/uniform";
19
+
20
+ import type {
21
+ GQLSchemaToReferencesOptions,
22
+ OpenDocsSortConfig,
23
+ FieldMetadata,
24
+ GQLSchemaMetadata,
25
+ SortItem
26
+ } from "./types";
27
+ import {DEFAULT_SORT_ORDER} from "./types";
28
+ import {graphqlTypesToUniformReferences} from "./converters/gql-types";
29
+ import {graphqlQueriesToUniformReferences} from "./converters/gql-query";
30
+ import {graphqlMutationsToUniformReferences} from "./converters/gql-mutation";
31
+ import {graphqlSubscriptionsToUniformReferences} from "./converters/gql-subscription";
32
+ import {OPEN_DOCS_DIRECTIVE_NAME, OPEN_DOCS_SCHEMA_DIRECTIVE_NAME, openDocsExtensionsToOptions} from "./opendocs";
33
+ import openDocsSchemaRaw from './opendocs.graphql'
34
+
35
+ // TODO: support multi graphql files
36
+ // TODO: sort by tag??
37
+ export async function gqlSchemaToReferences(
38
+ schemaLocation: string | string[],
39
+ options?: GQLSchemaToReferencesOptions
40
+ ): Promise<Reference[]> {
41
+ // 1. Convert schemaLocation to array
42
+ const schemaLocations = Array.isArray(schemaLocation) ? schemaLocation : [schemaLocation];
43
+
44
+ // Add opendocs.graphql to schema locations (first)
45
+ schemaLocations.unshift(openDocsSchemaRaw);
46
+
47
+ // 2. Read all schema contents
48
+ const schemaContents = await Promise.all(schemaLocations.map(async location => {
49
+ if (location.startsWith('http://') || location.startsWith('https://')) {
50
+ const response = await fetch(location);
51
+ if (!response.ok) {
52
+ throw new Error(`Failed to fetch schema from URL: ${location}`);
53
+ }
54
+ return response.text();
55
+ }
56
+ if (fs.existsSync(location)) {
57
+ return fs.readFileSync(location, 'utf-8');
58
+ }
59
+ return location;
60
+ }));
61
+
62
+ // 3. Merge all schema contents
63
+ const mergedTypeDefs = mergeTypeDefs(schemaContents);
64
+ const schemaString = print(mergedTypeDefs);
65
+
66
+ // 4. Build the schema
67
+ const schema = buildSchema(schemaString, {
68
+ assumeValid: true
69
+ });
70
+ if (schemaContents.length > 2) {
71
+ console.warn(`Warning: More than 2 schema files provided - no all featues will be supported!`);
72
+ }
73
+
74
+ docDirectiveChain(schemaContents[1], schema);
75
+ // TODO: fix schemaContents[1]
76
+
77
+ if (!options) {
78
+ options = {}
79
+ }
80
+
81
+ if (!options.hasOwnProperty('flat')) {
82
+ options.flat = true; // Default flat is true
83
+ }
84
+ options = {
85
+ ...options,
86
+ ...openDocsExtensionsToOptions(schema)
87
+ }
88
+
89
+ // 5. Generate uniform references from the schema
90
+ const references = [
91
+ // types
92
+ ...graphqlTypesToUniformReferences(schema, options),
93
+
94
+ // queries
95
+ ...graphqlQueriesToUniformReferences(schema, options),
96
+
97
+ // mutations
98
+ ...graphqlMutationsToUniformReferences(schema, options),
99
+
100
+ // subscriptions
101
+ ...graphqlSubscriptionsToUniformReferences(schema, options),
102
+ ]
103
+
104
+ // Sort references using provided sort config or defaults
105
+ const sortConfig = options.sort ?? {sort: DEFAULT_SORT_ORDER};
106
+ references.sort((a, b) => {
107
+ const aOrder = getSortOrder(a, sortConfig);
108
+ const bOrder = getSortOrder(b, sortConfig);
109
+ return aOrder - bOrder;
110
+ });
111
+
112
+ if (options.route) {
113
+ // TODO: types or better solution!!!
114
+ // @ts-ignore
115
+ references.__UNSAFE_route = () => options.route
116
+ }
117
+ return references
118
+ }
119
+
120
+ function getSortOrder(reference: Reference, sortConfig: OpenDocsSortConfig): number {
121
+ const sortItems = sortConfig.sort ?? DEFAULT_SORT_ORDER;
122
+ const sortStacks = sortConfig.sortStack ?? [];
123
+ const referenceGroups = getReferenceGroups(reference);
124
+
125
+ // First, find which primary group this reference belongs to
126
+ for (let groupIndex = 0; groupIndex < sortItems.length; groupIndex++) {
127
+ const sortItem = sortItems[groupIndex];
128
+
129
+ // Check if this reference matches the primary group
130
+ if (matchesPrimaryGroup(reference, sortItem)) {
131
+ // Determine which stack to use (default to 0 if not specified)
132
+ const stackIndex = sortItem.stack !== undefined ? sortItem.stack : 0;
133
+
134
+ // Calculate position within this group using the stack
135
+ const positionInGroup = calculatePositionInGroup(reference, stackIndex, sortStacks);
136
+
137
+ const result = (groupIndex * 1000) + positionInGroup;
138
+
139
+ // Return order: groupIndex * 1000 + positionInGroup
140
+ // This ensures all items in group 0 come before all items in group 1, etc.
141
+ return result;
142
+ }
143
+ }
144
+
145
+ return Number.MAX_SAFE_INTEGER;
146
+ }
147
+
148
+ function matchesPrimaryGroup(reference: Reference, sortItem: SortItem): boolean {
149
+ // Check node match first
150
+ if (sortItem.node) {
151
+ const context = reference.context as any;
152
+ if (context?.graphqlTypeShort === sortItem.node) {
153
+ return true;
154
+ }
155
+ // If node is specified but doesn't match, return false (don't fall through)
156
+ return false;
157
+ }
158
+
159
+ // Check group match
160
+ if (sortItem.group && sortItem.group.length > 0) {
161
+ const referenceGroups = getReferenceGroups(reference);
162
+ const match = sortItem.group.some(group => referenceGroups.includes(group));
163
+ return match;
164
+ }
165
+
166
+ return true;
167
+ }
168
+
169
+ function calculatePositionInGroup(reference: Reference, stackIndex: number, sortStacks: string[][]): number {
170
+ if (stackIndex < 0 || stackIndex >= sortStacks.length) {
171
+ return 0;
172
+ }
173
+
174
+ const stackGroups = sortStacks[stackIndex];
175
+ const referenceGroups = getReferenceGroups(reference);
176
+
177
+ // Find the position of the reference's type in the stack
178
+ for (let i = 0; i < stackGroups.length; i++) {
179
+ const stackGroup = stackGroups[i];
180
+ if (referenceGroups.includes(stackGroup)) {
181
+ return i;
182
+ }
183
+ }
184
+
185
+ return 999; // If not found in stack, put at the end of the group
186
+ }
187
+
188
+
189
+ function getReferenceGroups(reference: Reference): string[] {
190
+ // Extract groups from reference context
191
+ const context = reference.context as any;
192
+ if (context?.group && Array.isArray(context.group)) {
193
+ return context.group;
194
+ }
195
+
196
+ // Fallback: try to get groups from metadata if available
197
+ if (reference.__UNSAFE_selector) {
198
+ try {
199
+ const selector = reference.__UNSAFE_selector;
200
+ const metadata = selector("[metadata]");
201
+ if (metadata?.groups) {
202
+ return metadata.groups;
203
+ }
204
+ } catch (e) {
205
+ // Ignore errors from selector
206
+ }
207
+ }
208
+
209
+ return [];
210
+ }
211
+
212
+ function docDirectiveChain(
213
+ rawSDL: string,
214
+ schema: GraphQLSchema
215
+ ) {
216
+ const ast = parse(rawSDL);
217
+ const metadata: GQLSchemaMetadata = {
218
+ fields: new Map<string, FieldMetadata>()
219
+ };
220
+
221
+ // First, find root groups from @docs directive
222
+ visit(ast, {
223
+ SchemaExtension(node) {
224
+ for (const directive of node.directives || []) {
225
+ if (directive.name.value === OPEN_DOCS_SCHEMA_DIRECTIVE_NAME) {
226
+ const groupArg = directive.arguments?.find(arg => arg.name.value === 'group');
227
+ if (groupArg?.value.kind === 'ListValue') {
228
+ metadata.rootGroups = groupArg.value.values
229
+ .filter((v): v is StringValueNode => v.kind === 'StringValue')
230
+ .map(v => v.value);
231
+ }
232
+ }
233
+ }
234
+ }
235
+ });
236
+
237
+ // Then process type extensions and fields
238
+ visit(ast, {
239
+ ObjectTypeExtension(node: ObjectTypeExtensionNode) {
240
+ const validNodeTypes = ['Query', 'Mutation', 'Subscription'];
241
+ if (!validNodeTypes.includes(node.name.value)) return;
242
+
243
+ const typeLevelDoc = node.directives?.find(d => d.name.value === OPEN_DOCS_DIRECTIVE_NAME);
244
+ const typeLevelDocArgs = typeLevelDoc ? extractDocArgs(typeLevelDoc) : {};
245
+
246
+ for (const field of node.fields ?? []) {
247
+ const fieldName = field.name.value;
248
+
249
+ const fieldDoc = field.directives?.find(d => d.name.value === OPEN_DOCS_DIRECTIVE_NAME);
250
+ const fieldDocArgs = fieldDoc ? extractDocArgs(fieldDoc) : {};
251
+
252
+ // Merge paths: if both type and field have paths, join them
253
+ let path = fieldDocArgs.path;
254
+ if (typeLevelDocArgs.path && fieldDocArgs.path) {
255
+ path = `${typeLevelDocArgs.path}/${fieldDocArgs.path}`;
256
+ } else if (typeLevelDocArgs.path) {
257
+ path = typeLevelDocArgs.path;
258
+ }
259
+
260
+ if (!fieldDocArgs.path && path) {
261
+ path += "/" + fieldName
262
+ }
263
+
264
+ // Merge groups: combine root groups with type/field groups
265
+ const groups = fieldDocArgs.groups ?? typeLevelDocArgs.groups ?? [];
266
+
267
+ const effectiveDoc: FieldMetadata = {
268
+ groups,
269
+ path
270
+ };
271
+
272
+ metadata.fields.set(`${node.name.value}.${fieldName}`, effectiveDoc);
273
+ }
274
+ }
275
+ });
276
+
277
+ // Attach metadata to schema
278
+ (schema as any).__metadata = metadata;
279
+ }
280
+
281
+ function extractDocArgs(directive: DirectiveNode): FieldMetadata {
282
+ const info: FieldMetadata = {};
283
+ for (const arg of directive.arguments ?? []) {
284
+ if (arg.name.value === 'group' && arg.value.kind === 'ListValue') {
285
+ info.groups = arg.value.values
286
+ .filter((v): v is StringValueNode => v.kind === 'StringValue')
287
+ .map(v => v.value);
288
+ } else if (arg.name.value === 'path' && arg.value.kind === 'StringValue') {
289
+ info.path = arg.value.value;
290
+ }
291
+ }
292
+ return info;
293
+ }
package/src/types.ts ADDED
@@ -0,0 +1,103 @@
1
+ import type {GraphQLInputObjectType, GraphQLInterfaceType, GraphQLNamedType, GraphQLObjectType} from "graphql";
2
+ import {GraphQLField, OperationTypeNode} from "graphql";
3
+
4
+ import type {DefinitionProperty} from "@xyd-js/uniform";
5
+
6
+ // New sorting types based on the documentation
7
+ export interface SortItem {
8
+ node?: string;
9
+ group?: string[];
10
+ stack?: number;
11
+ }
12
+
13
+ export interface SortStack {
14
+ sortStack?: string[][];
15
+ sort?: SortItem[];
16
+ }
17
+
18
+ export interface OpenDocsSortConfig {
19
+ sortStack?: string[][];
20
+ sort?: SortItem[];
21
+ }
22
+
23
+ export const DEFAULT_SORT_ORDER: SortItem[] = [
24
+ { node: "query" },
25
+ { node: "mutation" },
26
+ { node: "subscription" },
27
+ { node: "object" },
28
+ { node: "interface" },
29
+ { node: "union" },
30
+ { node: "input" },
31
+ { node: "enum" },
32
+ { node: "scalar" },
33
+ ];
34
+
35
+ export interface GQLSchemaToReferencesOptions {
36
+ // TODO: support line ranged in the future?
37
+ regions?: string[] // TODO: BETTER API - UNIFY FOR REST API / GRAPHQL ETC
38
+
39
+ flat?: boolean;
40
+ sort?: OpenDocsSortConfig;
41
+ route?: string
42
+ }
43
+
44
+ export type NestedGraphqlType = {
45
+ __definitionProperties?: DefinitionProperty[];
46
+ } & (GraphQLObjectType | GraphQLInputObjectType | GraphQLInterfaceType)
47
+
48
+ // needed cuz GraphQLField does not have operation info?
49
+ export class GQLOperation implements GraphQLField<any, any> {
50
+ public _operationType!: OperationTypeNode
51
+ field: GraphQLField<any, any>;
52
+
53
+ constructor(field: GraphQLField<any, any>) {
54
+ this.field = field;
55
+ }
56
+
57
+ get name() {
58
+ return this.field.name;
59
+ }
60
+
61
+ get description() {
62
+ return this.field.description;
63
+ }
64
+
65
+ get type() {
66
+ return this.field.type;
67
+ }
68
+
69
+ get args() {
70
+ return this.field.args;
71
+ }
72
+
73
+ get deprecationReason() {
74
+ return this.field.deprecationReason;
75
+ }
76
+
77
+ get extensions() {
78
+ return this.field.extensions;
79
+ }
80
+
81
+ get astNode() {
82
+ return this.field.astNode;
83
+ }
84
+
85
+ set __operationType(operationType: OperationTypeNode) {
86
+ this._operationType = operationType;
87
+ }
88
+ }
89
+
90
+ export interface GQLTypeInfo {
91
+ typeFlat?: GraphQLNamedType
92
+ }
93
+
94
+ export interface FieldMetadata {
95
+ path?: string;
96
+ groups?: string[];
97
+ }
98
+
99
+ export interface GQLSchemaMetadata {
100
+ fields: Map<string, FieldMetadata>;
101
+ rootGroups?: string[];
102
+ }
103
+
package/src/utils.ts ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Helper function to filter fields based on region patterns
3
+ * @param fields - The fields to filter
4
+ * @param prefix - The prefix for the region key (e.g., "Query" or "Mutation")
5
+ * @param regions - The regions to filter by
6
+ * @returns Filtered fields object
7
+ */
8
+ export function filterFieldsByRegions<T>(
9
+ fields: Record<string, T>,
10
+ prefix: string,
11
+ regions?: string[]
12
+ ): Record<string, T> {
13
+ if (!regions || regions.length === 0) {
14
+ return fields;
15
+ }
16
+
17
+ const filteredFields: Record<string, T> = {};
18
+ for (const [fieldName, field] of Object.entries(fields)) {
19
+ const regionKey = `${prefix}.${fieldName}`;
20
+ if (regions.some(region => region === regionKey)) {
21
+ filteredFields[fieldName] = field;
22
+ }
23
+ }
24
+ return filteredFields;
25
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "moduleResolution": "node",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "outDir": "./dist",
11
+ "declaration": true,
12
+ "declarationMap": true,
13
+ "incremental": true,
14
+ "tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo"
15
+ },
16
+ "include": ["examples/basic-example/**/*.ts", "src/**/*.ts", "declarations.d.ts"],
17
+ "exclude": ["node_modules", "dist"]
18
+ }