@lewebsimple/nuxt-graphql 0.1.7 → 0.1.9

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
@@ -23,6 +23,25 @@ Install the module to your Nuxt application with one command:
23
23
  npx nuxi module add @lewebsimple/nuxt-graphql
24
24
  ```
25
25
 
26
+ Optionnally adjust options in your Nuxt config. The defaults shown below:
27
+
28
+ ```ts
29
+ // nuxt.config.ts
30
+ export default defineNuxtConfig({
31
+ modules: ["@lewebsimple/nuxt-graphql"],
32
+ graphql: {
33
+ // GraphQL HTTP endpoint served by Yoga
34
+ endpoint: "/api/graphql",
35
+ // Codegen controls document scanning and outputs
36
+ codegen: {
37
+ enabled: true,
38
+ pattern: "**/*.gql", // scan .gql files across layers
39
+ schemaOutput: "server/graphql/schema.graphql", // saved SDL
40
+ },
41
+ },
42
+ });
43
+ ```
44
+
26
45
  Define your GraphQL schema in `server/graphql/schema.ts`:
27
46
 
28
47
  ```ts
@@ -43,7 +62,7 @@ export const schema = createSchema<GraphQLContext>({
43
62
  });
44
63
  ```
45
64
 
46
- Define your GraphQL context in `server/graphql/context.ts`:
65
+ Optionnally define your GraphQL context in `server/graphql/context.ts`:
47
66
 
48
67
  ```ts
49
68
  import type { H3Event } from "h3";
@@ -59,6 +78,7 @@ export type GraphQLContext = Awaited<ReturnType<typeof createContext>>;
59
78
 
60
79
  That's it! You can now use Nuxt GraphQL in your Nuxt app ✨
61
80
 
81
+ Yoga GraphiQL is available at `http://localhost:3000/api/graphql` by default.
62
82
 
63
83
  ## Contribution
64
84
 
package/dist/module.d.mts CHANGED
@@ -3,7 +3,6 @@ import * as _nuxt_schema from '@nuxt/schema';
3
3
  interface ModuleOptions {
4
4
  endpoint?: string;
5
5
  codegen?: {
6
- enabled?: boolean;
7
6
  pattern?: string;
8
7
  schemaOutput?: string;
9
8
  };
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lewebsimple/nuxt-graphql",
3
3
  "configKey": "graphql",
4
- "version": "0.1.7",
4
+ "version": "0.1.9",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -44,6 +44,16 @@ function writeFileIfChanged(path, content) {
44
44
  return true;
45
45
  }
46
46
 
47
+ async function loadGraphQLSchema(schemaPath) {
48
+ const { createJiti } = await import('jiti');
49
+ const jiti = createJiti(import.meta.url, { interopDefault: true });
50
+ const module = await jiti.import(schemaPath);
51
+ if (!module.schema) {
52
+ throw new Error(`${schemaPath} must export a 'schema' variable`);
53
+ }
54
+ const { printSchema, lexicographicSortSchema } = await import('graphql');
55
+ return printSchema(lexicographicSortSchema(module.schema));
56
+ }
47
57
  function analyzeGraphQLDocuments(docs) {
48
58
  const byFile = /* @__PURE__ */ new Map();
49
59
  const operationsByType = {
@@ -92,9 +102,9 @@ function analyzeGraphQLDocuments(docs) {
92
102
  return { byFile, operationsByType };
93
103
  }
94
104
  function generateRegistryByTypeSource(analysis) {
95
- const q = analysis.query.map((o) => o.name);
96
- const m = analysis.mutation.map((o) => o.name);
97
- const s = analysis.subscription.map((o) => o.name);
105
+ const queries = analysis.query.map((o) => o.name);
106
+ const mutations = analysis.mutation.map((o) => o.name);
107
+ const subscriptions = analysis.subscription.map((o) => o.name);
98
108
  const lines = [
99
109
  `import type { TypedDocumentNode } from "@graphql-typed-document-node/core";`,
100
110
  `import * as ops from "#graphql/operations";`,
@@ -102,11 +112,11 @@ function generateRegistryByTypeSource(analysis) {
102
112
  `type ResultOf<T> = T extends { __apiType?: (variables: infer _) => infer R } ? R : never;`,
103
113
  `type VariablesOf<T> = T extends { __apiType?: (variables: infer V) => infer _ } ? V : never;`
104
114
  ];
105
- if (q.length > 0) {
115
+ if (queries.length > 0) {
106
116
  lines.push(
107
117
  ``,
108
118
  `export const queries = {`,
109
- ...q.map((name) => ` ${name}: ops.${name}Document,`),
119
+ ...queries.map((name) => ` ${name}: ops.${name}Document,`),
110
120
  `} as const;`
111
121
  );
112
122
  } else {
@@ -117,11 +127,11 @@ function generateRegistryByTypeSource(analysis) {
117
127
  `export type QueryResult<N extends QueryName> = ResultOf<(typeof queries)[N]>;`,
118
128
  `export type QueryVariables<N extends QueryName> = VariablesOf<(typeof queries)[N]>;`
119
129
  );
120
- if (m.length > 0) {
130
+ if (mutations.length > 0) {
121
131
  lines.push(
122
132
  ``,
123
133
  `export const mutations = {`,
124
- ...m.map((name) => ` ${name}: ops.${name}Document,`),
134
+ ...mutations.map((name) => ` ${name}: ops.${name}Document,`),
125
135
  `} as const;`
126
136
  );
127
137
  } else {
@@ -132,11 +142,11 @@ function generateRegistryByTypeSource(analysis) {
132
142
  `export type MutationResult<N extends MutationName> = ResultOf<(typeof mutations)[N]>;`,
133
143
  `export type MutationVariables<N extends MutationName> = VariablesOf<(typeof mutations)[N]>;`
134
144
  );
135
- if (s.length > 0) {
145
+ if (subscriptions.length > 0) {
136
146
  lines.push(
137
147
  ``,
138
148
  `export const subscriptions = {`,
139
- ...s.map((name) => ` ${name}: ops.${name}Document,`),
149
+ ...subscriptions.map((name) => ` ${name}: ops.${name}Document,`),
140
150
  `} as const;`
141
151
  );
142
152
  } else {
@@ -164,16 +174,6 @@ function formatDefinitions(defs) {
164
174
  };
165
175
  return defs.map((def) => `${colorOf(def)}${def.name}${reset}`).join(`${dim} / ${reset}`);
166
176
  }
167
- async function loadGraphQLSchema(schemaPath) {
168
- const { createJiti } = await import('jiti');
169
- const jiti = createJiti(import.meta.url, { interopDefault: true });
170
- const module = await jiti.import(schemaPath);
171
- if (!module.schema) {
172
- throw new Error(`${schemaPath} must export a 'schema' variable`);
173
- }
174
- const { printSchema, lexicographicSortSchema } = await import('graphql');
175
- return printSchema(lexicographicSortSchema(module.schema));
176
- }
177
177
  async function runCodegen(options) {
178
178
  const { sdl, documents, operationsFile } = options;
179
179
  if (documents.length === 0) {
@@ -219,13 +219,17 @@ const module$1 = defineNuxtModule({
219
219
  defaults: {
220
220
  endpoint: "/api/graphql",
221
221
  codegen: {
222
- enabled: true,
223
222
  pattern: "**/*.gql",
224
223
  schemaOutput: "server/graphql/schema.graphql"
225
224
  }
226
225
  },
227
226
  async setup(options, nuxt) {
228
227
  const { resolve } = createResolver(import.meta.url);
228
+ if (options.endpoint) {
229
+ if (!options.endpoint.startsWith("/")) {
230
+ logger.warn("GraphQL endpoint should start with '/' (e.g., '/api/graphql')");
231
+ }
232
+ }
229
233
  const { rootDir, serverDir } = nuxt.options;
230
234
  const layerDirs = [
231
235
  ...getLayerDirectories(nuxt),
@@ -250,51 +254,54 @@ const module$1 = defineNuxtModule({
250
254
  logger.success(`GraphQL Yoga ready at ${cyan}${url.replace(/\/$/, "")}${endpoint}${reset}`);
251
255
  });
252
256
  nuxt.options.runtimeConfig.public.graphql = { endpoint };
253
- if (options.codegen?.enabled !== false) {
254
- const codegenPattern = options.codegen?.pattern ?? "**/*.gql";
255
- const graphqlrcFile = join(rootDir, ".graphqlrc");
256
- const operationsFile = join(nuxt.options.buildDir, "graphql/operations.ts");
257
- const registryFile = join(nuxt.options.buildDir, "graphql/registry.ts");
258
- const schemaOutput = options.codegen?.schemaOutput ?? "server/graphql/schema.graphql";
259
- const schemaFile = join(rootDir, schemaOutput);
260
- const generate = async () => {
261
- const [sdl, documents] = await Promise.all([
262
- loadGraphQLSchema(schemaPath),
263
- findMultipleFiles(layerRootDirs, codegenPattern)
264
- ]);
265
- const docs = documents.map((document) => ({ path: document, content: readFileSync(document, "utf-8") }));
266
- const analysis = analyzeGraphQLDocuments(docs);
267
- for (const doc of docs) {
268
- const relativePath = doc.path.startsWith(rootDir) ? doc.path.slice(rootDir.length + 1) : doc.path;
269
- const defs = analysis.byFile.get(doc.path) ?? [];
270
- logger.info(`${cyan}${relativePath}${reset} [${formatDefinitions(defs)}]`);
271
- }
272
- await runCodegen({ sdl, documents, operationsFile });
273
- if (writeFileIfChanged(schemaFile, sdl)) {
274
- logger.info(`GraphQL schema saved to ${cyan}${schemaOutput}${reset}`);
275
- }
276
- const config = JSON.stringify({ schema: relative(rootDir, schemaFile), documents: codegenPattern }, null, 2);
277
- if (writeFileIfChanged(graphqlrcFile, config)) {
278
- logger.info(`GraphQL config saved to ${cyan}.graphqlrc${reset}`);
279
- }
280
- if (writeFileIfChanged(registryFile, generateRegistryByTypeSource(analysis.operationsByType))) {
281
- logger.info(`GraphQL registry saved to ${cyan}${relative(rootDir, registryFile)}${reset}`);
257
+ const codegenPattern = options.codegen?.pattern ?? "**/*.gql";
258
+ const graphqlrcFile = join(rootDir, ".graphqlrc");
259
+ const operationsFile = join(nuxt.options.buildDir, "graphql/operations.ts");
260
+ const registryFile = join(nuxt.options.buildDir, "graphql/registry.ts");
261
+ nuxt.options.alias["#graphql/operations"] = operationsFile;
262
+ nuxt.options.alias["#graphql/registry"] = registryFile;
263
+ const schemaOutput = options.codegen?.schemaOutput ?? "server/graphql/schema.graphql";
264
+ if (schemaOutput) {
265
+ if (!schemaOutput.endsWith(".graphql")) {
266
+ logger.warn(`Schema output '${schemaOutput}' should have .graphql extension.`);
267
+ }
268
+ }
269
+ const schemaFile = join(rootDir, schemaOutput);
270
+ const generate = async () => {
271
+ const [sdl, documents] = await Promise.all([
272
+ loadGraphQLSchema(schemaPath),
273
+ findMultipleFiles(layerRootDirs, codegenPattern)
274
+ ]);
275
+ const docs = documents.map((document) => ({ path: document, content: readFileSync(document, "utf-8") }));
276
+ const analysis = analyzeGraphQLDocuments(docs);
277
+ for (const doc of docs) {
278
+ const relativePath = doc.path.startsWith(rootDir) ? doc.path.slice(rootDir.length + 1) : doc.path;
279
+ const defs = analysis.byFile.get(doc.path) ?? [];
280
+ logger.info(`${cyan}${relativePath}${reset} [${formatDefinitions(defs)}]`);
281
+ }
282
+ await runCodegen({ sdl, documents, operationsFile });
283
+ if (writeFileIfChanged(schemaFile, sdl)) {
284
+ logger.info(`GraphQL schema saved to ${cyan}${schemaOutput}${reset}`);
285
+ }
286
+ const config = JSON.stringify({ schema: relative(rootDir, schemaFile), documents: codegenPattern }, null, 2);
287
+ if (writeFileIfChanged(graphqlrcFile, config)) {
288
+ logger.info(`GraphQL config saved to ${cyan}.graphqlrc${reset}`);
289
+ }
290
+ if (writeFileIfChanged(registryFile, generateRegistryByTypeSource(analysis.operationsByType))) {
291
+ logger.info(`GraphQL registry saved to ${cyan}${relative(rootDir, registryFile)}${reset}`);
292
+ }
293
+ };
294
+ nuxt.hook("prepare:types", async ({ references }) => {
295
+ await generate();
296
+ if (existsSync(operationsFile)) references.push({ path: operationsFile });
297
+ if (existsSync(registryFile)) references.push({ path: registryFile });
298
+ });
299
+ if (nuxt.options.dev) {
300
+ nuxt.hook("builder:watch", async (event, path) => {
301
+ if (path.endsWith(".gql")) {
302
+ await generate();
282
303
  }
283
- };
284
- nuxt.hook("prepare:types", async ({ references }) => {
285
- await generate();
286
- if (existsSync(operationsFile)) references.push({ path: operationsFile });
287
- if (existsSync(registryFile)) references.push({ path: registryFile });
288
304
  });
289
- if (nuxt.options.dev) {
290
- nuxt.hook("builder:watch", async (event, path) => {
291
- if (path.endsWith(".gql")) {
292
- await generate();
293
- }
294
- });
295
- }
296
- nuxt.options.alias["#graphql/operations"] = operationsFile;
297
- nuxt.options.alias["#graphql/registry"] = registryFile;
298
305
  }
299
306
  addImportsDir(resolve("./runtime/composables"));
300
307
  addPlugin(resolve("./runtime/plugins/graphql"));
@@ -1,10 +1,9 @@
1
- import { type Ref } from "vue";
2
1
  import { type MutationName, type MutationResult, type MutationVariables } from "#graphql/registry";
3
- type IsEmptyObject<T> = T extends Record<string, never> ? true : keyof T extends never ? true : false;
2
+ import type { IsEmptyObject } from "../utils/helpers.js";
4
3
  export declare function useGraphQLMutation<N extends MutationName>(operationName: N): {
5
- mutate: (...args: IsEmptyObject<MutationVariables<N>> extends true ? [variables?: MutationVariables<N>] : [variables: MutationVariables<N>]) => Promise<MutationResult<N>>;
6
- data: Ref<MutationResult<N> | null, MutationResult<N> | null>;
7
- error: Ref<Error | null, Error | null>;
8
- pending: Ref<boolean, boolean>;
4
+ mutate: (...args: IsEmptyObject<MutationVariables<N>> extends true ? [variables?: MutationVariables<N>] : [variables: MutationVariables<N>]) => Promise<{
5
+ data: MutationResult<N> | null;
6
+ error: Error | null;
7
+ }>;
8
+ pending: import("vue").Ref<boolean, boolean>;
9
9
  };
10
- export {};
@@ -4,22 +4,18 @@ import { mutations } from "#graphql/registry";
4
4
  export function useGraphQLMutation(operationName) {
5
5
  const document = mutations[operationName];
6
6
  const { request } = useGraphQL();
7
- const data = ref(null);
8
- const error = ref(null);
9
7
  const pending = ref(false);
10
8
  async function mutate(...args) {
11
9
  pending.value = true;
12
- error.value = null;
13
10
  try {
14
11
  const result = await request(document, args[0]);
15
- data.value = result;
16
- return result;
12
+ return { data: result, error: null };
17
13
  } catch (e) {
18
- error.value = e instanceof Error ? e : new Error(String(e));
19
- throw e;
14
+ const error = e instanceof Error ? e : new Error(String(e));
15
+ return { data: null, error };
20
16
  } finally {
21
17
  pending.value = false;
22
18
  }
23
19
  }
24
- return { mutate, data, error, pending };
20
+ return { mutate, pending };
25
21
  }
@@ -1,5 +1,4 @@
1
1
  import type { AsyncData, AsyncDataOptions } from "#app";
2
2
  import { type QueryName, type QueryResult, type QueryVariables } from "#graphql/registry";
3
- type IsEmptyObject<T> = T extends Record<string, never> ? true : keyof T extends never ? true : false;
3
+ import type { IsEmptyObject } from "../utils/helpers.js";
4
4
  export declare function useGraphQLQuery<N extends QueryName>(operationName: N, ...args: IsEmptyObject<QueryVariables<N>> extends true ? [variables?: QueryVariables<N>, opts?: AsyncDataOptions<QueryResult<N>>] : [variables: QueryVariables<N>, opts?: AsyncDataOptions<QueryResult<N>>]): AsyncData<QueryResult<N>, Error | null>;
5
- export {};
@@ -6,6 +6,6 @@ export function useGraphQLQuery(operationName, ...args) {
6
6
  const document = queries[operationName];
7
7
  const [variables, opts] = args;
8
8
  const { request } = useGraphQL();
9
- const key = `gql:q:${operationName}:${hash(variables ?? {})}`;
9
+ const key = `graphql:query:${operationName}:${hash(variables ?? {})}`;
10
10
  return useAsyncData(key, () => request(document, variables), opts);
11
11
  }
@@ -0,0 +1 @@
1
+ export type IsEmptyObject<T> = T extends Record<string, never> ? true : keyof T extends never ? true : false;
File without changes
@@ -1,7 +1,8 @@
1
1
  import { createYoga } from "graphql-yoga";
2
- import { defineEventHandler, toWebRequest, sendWebResponse } from "h3";
2
+ import { defineEventHandler, toWebRequest, sendWebResponse, createError } from "h3";
3
3
  import { schema } from "#graphql/schema";
4
4
  import { createContext } from "#graphql/context";
5
+ import { useLogger } from "@nuxt/kit";
5
6
 
6
7
  let yoga = null;
7
8
 
@@ -11,14 +12,23 @@ function getYoga() {
11
12
  schema,
12
13
  graphqlEndpoint: "{{endpoint}}",
13
14
  fetchAPI: globalThis,
15
+ graphiql: process.env.NODE_ENV !== "production",
14
16
  });
15
17
  }
16
18
  return yoga;
17
19
  }
18
20
 
19
21
  export default defineEventHandler(async (event) => {
20
- const request = toWebRequest(event);
21
- const context = await createContext(event);
22
- const response = await getYoga().handleRequest(request, context);
23
- return sendWebResponse(event, response);
22
+ const logger = useLogger();
23
+ try {
24
+ const request = toWebRequest(event);
25
+ const context = await createContext(event);
26
+ const response = await getYoga().handleRequest(request, context);
27
+ return sendWebResponse(event, response);
28
+ }
29
+ catch (error) {
30
+ const message = error instanceof Error ? error.message : String(error);
31
+ logger.error("GraphQL Server Error:", message);
32
+ throw createError({ statusCode: 500, message: "GraphQL server error" });
33
+ }
24
34
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lewebsimple/nuxt-graphql",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Opinionated Nuxt module for using GraphQL",
5
5
  "repository": "lewebsimple/nuxt-graphql",
6
6
  "license": "MIT",
@@ -22,17 +22,6 @@
22
22
  "files": [
23
23
  "dist"
24
24
  ],
25
- "scripts": {
26
- "prepack": "nuxt-module-build build",
27
- "dev": "npm run dev:prepare && nuxi dev playground",
28
- "dev:build": "nuxi build playground",
29
- "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground",
30
- "release": "npm run lint && npm run test && npm run prepack && changelogen --release && npm publish && git push --follow-tags",
31
- "lint": "eslint .",
32
- "test": "vitest run",
33
- "test:watch": "vitest watch",
34
- "test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit"
35
- },
36
25
  "dependencies": {
37
26
  "@graphql-codegen/cli": "^5.0.7",
38
27
  "@graphql-codegen/typed-document-node": "^5.1.2",
@@ -42,7 +31,7 @@
42
31
  "@nuxt/kit": "^4.2.2",
43
32
  "graphql": "^16.12.0",
44
33
  "graphql-request": "^7.4.0",
45
- "graphql-yoga": "^5.17.1",
34
+ "graphql-yoga": "^5.18.0",
46
35
  "jiti": "^2.6.1",
47
36
  "ohash": "^2.0.11",
48
37
  "tinyglobby": "^0.2.15"
@@ -59,10 +48,25 @@
59
48
  "eslint": "^9.39.2",
60
49
  "nuxt": "^4.2.2",
61
50
  "typescript": "~5.9.3",
62
- "vitest": "^4.0.15",
63
- "vue-tsc": "^3.1.8"
51
+ "vitest": "^4.0.16",
52
+ "vue-tsc": "^3.2.1"
64
53
  },
65
54
  "publishConfig": {
66
55
  "access": "public"
56
+ },
57
+ "changlog": {
58
+ "types": {
59
+ "chore": false
60
+ }
61
+ },
62
+ "scripts": {
63
+ "dev": "npm run dev:prepare && nuxi dev playground",
64
+ "dev:build": "nuxi build playground",
65
+ "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground",
66
+ "release": "npm run lint && npm run test && npm run prepack && changelogen --release --push && npm publish",
67
+ "lint": "eslint .",
68
+ "test": "vitest run",
69
+ "test:watch": "vitest watch",
70
+ "test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit"
67
71
  }
68
72
  }