@lewebsimple/nuxt-graphql 0.2.0 โ†’ 0.2.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.
package/README.md CHANGED
@@ -14,6 +14,7 @@ Opinionated Nuxt module for using GraphQL Yoga with graphql-request / graphql-ss
14
14
  - ๐Ÿง˜โ€โ™‚๏ธ GraphQL Yoga server handler with user-provided schema / context
15
15
  - ๐Ÿ“„ Auto-import GraphQL documents from `**/*.gql` (configurable)
16
16
  - ๐Ÿงฉ Type-safe composables to call operations by name, i.e. `useGraphQLQuery("Hello")`
17
+ - ๐Ÿงต Optional stitching of local schema with remote schemas (custom headers), with stitched SDL emitted to `server/graphql/schema.graphql`
17
18
 
18
19
  ## Quick Setup
19
20
 
package/dist/module.d.mts CHANGED
@@ -1,18 +1,31 @@
1
1
  import * as _nuxt_schema from '@nuxt/schema';
2
2
  import { GraphQLCacheConfig } from '../dist/runtime/app/utils/graphql-cache.js';
3
- import { CodegenConfig } from '@graphql-codegen/cli';
4
3
 
5
- interface ModuleOptions {
4
+ type LocalSchema = {
5
+ type: "local";
6
+ path: string;
7
+ };
8
+ type RemoteSchema = {
9
+ type: "remote";
10
+ url: string;
6
11
  headers?: Record<string, string>;
7
- cache?: Partial<GraphQLCacheConfig>;
12
+ };
13
+ type SchemaDefinition = LocalSchema | RemoteSchema;
14
+
15
+ interface ModuleOptions {
16
+ context?: string;
17
+ schemas: Record<string, SchemaDefinition>;
8
18
  codegen?: {
9
- pattern?: string;
10
- schemaOutput?: string;
19
+ documents?: string;
20
+ saveSchema?: string;
11
21
  scalars?: Record<string, string | {
12
22
  input: string;
13
23
  output: string;
14
24
  }>;
15
- generates?: CodegenConfig["generates"];
25
+ };
26
+ client?: {
27
+ cache?: Partial<GraphQLCacheConfig>;
28
+ headers?: Record<string, string>;
16
29
  };
17
30
  }
18
31
  declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lewebsimple/nuxt-graphql",
3
3
  "configKey": "graphql",
4
- "version": "0.2.0",
4
+ "version": "0.2.1",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -1,9 +1,9 @@
1
- import { useLogger, defineNuxtModule, createResolver, getLayerDirectories, addServerHandler, addImportsDir, addServerImportsDir, addPlugin } from '@nuxt/kit';
1
+ import { useLogger, defineNuxtModule, createResolver, getLayerDirectories, addServerHandler, addPlugin, addImportsDir, addServerImportsDir } from '@nuxt/kit';
2
+ import { join, parse, relative, dirname } from 'node:path';
2
3
  import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
3
- import { join, dirname, relative } from 'node:path';
4
4
  import { glob } from 'tinyglobby';
5
5
  import { generate } from '@graphql-codegen/cli';
6
- import { parse, Kind } from 'graphql';
6
+ import { parse as parse$1, Kind, getIntrospectionQuery, buildClientSchema, printSchema } from 'graphql';
7
7
  import { GRAPHQL_ENDPOINT } from '../dist/runtime/server/lib/constants.js';
8
8
 
9
9
  const logger = useLogger("@lewebsimple/nuxt-graphql");
@@ -44,8 +44,16 @@ function writeFileIfChanged(path, content) {
44
44
  writeFileSync(path, content, "utf-8");
45
45
  return true;
46
46
  }
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;
52
+ }
53
+ return importPath;
54
+ }
47
55
 
48
- async function loadGraphQLSchema(schemaPath) {
56
+ async function loadSchemaSdl(schemaPath) {
49
57
  const { createJiti } = await import('jiti');
50
58
  const jiti = createJiti(import.meta.url, { interopDefault: true });
51
59
  const module = await jiti.import(schemaPath);
@@ -55,7 +63,67 @@ async function loadGraphQLSchema(schemaPath) {
55
63
  const { printSchema, lexicographicSortSchema } = await import('graphql');
56
64
  return printSchema(lexicographicSortSchema(module.schema));
57
65
  }
58
- function analyzeGraphQLDocuments(docs) {
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()";
101
+ }
102
+ }
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);
124
+ }
125
+ function analyzeDocuments(documents) {
126
+ const docs = documents.map((path) => ({ path, content: readFileSync(path, "utf-8") }));
59
127
  const byFile = /* @__PURE__ */ new Map();
60
128
  const operationsByType = {
61
129
  query: [],
@@ -65,7 +133,7 @@ function analyzeGraphQLDocuments(docs) {
65
133
  const operationNameToFile = /* @__PURE__ */ new Map();
66
134
  const fragmentNameToFile = /* @__PURE__ */ new Map();
67
135
  for (const doc of docs) {
68
- const ast = parse(doc.content);
136
+ const ast = parse$1(doc.content);
69
137
  const defs = [];
70
138
  for (const def of ast.definitions) {
71
139
  if (def.kind === Kind.FRAGMENT_DEFINITION) {
@@ -102,11 +170,26 @@ function analyzeGraphQLDocuments(docs) {
102
170
  }
103
171
  return { byFile, operationsByType };
104
172
  }
105
- function generateRegistryByTypeSource(analysis) {
106
- const queries = analysis.query.map((o) => o.name);
107
- const mutations = analysis.mutation.map((o) => o.name);
108
- const subscriptions = analysis.subscription.map((o) => o.name);
109
- const lines = [
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
+ const content = [
110
193
  `import type { TypedDocumentNode } from "@graphql-typed-document-node/core";`,
111
194
  `import * as ops from "#graphql/operations";`,
112
195
  ``,
@@ -114,138 +197,135 @@ function generateRegistryByTypeSource(analysis) {
114
197
  `type VariablesOf<T> = T extends { __apiType?: (variables: infer V) => infer _ } ? V : never;`
115
198
  ];
116
199
  if (queries.length > 0) {
117
- lines.push(
200
+ content.push(
118
201
  ``,
202
+ `// Queries`,
119
203
  `export const queries = {`,
120
204
  ...queries.map((name) => ` ${name}: ops.${name}Document,`),
121
205
  `} as const;`
122
206
  );
123
207
  } else {
124
- lines.push(``, `export const queries = {} as const;`);
208
+ content.push(``, `export const queries = {} as const;`);
125
209
  }
126
- lines.push(
210
+ content.push(
211
+ ``,
127
212
  `export type QueryName = keyof typeof queries;`,
128
213
  `export type QueryResult<N extends QueryName> = ResultOf<(typeof queries)[N]>;`,
129
214
  `export type QueryVariables<N extends QueryName> = VariablesOf<(typeof queries)[N]>;`
130
215
  );
131
216
  if (mutations.length > 0) {
132
- lines.push(
217
+ content.push(
133
218
  ``,
219
+ `// Mutations`,
134
220
  `export const mutations = {`,
135
221
  ...mutations.map((name) => ` ${name}: ops.${name}Document,`),
136
222
  `} as const;`
137
223
  );
138
224
  } else {
139
- lines.push(``, `export const mutations = {} as const;`);
225
+ content.push(``, `export const mutations = {} as const;`);
140
226
  }
141
- lines.push(
227
+ content.push(
228
+ ``,
142
229
  `export type MutationName = keyof typeof mutations;`,
143
230
  `export type MutationResult<N extends MutationName> = ResultOf<(typeof mutations)[N]>;`,
144
231
  `export type MutationVariables<N extends MutationName> = VariablesOf<(typeof mutations)[N]>;`
145
232
  );
146
233
  if (subscriptions.length > 0) {
147
- lines.push(
234
+ content.push(
148
235
  ``,
236
+ `// Subscriptions`,
149
237
  `export const subscriptions = {`,
150
238
  ...subscriptions.map((name) => ` ${name}: ops.${name}Document,`),
151
239
  `} as const;`
152
240
  );
153
241
  } else {
154
- lines.push(``, `export const subscriptions = {} as const;`);
242
+ content.push(``, `export const subscriptions = {} as const;`);
155
243
  }
156
- lines.push(
244
+ content.push(
245
+ ``,
157
246
  `export type SubscriptionName = keyof typeof subscriptions;`,
158
247
  `export type SubscriptionResult<N extends SubscriptionName> = ResultOf<(typeof subscriptions)[N]>;`,
159
248
  `export type SubscriptionVariables<N extends SubscriptionName> = VariablesOf<(typeof subscriptions)[N]>;`
160
249
  );
161
- return lines.join("\n") + "\n";
250
+ return writeFileIfChanged(registryPath, content.join("\n") + "\n");
162
251
  }
163
- function formatDefinitions(defs) {
164
- if (defs.length === 0) return "";
165
- const colorOf = (def) => {
166
- if (def.kind === "fragment") return green;
167
- switch (def.type) {
168
- case "query":
169
- return blue;
170
- case "mutation":
171
- return magenta;
172
- case "subscription":
173
- return yellow;
174
- }
175
- };
176
- return defs.map((def) => `${colorOf(def)}${def.name}${reset}`).join(`${dim} / ${reset}`);
177
- }
178
- async function runCodegen(options) {
179
- const { schema, documents, operationsFile, schemasFile, scalars, generates: customGenerates } = options;
180
- if (documents.length === 0) {
181
- logger.warn("No GraphQL documents found");
182
- return;
183
- }
184
- const zodScalars = {};
185
- if (scalars) {
186
- for (const [name, config] of Object.entries(scalars)) {
187
- const inputType = typeof config === "string" ? config : config.input;
188
- switch (inputType) {
189
- case "Date":
190
- zodScalars[name] = "z.coerce.date()";
191
- break;
192
- case "number":
193
- zodScalars[name] = "z.coerce.number()";
194
- break;
195
- case "boolean":
196
- zodScalars[name] = "z.coerce.boolean()";
197
- break;
198
- default:
199
- zodScalars[name] = "z.string()";
200
- }
201
- }
252
+
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}`);
202
259
  }
203
- try {
204
- const generates = {
205
- [operationsFile]: {
206
- schema,
207
- documents,
208
- plugins: ["typescript", "typescript-operations", "typed-document-node"],
209
- config: {
210
- useTypeImports: true,
211
- enumsAsTypes: true,
212
- skipTypename: true,
213
- documentVariableSuffix: "Document",
214
- documentMode: "documentNode",
215
- strictScalars: true,
216
- defaultScalarType: "never",
217
- scalars
218
- }
219
- }
220
- };
221
- if (schemasFile) {
222
- generates[schemasFile] = {
223
- schema,
224
- documents,
225
- plugins: ["typescript-validation-schema"],
226
- config: {
227
- schema: "zodv4",
228
- importFrom: "#graphql/operations",
229
- useTypeImports: true,
230
- directives: {
231
- constraint: {
232
- minLength: "min",
233
- maxLength: "max",
234
- pattern: "regex"
235
- }
236
- },
237
- scalarSchemas: zodScalars
238
- }
239
- };
240
- if (customGenerates) {
241
- Object.assign(generates, customGenerates);
242
- }
243
- }
244
- await generate({ generates, silent: true, errorsOnly: true }, true);
245
- logger.success(`Generated types for ${documents.length} document(s)`);
246
- } catch (error) {
247
- logger.error("GraphQL codegen failed:", error instanceof Error ? error.message : error);
260
+ const content = [
261
+ `export { schema } from ${JSON.stringify(toImportPath(modulePath, localPath))};`
262
+ ].join("\n");
263
+ return writeFileIfChanged(modulePath, content);
264
+ }
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)}`);
248
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) : "{}";
282
+ const content = [
283
+ `import { buildSchema, print } from "graphql";`,
284
+ `import type { Executor } from "@graphql-tools/utils";`,
285
+ `import type { SubschemaConfig } from "@graphql-tools/delegate";`,
286
+ `import { sdl } from ${JSON.stringify(toImportPath(modulePath, remoteSdlPath))};`,
287
+ ``,
288
+ `const endpoint = ${JSON.stringify(url)};`,
289
+ `const headers = ${headerSource} as Record<string, string>;`,
290
+ ``,
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
+ `};`,
301
+ ``,
302
+ `export const schema: SubschemaConfig = {`,
303
+ ` schema: buildSchema(sdl),`,
304
+ ` executor,`,
305
+ `};`,
306
+ ``
307
+ ].join("\n");
308
+ return writeFileIfChanged(modulePath, content);
309
+ }
310
+ function writeStitchedSchemaModule(schemaNames, modulePath) {
311
+ const schemas = schemaNames.map((name) => ({
312
+ path: `./schemas/${name}`,
313
+ ref: `${name}Schema`
314
+ }));
315
+ 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)};`),
320
+ ``,
321
+ `const subschemas: Array<GraphQLSchema | SubschemaConfig> = [`,
322
+ ...schemas.map(({ ref }) => ` ${ref},`),
323
+ `];`,
324
+ ``,
325
+ `export const schema = stitchSchemas({ subschemas });`,
326
+ ``
327
+ ].join("\n");
328
+ return writeFileIfChanged(modulePath, content);
249
329
  }
250
330
 
251
331
  const module$1 = defineNuxtModule({
@@ -254,102 +334,96 @@ const module$1 = defineNuxtModule({
254
334
  configKey: "graphql"
255
335
  },
256
336
  defaults: {
337
+ schemas: {},
257
338
  codegen: {
258
- pattern: "**/*.gql",
259
- schemaOutput: "server/graphql/schema.graphql"
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
+ }
260
349
  }
261
350
  },
262
351
  async setup(options, nuxt) {
263
352
  const { resolve } = createResolver(import.meta.url);
264
- const { rootDir, serverDir } = nuxt.options;
265
- const layerDirs = [
266
- ...getLayerDirectories(nuxt),
267
- { root: rootDir, server: serverDir.replace(rootDir, `${rootDir}/playground`) }
268
- ];
269
- const layerServerDirs = layerDirs.map(({ server }) => server);
270
- const layerRootDirs = layerDirs.map(({ root }) => root);
271
- const schemaPath = await findSingleFile(layerServerDirs, "graphql/schema.{ts,mjs}", true);
272
- const contextPath = await findSingleFile(layerServerDirs, "graphql/context.{ts,mjs}") || resolve("./runtime/server/graphql/default-context.ts");
273
- const codegenPattern = options.codegen?.pattern ?? "**/*.gql";
274
- const graphqlrcFile = join(rootDir, ".graphqlrc");
275
- const operationsFile = join(nuxt.options.buildDir, "graphql/operations.ts");
276
- const registryFile = join(nuxt.options.buildDir, "graphql/registry.ts");
277
- const zodSchemasFile = join(nuxt.options.buildDir, "graphql/zod.ts");
278
- const schemaOutput = options.codegen?.schemaOutput ?? "server/graphql/schema.graphql";
279
- if (schemaOutput && !schemaOutput.endsWith(".graphql")) {
280
- logger.warn(`Schema output '${schemaOutput}' should have .graphql extension.`);
353
+ 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
+ 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.");
281
359
  }
282
- const schemaFile = join(rootDir, schemaOutput);
283
- const setupAliases = () => {
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}'`);
383
+ }
384
+ }
385
+ writeStitchedSchemaModule(Object.keys(options.schemas), stitchedPath);
284
386
  nuxt.hook("nitro:config", (config) => {
285
387
  config.alias ||= {};
286
- config.alias["#graphql/schema"] = schemaPath;
287
388
  config.alias["#graphql/context"] = contextPath;
288
- });
289
- nuxt.options.alias["#graphql/operations"] = operationsFile;
290
- nuxt.options.alias["#graphql/registry"] = registryFile;
291
- nuxt.options.alias["#graphql/zod"] = zodSchemasFile;
292
- };
293
- const setupHandler = () => {
294
- addServerHandler({ route: GRAPHQL_ENDPOINT, handler: resolve("./runtime/server/api/graphql-handler") });
295
- nuxt.hook("listen", (_, { url }) => {
296
- logger.success(`GraphQL Yoga ready at ${cyan}${url.replace(/\/$/, "")}${GRAPHQL_ENDPOINT}${reset}`);
297
- });
298
- };
299
- const setupRuntimeConfig = () => {
300
- nuxt.options.runtimeConfig.public.graphql = {
301
- endpoint: GRAPHQL_ENDPOINT,
302
- headers: options.headers || {},
303
- cache: {
304
- enabled: options.cache?.enabled ?? false,
305
- ttl: options.cache?.ttl ?? 6e4,
306
- storage: options.cache?.storage ?? "memory"
389
+ for (const name of Object.keys(options.schemas)) {
390
+ config.alias[`#graphql/schemas/${name}`] = schemasPath[name];
307
391
  }
308
- };
309
- };
310
- const setupCodegen = () => {
311
- const generate = async () => {
312
- const [schema, documents] = await Promise.all([
313
- loadGraphQLSchema(schemaPath),
314
- findMultipleFiles(layerRootDirs, codegenPattern)
315
- ]);
316
- const docs = documents.map((document) => ({ path: document, content: readFileSync(document, "utf-8") }));
317
- const analysis = analyzeGraphQLDocuments(docs);
318
- for (const doc of docs) {
319
- const relativePath = doc.path.startsWith(rootDir) ? doc.path.slice(rootDir.length + 1) : doc.path;
320
- const defs = analysis.byFile.get(doc.path) ?? [];
321
- logger.info(`${cyan}${relativePath}${reset} [${formatDefinitions(defs)}]`);
322
- }
323
- await runCodegen({
324
- schema,
325
- documents,
326
- operationsFile,
327
- schemasFile: zodSchemasFile,
328
- scalars: options.codegen?.scalars,
329
- generates: options.codegen?.generates
330
- });
331
- if (writeFileIfChanged(schemaFile, schema)) {
332
- logger.info(`GraphQL schema saved to ${cyan}${schemaOutput}${reset}`);
333
- }
334
- const graphqlrc = {
335
- schema: relative(rootDir, schemaFile),
336
- documents: codegenPattern
337
- };
338
- if (options.codegen?.scalars) {
339
- graphqlrc.scalars = options.codegen.scalars;
340
- }
341
- if (writeFileIfChanged(graphqlrcFile, JSON.stringify(graphqlrc, null, 2))) {
342
- logger.info(`GraphQL config saved to ${cyan}.graphqlrc${reset}`);
343
- }
344
- if (writeFileIfChanged(registryFile, generateRegistryByTypeSource(analysis.operationsByType))) {
345
- logger.info(`GraphQL registry saved to ${cyan}${relative(rootDir, registryFile)}${reset}`);
392
+ config.alias["#graphql/schema"] = stitchedPath;
393
+ });
394
+ }
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}`);
346
420
  }
347
- };
421
+ }
348
422
  nuxt.hook("prepare:types", async ({ references }) => {
349
423
  await generate();
350
- if (existsSync(operationsFile)) references.push({ path: operationsFile });
351
- if (existsSync(registryFile)) references.push({ path: registryFile });
352
- if (existsSync(zodSchemasFile)) references.push({ path: zodSchemasFile });
424
+ references.push({ path: operationsPath });
425
+ references.push({ path: registryPath });
426
+ references.push({ path: zodPath });
353
427
  });
354
428
  if (nuxt.options.dev) {
355
429
  nuxt.hook("builder:watch", async (event, path) => {
@@ -358,17 +432,31 @@ const module$1 = defineNuxtModule({
358
432
  }
359
433
  });
360
434
  }
361
- };
362
- const setupAppRuntime = () => {
435
+ }
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
+ });
441
+ }
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"
450
+ }
451
+ };
452
+ addPlugin(resolve("./runtime/app/plugins/graphql"));
363
453
  addImportsDir(resolve("./runtime/app/composables"));
364
454
  addServerImportsDir(resolve("./runtime/server/utils"));
365
- addPlugin(resolve("./runtime/app/plugins/graphql"));
366
- };
367
- setupAliases();
368
- setupHandler();
369
- setupRuntimeConfig();
370
- setupCodegen();
371
- setupAppRuntime();
455
+ }
456
+ await setupContextSchemas();
457
+ await setupCodegen();
458
+ setupYogaHandler();
459
+ setupClient();
372
460
  }
373
461
  });
374
462
 
@@ -1,5 +1,4 @@
1
- import { type MutationName, type MutationResult, type MutationVariables } from "#graphql/registry";
2
- import type { IsEmptyObject } from "../../../helpers/is-empty-object.js";
1
+ import { type MutationName, type MutationResult } from "#graphql/registry";
3
2
  /**
4
3
  * Client-side GraphQL mutation composable
5
4
  *
@@ -7,7 +6,7 @@ import type { IsEmptyObject } from "../../../helpers/is-empty-object.js";
7
6
  * @returns Object with mutate function and pending state
8
7
  */
9
8
  export declare function useGraphQLMutation<N extends MutationName>(operationName: N): {
10
- mutate: (...args: IsEmptyObject<MutationVariables<N>> extends true ? [variables?: MutationVariables<N>, headers?: HeadersInit] : [variables: MutationVariables<N>, headers?: HeadersInit]) => Promise<{
9
+ mutate: (variables: MutationVariables<N>, headers?: HeadersInit | undefined) => Promise<{
11
10
  data: MutationResult<N> | null;
12
11
  error: Error | null;
13
12
  }>;
@@ -1,16 +1,14 @@
1
1
  import { defineEventHandler, toWebRequest, sendWebResponse, createError } from "h3";
2
- import { logger } from "../lib/logger.js";
3
2
  import { getYoga } from "../lib/create-yoga.js";
4
- import { createContext } from "#graphql/context";
5
3
  export default defineEventHandler(async (event) => {
6
4
  try {
7
5
  const request = toWebRequest(event);
8
- const context = await createContext(event);
6
+ const context = {};
9
7
  const response = await getYoga().handleRequest(request, context);
10
8
  return sendWebResponse(event, response);
11
9
  } catch (error) {
12
10
  const message = error instanceof Error ? error.message : String(error);
13
- logger.error("GraphQL Server Error:", message);
11
+ console.error("GraphQL Server Error:", message);
14
12
  throw createError({ statusCode: 500, message: "GraphQL server error" });
15
13
  }
16
14
  });
@@ -1,6 +1,5 @@
1
1
  import type { H3Event } from "h3";
2
- import { type MutationName, type MutationResult, type MutationVariables } from "#graphql/registry";
3
- import type { IsEmptyObject } from "../../../helpers/is-empty-object.js";
2
+ import { type MutationName, type MutationResult } from "#graphql/registry";
4
3
  /**
5
4
  * Server-side GraphQL mutation composable
6
5
  *
@@ -9,7 +8,7 @@ import type { IsEmptyObject } from "../../../helpers/is-empty-object.js";
9
8
  * @returns Object with mutate function
10
9
  */
11
10
  export declare function useServerGraphQLMutation<N extends MutationName>(event: H3Event, operationName: N): Promise<{
12
- mutate: (...args: IsEmptyObject<MutationVariables<N>> extends true ? [variables?: MutationVariables<N>, headers?: HeadersInit] : [variables: MutationVariables<N>, headers?: HeadersInit]) => Promise<{
11
+ mutate: (variables: MutationVariables<N>, headers?: HeadersInit | undefined) => Promise<{
13
12
  data: MutationResult<N> | null;
14
13
  error: Error | null;
15
14
  }>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lewebsimple/nuxt-graphql",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Opinionated Nuxt module for using GraphQL",
5
5
  "repository": "lewebsimple/nuxt-graphql",
6
6
  "license": "MIT",
@@ -39,6 +39,10 @@
39
39
  "@graphql-codegen/typed-document-node": "^6.1.5",
40
40
  "@graphql-codegen/typescript": "^5.0.7",
41
41
  "@graphql-codegen/typescript-operations": "^5.0.7",
42
+ "@graphql-tools/delegate": "^12.0.2",
43
+ "@graphql-tools/stitch": "^10.1.6",
44
+ "@graphql-tools/utils": "^10.11.0",
45
+ "@graphql-tools/wrap": "^11.1.2",
42
46
  "@graphql-typed-document-node/core": "^3.2.0",
43
47
  "@nuxt/kit": "^4.2.2",
44
48
  "graphql": "^16.12.0",
@@ -59,13 +63,13 @@
59
63
  "@nuxt/schema": "^4.2.2",
60
64
  "@nuxt/test-utils": "^3.22.0",
61
65
  "@types/node": "latest",
62
- "@vitest/coverage-v8": "^4.0.16",
66
+ "@vitest/coverage-v8": "^3.2.4",
63
67
  "changelogen": "^0.6.2",
64
68
  "eslint": "^9.39.2",
65
69
  "nuxt": "^4.2.2",
66
70
  "typescript": "~5.9.3",
67
- "vitest": "^4.0.16",
68
- "vue-tsc": "^3.2.1"
71
+ "vitest": "^3.2.4",
72
+ "vue-tsc": "^3.2.2"
69
73
  },
70
74
  "publishConfig": {
71
75
  "access": "public"
@@ -1,2 +0,0 @@
1
- import { consola } from "consola";
2
- export declare const logger: consola.ConsolaInstance;
@@ -1,2 +0,0 @@
1
- import { consola } from "consola";
2
- export const logger = consola.withTag("graphql");
@@ -1 +0,0 @@
1
- export { default } from "./api/graphql-handler.js";
@@ -1 +0,0 @@
1
- export { default } from "./api/graphql-handler.js";