@lewebsimple/nuxt-graphql 0.2.2 → 0.3.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 (63) hide show
  1. package/README.md +361 -55
  2. package/dist/module.d.mts +33 -21
  3. package/dist/module.json +1 -1
  4. package/dist/module.mjs +337 -373
  5. package/dist/runtime/app/composables/useGraphQLCache.client.d.ts +10 -0
  6. package/dist/runtime/app/composables/useGraphQLCache.client.js +41 -0
  7. package/dist/runtime/app/composables/useGraphQLMutation.d.ts +17 -8
  8. package/dist/runtime/app/composables/useGraphQLMutation.js +10 -9
  9. package/dist/runtime/app/composables/useGraphQLQuery.d.ts +5 -12
  10. package/dist/runtime/app/composables/useGraphQLQuery.js +74 -28
  11. package/dist/runtime/app/composables/useGraphQLSubscription.d.ts +5 -6
  12. package/dist/runtime/app/composables/useGraphQLSubscription.js +13 -12
  13. package/dist/runtime/app/lib/graphql-cache.d.ts +7 -0
  14. package/dist/runtime/app/lib/graphql-cache.js +24 -0
  15. package/dist/runtime/app/lib/persisted.d.ts +4 -0
  16. package/dist/runtime/app/lib/persisted.js +70 -0
  17. package/dist/runtime/app/plugins/graphql-request.d.ts +7 -0
  18. package/dist/runtime/app/plugins/graphql-request.js +21 -0
  19. package/dist/runtime/app/plugins/graphql-sse.client.d.ts +7 -0
  20. package/dist/runtime/app/plugins/graphql-sse.client.js +15 -0
  21. package/dist/runtime/app/types/nuxt-graphql.d.ts +37 -0
  22. package/dist/runtime/server/api/yoga-handler.js +42 -0
  23. package/dist/runtime/server/lib/define-graphql-context.d.ts +5 -0
  24. package/dist/runtime/server/lib/define-graphql-context.js +4 -0
  25. package/dist/runtime/server/lib/define-remote-exec-middleware.d.ts +30 -0
  26. package/dist/runtime/server/lib/define-remote-exec-middleware.js +3 -0
  27. package/dist/runtime/server/lib/define-yoga-middleware.d.ts +21 -0
  28. package/dist/runtime/server/lib/define-yoga-middleware.js +3 -0
  29. package/dist/runtime/server/lib/execute-server-graphql.d.ts +7 -0
  30. package/dist/runtime/server/lib/execute-server-graphql.js +34 -0
  31. package/dist/runtime/server/lib/remote-executor.d.ts +35 -0
  32. package/dist/runtime/server/lib/remote-executor.js +64 -0
  33. package/dist/runtime/server/tsconfig.json +1 -1
  34. package/dist/runtime/server/utils/useServerGraphQLMutation.d.ts +8 -14
  35. package/dist/runtime/server/utils/useServerGraphQLMutation.js +8 -11
  36. package/dist/runtime/server/utils/useServerGraphQLQuery.d.ts +2 -10
  37. package/dist/runtime/server/utils/useServerGraphQLQuery.js +3 -4
  38. package/dist/runtime/shared/lib/graphql-error.d.ts +17 -0
  39. package/dist/runtime/shared/lib/graphql-error.js +28 -0
  40. package/dist/runtime/shared/lib/headers.d.ts +3 -0
  41. package/dist/runtime/shared/lib/headers.js +39 -0
  42. package/dist/types.d.mts +13 -1
  43. package/package.json +10 -14
  44. package/dist/runtime/app/composables/useGraphQLCache.d.ts +0 -10
  45. package/dist/runtime/app/composables/useGraphQLCache.js +0 -15
  46. package/dist/runtime/app/plugins/graphql.d.ts +0 -31
  47. package/dist/runtime/app/plugins/graphql.js +0 -42
  48. package/dist/runtime/app/utils/graphql-cache.d.ts +0 -36
  49. package/dist/runtime/app/utils/graphql-cache.js +0 -65
  50. package/dist/runtime/app/utils/graphql-error.d.ts +0 -12
  51. package/dist/runtime/app/utils/graphql-error.js +0 -24
  52. package/dist/runtime/server/api/graphql-handler.js +0 -15
  53. package/dist/runtime/server/lib/constants.d.ts +0 -1
  54. package/dist/runtime/server/lib/constants.js +0 -1
  55. package/dist/runtime/server/lib/create-yoga.d.ts +0 -1
  56. package/dist/runtime/server/lib/create-yoga.js +0 -17
  57. package/dist/runtime/server/lib/default-context.d.ts +0 -7
  58. package/dist/runtime/server/lib/default-context.js +0 -1
  59. package/dist/runtime/server/utils/graphql-client.d.ts +0 -14
  60. package/dist/runtime/server/utils/graphql-client.js +0 -14
  61. package/dist/runtime/server/utils/remote-middleware.d.ts +0 -18
  62. package/dist/runtime/server/utils/remote-middleware.js +0 -0
  63. /package/dist/runtime/server/api/{graphql-handler.d.ts → yoga-handler.d.ts} +0 -0
package/dist/module.mjs CHANGED
@@ -1,19 +1,34 @@
1
- import { useLogger, defineNuxtModule, createResolver, getLayerDirectories, addServerHandler, addPlugin, addImportsDir, addServerImportsDir } from '@nuxt/kit';
2
- import { join, parse, relative, dirname } from 'node:path';
1
+ import { join, relative, resolve, dirname } from 'node:path';
2
+ import { useLogger, defineNuxtModule, createResolver, getLayerDirectories, addServerHandler, addServerImportsDir, addPlugin, addImportsDir } from '@nuxt/kit';
3
3
  import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
4
4
  import { glob } from 'tinyglobby';
5
5
  import { generate } from '@graphql-codegen/cli';
6
- import { parse as parse$1, Kind, getIntrospectionQuery, buildClientSchema, printSchema } from 'graphql';
7
- import { GRAPHQL_ENDPOINT } from '../dist/runtime/server/lib/constants.js';
6
+ import { parse, Kind } from 'graphql';
7
+ export { defineGraphQLContext } from '../dist/runtime/server/lib/define-graphql-context.js';
8
+ export { defineRemoteExecMiddleware } from '../dist/runtime/server/lib/define-remote-exec-middleware.js';
9
+ export { defineYogaMiddleware } from '../dist/runtime/server/lib/define-yoga-middleware.js';
8
10
 
9
- const logger = useLogger("@lewebsimple/nuxt-graphql");
10
- const blue = "\x1B[34m";
11
- const cyan = "\x1B[36m";
12
- const dim = "\x1B[2m";
13
- const green = "\x1B[32m";
14
- const magenta = "\x1B[35m";
15
- const reset = "\x1B[0m";
16
- const yellow = "\x1B[33m";
11
+ function removeFileExtension(filePath) {
12
+ return filePath.replace(/\.[^/.]+$/, "");
13
+ }
14
+ function getGenericServerProxy(modulePath) {
15
+ return `export * from ${JSON.stringify(removeFileExtension(modulePath))};`;
16
+ }
17
+
18
+ function getGraphQLContextProxy(contextPath) {
19
+ if (!contextPath) {
20
+ return [
21
+ `export const getGraphQLContext = async () => ({});`,
22
+ `export type GraphQLContext = Awaited<ReturnType<typeof getGraphQLContext>>;`
23
+ ].join("\n");
24
+ }
25
+ return [
26
+ `import context from ${JSON.stringify(removeFileExtension(contextPath))};`,
27
+ ``,
28
+ `export const getGraphQLContext = context.getGraphQLContext;`,
29
+ `export type GraphQLContext = Awaited<ReturnType<typeof getGraphQLContext>>;`
30
+ ].join("\n");
31
+ }
17
32
 
18
33
  async function findSingleFile(dirs, pattern, isRequired = false) {
19
34
  for (const dir of dirs) {
@@ -24,7 +39,7 @@ async function findSingleFile(dirs, pattern, isRequired = false) {
24
39
  }
25
40
  }
26
41
  if (isRequired) {
27
- throw new Error(`File not found: ${cyan}${pattern}${reset} in directories:
42
+ throw new Error(`File not found: ${pattern} in directories:
28
43
  ${dirs.join("\n")}`);
29
44
  }
30
45
  }
@@ -44,299 +59,253 @@ function writeFileIfChanged(path, content) {
44
59
  writeFileSync(path, content, "utf-8");
45
60
  return true;
46
61
  }
47
- function toImportPath(from, to) {
48
- const { dir, name } = parse(to);
49
- let importPath = relative(dirname(from), join(dir, name));
50
- if (!importPath.startsWith(".")) {
51
- importPath = "./" + importPath;
62
+ function toRelativePath(from, to) {
63
+ let relativePath = relative(resolve(from), resolve(to));
64
+ relativePath = relativePath.replace(/\\/g, "/");
65
+ if (!relativePath.startsWith("./") && !relativePath.startsWith("../")) {
66
+ relativePath = `./${relativePath}`;
52
67
  }
53
- return importPath;
68
+ return relativePath;
54
69
  }
55
70
 
56
- async function loadSchemaSdl(schemaPath) {
57
- const { createJiti } = await import('jiti');
58
- const jiti = createJiti(import.meta.url, { interopDefault: true });
59
- const module = await jiti.import(schemaPath);
60
- if (!module.schema) {
61
- throw new Error(`${schemaPath} must export a 'schema' variable`);
62
- }
63
- const { printSchema, lexicographicSortSchema } = await import('graphql');
64
- return printSchema(lexicographicSortSchema(module.schema));
71
+ const defaultCacheConfig = {
72
+ cachePolicy: "cache-first",
73
+ cacheVersion: "1",
74
+ keyPrefix: "gql",
75
+ ttl: 60
76
+ };
77
+ function resolveCacheConfig(...overrides) {
78
+ return Object.assign({}, defaultCacheConfig, ...overrides);
65
79
  }
66
- async function runCodegen({ schema, documents, operationsPath, zodPath, scalars }) {
67
- const generates = {
68
- [operationsPath]: {
69
- schema,
70
- documents,
71
- plugins: ["typescript", "typescript-operations", "typed-document-node"],
72
- config: {
73
- useTypeImports: true,
74
- enumsAsTypes: true,
75
- skipTypename: true,
76
- documentVariableSuffix: "Document",
77
- documentMode: "documentNode",
78
- strictScalars: true,
79
- defaultScalarType: "never",
80
- scalars
81
- }
82
- }
83
- };
84
- if (zodPath) {
85
- const zodScalars = {};
86
- if (scalars) {
87
- for (const [name, config] of Object.entries(scalars)) {
88
- const inputType = typeof config === "string" ? config : config.input;
89
- switch (inputType) {
90
- case "Date":
91
- zodScalars[name] = "z.coerce.date()";
92
- break;
93
- case "number":
94
- zodScalars[name] = "z.coerce.number()";
95
- break;
96
- case "boolean":
97
- zodScalars[name] = "z.coerce.boolean()";
98
- break;
99
- default:
100
- zodScalars[name] = "z.string()";
80
+
81
+ async function runGraphQLCodegen({ schema, documents, typedDocumentsPath, zodPath }) {
82
+ const config = {
83
+ schema,
84
+ documents,
85
+ generates: {
86
+ // Typed document nodes
87
+ [typedDocumentsPath]: {
88
+ plugins: ["typescript", "typescript-operations", "typed-document-node"],
89
+ config: {
90
+ defaultScalarType: "never",
91
+ documentMode: "documentNode",
92
+ documentVariableSuffix: "Document",
93
+ enumsAsTypes: true,
94
+ inlineFragmentTypes: "combine",
95
+ preResolveTypes: false,
96
+ skipTypename: true,
97
+ strictScalars: true,
98
+ useTypeImports: true
99
+ }
100
+ },
101
+ // Zod schemas
102
+ [zodPath]: {
103
+ plugins: ["typescript-validation-schema"],
104
+ config: {
105
+ schema: "zodv4",
106
+ importFrom: "#graphql/operations",
107
+ useTypeImports: true
101
108
  }
102
109
  }
103
- }
104
- generates[zodPath] = {
105
- schema,
106
- documents,
107
- plugins: ["typescript-validation-schema"],
108
- config: {
109
- schema: "zodv4",
110
- importFrom: "#graphql/operations",
111
- useTypeImports: true,
112
- directives: {
113
- constraint: {
114
- minLength: "min",
115
- maxLength: "max",
116
- pattern: "regex"
117
- }
118
- },
119
- scalarSchemas: zodScalars
120
- }
121
- };
122
- }
123
- await generate({ generates, silent: true, errorsOnly: true }, true);
110
+ },
111
+ ignoreNoDocuments: true,
112
+ silent: true
113
+ };
114
+ const result = await generate(config, true);
115
+ return result.map(({ filename }) => filename);
124
116
  }
125
- function analyzeDocuments(documents) {
126
- const docs = documents.map((path) => ({ path, content: readFileSync(path, "utf-8") }));
127
- const byFile = /* @__PURE__ */ new Map();
128
- const operationsByType = {
129
- query: [],
130
- mutation: [],
131
- subscription: []
117
+
118
+ const logger = useLogger("graphql");
119
+ const cyan = "\x1B[36m";
120
+ const reset = "\x1B[0m";
121
+
122
+ async function getRegistryContent({ layerRootDirs, rootDir, documents }) {
123
+ const docs = (await findMultipleFiles(layerRootDirs, documents)).map((path) => ({
124
+ path: toRelativePath(rootDir, path),
125
+ content: readFileSync(path, "utf-8")
126
+ }));
127
+ const parsed = {
128
+ fragments: /* @__PURE__ */ new Map(),
129
+ operations: {
130
+ query: /* @__PURE__ */ new Map(),
131
+ mutation: /* @__PURE__ */ new Map(),
132
+ subscription: /* @__PURE__ */ new Map()
133
+ }
132
134
  };
133
- const operationNameToFile = /* @__PURE__ */ new Map();
134
- const fragmentNameToFile = /* @__PURE__ */ new Map();
135
135
  for (const doc of docs) {
136
- const ast = parse$1(doc.content);
137
- const defs = [];
136
+ const ast = parse(doc.content);
138
137
  for (const def of ast.definitions) {
139
- if (def.kind === Kind.FRAGMENT_DEFINITION) {
140
- const name2 = def.name.value;
141
- const prev2 = fragmentNameToFile.get(name2);
142
- if (prev2) {
143
- throw new Error(`Duplicate fragment name '${name2}' in:
144
- - ${prev2}
145
- - ${doc.path}`);
138
+ switch (def.kind) {
139
+ // Process fragment definitions
140
+ case Kind.FRAGMENT_DEFINITION: {
141
+ const name = def.name.value;
142
+ const existing = parsed.fragments.get(name);
143
+ if (existing) {
144
+ throw new Error(`Duplicate fragment name ${name} in document ${doc.path} (previously defined in ${existing.path})`);
145
+ }
146
+ parsed.fragments.set(name, { kind: "fragment", name, path: doc.path });
147
+ break;
148
+ }
149
+ // Process operation definitions
150
+ case Kind.OPERATION_DEFINITION: {
151
+ const type = def.operation;
152
+ if (!["query", "mutation", "subscription"].includes(type)) continue;
153
+ const name = def.name?.value;
154
+ if (!name) throw new Error(`Unnamed ${type} operation in document ${doc.path}`);
155
+ const existing = parsed.operations[type].get(name);
156
+ if (existing) {
157
+ throw new Error(`Duplicate operation name ${name} in document ${doc.path} (previously defined in ${existing.path})`);
158
+ }
159
+ parsed.operations[type].set(name, { kind: "operation", type, name, path: doc.path });
160
+ break;
146
161
  }
147
- fragmentNameToFile.set(name2, doc.path);
148
- defs.push({ kind: "fragment", name: name2 });
149
- continue;
150
- }
151
- if (def.kind !== Kind.OPERATION_DEFINITION) continue;
152
- const type = def.operation;
153
- if (!["query", "mutation", "subscription"].includes(type)) continue;
154
- const name = def.name?.value;
155
- if (!name) {
156
- throw new Error(`Unnamed ${type} operation in ${doc.path}`);
157
- }
158
- const prev = operationNameToFile.get(name);
159
- if (prev) {
160
- throw new Error(`Duplicate ${type} operation name '${name}' in:
161
- - ${prev}
162
- - ${doc.path}`);
163
162
  }
164
- operationNameToFile.set(name, doc.path);
165
- const op = { kind: "operation", type, name };
166
- defs.push(op);
167
- operationsByType[type].push(op);
168
163
  }
169
- byFile.set(doc.path, defs);
170
164
  }
171
- return { byFile, operationsByType };
172
- }
173
- function formatDefinitions(defs) {
174
- if (defs.length === 0) return "";
175
- const colorOf = (def) => {
176
- if (def.kind === "fragment") return green;
177
- switch (def.type) {
178
- case "query":
179
- return blue;
180
- case "mutation":
181
- return magenta;
182
- case "subscription":
183
- return yellow;
184
- }
185
- };
186
- return defs.map((def) => `${colorOf(def)}${def.name}${reset}`).join(`${dim} / ${reset}`);
187
- }
188
- function writeRegistryModule({ registryPath, operationsByType }) {
189
- const queries = operationsByType.query.map((o) => o.name);
190
- const mutations = operationsByType.mutation.map((o) => o.name);
191
- const subscriptions = operationsByType.subscription.map((o) => o.name);
192
165
  const content = [
193
- `import type { TypedDocumentNode } from "@graphql-typed-document-node/core";`,
194
- `import * as ops from "#graphql/operations";`,
166
+ `import * as ops from "#graphql/typed-documents";`,
195
167
  ``,
196
168
  `type ResultOf<T> = T extends { __apiType?: (variables: infer _) => infer R } ? R : never;`,
197
- `type VariablesOf<T> = T extends { __apiType?: (variables: infer V) => infer _ } ? V : never;`
169
+ `type VariablesOf<T> = T extends { __apiType?: (variables: infer V) => infer _ } ? V : never;`,
170
+ ``
198
171
  ];
199
- if (queries.length > 0) {
200
- content.push(
201
- ``,
202
- `// Queries`,
203
- `export const queries = {`,
204
- ...queries.map((name) => ` ${name}: ops.${name}Document,`),
205
- `} as const;`
206
- );
207
- } else {
208
- content.push(``, `export const queries = {} as const;`);
209
- }
172
+ const queries = Array.from(parsed.operations.query.values());
210
173
  content.push(
211
- ``,
174
+ `// Queries`,
175
+ `export const queries = {`,
176
+ ...queries.map(({ name }) => ` ${name}: ops.${name}Document,`),
177
+ `};`,
212
178
  `export type QueryName = keyof typeof queries;`,
213
179
  `export type QueryResult<N extends QueryName> = ResultOf<(typeof queries)[N]>;`,
214
- `export type QueryVariables<N extends QueryName> = VariablesOf<(typeof queries)[N]>;`
180
+ `export type QueryVariables<N extends QueryName> = VariablesOf<(typeof queries)[N]>;`,
181
+ ``
215
182
  );
216
- if (mutations.length > 0) {
217
- content.push(
218
- ``,
219
- `// Mutations`,
220
- `export const mutations = {`,
221
- ...mutations.map((name) => ` ${name}: ops.${name}Document,`),
222
- `} as const;`
223
- );
224
- } else {
225
- content.push(``, `export const mutations = {} as const;`);
226
- }
183
+ const mutations = Array.from(parsed.operations.mutation.values());
227
184
  content.push(
228
- ``,
185
+ `// Mutations`,
186
+ `export const mutations = {`,
187
+ ...mutations.map(({ name }) => ` ${name}: ops.${name}Document,`),
188
+ `};`,
229
189
  `export type MutationName = keyof typeof mutations;`,
230
190
  `export type MutationResult<N extends MutationName> = ResultOf<(typeof mutations)[N]>;`,
231
- `export type MutationVariables<N extends MutationName> = VariablesOf<(typeof mutations)[N]>;`
191
+ `export type MutationVariables<N extends MutationName> = VariablesOf<(typeof mutations)[N]>;`,
192
+ ``
232
193
  );
233
- if (subscriptions.length > 0) {
234
- content.push(
235
- ``,
236
- `// Subscriptions`,
237
- `export const subscriptions = {`,
238
- ...subscriptions.map((name) => ` ${name}: ops.${name}Document,`),
239
- `} as const;`
240
- );
241
- } else {
242
- content.push(``, `export const subscriptions = {} as const;`);
243
- }
194
+ const subscriptions = Array.from(parsed.operations.subscription.values());
244
195
  content.push(
245
- ``,
196
+ `// Subscriptions`,
197
+ `export const subscriptions = {`,
198
+ ...subscriptions.map(({ name }) => ` ${name}: ops.${name}Document,`),
199
+ `};`,
246
200
  `export type SubscriptionName = keyof typeof subscriptions;`,
247
201
  `export type SubscriptionResult<N extends SubscriptionName> = ResultOf<(typeof subscriptions)[N]>;`,
248
- `export type SubscriptionVariables<N extends SubscriptionName> = VariablesOf<(typeof subscriptions)[N]>;`
202
+ `export type SubscriptionVariables<N extends SubscriptionName> = VariablesOf<(typeof subscriptions)[N]>;`,
203
+ ``
249
204
  );
250
- return writeFileIfChanged(registryPath, content.join("\n") + "\n");
205
+ return content.join("\n");
251
206
  }
252
207
 
253
- function writeLocalSchemaModule({ localPath, modulePath }) {
254
- if (!existsSync(localPath)) {
255
- throw new Error(`Local schema file not found at path: ${localPath}`);
208
+ function getRemoteExecMiddlewareProxy(middlewarePath) {
209
+ if (!middlewarePath) {
210
+ return [
211
+ `export const remoteExecMiddlewareHandler = undefined;`
212
+ ].join("\n");
256
213
  }
257
- const content = [
258
- `export { schema } from ${JSON.stringify(toImportPath(modulePath, localPath))};`
214
+ return [
215
+ `import middleware from ${JSON.stringify(removeFileExtension(middlewarePath))};`,
216
+ ``,
217
+ `export const remoteExecMiddlewareHandler = middleware.remoteExecMiddlewareHandler;`
259
218
  ].join("\n");
260
- return writeFileIfChanged(modulePath, content);
261
219
  }
262
- async function writeRemoteSchemaSdl({ schemaDef: { url, headers }, sdlPath }) {
263
- const response = await fetch(url, {
264
- method: "POST",
265
- headers: { "Content-Type": "application/json", ...headers },
266
- body: JSON.stringify({ query: getIntrospectionQuery() })
267
- });
268
- const json = await response.json();
269
- if (json.errors) {
270
- throw new Error(`Failed to fetch remote schema from ${url}: ${JSON.stringify(json.errors)}`);
271
- }
272
- const schema = buildClientSchema(json.data);
273
- const sdl = printSchema(schema);
274
- const content = `export const sdl = /* GraphQL */ \`${sdl.replace(/`/g, "\\`")}\`;`;
275
- return writeFileIfChanged(sdlPath, content);
276
- }
277
- function writeRemoteSchemaModule({ name, schemaDef: { url, headers }, sdlPath, modulePath, middlewarePath }) {
278
- const headerSource = headers && Object.keys(headers).length > 0 ? JSON.stringify(headers, null, 2) : "{}";
279
- const middlewareImport = middlewarePath ? `import middleware from ${JSON.stringify(toImportPath(modulePath, middlewarePath))};` : "";
220
+
221
+ async function getLocalSchemaProxy({ layerRootDirs, schemaDef }) {
222
+ const schemaPath = await findSingleFile(layerRootDirs, schemaDef.path, true);
280
223
  const content = [
281
- `import { buildSchema, print } from "graphql";`,
282
- `import type { Executor } from "@graphql-tools/utils";`,
224
+ `export { schema } from ${JSON.stringify(removeFileExtension(schemaPath))};`
225
+ ].join("\n");
226
+ return content;
227
+ }
228
+ async function getRemoteSchemaProxy({ rootDir, schemaName, schemaDef }) {
229
+ const middlewareContent = getRemoteExecMiddlewareProxy(schemaDef.middleware ? join(rootDir, schemaDef.middleware) : void 0);
230
+ const schema = await fetchGraphQLSchema(schemaDef.url, schemaDef.headers);
231
+ const sdl = await getSDLFromGraphQLSchema(schema);
232
+ const sdlContent = `export const sdl = /* GraphQL */ \`${sdl.replace(/`/g, "\\`")}\`;`;
233
+ const schemaContent = [
234
+ `import { buildSchema } from "graphql";`,
283
235
  `import type { SubschemaConfig } from "@graphql-tools/delegate";`,
284
- `import { sdl } from ${JSON.stringify(toImportPath(modulePath, sdlPath))};`,
285
- middlewareImport,
236
+ `import { remoteExecMiddlewareHandler } from "./${schemaName}-middleware";`,
237
+ `import { sdl } from "./${schemaName}-sdl";`,
238
+ `import { createRemoteExecutor } from "../remote-executor";`,
286
239
  ``,
287
- `const endpoint = ${JSON.stringify(url)};`,
288
- `const headers = ${headerSource} as Record<string, string>;`,
289
- `const remoteName = ${JSON.stringify(name)};`,
290
- `const mw = (typeof middleware === 'object' && middleware) || {};`,
240
+ `const headers = ${JSON.stringify(schemaDef.headers ?? {}, null, 2)} as HeadersInit;`,
291
241
  ``,
292
- `const executor: Executor = async ({ document, variables, context, operationName }) => {`,
293
- ` const query = typeof document === "string" ? document : print(document);`,
294
- ` let fetchOptions = {`,
295
- ` method: "POST",`,
296
- ` headers: { "Content-Type": "application/json", ...headers },`,
297
- ` body: JSON.stringify({ query, variables }),`,
298
- ` };`,
299
- ` const mwContext = { remoteName, operationName, context, fetchOptions };`,
300
- ` if (typeof mw.onRequest === "function") {`,
301
- ` const maybeOverride = await mw.onRequest(mwContext);`,
302
- ` if (maybeOverride && typeof maybeOverride === "object") {`,
303
- ` fetchOptions = maybeOverride;`,
304
- ` }`,
305
- ` }`,
306
- ` const response = await fetch(endpoint, fetchOptions);`,
307
- ` if (typeof mw.onResponse === "function") {`,
308
- ` await mw.onResponse({ ...mwContext, response });`,
309
- ` }`,
310
- ` return response.json();`,
311
- `};`,
242
+ `const executor = createRemoteExecutor({`,
243
+ ` url: ${JSON.stringify(schemaDef.url)},`,
244
+ ` remoteName: ${JSON.stringify(schemaName)},`,
245
+ ` headers,`,
246
+ ` middleware: remoteExecMiddlewareHandler,`,
247
+ `});`,
312
248
  ``,
313
249
  `export const schema: SubschemaConfig = {`,
314
250
  ` schema: buildSchema(sdl),`,
315
251
  ` executor,`,
316
- `};`,
317
- ``
252
+ `};`
318
253
  ].join("\n");
319
- return writeFileIfChanged(modulePath, content);
254
+ return { middlewareContent, sdlContent, schemaContent };
320
255
  }
321
- function writeStitchedSchemaModule({ schemaNames, modulePath }) {
322
- const schemas = schemaNames.map((name) => ({
323
- path: `./schemas/${name}`,
324
- ref: `${name}Schema`
325
- }));
256
+ async function getStitchedSchemaProxy({ schemaNames }) {
326
257
  const content = [
327
- `import { stitchSchemas } from "@graphql-tools/stitch";`,
328
- `import type { GraphQLSchema } from "graphql";`,
329
- `import type { SubschemaConfig } from "@graphql-tools/delegate";`,
330
- ...schemas.map(({ path, ref }) => `import { schema as ${ref} } from ${JSON.stringify(path)};`),
258
+ `import { stitchSchemas } from "@graphql-tools/stitch"; `,
259
+ `import type { GraphQLSchema } from "graphql"; `,
260
+ `import type { SubschemaConfig } from "@graphql-tools/delegate"; `,
261
+ ...schemaNames.map((name) => `import { schema as ${name}Schema } from ${JSON.stringify(`./schemas/${name}`)}; `),
331
262
  ``,
332
263
  `const subschemas: Array<GraphQLSchema | SubschemaConfig> = [`,
333
- ...schemas.map(({ ref }) => ` ${ref},`),
334
- `];`,
264
+ ...schemaNames.map((name) => ` ${name}Schema, `),
265
+ `]; `,
335
266
  ``,
336
- `export const schema = stitchSchemas({ subschemas });`,
337
- ``
267
+ `export const schema = stitchSchemas({ subschemas }); `
268
+ ].join("\n");
269
+ return content;
270
+ }
271
+ async function loadGraphQLSchema(schemaPath) {
272
+ const { createJiti } = await import('jiti');
273
+ const jiti = createJiti(import.meta.url, { interopDefault: true });
274
+ const module = await jiti.import(schemaPath);
275
+ if (!module.schema || !(module.schema instanceof Object) || typeof module.schema.getQueryType !== "function") {
276
+ throw new Error(`${schemaPath} must export a valid 'schema' of type GraphQLSchema.`);
277
+ }
278
+ return module.schema;
279
+ }
280
+ async function fetchGraphQLSchema(schemaUrl, headers) {
281
+ const { getIntrospectionQuery, buildClientSchema } = await import('graphql');
282
+ const response = await fetch(schemaUrl, {
283
+ method: "POST",
284
+ headers: { "Content-Type": "application/json", ...headers },
285
+ body: JSON.stringify({ query: getIntrospectionQuery() })
286
+ });
287
+ const json = await response.json();
288
+ if (json.errors) {
289
+ throw new Error(`Failed to fetch GraphQL schema from ${schemaUrl}: ${JSON.stringify(json.errors)} `);
290
+ }
291
+ return buildClientSchema(json.data);
292
+ }
293
+ async function getSDLFromGraphQLSchema(schema) {
294
+ const { printSchema, lexicographicSortSchema } = await import('graphql');
295
+ return printSchema(lexicographicSortSchema(schema));
296
+ }
297
+
298
+ function getYogaMiddlewareProxy(middlewarePath) {
299
+ if (!middlewarePath) {
300
+ return [
301
+ `export const yogaMiddlewareHandler = undefined;`
302
+ ].join("\n");
303
+ }
304
+ return [
305
+ `import middleware from ${JSON.stringify(removeFileExtension(middlewarePath))};`,
306
+ ``,
307
+ `export const yogaMiddlewareHandler = middleware.yogaMiddlewareHandler;`
338
308
  ].join("\n");
339
- return writeFileIfChanged(modulePath, content);
340
309
  }
341
310
 
342
311
  const module$1 = defineNuxtModule({
@@ -344,136 +313,131 @@ const module$1 = defineNuxtModule({
344
313
  name: "@lewebsimple/nuxt-graphql",
345
314
  configKey: "graphql"
346
315
  },
347
- defaults: {
348
- schemas: {},
349
- codegen: {
350
- documents: "**/*.gql",
351
- saveSchema: "server/graphql/schema.graphql"
352
- },
353
- client: {
354
- headers: {},
355
- cache: {
356
- enabled: false,
357
- ttl: 6e4,
358
- storage: "memory"
359
- }
360
- }
361
- },
316
+ defaults: {},
362
317
  async setup(options, nuxt) {
363
318
  const { resolve } = createResolver(import.meta.url);
319
+ const { buildDir, rootDir } = nuxt.options;
364
320
  const layerRootDirs = getLayerDirectories(nuxt).map(({ root }) => root);
365
- const middlewarePath = resolve("./runtime/server/utils/remote-middleware");
366
- const stitchedPath = join(nuxt.options.buildDir, "graphql/schema.ts");
367
- const sdlPath = join(nuxt.options.rootDir, options.codegen?.saveSchema || ".nuxt/graphql/schema.graphql");
368
321
  nuxt.options.alias ||= {};
369
- async function setupContextSchemas() {
370
- let contextPath;
371
- if (options.context) {
372
- contextPath = await findSingleFile(layerRootDirs, options.context, true);
373
- logger.info(`Using GraphQL context from ${cyan}${relative(nuxt.options.rootDir, contextPath)}${reset}`);
374
- } else {
375
- contextPath = resolve("./runtime/server/lib/default-context.ts");
376
- logger.info(`Using default GraphQL context`);
377
- }
378
- const schemasPath = {};
379
- const middlewaresPath = {};
380
- for (const [name, schemaDef] of Object.entries(options.schemas)) {
381
- schemasPath[name] = join(nuxt.options.buildDir, `graphql/schemas/${name}.ts`);
382
- if (schemaDef.type === "local") {
383
- const localPath = await findSingleFile(layerRootDirs, schemaDef.path, true);
384
- writeLocalSchemaModule({ localPath, modulePath: schemasPath[name] });
385
- logger.info(`Local GraphQL schema "${blue}${name}${reset}" loaded from ${cyan}${relative(nuxt.options.rootDir, localPath)}${reset}`);
386
- } else if (schemaDef.type === "remote") {
387
- const sdlPath2 = join(nuxt.options.buildDir, `graphql/schemas/${name}-sdl.ts`);
388
- if (schemaDef.middleware) {
389
- middlewaresPath[name] = await findSingleFile(layerRootDirs, schemaDef.middleware, true);
390
- }
391
- await writeRemoteSchemaSdl({ schemaDef, sdlPath: sdlPath2 });
392
- writeRemoteSchemaModule({ name, schemaDef, sdlPath: sdlPath2, modulePath: schemasPath[name], middlewarePath: middlewaresPath[name] });
393
- logger.info(`Remote GraphQL schema "${magenta}${name}${reset}" loaded from ${cyan}${schemaDef.url}${reset}`);
394
- } else {
395
- throw new Error(`Unknown schema type for schema '${name}'`);
396
- }
397
- }
398
- writeStitchedSchemaModule({ schemaNames: Object.keys(options.schemas), modulePath: stitchedPath });
399
- nuxt.hook("nitro:config", (config) => {
400
- config.alias ||= {};
401
- config.alias["#graphql/context"] = contextPath;
402
- config.alias["#graphql/middleware"] = middlewarePath;
403
- for (const name of Object.keys(options.schemas)) {
404
- config.alias[`#graphql/schemas/${name}`] = schemasPath[name];
322
+ nuxt.options.runtimeConfig.public.graphql = {
323
+ cacheConfig: resolveCacheConfig(options.cache)
324
+ };
325
+ const serverProxies = {};
326
+ const contextPath = options.context ? await findSingleFile(layerRootDirs, options.context, true) : void 0;
327
+ if (contextPath) {
328
+ logger.info(`GraphQL context registered: ${cyan}${toRelativePath(rootDir, contextPath)}${reset}`);
329
+ }
330
+ serverProxies["context"] = getGraphQLContextProxy(contextPath);
331
+ serverProxies["remote-executor"] = getGenericServerProxy(resolve("./runtime/server/lib/remote-executor.ts"));
332
+ for (const [schemaName, schemaDef] of Object.entries(options.schemas)) {
333
+ switch (schemaDef.type) {
334
+ case "local": {
335
+ serverProxies[`schemas/${schemaName}`] = await getLocalSchemaProxy({ layerRootDirs, schemaDef });
336
+ break;
405
337
  }
406
- for (const name of Object.keys(middlewaresPath)) {
407
- config.alias[`#graphql/middlewares/${name}`] = middlewaresPath[name];
338
+ case "remote": {
339
+ const { middlewareContent, sdlContent, schemaContent } = await getRemoteSchemaProxy({ rootDir, schemaName, schemaDef });
340
+ serverProxies[`schemas/${schemaName}-middleware`] = middlewareContent;
341
+ serverProxies[`schemas/${schemaName}-sdl`] = sdlContent;
342
+ serverProxies[`schemas/${schemaName}`] = schemaContent;
343
+ break;
408
344
  }
409
- config.alias["#graphql/schema"] = stitchedPath;
410
- });
345
+ default:
346
+ throw new Error(`Unsupported GraphQL schema type: ${schemaDef.type}`);
347
+ }
411
348
  }
412
- async function setupCodegen() {
413
- const configPath = join(nuxt.options.rootDir, ".graphqlrc");
414
- const operationsPath = nuxt.options.alias["#graphql/operations"] = join(nuxt.options.buildDir, "graphql/operations.ts");
415
- const registryPath = nuxt.options.alias["#graphql/registry"] = join(nuxt.options.buildDir, "graphql/registry.ts");
416
- const zodPath = nuxt.options.alias["#graphql/zod"] = join(nuxt.options.buildDir, "graphql/zod.ts");
417
- async function generate() {
418
- try {
419
- const sdlContent = await loadSchemaSdl(stitchedPath);
420
- writeFileIfChanged(sdlPath, sdlContent);
421
- const documentsPattern = options.codegen?.documents ?? "**/*.gql";
422
- const documents = await findMultipleFiles(layerRootDirs, documentsPattern);
423
- await runCodegen({ schema: sdlPath, documents, operationsPath, zodPath, scalars: options.codegen?.scalars });
424
- const { byFile, operationsByType } = analyzeDocuments(documents);
425
- byFile.forEach((defs, path) => {
426
- const relativePath = relative(nuxt.options.rootDir, path);
427
- logger.info(`${cyan}${relativePath}${reset} [${formatDefinitions(defs)}]`);
428
- });
429
- writeRegistryModule({ registryPath, operationsByType });
430
- const config = {
431
- schema: relative(nuxt.options.rootDir, sdlPath),
432
- documents: documentsPattern
433
- };
434
- writeFileIfChanged(configPath, JSON.stringify(config, null, 2));
435
- } catch (error) {
436
- logger.warn(`GraphQL codegen failed: ${error.message}`);
437
- }
349
+ serverProxies["schema"] = await getStitchedSchemaProxy({ schemaNames: Object.keys(options.schemas) });
350
+ if (options.middleware) {
351
+ const yogaMiddlewarePath = await findSingleFile(layerRootDirs, options.middleware, true);
352
+ logger.info(`GraphQL Yoga middleware registered: ${cyan}${toRelativePath(nuxt.options.rootDir, yogaMiddlewarePath)}${reset}`);
353
+ serverProxies["yoga-middleware"] = getYogaMiddlewareProxy(yogaMiddlewarePath);
354
+ } else {
355
+ serverProxies["yoga-middleware"] = getYogaMiddlewareProxy();
356
+ }
357
+ const sdlPath = options.saveSdl ? join(rootDir, options.saveSdl) : join(buildDir, "graphql/schema.graphql");
358
+ async function generateGraphQLSDL() {
359
+ const schema = await loadGraphQLSchema(join(buildDir, "graphql/schema.ts"));
360
+ const sdlContent = await getSDLFromGraphQLSchema(schema);
361
+ if (writeFileIfChanged(sdlPath, sdlContent)) {
362
+ logger.info(`GraphQL SDL generated: ${cyan}${toRelativePath(rootDir, sdlPath)}${reset}`);
438
363
  }
439
- nuxt.hook("prepare:types", async ({ references }) => {
440
- await generate();
441
- references.push({ path: operationsPath });
442
- references.push({ path: registryPath });
443
- references.push({ path: zodPath });
444
- });
445
- if (nuxt.options.dev) {
446
- nuxt.hook("builder:watch", async (event, path) => {
447
- if (path.endsWith(".gql")) {
448
- await generate();
449
- }
450
- });
364
+ }
365
+ const configPath = join(rootDir, options.saveConfig || "graphql.config.json");
366
+ const documents = options.documents || "**/*.gql";
367
+ async function generateGraphQLConfig() {
368
+ const configContent = JSON.stringify({ schema: toRelativePath(rootDir, sdlPath), documents }, null, 2);
369
+ if (writeFileIfChanged(configPath, configContent)) {
370
+ logger.info(`GraphQL Config generated: ${cyan}${toRelativePath(rootDir, configPath)}${reset}`);
451
371
  }
452
372
  }
453
- function setupYogaHandler() {
454
- addServerHandler({ route: GRAPHQL_ENDPOINT, handler: resolve("./runtime/server/api/graphql-handler") });
455
- nuxt.hook("listen", (_, { url }) => {
456
- logger.success(`GraphQL Yoga ready at ${cyan}${url.replace(/\/$/, "")}${GRAPHQL_ENDPOINT}${reset}`);
457
- });
373
+ const typedDocumentsPath = join(buildDir, "graphql/typed-documents.ts");
374
+ nuxt.options.alias["#graphql/typed-documents"] = typedDocumentsPath;
375
+ const zodPath = join(buildDir, "graphql/zod.ts");
376
+ nuxt.options.alias["#graphql/zod"] = zodPath;
377
+ async function generateGraphQLCodegen() {
378
+ try {
379
+ const files = await runGraphQLCodegen({ schema: sdlPath, documents, typedDocumentsPath, zodPath });
380
+ for (const file of files) {
381
+ logger.info(`GraphQL Codegen generated: ${cyan}${toRelativePath(rootDir, file)}${reset}`);
382
+ }
383
+ } catch (error) {
384
+ if (!nuxt.options.dev) {
385
+ throw error;
386
+ }
387
+ const message = error instanceof AggregateError ? error.errors[0].message : String(error);
388
+ logger.warn(message);
389
+ }
458
390
  }
459
- function setupClient() {
460
- nuxt.options.runtimeConfig.public.graphql = {
461
- endpoint: GRAPHQL_ENDPOINT,
462
- headers: options.client?.headers || {},
463
- cache: {
464
- enabled: options.client?.cache?.enabled ?? false,
465
- ttl: options.client?.cache?.ttl ?? 6e4,
466
- storage: options.client?.cache?.storage ?? "memory"
391
+ const registryPath = join(buildDir, "graphql/registry.ts");
392
+ nuxt.options.alias["#graphql/registry"] = registryPath;
393
+ async function generateGraphQLRegistry() {
394
+ const registryContent = await getRegistryContent({ layerRootDirs, rootDir, documents });
395
+ writeFileIfChanged(registryPath, registryContent);
396
+ }
397
+ let artifactsPromise;
398
+ async function generateGraphQLArtifacts() {
399
+ if (!artifactsPromise) {
400
+ artifactsPromise = (async () => {
401
+ await generateGraphQLSDL();
402
+ await generateGraphQLConfig();
403
+ await generateGraphQLCodegen();
404
+ await generateGraphQLRegistry();
405
+ })();
406
+ }
407
+ return artifactsPromise;
408
+ }
409
+ addServerHandler({ route: "/api/graphql", handler: resolve("./runtime/server/api/yoga-handler") });
410
+ nuxt.hook("listen", (_, { url }) => {
411
+ logger.success(`GraphQL Yoga ready: ${cyan}${url.replace(/\/$/, "")}/api/graphql${reset}`);
412
+ });
413
+ addServerImportsDir(resolve("./runtime/server/utils"));
414
+ addPlugin(resolve("./runtime/app/plugins/graphql-request"));
415
+ addPlugin(resolve("./runtime/app/plugins/graphql-sse.client"));
416
+ addImportsDir(resolve("./runtime/app/composables"));
417
+ nuxt.hook("nitro:config", async (nitroConfig) => {
418
+ nitroConfig.alias ||= {};
419
+ for (const [proxyName, proxyContent] of Object.entries(serverProxies)) {
420
+ const proxyPath = join(buildDir, "graphql", `${proxyName}.ts`);
421
+ nitroConfig.alias[`#graphql/${proxyName}`] = proxyPath;
422
+ writeFileIfChanged(proxyPath, proxyContent);
423
+ }
424
+ await generateGraphQLArtifacts();
425
+ });
426
+ nuxt.hook("prepare:types", async ({ references }) => {
427
+ await generateGraphQLArtifacts();
428
+ references.push({ path: registryPath });
429
+ references.push({ path: typedDocumentsPath });
430
+ references.push({ path: zodPath });
431
+ });
432
+ if (nuxt.options.dev) {
433
+ nuxt.hook("builder:watch", async (_event, path) => {
434
+ if (path.endsWith(".gql")) {
435
+ logger.info(`GraphQL document change detected: ${cyan}${toRelativePath(nuxt.options.rootDir, path)}${reset}`);
436
+ await generateGraphQLCodegen();
437
+ await generateGraphQLRegistry();
467
438
  }
468
- };
469
- addPlugin(resolve("./runtime/app/plugins/graphql"));
470
- addImportsDir(resolve("./runtime/app/composables"));
471
- addServerImportsDir(resolve("./runtime/server/utils"));
439
+ });
472
440
  }
473
- await setupContextSchemas();
474
- await setupCodegen();
475
- setupYogaHandler();
476
- setupClient();
477
441
  }
478
442
  });
479
443