@lewebsimple/nuxt-graphql 0.4.0 → 0.5.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.
Files changed (74) hide show
  1. package/README.md +134 -188
  2. package/dist/module.d.mts +74 -22
  3. package/dist/module.json +2 -2
  4. package/dist/module.mjs +408 -369
  5. package/dist/runtime/app/composables/useAsyncGraphQLQuery.d.ts +19 -0
  6. package/dist/runtime/app/composables/useAsyncGraphQLQuery.js +82 -0
  7. package/dist/runtime/app/composables/useGraphQLCache.client.d.ts +10 -7
  8. package/dist/runtime/app/composables/useGraphQLCache.client.js +16 -31
  9. package/dist/runtime/app/composables/useGraphQLMutation.d.ts +10 -18
  10. package/dist/runtime/app/composables/useGraphQLMutation.js +7 -11
  11. package/dist/runtime/app/composables/useGraphQLQuery.d.ts +11 -9
  12. package/dist/runtime/app/composables/useGraphQLQuery.js +20 -78
  13. package/dist/runtime/app/composables/useGraphQLSubscription.client.d.ts +19 -0
  14. package/dist/runtime/app/composables/{useGraphQLSubscription.js → useGraphQLSubscription.client.js} +9 -14
  15. package/dist/runtime/app/lib/cache.d.ts +18 -0
  16. package/dist/runtime/app/lib/cache.js +9 -0
  17. package/dist/runtime/app/lib/execute-http.d.ts +14 -0
  18. package/dist/runtime/app/lib/execute-http.js +8 -0
  19. package/dist/runtime/app/lib/in-flight.d.ts +14 -0
  20. package/dist/runtime/app/lib/{graphql-cache.js → in-flight.js} +2 -7
  21. package/dist/runtime/app/lib/persisted.d.ts +26 -0
  22. package/dist/runtime/app/plugins/graphql-request.d.ts +7 -2
  23. package/dist/runtime/app/plugins/graphql-request.js +13 -14
  24. package/dist/runtime/app/plugins/graphql-sse.client.d.ts +7 -2
  25. package/dist/runtime/server/api/graphql.d.ts +8 -0
  26. package/dist/runtime/server/api/graphql.js +16 -0
  27. package/dist/runtime/server/lib/context.d.ts +10 -0
  28. package/dist/runtime/server/lib/context.js +3 -0
  29. package/dist/runtime/server/lib/default-context.d.ts +5 -0
  30. package/dist/runtime/server/lib/default-context.js +2 -0
  31. package/dist/runtime/server/lib/default-schema.d.ts +7 -0
  32. package/dist/runtime/server/lib/default-schema.js +18 -0
  33. package/dist/runtime/server/lib/execute-schema.d.ts +11 -0
  34. package/dist/runtime/server/lib/execute-schema.js +23 -0
  35. package/dist/runtime/server/lib/remote-executor.d.ts +29 -34
  36. package/dist/runtime/server/lib/remote-executor.js +27 -59
  37. package/dist/runtime/server/lib/schemas.d.ts +14 -0
  38. package/dist/runtime/server/lib/schemas.js +3 -0
  39. package/dist/runtime/server/lib/yoga.d.ts +6 -0
  40. package/dist/runtime/server/lib/yoga.js +20 -0
  41. package/dist/runtime/server/utils/useServerGraphQLMutation.d.ts +16 -8
  42. package/dist/runtime/server/utils/useServerGraphQLMutation.js +12 -11
  43. package/dist/runtime/server/utils/useServerGraphQLQuery.d.ts +16 -3
  44. package/dist/runtime/server/utils/useServerGraphQLQuery.js +16 -4
  45. package/dist/runtime/shared/lib/cache-config.d.ts +42 -0
  46. package/dist/runtime/{app → shared}/lib/cache-config.js +3 -3
  47. package/dist/runtime/shared/lib/error.d.ts +56 -0
  48. package/dist/runtime/shared/lib/error.js +61 -0
  49. package/dist/runtime/shared/lib/headers.d.ts +13 -3
  50. package/dist/runtime/shared/lib/headers.js +10 -35
  51. package/dist/runtime/shared/lib/registry.d.ts +12 -0
  52. package/dist/runtime/shared/lib/registry.js +8 -0
  53. package/dist/runtime/shared/lib/utils.d.ts +1 -0
  54. package/dist/runtime/shared/lib/utils.js +0 -0
  55. package/dist/runtime/{app → shared}/types/nuxt-graphql.d.ts +1 -5
  56. package/dist/types.d.mts +4 -0
  57. package/package.json +19 -33
  58. package/dist/helpers.d.mts +0 -3
  59. package/dist/helpers.mjs +0 -3
  60. package/dist/runtime/app/composables/useGraphQLSubscription.d.ts +0 -17
  61. package/dist/runtime/app/lib/cache-config.d.ts +0 -8
  62. package/dist/runtime/app/lib/graphql-cache.d.ts +0 -7
  63. package/dist/runtime/server/api/yoga-handler.d.ts +0 -2
  64. package/dist/runtime/server/api/yoga-handler.js +0 -42
  65. package/dist/runtime/server/lib/define-graphql-context.d.ts +0 -5
  66. package/dist/runtime/server/lib/define-graphql-context.js +0 -4
  67. package/dist/runtime/server/lib/define-remote-exec-middleware.d.ts +0 -30
  68. package/dist/runtime/server/lib/define-remote-exec-middleware.js +0 -3
  69. package/dist/runtime/server/lib/define-yoga-middleware.d.ts +0 -21
  70. package/dist/runtime/server/lib/define-yoga-middleware.js +0 -3
  71. package/dist/runtime/server/lib/execute-server-graphql.d.ts +0 -7
  72. package/dist/runtime/server/lib/execute-server-graphql.js +0 -34
  73. package/dist/runtime/shared/lib/graphql-error.d.ts +0 -17
  74. package/dist/runtime/shared/lib/graphql-error.js +0 -28
package/dist/module.mjs CHANGED
@@ -1,260 +1,276 @@
1
- import { join, relative, resolve, dirname } from 'node:path';
2
- import { useLogger, defineNuxtModule, createResolver, getLayerDirectories, addServerHandler, addServerImportsDir, addPlugin, addImportsDir } from '@nuxt/kit';
3
- import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
4
- import { glob } from 'tinyglobby';
5
- import { generate } from '@graphql-codegen/cli';
6
- import { parse, Kind } from 'graphql';
7
- import { resolveCacheConfig } from '../dist/runtime/app/lib/cache-config.js';
1
+ import { mkdirSync, writeFileSync } from 'node:fs';
2
+ import { resolve, dirname, relative } from 'node:path';
3
+ import { defineNuxtModule, useLogger, createResolver, addTemplate, addServerHandler, addPlugin, addImportsDir, addServerImportsDir } from '@nuxt/kit';
4
+ import { hash } from 'ohash';
5
+ import { loadDocuments as loadDocuments$1 } from '@graphql-tools/load';
6
+ import { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader';
7
+ import { Kind, getIntrospectionQuery, buildClientSchema, GraphQLSchema } from 'graphql';
8
+ import { codegen } from '@graphql-codegen/core';
9
+ import * as typescriptPlugin from '@graphql-codegen/typescript';
10
+ import * as typescriptOperationsPlugin from '@graphql-codegen/typescript-operations';
11
+ import * as typedDocumentNodePlugin from '@graphql-codegen/typed-document-node';
12
+ import { mergeHeaders } from '../dist/runtime/shared/lib/headers.js';
13
+ import { stitchSchemas } from '@graphql-tools/stitch';
14
+ import { resolveCacheConfig } from '../dist/runtime/shared/lib/cache-config.js';
15
+ export { defineGraphQLContext } from '../dist/runtime/server/lib/context.js';
16
+ export { defineRemoteExecutorHooks } from '../dist/runtime/server/lib/remote-executor.js';
8
17
 
9
- function removeFileExtension(filePath) {
10
- return filePath.replace(/\.[^/.]+$/, "");
11
- }
12
- function getGenericServerProxy(modulePath) {
13
- return `export * from ${JSON.stringify(removeFileExtension(modulePath))};`;
14
- }
18
+ const cyan = "\x1B[36m";
19
+ const reset = "\x1B[0m";
15
20
 
16
- function getGraphQLContextProxy(contextPath) {
17
- if (!contextPath) {
18
- return [
19
- `export const getGraphQLContext = async () => ({});`,
20
- `export type GraphQLContext = Awaited<ReturnType<typeof getGraphQLContext>>;`
21
- ].join("\n");
22
- }
21
+ function renderContextTemplate({ contextModules }) {
22
+ const imports = contextModules.map((module, index) => `import context${index} from ${JSON.stringify(module)};`);
23
+ const types = contextModules.map((_, index) => `Awaited<ReturnType<typeof context${index}>>`);
23
24
  return [
24
- `import context from ${JSON.stringify(removeFileExtension(contextPath))};`,
25
- ``,
26
- `export const getGraphQLContext = context.getGraphQLContext;`,
27
- `export type GraphQLContext = Awaited<ReturnType<typeof getGraphQLContext>>;`
25
+ `import type { H3Event } from "h3";`,
26
+ ...imports,
27
+ "",
28
+ `export type GraphQLContext = ${types.join("\n & ")};`,
29
+ "",
30
+ "export async function createContext(event: H3Event): Promise<GraphQLContext> {",
31
+ " const parts = await Promise.all([",
32
+ ...contextModules.map((_, index) => ` context${index}(event),`),
33
+ " ]);",
34
+ " return Object.assign({}, ...parts) as GraphQLContext;",
35
+ "}"
28
36
  ].join("\n");
29
37
  }
30
38
 
31
- async function findSingleFile(dirs, pattern, isRequired = false) {
32
- for (const dir of dirs) {
33
- const fullPattern = join(dir, pattern);
34
- const files = await glob(fullPattern, { absolute: true });
35
- if (files.length > 0) {
36
- return files[0];
37
- }
38
- }
39
- if (isRequired) {
40
- throw new Error(`File not found: ${pattern} in directories:
41
- ${dirs.join("\n")}`);
39
+ async function loadDocuments(documents) {
40
+ try {
41
+ return await loadDocuments$1([
42
+ documents,
43
+ "!**/.cache/**",
44
+ "!**/.nuxt/**",
45
+ "!**/.output/**",
46
+ "!**/dist/**",
47
+ "!**/node_modules/**"
48
+ ], { loaders: [new GraphQLFileLoader()] });
49
+ } catch {
50
+ return [];
42
51
  }
43
52
  }
44
- async function findMultipleFiles(dirs, pattern) {
45
- const files = [];
46
- for (const dir of dirs) {
47
- const foundFiles = await glob(pattern, { cwd: dir, absolute: true });
48
- files.push(...foundFiles);
49
- }
50
- return Array.from(new Set(files));
51
- }
52
- function writeFileIfChanged(path, content) {
53
- if (existsSync(path) && readFileSync(path, "utf-8") === content) {
54
- return false;
53
+
54
+ async function renderFragmentsTemplate({ documents }) {
55
+ const fragments = collectFragments(documents);
56
+ if (fragments.length === 0) {
57
+ return `export { }`;
55
58
  }
56
- mkdirSync(dirname(path), { recursive: true });
57
- writeFileSync(path, content, "utf-8");
58
- return true;
59
+ return [
60
+ `export type {`,
61
+ ...fragments.map((name) => ` ${name},`),
62
+ `} from "./operations";`
63
+ ].join("\n");
59
64
  }
60
- function toRelativePath(from, to) {
61
- let relativePath = relative(resolve(from), resolve(to));
62
- relativePath = relativePath.replace(/\\/g, "/");
63
- if (!relativePath.startsWith("./") && !relativePath.startsWith("../")) {
64
- relativePath = `./${relativePath}`;
65
+ function collectFragments(documents) {
66
+ const fragments = /* @__PURE__ */ new Set();
67
+ for (const source of documents) {
68
+ const doc = source.document;
69
+ if (!doc) continue;
70
+ for (const def of doc.definitions) {
71
+ if (def.kind !== Kind.FRAGMENT_DEFINITION) continue;
72
+ const name = def.name?.value;
73
+ if (!name) continue;
74
+ if (fragments.has(name)) {
75
+ throw new Error(`Duplicate GraphQL fragment name "${name}"`);
76
+ }
77
+ fragments.add(name);
78
+ }
65
79
  }
66
- return relativePath;
80
+ return [...fragments.values()];
67
81
  }
68
82
 
69
- async function runGraphQLCodegen({ schema, documents, typedDocumentsPath }) {
70
- const config = {
83
+ async function renderOperationsTemplate({ schema, documents }) {
84
+ const output = await codegen({
85
+ filename: "operations.ts",
86
+ // @graphql-codegen/core codegen supports GraphQLSchema at runtime, but types expect DocumentNode
71
87
  schema,
72
88
  documents,
73
- generates: {
74
- // Typed document nodes
75
- [typedDocumentsPath]: {
76
- plugins: ["typescript", "typescript-operations", "typed-document-node"],
77
- config: {
78
- defaultScalarType: "never",
79
- documentMode: "documentNode",
80
- documentVariableSuffix: "Document",
81
- enumsAsTypes: true,
82
- inlineFragmentTypes: "combine",
83
- preResolveTypes: false,
84
- skipTypename: true,
85
- strictScalars: true,
86
- useTypeImports: true
87
- }
88
- }
89
+ plugins: [
90
+ { typescript: {} },
91
+ { typescriptOperations: {} },
92
+ { typedDocumentNode: {} }
93
+ ],
94
+ pluginMap: {
95
+ typescript: typescriptPlugin,
96
+ typescriptOperations: typescriptOperationsPlugin,
97
+ typedDocumentNode: typedDocumentNodePlugin
89
98
  },
90
- ignoreNoDocuments: true,
91
- silent: true
92
- };
93
- const result = await generate(config, true);
94
- return result.map(({ filename }) => filename);
95
- }
96
-
97
- const logger = useLogger("graphql");
98
- const cyan = "\x1B[36m";
99
- const reset = "\x1B[0m";
100
-
101
- async function getRegistryContent({ layerRootDirs, rootDir, documents }) {
102
- const docs = (await findMultipleFiles(layerRootDirs, documents)).map((path) => ({
103
- path: toRelativePath(rootDir, path),
104
- content: readFileSync(path, "utf-8")
105
- }));
106
- const parsed = {
107
- fragments: /* @__PURE__ */ new Map(),
108
- operations: {
109
- query: /* @__PURE__ */ new Map(),
110
- mutation: /* @__PURE__ */ new Map(),
111
- subscription: /* @__PURE__ */ new Map()
112
- }
113
- };
114
- for (const doc of docs) {
115
- const ast = parse(doc.content);
116
- for (const def of ast.definitions) {
117
- switch (def.kind) {
118
- // Process fragment definitions
119
- case Kind.FRAGMENT_DEFINITION: {
120
- const name = def.name.value;
121
- const existing = parsed.fragments.get(name);
122
- if (existing) {
123
- throw new Error(`Duplicate fragment name ${name} in document ${doc.path} (previously defined in ${existing.path})`);
124
- }
125
- parsed.fragments.set(name, { kind: "fragment", name, path: doc.path });
126
- break;
127
- }
128
- // Process operation definitions
129
- case Kind.OPERATION_DEFINITION: {
130
- const type = def.operation;
131
- if (!["query", "mutation", "subscription"].includes(type)) continue;
132
- const name = def.name?.value;
133
- if (!name) throw new Error(`Unnamed ${type} operation in document ${doc.path}`);
134
- const existing = parsed.operations[type].get(name);
135
- if (existing) {
136
- throw new Error(`Duplicate operation name ${name} in document ${doc.path} (previously defined in ${existing.path})`);
137
- }
138
- parsed.operations[type].set(name, { kind: "operation", type, name, path: doc.path });
139
- break;
140
- }
141
- }
99
+ config: {
100
+ defaultScalarType: "never",
101
+ documentMode: "documentNode",
102
+ documentVariableSuffix: "Document",
103
+ enumsAsTypes: true,
104
+ inlineFragmentTypes: "combine",
105
+ omitOperationSuffix: true,
106
+ operationResultSuffix: "Result",
107
+ operationVariablesSuffix: "Variables",
108
+ preResolveTypes: false,
109
+ skipTypename: true,
110
+ strictScalars: true,
111
+ useTypeImports: true
142
112
  }
143
- }
144
- const content = [
145
- `import * as ops from "#graphql/typed-documents";`,
146
- ``,
147
- `type ResultOf<T> = T extends { __apiType?: (variables: infer _) => infer R } ? R : never;`,
148
- `type VariablesOf<T> = T extends { __apiType?: (variables: infer V) => infer _ } ? V : never;`,
149
- ``
150
- ];
151
- const queries = Array.from(parsed.operations.query.values());
152
- content.push(
153
- `// Queries`,
154
- `export const queries = {`,
155
- ...queries.map(({ name }) => ` ${name}: ops.${name}Document,`),
156
- `};`,
157
- `export type QueryName = keyof typeof queries;`,
158
- `export type QueryResult<N extends QueryName> = ResultOf<(typeof queries)[N]>;`,
159
- `export type QueryVariables<N extends QueryName> = VariablesOf<(typeof queries)[N]>;`,
160
- ``
161
- );
162
- const mutations = Array.from(parsed.operations.mutation.values());
163
- content.push(
164
- `// Mutations`,
165
- `export const mutations = {`,
166
- ...mutations.map(({ name }) => ` ${name}: ops.${name}Document,`),
167
- `};`,
168
- `export type MutationName = keyof typeof mutations;`,
169
- `export type MutationResult<N extends MutationName> = ResultOf<(typeof mutations)[N]>;`,
170
- `export type MutationVariables<N extends MutationName> = VariablesOf<(typeof mutations)[N]>;`,
171
- ``
172
- );
173
- const subscriptions = Array.from(parsed.operations.subscription.values());
174
- content.push(
175
- `// Subscriptions`,
176
- `export const subscriptions = {`,
177
- ...subscriptions.map(({ name }) => ` ${name}: ops.${name}Document,`),
178
- `};`,
179
- `export type SubscriptionName = keyof typeof subscriptions;`,
180
- `export type SubscriptionResult<N extends SubscriptionName> = ResultOf<(typeof subscriptions)[N]>;`,
181
- `export type SubscriptionVariables<N extends SubscriptionName> = VariablesOf<(typeof subscriptions)[N]>;`,
182
- ``
183
- );
184
- return content.join("\n");
113
+ });
114
+ return output;
185
115
  }
186
116
 
187
- function getRemoteExecMiddlewareProxy(middlewarePath) {
188
- if (!middlewarePath) {
189
- return [
190
- `export const remoteExecMiddlewareHandler = undefined;`
191
- ].join("\n");
192
- }
117
+ async function renderRegistryTemplate({ documents }) {
118
+ const operations = collectOperations(documents);
193
119
  return [
194
- `import middleware from ${JSON.stringify(removeFileExtension(middlewarePath))};`,
120
+ `import type { DocumentNode } from "graphql";`,
121
+ `import {`,
122
+ ...operations.map(
123
+ ({ name }) => ` ${name}Document, type ${name}Variables, type ${name}Result,`
124
+ ),
125
+ `} from "./operations";`,
126
+ ``,
127
+ // Operation entry
128
+ `export interface OperationEntry<`,
129
+ ` TVariables,`,
130
+ ` TResult,`,
131
+ ` TKind extends "query" | "mutation" | "subscription"`,
132
+ `> {`,
133
+ ` kind: TKind;`,
134
+ ` variables: TVariables;`,
135
+ ` result: TResult;`,
136
+ ` document: DocumentNode;`,
137
+ `}`,
138
+ ``,
139
+ `export type OperationRegistry = {`,
140
+ ...operations.map(
141
+ ({ name, kind }) => ` ${name}: OperationEntry<${name}Variables, ${name}Result, "${kind}">;`
142
+ ),
143
+ `};`,
144
+ ``,
145
+ `export type OperationName = keyof OperationRegistry;`,
146
+ ``,
147
+ `export type QueryName = {`,
148
+ ` [K in keyof OperationRegistry]:`,
149
+ ` OperationRegistry[K]["kind"] extends "query" ? K : never`,
150
+ `}[keyof OperationRegistry];`,
151
+ ``,
152
+ `export type MutationName = {`,
153
+ ` [K in keyof OperationRegistry]:`,
154
+ ` OperationRegistry[K]["kind"] extends "mutation" ? K : never`,
155
+ `}[keyof OperationRegistry];`,
156
+ ``,
157
+ `export type SubscriptionName = {`,
158
+ ` [K in keyof OperationRegistry]:`,
159
+ ` OperationRegistry[K]["kind"] extends "subscription" ? K : never`,
160
+ `}[keyof OperationRegistry];`,
195
161
  ``,
196
- `export const remoteExecMiddlewareHandler = middleware.remoteExecMiddlewareHandler;`
162
+ // Projection helpers
163
+ `export type VariablesOf<TName extends keyof OperationRegistry> =`,
164
+ ` OperationRegistry[TName]["variables"];`,
165
+ ``,
166
+ `export type ResultOf<TName extends keyof OperationRegistry> =`,
167
+ ` OperationRegistry[TName]["result"];`,
168
+ ``,
169
+ // Runtime registry (document + kind only)
170
+ `export const registry: {`,
171
+ ` [K in keyof OperationRegistry]: {`,
172
+ ` kind: OperationRegistry[K]["kind"];`,
173
+ ` document: DocumentNode;`,
174
+ ` };`,
175
+ `} = {`,
176
+ ...operations.map(
177
+ ({ name, kind }) => ` ${name}: { kind: "${kind}", document: ${name}Document },`
178
+ ),
179
+ `};`
197
180
  ].join("\n");
198
181
  }
182
+ function collectOperations(documents) {
183
+ const operations = /* @__PURE__ */ new Map();
184
+ for (const source of documents) {
185
+ const doc = source.document;
186
+ if (!doc) continue;
187
+ for (const def of doc.definitions) {
188
+ if (def.kind !== Kind.OPERATION_DEFINITION) continue;
189
+ const name = def.name?.value;
190
+ if (!name) {
191
+ throw new Error("Anonymous GraphQL operations are not allowed");
192
+ }
193
+ if (operations.has(name)) {
194
+ throw new Error(`Duplicate GraphQL operation name "${name}"`);
195
+ }
196
+ operations.set(name, {
197
+ name,
198
+ kind: def.operation
199
+ });
200
+ }
201
+ }
202
+ return [...operations.values()];
203
+ }
199
204
 
200
- function getDummySchemaProxy() {
201
- return [
202
- `import { createSchema } from "graphql-yoga";`,
203
- ``,
204
- `export const schema = createSchema({`,
205
- ` typeDefs: /* GraphQL */ \``,
206
- ` type Query {`,
207
- ` hello: String!`,
208
- ` } `,
209
- ` \`,`,
210
- ` resolvers: {`,
211
- ` Query: {`,
212
- ` hello: () => "Hello world!",`,
213
- ` },`,
214
- ` },`,
215
- `});`
216
- ].join("\n");
205
+ function renderLocalSchemaTemplate({ path }) {
206
+ return `export { schema } from ${JSON.stringify(path)};`;
217
207
  }
218
- async function getLocalSchemaProxy({ layerRootDirs, schemaDef }) {
219
- const schemaPath = await findSingleFile(layerRootDirs, schemaDef.path, true);
220
- const content = [
221
- `export { schema } from ${JSON.stringify(removeFileExtension(schemaPath))};`
222
- ].join("\n");
223
- return content;
208
+ async function loadLocalSchema({ path }) {
209
+ const { createJiti } = await import('jiti');
210
+ const jiti = createJiti(import.meta.url, { interopDefault: true });
211
+ const module = await jiti.import(path);
212
+ if (!module.schema || !(module.schema instanceof Object) || typeof module.schema.getQueryType !== "function") {
213
+ throw new Error(`${path} must export a valid 'schema' of type GraphQLSchema.`);
214
+ }
215
+ return module.schema;
224
216
  }
225
- async function getRemoteSchemaProxy({ rootDir, schemaName, schemaDef }) {
226
- const middlewareContent = getRemoteExecMiddlewareProxy(schemaDef.middleware ? join(rootDir, schemaDef.middleware) : void 0);
227
- const schema = await fetchGraphQLSchema(schemaDef.url, schemaDef.headers);
228
- const sdl = await getSDLFromGraphQLSchema(schema);
229
- const sdlContent = `export const sdl = /* GraphQL */ \`${sdl.replace(/`/g, "\\`")}\`;`;
230
- const schemaContent = [
217
+ async function renderRemoteSchemaTemplate({ remoteExecutorModule, type, hooks, ...schemaDef }) {
218
+ const schema = await introspectRemoteSchema(schemaDef);
219
+ const sdl = await printSchemaSDL(schema);
220
+ const imports = (hooks || []).map((hookPath, index) => `import hooks${index} from ${JSON.stringify(hookPath)};`);
221
+ return [
231
222
  `import { buildSchema } from "graphql";`,
232
223
  `import type { SubschemaConfig } from "@graphql-tools/delegate";`,
233
- `import { remoteExecMiddlewareHandler } from "./${schemaName}-middleware";`,
234
- `import { sdl } from "./${schemaName}-sdl";`,
235
- `import { createRemoteExecutor } from "../remote-executor";`,
236
- ``,
237
- `const headers = ${JSON.stringify(schemaDef.headers ?? {}, null, 2)} as HeadersInit;`,
224
+ `import { createRemoteExecutor } from ${JSON.stringify(remoteExecutorModule)};`,
225
+ ...imports,
238
226
  ``,
239
- `const executor = createRemoteExecutor({`,
240
- ` url: ${JSON.stringify(schemaDef.url)},`,
241
- ` remoteName: ${JSON.stringify(schemaName)},`,
242
- ` headers,`,
243
- ` middleware: remoteExecMiddlewareHandler,`,
244
- `});`,
227
+ `const sdl = /* GraphQL */ \`${sdl.replace(/`/g, "\\`")}\`;`,
245
228
  ``,
246
229
  `export const schema: SubschemaConfig = {`,
247
230
  ` schema: buildSchema(sdl),`,
248
- ` executor,`,
231
+ ` executor: createRemoteExecutor({`,
232
+ ` ...${JSON.stringify(schemaDef)},`,
233
+ ` hooks: [`,
234
+ ...(hooks || []).map((_, index) => ` hooks${index},`),
235
+ ` ]`,
236
+ ` }),`,
249
237
  `};`
250
238
  ].join("\n");
251
- return { middlewareContent, sdlContent, schemaContent };
252
239
  }
253
- async function getStitchedSchemaProxy({ schemaNames }) {
254
- const content = [
255
- `import { stitchSchemas } from "@graphql-tools/stitch"; `,
240
+ async function introspectRemoteSchema({ url, headers }) {
241
+ const response = await fetch(url, {
242
+ method: "POST",
243
+ headers: mergeHeaders({ "Content-Type": "application/json" }, headers),
244
+ body: JSON.stringify({ query: getIntrospectionQuery() })
245
+ });
246
+ const json = await response.json();
247
+ if (json.errors) {
248
+ throw new Error(`Failed to fetch GraphQL schema from ${url}: ${JSON.stringify(json.errors)} `);
249
+ }
250
+ const schema = buildClientSchema(json.data);
251
+ return stripSubscriptions(schema);
252
+ }
253
+ function stripSubscriptions(schema) {
254
+ if (!schema.getSubscriptionType()) {
255
+ return schema;
256
+ }
257
+ return new GraphQLSchema({
258
+ query: schema.getQueryType() ?? void 0,
259
+ mutation: schema.getMutationType() ?? void 0,
260
+ subscription: void 0,
261
+ types: Object.values(schema.getTypeMap()),
262
+ directives: schema.getDirectives()
263
+ });
264
+ }
265
+ async function printSchemaSDL(schema) {
266
+ const { printSchema, lexicographicSortSchema } = await import('graphql');
267
+ return printSchema(lexicographicSortSchema(schema));
268
+ }
269
+ function renderStitchedSchemaTemplate({ schemaNames }) {
270
+ return [
256
271
  `import type { GraphQLSchema } from "graphql"; `,
257
272
  `import type { SubschemaConfig } from "@graphql-tools/delegate"; `,
273
+ `import { stitchSchemas } from "@graphql-tools/stitch"; `,
258
274
  ...schemaNames.map((name) => `import { schema as ${name}Schema } from ${JSON.stringify(`./schemas/${name}`)}; `),
259
275
  ``,
260
276
  `const subschemas: Array<GraphQLSchema | SubschemaConfig> = [`,
@@ -263,183 +279,206 @@ async function getStitchedSchemaProxy({ schemaNames }) {
263
279
  ``,
264
280
  `export const schema = stitchSchemas({ subschemas }); `
265
281
  ].join("\n");
266
- return content;
267
282
  }
268
- async function loadGraphQLSchema(schemaPath) {
269
- const { createJiti } = await import('jiti');
270
- const jiti = createJiti(import.meta.url, { interopDefault: true });
271
- const module = await jiti.import(schemaPath);
272
- if (!module.schema || !(module.schema instanceof Object) || typeof module.schema.getQueryType !== "function") {
273
- throw new Error(`${schemaPath} must export a valid 'schema' of type GraphQLSchema.`);
283
+ async function loadStitchedSchema(schemaDefs) {
284
+ const subschemas = [];
285
+ for (const schemaDef of Object.values(schemaDefs)) {
286
+ if (schemaDef.type === "local") {
287
+ subschemas.push(await loadLocalSchema(schemaDef));
288
+ } else if (schemaDef.type === "remote") {
289
+ subschemas.push(await introspectRemoteSchema(schemaDef));
290
+ }
274
291
  }
275
- return module.schema;
292
+ return stitchSchemas({ subschemas });
276
293
  }
277
- async function fetchGraphQLSchema(schemaUrl, headers) {
278
- const { getIntrospectionQuery, buildClientSchema } = await import('graphql');
279
- const response = await fetch(schemaUrl, {
280
- method: "POST",
281
- headers: { "Content-Type": "application/json", ...headers },
282
- body: JSON.stringify({ query: getIntrospectionQuery() })
283
- });
284
- const json = await response.json();
285
- if (json.errors) {
286
- throw new Error(`Failed to fetch GraphQL schema from ${schemaUrl}: ${JSON.stringify(json.errors)} `);
287
- }
288
- return buildClientSchema(json.data);
294
+
295
+ function renderTypesTemplate() {
296
+ return `// Nuxt GraphQL types
297
+ declare module "#graphql/context" {
298
+ export { createContext } from "#build/graphql/context";
299
+ export type { GraphQLContext } from "#build/graphql/context";
289
300
  }
290
- async function getSDLFromGraphQLSchema(schema) {
291
- const { printSchema, lexicographicSortSchema } = await import('graphql');
292
- return printSchema(lexicographicSortSchema(schema));
301
+
302
+ declare module "#graphql/operations" {
303
+ export * from "#build/graphql/operations";
293
304
  }
294
305
 
295
- function getYogaMiddlewareProxy(middlewarePath) {
296
- if (!middlewarePath) {
297
- return [
298
- `export const yogaMiddlewareHandler = undefined;`
299
- ].join("\n");
300
- }
301
- return [
302
- `import middleware from ${JSON.stringify(removeFileExtension(middlewarePath))};`,
303
- ``,
304
- `export const yogaMiddlewareHandler = middleware.yogaMiddlewareHandler;`
305
- ].join("\n");
306
+ declare module "#graphql/fragments" {
307
+ export * from "#build/graphql/fragments";
308
+ }
309
+
310
+ declare module "#graphql/registry" {
311
+ import type { DocumentNode } from "graphql";
312
+ export type { OperationName, QueryName, MutationName, SubscriptionName, VariablesOf, ResultOf } from "#build/graphql/registry";
313
+ export const registry: Readonly<Record<OperationName, { readonly document: DocumentNode; }>>;
314
+ }
315
+
316
+ declare module "#graphql/schema" {
317
+ export { schema } from "#build/graphql/schema";
318
+ }`;
306
319
  }
307
320
 
308
321
  const module$1 = defineNuxtModule({
309
322
  meta: {
310
- name: "@lewebsimple/nuxt-graphql",
323
+ name: "nuxt-graphql",
311
324
  configKey: "graphql"
312
325
  },
313
- defaults: {
314
- schemas: {}
315
- },
326
+ defaults: {},
316
327
  async setup(options, nuxt) {
317
- const { resolve } = createResolver(import.meta.url);
318
- const { buildDir, rootDir } = nuxt.options;
319
- const layerRootDirs = getLayerDirectories(nuxt).map(({ root }) => root);
320
- nuxt.options.alias ||= {};
321
- nuxt.options.runtimeConfig.public.graphql = {
322
- cacheConfig: resolveCacheConfig(options.cache)
323
- };
324
- const serverProxies = {};
325
- const contextPath = options.context ? await findSingleFile(layerRootDirs, options.context, true) : void 0;
326
- if (contextPath) {
327
- logger.info(`GraphQL context registered: ${cyan}${toRelativePath(rootDir, contextPath)}${reset}`);
328
- }
329
- serverProxies["context"] = getGraphQLContextProxy(contextPath);
330
- serverProxies["remote-executor"] = getGenericServerProxy(resolve("./runtime/server/lib/remote-executor.ts"));
331
- if (Object.keys(options.schemas || {}).length === 0) {
332
- logger.warn("No GraphQL schemas defined in nuxt.config.ts, using dummy schema.");
333
- serverProxies["schemas/dummy"] = getDummySchemaProxy();
334
- serverProxies["schema"] = await getStitchedSchemaProxy({ schemaNames: ["dummy"] });
335
- } else {
336
- for (const [schemaName, schemaDef] of Object.entries(options.schemas)) {
337
- switch (schemaDef.type) {
338
- case "local": {
339
- serverProxies[`schemas/${schemaName}`] = await getLocalSchemaProxy({ layerRootDirs, schemaDef });
340
- break;
341
- }
342
- case "remote": {
343
- const { middlewareContent, sdlContent, schemaContent } = await getRemoteSchemaProxy({ rootDir, schemaName, schemaDef });
344
- serverProxies[`schemas/${schemaName}-middleware`] = middlewareContent;
345
- serverProxies[`schemas/${schemaName}-sdl`] = sdlContent;
346
- serverProxies[`schemas/${schemaName}`] = schemaContent;
347
- break;
348
- }
349
- default:
350
- throw new Error(`Unsupported GraphQL schema type: ${schemaDef.type}`);
351
- }
352
- }
353
- serverProxies["schema"] = await getStitchedSchemaProxy({ schemaNames: Object.keys(options.schemas) });
354
- }
355
- if (options.middleware) {
356
- const yogaMiddlewarePath = await findSingleFile(layerRootDirs, options.middleware, true);
357
- logger.info(`GraphQL Yoga middleware registered: ${cyan}${toRelativePath(nuxt.options.rootDir, yogaMiddlewarePath)}${reset}`);
358
- serverProxies["yoga-middleware"] = getYogaMiddlewareProxy(yogaMiddlewarePath);
359
- } else {
360
- serverProxies["yoga-middleware"] = getYogaMiddlewareProxy();
361
- }
362
- const sdlPath = options.saveSdl ? join(rootDir, options.saveSdl) : join(buildDir, "graphql/schema.graphql");
363
- async function generateGraphQLSDL() {
364
- const schema = await loadGraphQLSchema(join(buildDir, "graphql/schema.ts"));
365
- const sdlContent = await getSDLFromGraphQLSchema(schema);
366
- if (writeFileIfChanged(sdlPath, sdlContent)) {
367
- logger.info(`GraphQL SDL generated: ${cyan}${toRelativePath(rootDir, sdlPath)}${reset}`);
328
+ const logger = useLogger("graphql");
329
+ const { resolve: resolveModule } = createResolver(import.meta.url);
330
+ const { resolve: resolveRoot, resolvePath: _resolveRootPath } = createResolver(nuxt.options.rootDir);
331
+ async function resolveRootPath(path, required = true) {
332
+ try {
333
+ if (!path) throw new Error("No path provided");
334
+ const resolvedPath = await _resolveRootPath(path);
335
+ return resolvedPath.replace(/\.(ts|mjs)$/u, "");
336
+ } catch {
337
+ if (required) throw new Error(`Cannot resolve path in rootDir: ${path}`);
338
+ return void 0;
368
339
  }
369
340
  }
370
- const configPath = join(rootDir, options.saveConfig || "graphql.config.json");
371
- const documents = options.documents || "**/*.gql";
372
- async function generateGraphQLConfig() {
373
- const configContent = JSON.stringify({ schema: toRelativePath(rootDir, sdlPath), documents }, null, 2);
374
- if (writeFileIfChanged(configPath, configContent)) {
375
- logger.info(`GraphQL Config generated: ${cyan}${toRelativePath(rootDir, configPath)}${reset}`);
341
+ function getRelativePath(to) {
342
+ let relativePath = relative(resolve(nuxt.options.rootDir), resolve(to));
343
+ relativePath = relativePath.replace(/\\/g, "/");
344
+ if (!relativePath.startsWith("./") && !relativePath.startsWith("../")) {
345
+ relativePath = `./${relativePath}`;
376
346
  }
347
+ return relativePath;
377
348
  }
378
- const typedDocumentsPath = join(buildDir, "graphql/typed-documents.ts");
379
- nuxt.options.alias["#graphql/typed-documents"] = typedDocumentsPath;
380
- async function generateGraphQLCodegen() {
381
- try {
382
- const files = await runGraphQLCodegen({ schema: sdlPath, documents, typedDocumentsPath });
383
- for (const file of files) {
384
- logger.info(`GraphQL Codegen generated: ${cyan}${toRelativePath(rootDir, file)}${reset}`);
385
- }
386
- } catch (error) {
387
- if (!nuxt.options.dev) {
388
- throw error;
389
- }
390
- const message = error instanceof AggregateError ? error.errors[0].message : String(error);
391
- logger.warn(message);
349
+ nuxt.options.alias ||= {};
350
+ nuxt.options.alias["#graphql"] = resolve(nuxt.options.buildDir, "graphql");
351
+ const nitroAlias = {};
352
+ const contextModules = [
353
+ resolveModule("./runtime/server/lib/default-context"),
354
+ ...await Promise.all((options.yoga?.context || []).map((path) => resolveRootPath(path, true)))
355
+ ];
356
+ const contextTemplate = addTemplate({
357
+ filename: "graphql/context.ts",
358
+ getContents: () => renderContextTemplate({ contextModules }),
359
+ write: true
360
+ });
361
+ nitroAlias["#graphql/context"] = contextTemplate.dst;
362
+ const schemaDefs = {};
363
+ for (const [schemaName, schemaDef] of Object.entries(options.yoga?.schemas || {})) {
364
+ let schemaTemplate2;
365
+ if (schemaDef.type === "local") {
366
+ const localSchemaDef = {
367
+ ...schemaDef,
368
+ path: await resolveRootPath(schemaDef.path, true)
369
+ };
370
+ schemaDefs[schemaName] = localSchemaDef;
371
+ schemaTemplate2 = addTemplate({
372
+ filename: `graphql/schemas/${schemaName}.ts`,
373
+ getContents: async () => renderLocalSchemaTemplate({ ...localSchemaDef }),
374
+ write: true
375
+ });
376
+ } else if (schemaDef.type === "remote") {
377
+ const remoteSchemaDef = {
378
+ ...schemaDef,
379
+ hooks: await Promise.all((schemaDef.hooks || []).map((hookPath) => resolveRootPath(hookPath, true))),
380
+ remoteExecutorModule: resolveModule("./runtime/server/lib/remote-executor")
381
+ };
382
+ schemaDefs[schemaName] = remoteSchemaDef;
383
+ schemaTemplate2 = addTemplate({
384
+ filename: `graphql/schemas/${schemaName}.ts`,
385
+ getContents: async () => await renderRemoteSchemaTemplate({ ...remoteSchemaDef }),
386
+ write: true
387
+ });
388
+ } else {
389
+ throw new Error(`Unknown schema type for schema "${schemaName}"`);
392
390
  }
391
+ nitroAlias[`#graphql/schemas/${schemaName}`] = schemaTemplate2.dst;
393
392
  }
394
- const registryPath = join(buildDir, "graphql/registry.ts");
395
- nuxt.options.alias["#graphql/registry"] = registryPath;
396
- async function generateGraphQLRegistry() {
397
- const registryContent = await getRegistryContent({ layerRootDirs, rootDir, documents });
398
- writeFileIfChanged(registryPath, registryContent);
393
+ const schemaTemplate = addTemplate({
394
+ filename: "graphql/schema.ts",
395
+ getContents: () => renderStitchedSchemaTemplate({
396
+ schemaNames: Object.keys(options.yoga?.schemas || {})
397
+ }),
398
+ write: true
399
+ });
400
+ nitroAlias["#graphql/schema"] = schemaTemplate.dst;
401
+ let documentsCache = null;
402
+ async function getDocuments(glob) {
403
+ const key = `documents:${glob}`;
404
+ if (documentsCache?.key === key) return documentsCache.data;
405
+ const documents = await loadDocuments(glob);
406
+ documentsCache = { key, data: documents };
407
+ return documents;
399
408
  }
400
- let artifactsPromise;
401
- async function generateGraphQLArtifacts() {
402
- if (!artifactsPromise) {
403
- artifactsPromise = (async () => {
404
- await generateGraphQLSDL();
405
- await generateGraphQLConfig();
406
- await generateGraphQLCodegen();
407
- await generateGraphQLRegistry();
408
- })();
409
- }
410
- return artifactsPromise;
409
+ const sdlPath = resolveRoot(options.saveSDL || "server/graphql/schema.graphql");
410
+ let schemaCache = null;
411
+ async function getStitchedSchema(schemaDefs2) {
412
+ const key = `schema:${hash(schemaDefs2)}`;
413
+ if (schemaCache?.key === key) return schemaCache.data;
414
+ const schema = await loadStitchedSchema(schemaDefs2);
415
+ schemaCache = { key, data: schema };
416
+ const sdl = await printSchemaSDL(schema);
417
+ mkdirSync(dirname(sdlPath), { recursive: true });
418
+ writeFileSync(sdlPath, sdl, { encoding: "utf-8" });
419
+ logger.info(`GraphQL SDL saved to: ${cyan}${getRelativePath(sdlPath)}${reset}`);
420
+ return schema;
411
421
  }
412
- addServerHandler({ route: "/api/graphql", handler: resolve("./runtime/server/api/yoga-handler") });
413
- nuxt.hook("listen", (_, { url }) => {
414
- logger.success(`GraphQL Yoga ready: ${cyan}${url.replace(/\/$/, "")}/api/graphql${reset}`);
422
+ addTemplate({
423
+ filename: "graphql/operations.ts",
424
+ getContents: async () => await renderOperationsTemplate({
425
+ schema: await getStitchedSchema(schemaDefs),
426
+ documents: await getDocuments(options.client?.documents || "**/*.gql")
427
+ }),
428
+ write: true
415
429
  });
416
- addServerImportsDir(resolve("./runtime/server/utils"));
417
- addPlugin(resolve("./runtime/app/plugins/graphql-request"));
418
- addPlugin(resolve("./runtime/app/plugins/graphql-sse.client"));
419
- addImportsDir(resolve("./runtime/app/composables"));
420
- nuxt.hook("nitro:config", async (nitroConfig) => {
421
- nitroConfig.alias ||= {};
422
- for (const [proxyName, proxyContent] of Object.entries(serverProxies)) {
423
- const proxyPath = join(buildDir, "graphql", `${proxyName}.ts`);
424
- nitroConfig.alias[`#graphql/${proxyName}`] = proxyPath;
425
- writeFileIfChanged(proxyPath, proxyContent);
426
- }
427
- await generateGraphQLArtifacts();
430
+ addTemplate({
431
+ filename: "graphql/fragments.ts",
432
+ getContents: async () => await renderFragmentsTemplate({
433
+ documents: await getDocuments(options.client?.documents || "**/*.gql")
434
+ }),
435
+ write: true
436
+ });
437
+ addTemplate({
438
+ filename: "graphql/registry.ts",
439
+ getContents: async () => await renderRegistryTemplate({
440
+ documents: await getDocuments(options.client?.documents || "**/*.gql")
441
+ }),
442
+ write: true
428
443
  });
429
- nuxt.hook("prepare:types", async ({ references }) => {
430
- await generateGraphQLArtifacts();
431
- references.push({ path: registryPath });
432
- references.push({ path: typedDocumentsPath });
444
+ const configPath = resolveRoot(options.saveConfig || "graphql.config.json");
445
+ const config = { schema: getRelativePath(sdlPath), documents: options.client?.documents || "**/*.gql" };
446
+ mkdirSync(dirname(configPath), { recursive: true });
447
+ writeFileSync(configPath, JSON.stringify(config, null, 2), { encoding: "utf-8" });
448
+ logger.info(`GraphQL config saved to: ${cyan}${getRelativePath(configPath)}${reset}`);
449
+ const typesTemplate = addTemplate({
450
+ filename: "graphql/types.d.ts",
451
+ getContents: () => renderTypesTemplate(),
452
+ write: true
453
+ });
454
+ nuxt.options.runtimeConfig.public.graphql = {
455
+ cacheConfig: resolveCacheConfig(options.client?.cache),
456
+ ssrForwardHeaders: options.client?.ssrForwardHeaders || ["authorization", "cookie"]
457
+ };
458
+ nuxt.hook("prepare:types", ({ sharedReferences }) => {
459
+ sharedReferences.push({ path: typesTemplate.dst });
460
+ sharedReferences.push({ path: resolveModule("./runtime/shared/types/nuxt-graphql.d.ts") });
461
+ });
462
+ nuxt.hook("nitro:config", (nitroConfig) => {
463
+ nitroConfig.alias ||= {};
464
+ Object.assign(nitroConfig.alias, nitroAlias);
433
465
  });
434
466
  if (nuxt.options.dev) {
435
- nuxt.hook("builder:watch", async (_event, path) => {
436
- if (path.endsWith(".gql")) {
437
- logger.info(`GraphQL document change detected: ${cyan}${toRelativePath(nuxt.options.rootDir, path)}${reset}`);
438
- await generateGraphQLCodegen();
439
- await generateGraphQLRegistry();
467
+ nuxt.hook("builder:watch", async (_event, changedPath) => {
468
+ if (changedPath.endsWith(".gql")) {
469
+ logger.info(`Documents change detected: ${cyan}${getRelativePath(changedPath)}${reset}`);
470
+ documentsCache = null;
440
471
  }
441
472
  });
442
473
  }
474
+ addServerHandler({ route: "/api/graphql", handler: resolveModule("./runtime/server/api/graphql") });
475
+ nuxt.hook("listen", (_, { url }) => {
476
+ logger.success(`GraphQL Yoga ready: ${cyan}${url.replace(/\/$/, "")} / api / graphql${reset}`);
477
+ });
478
+ addPlugin(resolveModule("./runtime/app/plugins/graphql-request"));
479
+ addPlugin(resolveModule("./runtime/app/plugins/graphql-sse.client"));
480
+ addImportsDir(resolveModule("./runtime/app/composables"));
481
+ addServerImportsDir(resolveModule("./runtime/server/utils"));
443
482
  }
444
483
  });
445
484