@lewebsimple/nuxt-graphql 0.2.1 → 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 (61) hide show
  1. package/README.md +366 -30
  2. package/dist/module.d.mts +34 -21
  3. package/dist/module.json +1 -1
  4. package/dist/module.mjs +336 -355
  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 -14
  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/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,288 +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
- const schemaHeader = "/* GraphQL */";
254
- const escapeSDL = (sdl) => sdl.replace(/`/g, "\\`");
255
- const serializeSDL = (sdl) => `${schemaHeader} \`${escapeSDL(sdl)}\``;
256
- function writeLocalSchemaModule(localPath, modulePath) {
257
- if (!existsSync(localPath)) {
258
- 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");
259
213
  }
260
- const content = [
261
- `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;`
262
218
  ].join("\n");
263
- return writeFileIfChanged(modulePath, content);
264
219
  }
265
- async function writeRemoteSchemaSdl({ url, headers }, sdlPath) {
266
- const response = await fetch(url, {
267
- method: "POST",
268
- headers: { "Content-Type": "application/json", ...headers },
269
- body: JSON.stringify({ query: getIntrospectionQuery() })
270
- });
271
- const json = await response.json();
272
- if (json.errors) {
273
- throw new Error(`Failed to fetch remote schema from ${url}: ${JSON.stringify(json.errors)}`);
274
- }
275
- const schema = buildClientSchema(json.data);
276
- const sdl = printSchema(schema);
277
- const content = [`export const sdl = ${serializeSDL(sdl)};`, ""].join("\n");
278
- return writeFileIfChanged(sdlPath, content);
279
- }
280
- function writeRemoteSchemaModule({ url, headers }, remoteSdlPath, modulePath) {
281
- const headerSource = headers && Object.keys(headers).length > 0 ? JSON.stringify(headers, null, 2) : "{}";
220
+
221
+ async function getLocalSchemaProxy({ layerRootDirs, schemaDef }) {
222
+ const schemaPath = await findSingleFile(layerRootDirs, schemaDef.path, true);
282
223
  const content = [
283
- `import { buildSchema, print } from "graphql";`,
284
- `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";`,
285
235
  `import type { SubschemaConfig } from "@graphql-tools/delegate";`,
286
- `import { sdl } from ${JSON.stringify(toImportPath(modulePath, remoteSdlPath))};`,
236
+ `import { remoteExecMiddlewareHandler } from "./${schemaName}-middleware";`,
237
+ `import { sdl } from "./${schemaName}-sdl";`,
238
+ `import { createRemoteExecutor } from "../remote-executor";`,
287
239
  ``,
288
- `const endpoint = ${JSON.stringify(url)};`,
289
- `const headers = ${headerSource} as Record<string, string>;`,
240
+ `const headers = ${JSON.stringify(schemaDef.headers ?? {}, null, 2)} as HeadersInit;`,
290
241
  ``,
291
- `const executor: Executor = async ({ document, variables }) => {`,
292
- ` const query = typeof document === "string" ? document : print(document);`,
293
- ` const response = await fetch(endpoint, {`,
294
- ` method: "POST",`,
295
- ` headers: { "Content-Type": "application/json", ...headers },`,
296
- ` body: JSON.stringify({ query, variables }),`,
297
- ` });`,
298
- ``,
299
- ` return response.json();`,
300
- `};`,
242
+ `const executor = createRemoteExecutor({`,
243
+ ` url: ${JSON.stringify(schemaDef.url)},`,
244
+ ` remoteName: ${JSON.stringify(schemaName)},`,
245
+ ` headers,`,
246
+ ` middleware: remoteExecMiddlewareHandler,`,
247
+ `});`,
301
248
  ``,
302
249
  `export const schema: SubschemaConfig = {`,
303
250
  ` schema: buildSchema(sdl),`,
304
251
  ` executor,`,
305
- `};`,
306
- ``
252
+ `};`
307
253
  ].join("\n");
308
- return writeFileIfChanged(modulePath, content);
254
+ return { middlewareContent, sdlContent, schemaContent };
309
255
  }
310
- function writeStitchedSchemaModule(schemaNames, modulePath) {
311
- const schemas = schemaNames.map((name) => ({
312
- path: `./schemas/${name}`,
313
- ref: `${name}Schema`
314
- }));
256
+ async function getStitchedSchemaProxy({ schemaNames }) {
315
257
  const content = [
316
- `import { stitchSchemas } from "@graphql-tools/stitch";`,
317
- `import type { GraphQLSchema } from "graphql";`,
318
- `import type { SubschemaConfig } from "@graphql-tools/delegate";`,
319
- ...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}`)}; `),
320
262
  ``,
321
263
  `const subschemas: Array<GraphQLSchema | SubschemaConfig> = [`,
322
- ...schemas.map(({ ref }) => ` ${ref},`),
323
- `];`,
264
+ ...schemaNames.map((name) => ` ${name}Schema, `),
265
+ `]; `,
324
266
  ``,
325
- `export const schema = stitchSchemas({ subschemas });`,
326
- ``
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;`
327
308
  ].join("\n");
328
- return writeFileIfChanged(modulePath, content);
329
309
  }
330
310
 
331
311
  const module$1 = defineNuxtModule({
@@ -333,130 +313,131 @@ const module$1 = defineNuxtModule({
333
313
  name: "@lewebsimple/nuxt-graphql",
334
314
  configKey: "graphql"
335
315
  },
336
- defaults: {
337
- schemas: {},
338
- codegen: {
339
- documents: "**/*.gql",
340
- saveSchema: "server/graphql/schema.graphql"
341
- },
342
- client: {
343
- headers: {},
344
- cache: {
345
- enabled: false,
346
- ttl: 6e4,
347
- storage: "memory"
348
- }
349
- }
350
- },
316
+ defaults: {},
351
317
  async setup(options, nuxt) {
352
318
  const { resolve } = createResolver(import.meta.url);
319
+ const { buildDir, rootDir } = nuxt.options;
353
320
  const layerRootDirs = getLayerDirectories(nuxt).map(({ root }) => root);
354
- const stitchedPath = join(nuxt.options.buildDir, "graphql/schema.ts");
355
- const sdlPath = join(nuxt.options.rootDir, options.codegen?.saveSchema || ".nuxt/graphql/schema.graphql");
356
321
  nuxt.options.alias ||= {};
357
- if (!Object.keys(options.schemas || {}).length) {
358
- throw new Error("No GraphQL schemas configured. Please set 'graphql.schemas' with at least one local or remote schema.");
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}`);
359
329
  }
360
- async function setupContextSchemas() {
361
- let contextPath;
362
- if (options.context) {
363
- contextPath = await findSingleFile(layerRootDirs, options.context, true);
364
- logger.info(`Using GraphQL context from ${cyan}${relative(nuxt.options.rootDir, contextPath)}${reset}`);
365
- } else {
366
- contextPath = resolve("./runtime/server/graphql/context");
367
- logger.info(`Using default GraphQL context`);
368
- }
369
- const schemasPath = {};
370
- for (const [name, schemaDef] of Object.entries(options.schemas)) {
371
- schemasPath[name] = join(nuxt.options.buildDir, `graphql/schemas/${name}.ts`);
372
- if (schemaDef.type === "local") {
373
- const localPath = await findSingleFile(layerRootDirs, schemaDef.path, true);
374
- writeLocalSchemaModule(localPath, schemasPath[name]);
375
- logger.info(`Local GraphQL schema "${blue}${name}${reset}" loaded from ${cyan}${relative(nuxt.options.rootDir, localPath)}${reset}`);
376
- } else if (schemaDef.type === "remote") {
377
- const remoteSdlPath = join(nuxt.options.buildDir, `graphql/schemas/${name}-sdl.ts`);
378
- await writeRemoteSchemaSdl(schemaDef, remoteSdlPath);
379
- writeRemoteSchemaModule(schemaDef, remoteSdlPath, schemasPath[name]);
380
- logger.info(`Remote GraphQL schema "${magenta}${name}${reset}" loaded from ${cyan}${schemaDef.url}${reset}`);
381
- } else {
382
- throw new Error(`Unknown schema type for schema '${name}'`);
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;
383
337
  }
384
- }
385
- writeStitchedSchemaModule(Object.keys(options.schemas), stitchedPath);
386
- nuxt.hook("nitro:config", (config) => {
387
- config.alias ||= {};
388
- config.alias["#graphql/context"] = contextPath;
389
- for (const name of Object.keys(options.schemas)) {
390
- config.alias[`#graphql/schemas/${name}`] = schemasPath[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;
391
344
  }
392
- config.alias["#graphql/schema"] = stitchedPath;
393
- });
345
+ default:
346
+ throw new Error(`Unsupported GraphQL schema type: ${schemaDef.type}`);
347
+ }
394
348
  }
395
- async function setupCodegen() {
396
- const configPath = join(nuxt.options.rootDir, ".graphqlrc");
397
- const operationsPath = nuxt.options.alias["#graphql/operations"] = join(nuxt.options.buildDir, "graphql/operations.ts");
398
- const registryPath = nuxt.options.alias["#graphql/registry"] = join(nuxt.options.buildDir, "graphql/registry.ts");
399
- const zodPath = nuxt.options.alias["#graphql/zod"] = join(nuxt.options.buildDir, "graphql/zod.ts");
400
- async function generate() {
401
- try {
402
- const sdlContent = await loadSchemaSdl(stitchedPath);
403
- writeFileIfChanged(sdlPath, sdlContent);
404
- const documentsPattern = options.codegen?.documents ?? "**/*.gql";
405
- const documents = await findMultipleFiles(layerRootDirs, documentsPattern);
406
- await runCodegen({ schema: sdlPath, documents, operationsPath, zodPath, scalars: options.codegen?.scalars });
407
- const analysis = analyzeDocuments(documents);
408
- analysis.byFile.forEach((defs, path) => {
409
- const relativePath = relative(nuxt.options.rootDir, path);
410
- logger.info(`${cyan}${relativePath}${reset} [${formatDefinitions(defs)}]`);
411
- });
412
- writeRegistryModule(registryPath, analysis);
413
- const config = {
414
- schema: relative(nuxt.options.rootDir, sdlPath),
415
- documents: documentsPattern
416
- };
417
- writeFileIfChanged(configPath, JSON.stringify(config, null, 2));
418
- } catch (error) {
419
- logger.warn(`GraphQL codegen failed: ${error.message}`);
420
- }
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}`);
421
363
  }
422
- nuxt.hook("prepare:types", async ({ references }) => {
423
- await generate();
424
- references.push({ path: operationsPath });
425
- references.push({ path: registryPath });
426
- references.push({ path: zodPath });
427
- });
428
- if (nuxt.options.dev) {
429
- nuxt.hook("builder:watch", async (event, path) => {
430
- if (path.endsWith(".gql")) {
431
- await generate();
432
- }
433
- });
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}`);
434
371
  }
435
372
  }
436
- function setupYogaHandler() {
437
- addServerHandler({ route: GRAPHQL_ENDPOINT, handler: resolve("./runtime/server/api/graphql-handler") });
438
- nuxt.hook("listen", (_, { url }) => {
439
- logger.success(`GraphQL Yoga ready at ${cyan}${url.replace(/\/$/, "")}${GRAPHQL_ENDPOINT}${reset}`);
440
- });
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
+ }
441
390
  }
442
- function setupClient() {
443
- nuxt.options.runtimeConfig.public.graphql = {
444
- endpoint: GRAPHQL_ENDPOINT,
445
- headers: options.client?.headers || {},
446
- cache: {
447
- enabled: options.client?.cache?.enabled ?? false,
448
- ttl: options.client?.cache?.ttl ?? 6e4,
449
- 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();
450
438
  }
451
- };
452
- addPlugin(resolve("./runtime/app/plugins/graphql"));
453
- addImportsDir(resolve("./runtime/app/composables"));
454
- addServerImportsDir(resolve("./runtime/server/utils"));
439
+ });
455
440
  }
456
- await setupContextSchemas();
457
- await setupCodegen();
458
- setupYogaHandler();
459
- setupClient();
460
441
  }
461
442
  });
462
443