@lewebsimple/nuxt-graphql 0.6.20 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +61 -74
  2. package/dist/module.d.mts +31 -5
  3. package/dist/module.json +3 -3
  4. package/dist/module.mjs +442 -341
  5. package/dist/runtime/app/composables/useAsyncGraphQLQuery.d.ts +14 -10
  6. package/dist/runtime/app/composables/useAsyncGraphQLQuery.js +71 -80
  7. package/dist/runtime/app/composables/useGraphQLCache.client.d.ts +9 -14
  8. package/dist/runtime/app/composables/useGraphQLCache.client.js +40 -61
  9. package/dist/runtime/app/composables/useGraphQLLoadMore.d.ts +16 -9
  10. package/dist/runtime/app/composables/useGraphQLLoadMore.js +36 -40
  11. package/dist/runtime/app/composables/useGraphQLMutation.d.ts +29 -31
  12. package/dist/runtime/app/composables/useGraphQLMutation.js +17 -32
  13. package/dist/runtime/app/composables/useGraphQLQuery.d.ts +10 -3
  14. package/dist/runtime/app/composables/useGraphQLQuery.js +4 -14
  15. package/dist/runtime/app/composables/useGraphQLSubscription.client.d.ts +4 -5
  16. package/dist/runtime/app/composables/useGraphQLSubscription.client.js +7 -5
  17. package/dist/runtime/app/lib/cache-config.d.ts +18 -0
  18. package/dist/runtime/app/lib/cache-config.js +9 -0
  19. package/dist/runtime/app/lib/cache.d.ts +81 -49
  20. package/dist/runtime/app/lib/cache.js +65 -55
  21. package/dist/runtime/app/lib/persisted.d.ts +18 -12
  22. package/dist/runtime/app/lib/persisted.js +42 -45
  23. package/dist/runtime/app/plugins/graphql-sse.client.js +1 -2
  24. package/dist/runtime/app/plugins/graphql.d.ts +24 -0
  25. package/dist/runtime/app/plugins/graphql.js +16 -0
  26. package/dist/runtime/server/api/graphql.d.ts +6 -0
  27. package/dist/runtime/server/api/graphql.js +1 -1
  28. package/dist/runtime/server/{utils/defineGraphQLContext.d.ts → lib/context.d.ts} +10 -1
  29. package/dist/runtime/server/lib/remote-executor.d.ts +42 -19
  30. package/dist/runtime/server/lib/remote-executor.js +11 -13
  31. package/dist/runtime/server/lib/yoga.d.ts +0 -1
  32. package/dist/runtime/server/lib/yoga.js +1 -2
  33. package/dist/runtime/server/tsconfig.json +1 -1
  34. package/dist/runtime/server/utils/execute-schema.d.ts +11 -0
  35. package/dist/runtime/server/utils/execute-schema.js +24 -0
  36. package/dist/runtime/shared/lib/headers.d.ts +14 -2
  37. package/dist/runtime/shared/lib/headers.js +18 -1
  38. package/dist/runtime/shared/utils/error.d.ts +39 -0
  39. package/dist/runtime/shared/utils/error.js +67 -0
  40. package/dist/runtime/shared/utils/execute.d.ts +33 -0
  41. package/dist/runtime/shared/utils/execute.js +26 -0
  42. package/dist/runtime/shared/utils/registry.d.ts +72 -0
  43. package/dist/runtime/shared/utils/registry.js +37 -0
  44. package/package.json +48 -36
  45. package/dist/runtime/app/lib/in-flight.d.ts +0 -14
  46. package/dist/runtime/app/lib/in-flight.js +0 -19
  47. package/dist/runtime/app/plugins/execute-graphql.d.ts +0 -18
  48. package/dist/runtime/app/plugins/execute-graphql.js +0 -25
  49. package/dist/runtime/server/lib/execute-graphql-schema.d.ts +0 -3
  50. package/dist/runtime/server/lib/execute-graphql-schema.js +0 -22
  51. package/dist/runtime/server/utils/defineRemoteExecutorHooks.d.ts +0 -8
  52. package/dist/runtime/server/utils/defineRemoteExecutorHooks.js +0 -3
  53. package/dist/runtime/server/utils/useGraphQLOperation.d.ts +0 -16
  54. package/dist/runtime/server/utils/useGraphQLOperation.js +0 -12
  55. package/dist/runtime/shared/lib/error.d.ts +0 -42
  56. package/dist/runtime/shared/lib/error.js +0 -52
  57. package/dist/runtime/shared/lib/registry.d.ts +0 -12
  58. package/dist/runtime/shared/lib/registry.js +0 -8
  59. package/dist/runtime/shared/lib/types.d.ts +0 -30
  60. package/dist/runtime/shared/lib/types.js +0 -0
  61. package/dist/runtime/shared/utils/execute-graphql-http.d.ts +0 -7
  62. package/dist/runtime/shared/utils/execute-graphql-http.js +0 -31
  63. /package/dist/runtime/server/{utils/defineGraphQLContext.js → lib/context.js} +0 -0
package/dist/module.mjs CHANGED
@@ -1,18 +1,21 @@
1
- import { mkdirSync, writeFileSync } from 'node:fs';
2
- import { relative, resolve, dirname } from 'node:path';
3
- import { defu } from 'defu';
1
+ import { mkdir, writeFile } from 'node:fs/promises';
2
+ import { relative, resolve, parse, dirname } from 'node:path';
4
3
  import { stitchSchemas } from '@graphql-tools/stitch';
5
- import { addTemplate, addServerTemplate, defineNuxtModule, useLogger, createResolver, addServerHandler, addPlugin, addImportsDir, addServerImportsDir } from '@nuxt/kit';
4
+ import { createResolver, addTemplate, addTypeTemplate, defineNuxtModule, useLogger, addServerImports, updateTemplates, addServerHandler, addPlugin, addImportsDir, addServerImportsDir } from '@nuxt/kit';
5
+ import { defu } from 'defu';
6
+ import { parse as parse$1, printSchema, lexicographicSortSchema, getIntrospectionQuery, buildClientSchema, GraphQLSchema, buildSchema } from 'graphql';
7
+ import { cyan } from 'picocolors';
8
+ import picomatch from 'picomatch';
6
9
  import { hash } from 'ohash';
7
- import { createRequire } from 'node:module';
8
- import { printSchema, lexicographicSortSchema, buildSchema, getIntrospectionQuery, buildClientSchema, GraphQLSchema, Kind } from 'graphql';
9
- import { loadDocuments } from '@graphql-tools/load';
10
+ import { CodeFileLoader } from '@graphql-tools/code-file-loader';
10
11
  import { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader';
12
+ import { loadDocuments as loadDocuments$1 } from '@graphql-tools/load';
11
13
  import { codegen } from '@graphql-codegen/core';
12
- import * as typescriptPlugin from '@graphql-codegen/typescript';
13
- import * as typescriptOperationsPlugin from '@graphql-codegen/typescript-operations';
14
- import * as typedDocumentNodePlugin from '@graphql-codegen/typed-document-node';
15
- import { resolveCacheConfig } from '../dist/runtime/app/lib/cache.js';
14
+ import zodPreset from '@lewebsimple/graphql-codegen-zod';
15
+ import { createRequire } from 'node:module';
16
+ import { resolveCacheConfig } from '../dist/runtime/app/lib/cache-config.js';
17
+
18
+ const version = "0.7.0";
16
19
 
17
20
  const buildCache = /* @__PURE__ */ new Map();
18
21
  function getCachedLoader(baseKey, loader) {
@@ -30,22 +33,98 @@ function getCachedLoader(baseKey, loader) {
30
33
  function clearBuildCache(baseKey) {
31
34
  if (!baseKey) {
32
35
  buildCache.clear();
36
+ return;
33
37
  }
34
38
  const baseKeys = Array.isArray(baseKey) ? baseKey : [baseKey];
35
39
  for (const key of buildCache.keys()) {
36
- for (const baseKey2 of baseKeys) {
37
- if (key.startsWith(`${baseKey2}:`)) {
40
+ for (const targetBaseKey of baseKeys) {
41
+ if (key.startsWith(`${targetBaseKey}:`)) {
38
42
  buildCache.delete(key);
43
+ break;
39
44
  }
40
45
  }
41
46
  }
42
47
  }
43
48
 
44
- const cyan = "\x1B[36m";
45
- const reset = "\x1B[0m";
49
+ function toRelativePath(from, to) {
50
+ let relativePath = relative(resolve(from), resolve(to));
51
+ relativePath = relativePath.replace(/\\/g, "/");
52
+ if (!relativePath.startsWith("./") && !relativePath.startsWith("../")) {
53
+ relativePath = `./${relativePath}`;
54
+ }
55
+ return relativePath;
56
+ }
57
+ function stripExtension(filePath) {
58
+ const { dir, name } = parse(filePath);
59
+ return dir ? `${dir}/${name}` : name;
60
+ }
61
+
62
+ async function resolveContextInput({ paths }, nuxt) {
63
+ const { resolvePath } = createResolver(nuxt.options.rootDir);
64
+ return {
65
+ paths: await Promise.all(
66
+ paths.map(async (contextPath) => {
67
+ const path = await resolvePath(contextPath, { alias: nuxt.options.alias });
68
+ return path.replaceAll("\\", "/");
69
+ })
70
+ )
71
+ };
72
+ }
73
+ function getContextTemplate({ paths }) {
74
+ const imports = [
75
+ ...paths.map(
76
+ (path, index) => `import factory${index} from ${JSON.stringify(stripExtension(path))};`
77
+ ),
78
+ `import type { H3Event } from "h3";`
79
+ ];
80
+ const factories = ["(_event: H3Event) => ({})", ...paths.map((_, index) => `factory${index}`)];
81
+ const types = ["{}", ...paths.map((_, index) => `Awaited<ReturnType<typeof factory${index}>>`)];
82
+ return [
83
+ ...imports,
84
+ "",
85
+ `export type GraphQLContext = ${types.join(" & ")};`,
86
+ "",
87
+ `const factories = [${factories.join(", ")}];`,
88
+ "",
89
+ `export async function createContext(event: H3Event): Promise<GraphQLContext> {`,
90
+ ` const contexts = await Promise.all(factories.map((factory) => factory(event)));`,
91
+ ` return Object.assign({}, ...contexts);`,
92
+ `}`
93
+ ].join("\n");
94
+ }
95
+
96
+ async function resolveDocumentGlobs(globs, nuxt) {
97
+ const { resolvePath } = createResolver(nuxt.options.rootDir);
98
+ return Promise.all(
99
+ globs.map(
100
+ async (glob) => (await resolvePath(glob, { alias: nuxt.options.alias })).replaceAll("\\", "/")
101
+ )
102
+ );
103
+ }
104
+ async function loadDocuments(globs) {
105
+ if (!globs.length) return [];
106
+ try {
107
+ const docs = await loadDocuments$1(globs, {
108
+ loaders: [new GraphQLFileLoader(), new CodeFileLoader()],
109
+ ignore: ["**/.nuxt/**", "**/.output/**", "**/dist/**", "**/node_modules/**"]
110
+ });
111
+ const seen = /* @__PURE__ */ new Set();
112
+ return docs.filter((doc) => {
113
+ const key = `${doc.location ?? ""}:${doc.rawSDL ?? ""}`;
114
+ if (seen.has(key)) return false;
115
+ seen.add(key);
116
+ return Boolean(doc.document);
117
+ });
118
+ } catch (error) {
119
+ if (typeof error === "object" && error !== null && "name" in error && error.name === "NoTypeDefinitionsFound") {
120
+ return [];
121
+ }
122
+ throw error;
123
+ }
124
+ }
46
125
 
47
126
  const require$1 = createRequire(import.meta.url);
48
- function splitModule(ts) {
127
+ function compileTsModule(ts) {
49
128
  const tsCompiler = require$1("typescript");
50
129
  const fileName = "module.ts";
51
130
  const outputs = {};
@@ -53,121 +132,251 @@ function splitModule(ts) {
53
132
  target: tsCompiler.ScriptTarget.ES2020,
54
133
  module: tsCompiler.ModuleKind.ESNext,
55
134
  moduleResolution: tsCompiler.ModuleResolutionKind.NodeNext,
56
- declaration: true,
57
- importsNotUsedAsValues: tsCompiler.ImportsNotUsedAsValues.Remove,
58
- preserveValueImports: false
135
+ declaration: true
59
136
  };
60
137
  const host = tsCompiler.createCompilerHost(compilerOptions, true);
61
138
  host.getSourceFile = (name) => name === fileName ? tsCompiler.createSourceFile(fileName, ts, tsCompiler.ScriptTarget.ESNext, true) : void 0;
62
139
  host.readFile = () => ts;
63
140
  host.fileExists = (f) => f === fileName;
64
- host.writeFile = (name, content) => {
65
- outputs[name] = content;
66
- };
141
+ host.writeFile = (name, content) => outputs[name] = content;
67
142
  const program = tsCompiler.createProgram([fileName], compilerOptions, host);
68
143
  program.emit();
69
144
  return {
70
- mjs: Object.entries(outputs).find(([n]) => n.endsWith(".js"))?.[1]?.trim() ?? "",
71
- dts: Object.entries(outputs).find(([n]) => n.endsWith(".d.ts"))?.[1]?.trim() ?? ""
145
+ mjs: getCompiledContent(outputs, ".js"),
146
+ dts: getCompiledContent(outputs, ".d.ts")
72
147
  };
73
148
  }
74
-
75
- function getContextTemplate({ importPaths }) {
76
- const contextImports = importPaths.map((importPath, index) => `import context${index} from ${JSON.stringify(importPath)};`);
77
- const contextTypes = ["{}", ...importPaths.map((_, index) => `Awaited<ReturnType<typeof context${index}>>`)];
78
- const contextArray = ["(event: H3Event) => ({})", ...importPaths.map((_, index) => `context${index}`)];
79
- const ts = `
80
- import type { H3Event } from "h3";
81
- ${contextImports.join("\n")}
82
-
83
- export type GraphQLContext = ${contextTypes.join(" & ")};
84
-
85
- const contextFactories = [${contextArray.join(", ")}];
86
-
87
- export async function createContext(event: H3Event): Promise<GraphQLContext> {
88
- return Object.assign(
89
- {},
90
- ...await Promise.all(contextFactories.map((factory) => factory(event)))
91
- )
92
- }`.trim();
93
- return { ts, ...splitModule(ts) };
149
+ function getCompiledContent(outputs, extension) {
150
+ return Object.entries(outputs).find(([name]) => name.endsWith(extension))?.[1]?.trim() ?? "";
151
+ }
152
+ async function addCompiledTemplate({ filename, getContents }, nuxt) {
153
+ if (nuxt.options.dev || nuxt.options._prepare) {
154
+ return addTemplate({ filename: `${filename}.ts`, getContents, write: true });
155
+ } else {
156
+ const { mjs, dts } = compileTsModule(await getContents());
157
+ addTemplate({
158
+ filename: `${filename}.d.ts`,
159
+ getContents: () => dts,
160
+ write: true
161
+ });
162
+ return addTemplate({
163
+ filename: `${filename}.mjs`,
164
+ getContents: () => mjs,
165
+ write: true
166
+ });
167
+ }
94
168
  }
95
169
 
96
- function getRelativePath(from, to) {
97
- let relativePath = relative(resolve(from), resolve(to));
98
- relativePath = relativePath.replace(/\\/g, "/");
99
- if (!relativePath.startsWith("./") && !relativePath.startsWith("../")) {
100
- relativePath = `./${relativePath}`;
170
+ async function generateRegistryArtifacts({
171
+ schema,
172
+ documents
173
+ }) {
174
+ const generates = await zodPreset.buildGeneratesSection({
175
+ baseOutputDir: "registry.ts",
176
+ schema: parse$1(printSchema(schema)),
177
+ documents,
178
+ config: {},
179
+ pluginMap: {},
180
+ plugins: [],
181
+ presetConfig: {}
182
+ });
183
+ if (!documents.length) {
184
+ return {
185
+ "registry.ts": getRegistryFallback(),
186
+ "types.d.ts": getTypesFallback()
187
+ };
101
188
  }
102
- return relativePath;
189
+ const files = {};
190
+ await Promise.all(
191
+ generates.map(
192
+ (generate) => codegen(generate).then((content) => {
193
+ files[generate.filename] = content;
194
+ })
195
+ )
196
+ );
197
+ const artifacts = {
198
+ ...files,
199
+ "registry.ts": files["registry.ts"] || getRegistryFallback(),
200
+ "types.d.ts": files["types.d.ts"] || getTypesFallback()
201
+ };
202
+ return artifacts;
103
203
  }
104
- function removeExtension(path) {
105
- return path.replace(/\.(ts|mjs)$/, "");
204
+ async function addRegistryArtifactTemplates(artifacts, nuxt) {
205
+ let registryDst = "";
206
+ let typesDts = "";
207
+ for (const [filename, content] of Object.entries(artifacts)) {
208
+ switch (filename) {
209
+ case "types.d.ts":
210
+ typesDts = addTypeTemplate(
211
+ {
212
+ filename: "graphql/types.d.ts",
213
+ getContents: () => content,
214
+ write: true
215
+ },
216
+ { nitro: true, nuxt: true }
217
+ ).dst;
218
+ break;
219
+ default: {
220
+ const template = await addCompiledTemplate(
221
+ {
222
+ filename: `graphql/${stripExtension(filename)}`,
223
+ getContents: () => content
224
+ },
225
+ nuxt
226
+ );
227
+ if (filename === "registry.ts") {
228
+ registryDst = template.dst;
229
+ }
230
+ break;
231
+ }
232
+ }
233
+ }
234
+ if (!registryDst || !typesDts) {
235
+ throw new Error("Failed to register GraphQL registry artifacts");
236
+ }
237
+ return { registryDst, typesDts };
106
238
  }
239
+ function getRegistryFallback() {
240
+ return `
241
+ import type { TypedDocumentNode } from "@graphql-typed-document-node/core";
242
+ import type * as z from "zod";
107
243
 
108
- function getSchemaTemplate({ local, remote }) {
109
- const localImports = Object.entries(local).map(([name, { importPath }]) => `import { schema as ${name}LocalSchema } from ${JSON.stringify(importPath)};`);
110
- const localSchemas = Object.keys(local).map((name) => `${name}LocalSchema`);
111
- const mergedSchema = `mergeSchemas({ schemas: [${localSchemas.join(", ")}] })`;
112
- const remoteImports = Object.entries(remote).map(([name, { importPath }]) => `import { schema as ${name}RemoteSchema } from ${JSON.stringify(importPath)};`);
113
- const remoteSchemas = Object.keys(remote).map((name) => `${name}RemoteSchema`);
114
- const ts = `
115
- import { mergeSchemas } from "@graphql-tools/schema";
116
- import { stitchSchemas } from "@graphql-tools/stitch";
117
- ${localImports.join("\n")}
118
- ${remoteImports.join("\n")}
244
+ type EnumEntry = { schema: z.ZodEnum<Record<string, string>> };
119
245
 
120
- export const schema = stitchSchemas({
121
- subschemas: [${[mergedSchema, ...remoteSchemas].join(", ")}],
122
- });
123
- `.trim();
124
- return { ts, ...splitModule(ts) };
246
+ type FragmentEntry = { schema: z.ZodObject<z.ZodRawShape> };
247
+
248
+ type OperationEntry = {
249
+ kind: "query" | "mutation" | "subscription";
250
+ document: TypedDocumentNode<any, any>;
251
+ resultSchema: z.ZodObject<z.ZodRawShape>;
252
+ variablesSchema: z.ZodObject<z.ZodRawShape>;
253
+ };
254
+
255
+ export const enums: Record<string, EnumEntry> = {};
256
+ export const fragments: Record<string, FragmentEntry> = {};
257
+ export const operations: Record<string, OperationEntry> = {};
258
+ `.trim();
125
259
  }
126
- async function loadLocalSchema({ importPath }) {
127
- const { createJiti } = await import('jiti');
128
- const jiti = createJiti(import.meta.url, { interopDefault: true });
129
- const module = await jiti.import(importPath);
130
- if (!module.schema || !(module.schema instanceof Object) || typeof module.schema.getQueryType !== "function") {
131
- throw new Error(`${importPath} must export a valid 'schema' of type GraphQLSchema.`);
132
- }
133
- return module.schema;
260
+ function getTypesFallback() {
261
+ return `export type {};`;
134
262
  }
135
- async function getRemoteSchemaTemplate({ endpoint, headers, hooks, loadSchema }) {
136
- const hooksImports = hooks.map((hook, index) => `import hook${index} from ${JSON.stringify(hook.importPath)};`);
137
- const hooksArray = hooks.map((_, index) => `hook${index}`);
138
- const schema = await loadSchema();
139
- const sdl = getSchemaSDL(schema);
140
- const ts = `
141
- import type { GraphQLSchema } from "graphql";
142
- import { buildSchema } from "graphql";
263
+
264
+ async function resolveSchemaDefs(schemaDefs, nuxt) {
265
+ const { resolvePath } = createResolver(nuxt.options.rootDir);
266
+ return Promise.all(
267
+ schemaDefs.map(async (schemaDef) => {
268
+ switch (schemaDef.type) {
269
+ case "local": {
270
+ const path = await resolvePath(schemaDef.path, { alias: nuxt.options.alias });
271
+ return {
272
+ type: "local",
273
+ path: path.replaceAll("\\", "/")
274
+ };
275
+ }
276
+ case "remote": {
277
+ const hooks = await Promise.all(
278
+ (schemaDef.hooks ?? []).map(async (hookPath) => {
279
+ const path = await resolvePath(hookPath, { alias: nuxt.options.alias });
280
+ return path.replaceAll("\\", "/");
281
+ })
282
+ );
283
+ return {
284
+ type: "remote",
285
+ endpoint: schemaDef.endpoint,
286
+ headers: schemaDef.headers ?? {},
287
+ hooks
288
+ };
289
+ }
290
+ }
291
+ })
292
+ );
293
+ }
294
+ function getRemoteSchemaServerTemplate({
295
+ endpoint,
296
+ headers,
297
+ hooks,
298
+ sdl
299
+ }) {
300
+ const hookImports = hooks.map(
301
+ (hookPath, index) => `import hook${index} from ${JSON.stringify(hookPath)};`
302
+ );
303
+ const hookRefs = hooks.map((_, index) => `hook${index}`);
304
+ return `
143
305
  import { getRemoteExecutor } from "#graphql/runtime/remote-executor";
144
- ${hooksImports.join("\n")}
306
+ import { buildSchema } from "graphql";
307
+ ${hookImports.join("\n")}
145
308
 
146
309
  const executor = getRemoteExecutor({
147
- endpoint: "${endpoint}",
310
+ endpoint: ${JSON.stringify(endpoint)},
148
311
  headers: ${JSON.stringify(headers)},
149
- hooks: [${hooksArray.join(", ")}],
312
+ hooks: [${hookRefs.join(", ")}],
150
313
  });
151
314
 
152
- const sdl = ${JSON.stringify(sdl)};
153
-
154
- // SubschemaConfig exported for stitching
155
315
  export const schema = {
156
- schema: buildSchema(sdl),
316
+ schema: buildSchema(${JSON.stringify(sdl)}),
157
317
  executor,
158
- } as unknown as GraphQLSchema;
159
- `.trim();
160
- return { ts, ...splitModule(ts) };
318
+ };
319
+ `.trim();
161
320
  }
162
- async function introspectRemoteSchema({ endpoint }) {
321
+ function getSchemaServerTemplate({ localPaths, remotePaths }) {
322
+ const imports = [
323
+ ...localPaths.map(
324
+ (schemaPath, index) => `import { schema as localSchema${index} } from ${JSON.stringify(schemaPath)};`
325
+ ),
326
+ ...remotePaths.map(
327
+ (schemaPath, index) => `import { schema as remoteSchema${index} } from ${JSON.stringify(schemaPath)};`
328
+ )
329
+ ];
330
+ const localSchemaRefs = localPaths.map((_, index) => `localSchema${index}`);
331
+ const remoteSchemaRefs = remotePaths.map((_, index) => `remoteSchema${index}`);
332
+ const schemaRefs = [];
333
+ if (localSchemaRefs.length === 1) {
334
+ schemaRefs.push(localSchemaRefs[0]);
335
+ } else if (localSchemaRefs.length > 1) {
336
+ imports.unshift(`import { mergeSchemas } from "@graphql-tools/schema";`);
337
+ schemaRefs.push(`mergeSchemas({ schemas: [${localSchemaRefs.join(", ")}] })`);
338
+ }
339
+ schemaRefs.push(...remoteSchemaRefs);
340
+ let schemaRef;
341
+ if (schemaRefs.length === 0) {
342
+ imports.unshift(`import { buildSchema } from "graphql";`);
343
+ schemaRef = `buildSchema("type Query { _empty: String }")`;
344
+ } else if (schemaRefs.length === 1) {
345
+ schemaRef = schemaRefs[0];
346
+ } else {
347
+ imports.unshift(`import { stitchSchemas } from "@graphql-tools/stitch";`);
348
+ schemaRef = `stitchSchemas({ subschemas: [${schemaRefs.join(", ")}] })`;
349
+ }
350
+ return [...imports, "", `export const schema = ${schemaRef};`].join("\n");
351
+ }
352
+ async function loadLocalSchema(path, nuxt) {
353
+ const { createJiti } = await import('jiti');
354
+ const jiti = createJiti(import.meta.url, { interopDefault: true, alias: nuxt.options.alias });
355
+ const module = await jiti.import(path);
356
+ if (!module.schema || !(module.schema instanceof Object) || typeof module.schema.getQueryType !== "function") {
357
+ throw new Error(`${path} must export a valid 'schema' of type GraphQLSchema.`);
358
+ }
359
+ return module.schema;
360
+ }
361
+ async function loadRemoteSchema(endpoint, headers) {
163
362
  const response = await fetch(endpoint, {
164
363
  method: "POST",
165
- headers: { "Content-Type": "application/json" },
364
+ headers: { "Content-Type": "application/json", ...headers },
166
365
  body: JSON.stringify({ query: getIntrospectionQuery() })
167
366
  });
367
+ if (!response.ok) {
368
+ throw new Error(
369
+ `Failed to introspect remote GraphQL schema at "${endpoint}" (HTTP ${response.status}).`
370
+ );
371
+ }
168
372
  const json = await response.json();
169
373
  if (json.errors) {
170
- throw new Error(`Failed to fetch GraphQL schema from ${endpoint}: ${JSON.stringify(json.errors)} `);
374
+ throw new Error(
375
+ `Failed to introspect remote GraphQL schema at "${endpoint}": ${JSON.stringify(json.errors)}`
376
+ );
377
+ }
378
+ if (!json.data || typeof json.data !== "object") {
379
+ throw new Error(`Remote GraphQL introspection at "${endpoint}" returned no data.`);
171
380
  }
172
381
  const schema = buildClientSchema(json.data);
173
382
  return stripSubscriptions(schema);
@@ -187,284 +396,176 @@ function stripSubscriptions(schema) {
187
396
  function getSchemaSDL(schema) {
188
397
  return printSchema(lexicographicSortSchema(schema));
189
398
  }
190
- function getDefaultSchema() {
191
- return buildSchema(`type Query { _empty: String }`);
192
- }
193
-
194
- function addUniversalTemplate({ filename, getContents, emitTs }) {
195
- let modulePath;
196
- if (emitTs) {
197
- modulePath = addTemplate({ filename: `${filename}.ts`, getContents: async () => (await getContents()).ts, write: true }).dst;
198
- } else {
199
- modulePath = addTemplate({ filename: `${filename}.mjs`, getContents: async () => (await getContents()).mjs, write: true }).dst;
200
- addTemplate({ filename: `${filename}.d.ts`, getContents: async () => (await getContents()).dts });
201
- }
202
- addServerTemplate({ filename: `${filename}.mjs`, getContents: async () => (await getContents()).mjs });
203
- return modulePath;
204
- }
205
-
206
- const version = "0.6.20";
207
-
208
- async function getDocuments(documentsGlob) {
209
- try {
210
- return await loadDocuments([
211
- documentsGlob,
212
- "!**/.cache/**",
213
- "!**/.nuxt/**",
214
- "!**/.output/**",
215
- "!**/dist/**",
216
- "!**/node_modules/**"
217
- ], { loaders: [new GraphQLFileLoader()] });
218
- } catch (error) {
219
- if (typeof error === "object" && error !== null && "name" in error && error.name === "NoTypeDefinitionsFound") {
220
- return [];
221
- }
222
- throw error;
223
- }
224
- }
225
-
226
- async function getOperationsTemplate({ loadSchema, loadDocuments, documentGlob }) {
227
- const ts = await codegen({
228
- filename: "operations.ts",
229
- schema: await loadSchema(),
230
- documents: await loadDocuments(documentGlob),
231
- plugins: [
232
- {
233
- typescript: {
234
- avoidOptionals: {
235
- field: true,
236
- object: true,
237
- inputValue: false,
238
- defaultValue: false
239
- },
240
- defaultScalarType: "never",
241
- enumsAsConst: true,
242
- preResolveTypes: false,
243
- strictScalars: true,
244
- useTypeImports: true
245
- }
246
- },
247
- {
248
- typescriptOperations: {
249
- avoidOptionals: {
250
- field: true,
251
- object: true,
252
- inputValue: false,
253
- defaultValue: false
254
- },
255
- defaultScalarType: "never",
256
- enumsAsConst: true,
257
- exportFragmentSpreadSubTypes: true,
258
- inlineFragmentTypes: "combine",
259
- operationResultSuffix: "Result",
260
- operationVariablesSuffix: "Variables",
261
- preResolveTypes: false,
262
- skipTypename: true,
263
- strictScalars: true,
264
- useTypeImports: true
265
- }
266
- },
267
- {
268
- typedDocumentNode: {
269
- documentVariableSuffix: "Document",
270
- operationResultSuffix: "Result",
271
- operationVariablesSuffix: "Variables",
272
- optimizeDocumentNode: true,
273
- useTypeImports: true
274
- }
275
- }
276
- ],
277
- pluginMap: {
278
- typescript: typescriptPlugin,
279
- typescriptOperations: typescriptOperationsPlugin,
280
- typedDocumentNode: typedDocumentNodePlugin
281
- },
282
- config: {}
283
- });
284
- return { ts, ...splitModule(ts) };
285
- }
286
-
287
- async function getRegistryTemplate({ loadDocuments, documentGlob }) {
288
- const documents = await loadDocuments(documentGlob);
289
- const operations = collectOperations(documents);
290
- function capitalize(value) {
291
- if (!value) return value;
292
- return value.charAt(0).toUpperCase() + value.slice(1);
293
- }
294
- const ts = `
295
- import type { DocumentNode } from "graphql";
296
- import {
297
- ${operations.map(({ name, kind }) => `${name}Document, type ${name}${capitalize(kind)}Variables, type ${name}${capitalize(kind)}Result,`).join("\n ")}
298
- } from "./operations";
299
-
300
- // Operation entry
301
- export interface OperationEntry<TVariables, TResult, TKind extends "query" | "mutation" | "subscription"> {
302
- kind: TKind;
303
- variables: TVariables;
304
- result: TResult;
305
- document: DocumentNode;
306
- }
307
-
308
- // Operation registry type
309
- export type OperationRegistry = {
310
- ${operations.map(({ name, kind }) => `${name}: OperationEntry<${name}${capitalize(kind)}Variables, ${name}${capitalize(kind)}Result, "${kind}">;`).join("\n ")}
311
- };
312
-
313
- // Operation name types
314
- export type OperationName = keyof OperationRegistry;
315
- export type QueryName = { [K in keyof OperationRegistry]: OperationRegistry[K]["kind"] extends "query" ? K : never }[keyof OperationRegistry];
316
- export type MutationName = { [K in keyof OperationRegistry]: OperationRegistry[K]["kind"] extends "mutation" ? K : never }[keyof OperationRegistry];
317
- export type SubscriptionName = { [K in keyof OperationRegistry]: OperationRegistry[K]["kind"] extends "subscription" ? K : never }[keyof OperationRegistry];
318
-
319
- // Projection helpers (variables / result)
320
- export type VariablesOf<TName extends keyof OperationRegistry> = OperationRegistry[TName]["variables"];
321
- export type ResultOf<TName extends keyof OperationRegistry> = OperationRegistry[TName]["result"];
322
-
323
- export const registry: { [K in keyof OperationRegistry]: { kind: OperationRegistry[K]["kind"]; document: DocumentNode; }; } = {
324
- ${operations.map(({ name, kind }) => `${name}: { kind: "${kind}", document: ${name}Document },`).join("\n ")}
325
- };`.trim();
326
- return { ts, ...splitModule(ts) };
327
- }
328
- function collectOperations(documents) {
329
- const operations = /* @__PURE__ */ new Map();
330
- for (const source of documents) {
331
- const doc = source.document;
332
- if (!doc) continue;
333
- for (const def of doc.definitions) {
334
- if (def.kind !== Kind.OPERATION_DEFINITION) continue;
335
- const name = def.name?.value;
336
- if (!name) {
337
- throw new Error("Anonymous GraphQL operations are not allowed");
338
- }
339
- if (operations.has(name)) {
340
- throw new Error(`Duplicate GraphQL operation name "${name}"`);
341
- }
342
- operations.set(name, { name, kind: def.operation });
343
- }
344
- }
345
- return Array.from(operations.values());
346
- }
347
399
 
348
400
  const module$1 = defineNuxtModule({
349
401
  meta: {
350
- name: "@lewebsimple/nuxt-graphql",
402
+ name: "nuxt-graphql",
351
403
  configKey: "graphql"
352
404
  },
353
405
  defaults: {},
354
406
  async setup(options, nuxt) {
355
407
  const logger = useLogger("@lewebsimple/nuxt-graphql");
356
408
  logger.info(`@lewebsimple/nuxt-graphql v${version} loaded`);
357
- const { resolve: resolveModule } = createResolver(import.meta.url);
358
409
  const { rootDir } = nuxt.options;
359
- const { resolve: resolveRoot, resolvePath: rawResolveRootPath } = createResolver(rootDir);
360
- async function resolveRootPath(path) {
361
- return removeExtension(await rawResolveRootPath(path));
362
- }
410
+ const { resolve: resolveModule } = createResolver(import.meta.url);
411
+ const { resolve: resolveRoot } = createResolver(rootDir);
363
412
  const nuxtAliases = {};
364
413
  const nitroAliases = {};
365
- const emitTs = Boolean(nuxt.options.dev) || Boolean(process.env.NUXT_MODULE_PREPARE);
366
- nuxtAliases["#graphql/runtime/remote-executor"] = resolveModule("./runtime/server/lib/remote-executor");
367
- nitroAliases["#graphql/runtime/remote-executor"] = resolveModule("./runtime/server/lib/remote-executor");
368
- const contextInput = {
369
- importPaths: await Promise.all((options.server?.context || []).map((path) => resolveRootPath(path)))
414
+ const transpileEntries = /* @__PURE__ */ new Set();
415
+ addServerImports({
416
+ name: "defineGraphQLContext",
417
+ from: resolveModule("runtime/server/lib/context")
418
+ });
419
+ const contextInput = await resolveContextInput({ paths: options.server?.context ?? [] }, nuxt);
420
+ contextInput.paths.forEach((path) => transpileEntries.add(dirname(path)));
421
+ const { dst: contextDst } = await addCompiledTemplate(
422
+ { filename: "graphql/context", getContents: () => getContextTemplate(contextInput) },
423
+ nuxt
424
+ );
425
+ nitroAliases["#graphql/context"] = contextDst;
426
+ addServerImports({
427
+ name: "defineRemoteExecutorHooks",
428
+ from: resolveModule("runtime/server/lib/remote-executor")
429
+ });
430
+ nitroAliases["#graphql/runtime/remote-executor"] = resolveModule(
431
+ "runtime/server/lib/remote-executor"
432
+ );
433
+ const schemaDefs = await resolveSchemaDefs(options.server?.schema ?? [], nuxt);
434
+ const schemaInput = {
435
+ localPaths: [],
436
+ remotePaths: []
370
437
  };
371
- const contextPath = addUniversalTemplate({ filename: "graphql/context", getContents: () => getContextTemplate(contextInput), emitTs });
372
- nuxtAliases["#graphql/context"] = contextPath;
373
- nitroAliases["#graphql/context"] = contextPath;
374
- const schemaInput = { local: {}, remote: {} };
375
- const schemaLoaders = {};
376
- for (const [schemaName, schemaDef] of Object.entries(options.server?.schema || {})) {
377
- if (!/^[a-z_][\w-]*$/i.test(schemaName)) {
378
- throw new Error(`Invalid schema name "${schemaName}". Only alphanumeric characters, underscores and hyphens are allowed, and it cannot start with a number.`);
379
- }
380
- if (schemaDef.type === "local") {
381
- const importPath = await resolveRootPath(schemaDef.path);
382
- const loadSchema2 = getCachedLoader(`schema:local:${schemaName}`, async () => await loadLocalSchema({ importPath }));
383
- schemaInput.local[schemaName] = { importPath };
384
- schemaLoaders[schemaName] = loadSchema2;
385
- } else if (schemaDef.type === "remote") {
386
- const { endpoint } = schemaDef;
387
- const loadSchema2 = getCachedLoader(`schema:remote:${schemaName}`, async () => await introspectRemoteSchema({ endpoint }));
388
- const hooks = await Promise.all((schemaDef.hooks || []).map(async (hookPath) => ({ importPath: await resolveRootPath(hookPath) })));
389
- const remoteSchemaInput = { endpoint, headers: schemaDef.headers || {}, hooks, loadSchema: loadSchema2 };
390
- addUniversalTemplate({ filename: `graphql/schemas/${schemaName}`, getContents: () => getRemoteSchemaTemplate(remoteSchemaInput), emitTs });
391
- schemaInput.remote[schemaName] = { importPath: `./schemas/${schemaName}` };
392
- schemaLoaders[schemaName] = loadSchema2;
393
- } else {
394
- throw new Error(`Unknown schema type for schema "${schemaName}"`);
438
+ const schemaLoaders = [];
439
+ for (const [index, schemaDef] of schemaDefs.entries()) {
440
+ switch (schemaDef.type) {
441
+ case "local": {
442
+ const loadSchema = getCachedLoader(
443
+ `graphql:schema:local-${index}`,
444
+ async () => await loadLocalSchema(schemaDef.path, nuxt)
445
+ );
446
+ schemaLoaders.push(loadSchema);
447
+ transpileEntries.add(dirname(schemaDef.path));
448
+ schemaInput.localPaths.push(schemaDef.path);
449
+ break;
450
+ }
451
+ case "remote": {
452
+ const schemaLoader = getCachedLoader(
453
+ `graphql:schema:remote-${index}`,
454
+ async () => await loadRemoteSchema(schemaDef.endpoint, schemaDef.headers ?? {})
455
+ );
456
+ schemaLoaders.push(schemaLoader);
457
+ (schemaDef.hooks ?? []).forEach((hookPath) => transpileEntries.add(dirname(hookPath)));
458
+ await addCompiledTemplate(
459
+ {
460
+ filename: `graphql/schemas/remote-${index}`,
461
+ getContents: async () => getRemoteSchemaServerTemplate({
462
+ ...schemaDef,
463
+ sdl: getSchemaSDL(await schemaLoader())
464
+ })
465
+ },
466
+ nuxt
467
+ );
468
+ schemaInput.remotePaths.push(`./schemas/remote-${index}`);
469
+ break;
470
+ }
395
471
  }
396
472
  }
397
- const schemaPath = addUniversalTemplate({ filename: "graphql/schema", getContents: () => getSchemaTemplate(schemaInput), emitTs });
398
- nuxtAliases["#graphql/schema"] = schemaPath;
399
- nitroAliases["#graphql/schema"] = schemaPath;
400
- const sdlPath = resolveRoot(options.saveSDL || "server/graphql/schema.graphql");
401
- const loadSchema = getCachedLoader("schema:stitched", async () => {
402
- const subschemas = await Promise.all(Object.values(schemaLoaders).map((loadSchema2) => loadSchema2()));
403
- if (subschemas.length === 0) {
404
- logger.warn(`No GraphQL schemas defined: using default empty schema.`);
405
- subschemas.push(getDefaultSchema());
473
+ const { dst: schemaDst } = await addCompiledTemplate(
474
+ {
475
+ filename: "graphql/schema",
476
+ getContents: () => getSchemaServerTemplate(schemaInput)
477
+ },
478
+ nuxt
479
+ );
480
+ nitroAliases["#graphql/schema"] = schemaDst;
481
+ const sdlPath = resolveRoot(options.saveSDL || ".nuxt/graphql/schema.graphql");
482
+ await mkdir(dirname(sdlPath), { recursive: true });
483
+ const loadSchemaCached = getCachedLoader("graphql:schema", async () => {
484
+ const schemas = await Promise.all(schemaLoaders.map((loader) => loader()));
485
+ let schema;
486
+ if (!schemas.length) {
487
+ if (!nuxt.options._prepare) {
488
+ logger.warn("No GraphQL schemas loaded: using default empty schema.");
489
+ }
490
+ schema = buildSchema("type Query { _empty: String }");
491
+ } else {
492
+ schema = stitchSchemas({ subschemas: schemas });
493
+ if (!schema) {
494
+ throw new Error("Failed to load GraphQL schema");
495
+ }
406
496
  }
407
- const schema = stitchSchemas({ subschemas });
408
497
  if (nuxt.options.dev) {
409
498
  const sdl = getSchemaSDL(schema);
410
- mkdirSync(dirname(sdlPath), { recursive: true });
411
- writeFileSync(sdlPath, sdl, { encoding: "utf-8" });
412
- logger.info(`Stitched GraphQL SDL saved to: ${cyan}${getRelativePath(rootDir, sdlPath)}${reset}`);
499
+ await writeFile(sdlPath, sdl);
413
500
  }
414
501
  return schema;
415
502
  });
416
- const loadDocuments = getCachedLoader("documents", async (documentsGlob) => {
417
- const documents = await getDocuments(documentsGlob);
418
- if (documents.length === 0) {
419
- logger.warn(`No GraphQL documents found for glob pattern: ${cyan}${documentsGlob}${reset}`);
420
- }
421
- return documents;
422
- });
423
- const operationsInput = { loadSchema, loadDocuments, documentGlob: options.client?.documents || "**/*.gql" };
424
- const operationsPath = addUniversalTemplate({ filename: "graphql/operations", getContents: () => getOperationsTemplate(operationsInput), emitTs });
425
- nuxtAliases["#graphql/operations"] = operationsPath;
426
- nitroAliases["#graphql/operations"] = operationsPath;
427
- const registryInput = { loadDocuments, documentGlob: options.client?.documents || "**/*.gql" };
428
- const registryPath = addUniversalTemplate({ filename: "graphql/registry", getContents: () => getRegistryTemplate(registryInput), emitTs });
429
- nuxtAliases["#graphql/registry"] = registryPath;
430
- nitroAliases["#graphql/registry"] = registryPath;
503
+ const documentGlobs = await resolveDocumentGlobs(
504
+ options.client?.documents ?? nuxt.options._prepare ? [] : ["app/**/*.{gql,ts,vue}", "server/**/*.{gql,ts}", "shared/**/*.{gql,ts}"],
505
+ nuxt
506
+ );
507
+ const loadDocumentsCached = getCachedLoader(
508
+ "graphql:documents",
509
+ async (globs) => loadDocuments(globs)
510
+ );
511
+ const generateRegistryCached = getCachedLoader(
512
+ "graphql:registry",
513
+ async (globs) => await generateRegistryArtifacts({
514
+ schema: await loadSchemaCached(),
515
+ documents: await loadDocumentsCached(globs)
516
+ })
517
+ );
518
+ const syncRegistryTemplates = async () => await addRegistryArtifactTemplates(await generateRegistryCached(documentGlobs), nuxt);
519
+ const { registryDst, typesDts } = await syncRegistryTemplates();
520
+ const typesPath = typesDts.replace(/\.d\.ts$/, "");
521
+ nuxtAliases["#graphql/registry"] = registryDst;
522
+ nitroAliases["#graphql/registry"] = registryDst;
523
+ nuxtAliases["#graphql/types"] = typesPath;
524
+ nitroAliases["#graphql/types"] = typesPath;
525
+ if (nuxt.options.dev) {
526
+ const schemaWatchPaths = schemaDefs.filter((schemaDef) => schemaDef.type === "local").map((schemaDef) => schemaDef.path);
527
+ const isDocument = picomatch(documentGlobs);
528
+ nuxt.hook("builder:watch", async (_event, path) => {
529
+ if (schemaWatchPaths.some((schemaPath) => path.includes(schemaPath))) {
530
+ logger.info(`Local schema change detected: ${path}`);
531
+ clearBuildCache(["graphql:schema", "graphql:registry"]);
532
+ await syncRegistryTemplates();
533
+ await updateTemplates({ filter: (template) => template.filename.startsWith("graphql/") });
534
+ }
535
+ if (isDocument(path)) {
536
+ logger.info(`Document change detected: ${path}`);
537
+ clearBuildCache(["graphql:documents", "graphql:registry"]);
538
+ await syncRegistryTemplates();
539
+ await updateTemplates({ filter: (template) => template.filename.startsWith("graphql/") });
540
+ }
541
+ });
542
+ }
431
543
  if (nuxt.options.dev) {
432
544
  const configPath = resolveRoot(options.saveConfig || "graphql.config.json");
433
545
  const config = {
434
- schema: getRelativePath(rootDir, sdlPath),
435
- documents: options.client?.documents || "**/*.gql"
546
+ schema: toRelativePath(rootDir, sdlPath),
547
+ documents: documentGlobs.map((glob) => toRelativePath(rootDir, glob))
436
548
  };
437
- mkdirSync(dirname(configPath), { recursive: true });
438
- writeFileSync(configPath, JSON.stringify(config, null, 2), { encoding: "utf-8" });
439
- logger.info(`GraphQL config saved to: ${cyan}${getRelativePath(rootDir, configPath)}${reset}`);
549
+ await writeFile(configPath, JSON.stringify(config, null, 2));
440
550
  }
441
551
  nuxt.options.alias = defu(nuxt.options.alias, nuxtAliases);
442
- nuxt.hook("nitro:config", (nitroConfig) => {
443
- nitroConfig.alias = defu(nitroConfig.alias, nitroAliases);
444
- });
552
+ nuxt.options.nitro.alias = defu(nuxt.options.nitro.alias, nitroAliases);
553
+ nuxt.options.build.transpile = [...nuxt.options.build.transpile, ...transpileEntries];
445
554
  nuxt.options.runtimeConfig.public.graphql = defu(nuxt.options.runtimeConfig.public.graphql, {
446
555
  cacheConfig: resolveCacheConfig(options.client?.cache),
447
556
  ssrForwardHeaders: options.client?.ssrForwardHeaders || ["authorization", "cookie"]
448
557
  });
449
- if (nuxt.options.dev) {
450
- nuxt.hook("builder:watch", async (_event, changedPath) => {
451
- if (changedPath.endsWith(".gql")) {
452
- logger.info(`Documents change detected: ${cyan}${getRelativePath(rootDir, changedPath)}${reset}`);
453
- clearBuildCache(["documents", "operations", "registry"]);
454
- }
455
- });
456
- }
457
- const handler = resolveModule("./runtime/server/api/graphql");
558
+ const handler = resolveModule("runtime/server/api/graphql");
458
559
  addServerHandler({ route: "/api/graphql", handler });
459
560
  nuxt.hook("listen", (_, { url }) => {
460
- logger.success(`GraphQL Yoga ready: ${cyan}${url.replace(/\/$/, "")}/api/graphql${reset}`);
561
+ logger.success(`GraphQL endpoint available at ${cyan(`${url}api/graphql`)}`);
461
562
  });
462
- addPlugin(resolveModule("./runtime/app/plugins/execute-graphql"));
463
- addPlugin(resolveModule("./runtime/app/plugins/graphql-sse.client"));
464
- addImportsDir(resolveModule("./runtime/app/composables"));
465
- addImportsDir(resolveModule("./runtime/shared/utils"));
466
- addServerImportsDir(resolveModule("./runtime/server/utils"));
467
- addServerImportsDir(resolveModule("./runtime/shared/utils"));
563
+ addPlugin(resolveModule("runtime/app/plugins/graphql"));
564
+ addPlugin(resolveModule("runtime/app/plugins/graphql-sse.client"));
565
+ addImportsDir(resolveModule("runtime/app/composables"));
566
+ addImportsDir(resolveModule("runtime/shared/utils"));
567
+ addServerImportsDir(resolveModule("runtime/server/utils"));
568
+ addServerImportsDir(resolveModule("runtime/shared/utils"));
468
569
  }
469
570
  });
470
571