@lewebsimple/nuxt-graphql 0.2.2 → 0.3.1

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