nuxt-graphql-middleware 5.0.0-alpha.2 → 5.0.0-alpha.20

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 (93) hide show
  1. package/README.md +101 -19
  2. package/dist/client/200.html +10 -10
  3. package/dist/client/404.html +10 -10
  4. package/dist/client/_nuxt/BM34SYth.js +1 -0
  5. package/dist/client/_nuxt/CROlboVl.js +1 -0
  6. package/dist/client/_nuxt/D5hBL5aZ.js +25 -0
  7. package/dist/client/_nuxt/FTbv7CO6.js +1 -0
  8. package/dist/client/_nuxt/builds/latest.json +1 -1
  9. package/dist/client/_nuxt/builds/meta/de61b0f7-ec5c-4f2f-addb-b5017c30afb1.json +1 -0
  10. package/dist/client/_nuxt/entry.Cn9qfNGa.css +1 -0
  11. package/dist/client/_nuxt/error-404.ehK72JOs.css +1 -0
  12. package/dist/client/_nuxt/error-500._g0akJim.css +1 -0
  13. package/dist/client/_nuxt/index.DGEN-H8t.css +1 -0
  14. package/dist/client/_nuxt/lIgCBhS_.js +2 -0
  15. package/dist/client/index.html +10 -10
  16. package/dist/client-options.d.mts +6 -0
  17. package/dist/client-options.mjs +5 -0
  18. package/dist/module.d.mts +74 -340
  19. package/dist/module.json +4 -4
  20. package/dist/module.mjs +1384 -589
  21. package/dist/runtime/components/CodeFrame.vue +52 -0
  22. package/dist/runtime/components/CodeFrame.vue.d.ts +7 -0
  23. package/dist/runtime/components/DevModeOverlay.vue +52 -0
  24. package/dist/runtime/components/DevModeOverlay.vue.d.ts +3 -0
  25. package/dist/runtime/components/ErrorExtensions.vue +21 -0
  26. package/dist/runtime/components/ErrorExtensions.vue.d.ts +5 -0
  27. package/dist/runtime/components/ErrorGroup.vue +78 -0
  28. package/dist/runtime/components/ErrorGroup.vue.d.ts +9 -0
  29. package/dist/runtime/composables/nuxtApp.d.ts +2 -2
  30. package/dist/runtime/composables/nuxtApp.js +21 -1
  31. package/dist/runtime/composables/useAsyncGraphqlQuery.d.ts +7 -7
  32. package/dist/runtime/composables/useAsyncGraphqlQuery.js +10 -2
  33. package/dist/runtime/composables/useGraphqlMutation.d.ts +4 -4
  34. package/dist/runtime/composables/useGraphqlMutation.js +1 -1
  35. package/dist/runtime/composables/useGraphqlQuery.d.ts +4 -4
  36. package/dist/runtime/composables/useGraphqlQuery.js +1 -1
  37. package/dist/runtime/composables/useGraphqlState.d.ts +1 -1
  38. package/dist/runtime/composables/useGraphqlState.js +1 -1
  39. package/dist/runtime/composables/useGraphqlUploadMutation.d.ts +4 -4
  40. package/dist/runtime/composables/useGraphqlUploadMutation.js +2 -2
  41. package/dist/runtime/css/output.css +1 -0
  42. package/dist/runtime/helpers/composables.d.ts +17 -20
  43. package/dist/runtime/helpers/composables.js +0 -5
  44. package/dist/runtime/plugins/devMode.d.ts +2 -0
  45. package/dist/runtime/plugins/devMode.js +23 -0
  46. package/dist/runtime/plugins/provideState.d.ts +1 -1
  47. package/dist/runtime/{serverHandler → server/api}/debug.js +3 -7
  48. package/dist/runtime/server/api/mutation.js +29 -0
  49. package/dist/runtime/server/api/query.js +30 -0
  50. package/dist/runtime/server/api/upload.d.ts +2 -0
  51. package/dist/runtime/{serverHandler → server/api}/upload.js +13 -11
  52. package/dist/runtime/{serverHandler → server}/helpers/index.d.ts +10 -12
  53. package/dist/runtime/{serverHandler → server}/helpers/index.js +9 -26
  54. package/dist/runtime/server/utils/doGraphqlRequest.d.ts +18 -0
  55. package/dist/runtime/server/utils/doGraphqlRequest.js +68 -0
  56. package/dist/runtime/server/utils/index.d.ts +1 -1
  57. package/dist/runtime/server/utils/index.js +1 -1
  58. package/dist/runtime/server/utils/useGraphqlMutation.d.ts +4 -4
  59. package/dist/runtime/server/utils/useGraphqlQuery.d.ts +4 -4
  60. package/dist/runtime/settings/index.d.ts +0 -14
  61. package/dist/runtime/settings/index.js +0 -6
  62. package/dist/runtime/types.d.ts +204 -3
  63. package/dist/server-options.d.mts +8 -0
  64. package/dist/server-options.mjs +5 -0
  65. package/dist/shared/nuxt-graphql-middleware.xfMm4rGk.d.mts +523 -0
  66. package/dist/types.d.mts +1 -7
  67. package/dist/utils.d.mts +15 -0
  68. package/dist/utils.mjs +18 -0
  69. package/package.json +64 -57
  70. package/dist/client/_nuxt/BS583yk8.js +0 -25
  71. package/dist/client/_nuxt/CZ2Qwgdk.js +0 -2
  72. package/dist/client/_nuxt/DpxjPVZy.js +0 -1
  73. package/dist/client/_nuxt/GOrnHr4p.js +0 -1
  74. package/dist/client/_nuxt/builds/meta/f823ebfd-daab-468f-8f6f-07a236da64bd.json +0 -1
  75. package/dist/client/_nuxt/entry.AjgXSF89.css +0 -1
  76. package/dist/client/_nuxt/error-404.BJkSn6RI.css +0 -1
  77. package/dist/client/_nuxt/error-500.TOCKLquH.css +0 -1
  78. package/dist/client/_nuxt/exxdaCPN.js +0 -1
  79. package/dist/client/_nuxt/index.D19Q16VT.css +0 -1
  80. package/dist/module.cjs +0 -5
  81. package/dist/module.d.ts +0 -358
  82. package/dist/runtime/clientOptions/index.d.ts +0 -2
  83. package/dist/runtime/clientOptions/index.js +0 -3
  84. package/dist/runtime/serverHandler/index.js +0 -78
  85. package/dist/runtime/serverHandler/tsconfig.json +0 -3
  86. package/dist/runtime/serverOptions/defineGraphqlServerOptions.d.ts +0 -3
  87. package/dist/runtime/serverOptions/defineGraphqlServerOptions.js +0 -3
  88. package/dist/runtime/serverOptions/index.d.ts +0 -2
  89. package/dist/runtime/serverOptions/index.js +0 -2
  90. package/dist/types.d.ts +0 -7
  91. /package/dist/runtime/{serverHandler → server/api}/debug.d.ts +0 -0
  92. /package/dist/runtime/{serverHandler/index.d.ts → server/api/mutation.d.ts} +0 -0
  93. /package/dist/runtime/{serverHandler/upload.d.ts → server/api/query.d.ts} +0 -0
package/dist/module.mjs CHANGED
@@ -1,94 +1,27 @@
1
- import { loadSchema } from '@graphql-tools/load';
2
1
  import { fileURLToPath } from 'url';
3
- import { relative } from 'pathe';
4
- import { defu } from 'defu';
5
- import { useLogger, resolveFiles, defineNuxtModule, resolveAlias, createResolver, addImports, addServerImports, addTemplate, addServerHandler, addPlugin } from '@nuxt/kit';
6
- import { onDevToolsInitialized, extendServerRpc } from '@nuxt/devtools-kit';
7
- import { existsSync } from 'fs';
8
- import { GraphqlMiddlewareTemplate } from '../dist/runtime/settings/index.js';
9
- import { promises, existsSync as existsSync$1 } from 'node:fs';
10
- import { generate } from '@graphql-codegen/cli';
11
- import * as PluginSchemaAst from '@graphql-codegen/schema-ast';
2
+ import { useLogger, addTemplate, addServerTemplate, addTypeTemplate, createResolver, resolveAlias, resolveFiles, addPlugin, addServerHandler, addImports, addServerImports, useNitro, defineNuxtModule } from '@nuxt/kit';
3
+ import { existsSync, promises } from 'node:fs';
12
4
  import { basename } from 'node:path';
13
- import { Source, parse, printSourceLocation } from 'graphql';
14
- import { Generator } from 'graphql-typescript-deluxe';
15
- import { pascalCase } from 'change-case-all';
16
- import colors from 'picocolors';
5
+ import { relative, join } from 'pathe';
6
+ import { printSourceLocation, parse, Source, OperationTypeNode } from 'graphql';
7
+ import { Generator, FieldNotFoundError, TypeNotFoundError, FragmentNotFoundError } from 'graphql-typescript-deluxe';
8
+ import color from 'picocolors';
17
9
  import { validateGraphQlDocuments } from '@graphql-tools/utils';
10
+ import fs from 'node:fs/promises';
11
+ import { generate } from '@graphql-codegen/cli';
12
+ import * as PluginSchemaAst from '@graphql-codegen/schema-ast';
13
+ import { loadSchema } from '@graphql-tools/load';
14
+ import { defu } from 'defu';
15
+ import micromatch from 'micromatch';
16
+ import { ConfirmPrompt } from '@clack/core';
17
+ import isUnicodeSupported from 'is-unicode-supported';
18
+ import { existsSync as existsSync$1 } from 'fs';
19
+ import { onDevToolsInitialized, extendServerRpc } from '@nuxt/devtools-kit';
18
20
 
19
21
  const name = "nuxt-graphql-middleware";
20
- const version = "5.0.0-alpha.2";
21
-
22
- const DEVTOOLS_UI_ROUTE = "/__nuxt-graphql-middleware";
23
- const DEVTOOLS_UI_LOCAL_PORT = 3300;
24
- function setupDevToolsUI(nuxt, clientPath) {
25
- const isProductionBuild = existsSync(clientPath);
26
- if (isProductionBuild) {
27
- nuxt.hook("vite:serverCreated", async (server) => {
28
- const sirv = await import('sirv').then((r) => r.default || r);
29
- server.middlewares.use(
30
- DEVTOOLS_UI_ROUTE,
31
- sirv(clientPath, { dev: true, single: true })
32
- );
33
- });
34
- } else {
35
- nuxt.hook("vite:extendConfig", (config) => {
36
- config.server = config.server || {};
37
- config.server.proxy = config.server.proxy || {};
38
- config.server.proxy[DEVTOOLS_UI_ROUTE] = {
39
- target: "http://localhost:" + DEVTOOLS_UI_LOCAL_PORT + DEVTOOLS_UI_ROUTE,
40
- changeOrigin: true,
41
- followRedirects: true,
42
- rewrite: (path) => path.replace(DEVTOOLS_UI_ROUTE, "")
43
- };
44
- });
45
- }
46
- nuxt.hook("devtools:customTabs", (tabs) => {
47
- tabs.push({
48
- // unique identifier
49
- name: "nuxt-graphql-middleware",
50
- // title to display in the tab
51
- title: "GraphQL Middleware",
52
- // any icon from Iconify, or a URL to an image
53
- icon: "akar-icons:graphql-fill",
54
- // iframe view
55
- view: {
56
- type: "iframe",
57
- src: DEVTOOLS_UI_ROUTE
58
- }
59
- });
60
- });
61
- }
62
-
63
- function pluginLoader(name) {
64
- switch (name) {
65
- case "@graphql-codegen/schema-ast":
66
- return Promise.resolve(PluginSchemaAst);
67
- }
68
- throw new Error(`graphql-codegen plugin not found: ${name}`);
69
- }
70
- function generateSchema(moduleOptions, dest, writeToDisk) {
71
- const pluginConfig = moduleOptions.codegenSchemaConfig?.urlSchemaOptions;
72
- const schemaAstConfig = moduleOptions.codegenSchemaConfig?.schemaAstConfig || {
73
- sort: true
74
- };
75
- const config = {
76
- schema: moduleOptions.graphqlEndpoint,
77
- pluginLoader,
78
- silent: true,
79
- errorsOnly: true,
80
- config: pluginConfig,
81
- generates: {
82
- [dest]: {
83
- plugins: ["schema-ast"],
84
- config: schemaAstConfig
85
- }
86
- }
87
- };
88
- return generate(config, writeToDisk).then((v) => v[0]);
89
- }
22
+ const version = "5.0.0-alpha.20";
90
23
 
91
- const logger = useLogger(name);
24
+ const logger = useLogger("nuxt-graphql-middleware");
92
25
  const defaultOptions = {
93
26
  downloadSchema: true,
94
27
  schemaPath: "~~/schema.graphql",
@@ -97,150 +30,29 @@ const defaultOptions = {
97
30
  debug: false,
98
31
  includeComposables: true,
99
32
  documents: [],
100
- devtools: true
33
+ devtools: true,
34
+ errorOverlay: true,
35
+ graphqlConfigFilePath: "./graphql.config.ts"
101
36
  };
102
37
  function validateOptions(options) {
103
38
  if (!options.graphqlEndpoint) {
104
39
  throw new Error("Missing graphqlEndpoint.");
105
40
  }
106
41
  }
107
- async function getSchemaPath(schemaPath, options, resolver, writeToDisk = false) {
108
- const dest = resolver(schemaPath);
109
- if (!options.downloadSchema) {
110
- const fileExists2 = await promises.access(dest).then(() => true).catch(() => false);
111
- if (!fileExists2) {
112
- logger.error(
113
- '"downloadSchema" is set to false but no schema exists at ' + dest
114
- );
115
- throw new Error("Missing GraphQL schema.");
116
- }
117
- const schemaContent = await promises.readFile(dest).then((v) => v.toString());
118
- return { schemaPath, schemaContent };
119
- }
120
- if (!options.graphqlEndpoint) {
121
- throw new Error("Missing graphqlEndpoint config.");
122
- }
123
- const result = await generateSchema(options, dest, writeToDisk);
124
- return { schemaPath, schemaContent: result.content };
125
- }
126
42
  const fileExists = (path, extensions = ["js", "ts", "mjs"]) => {
127
43
  if (!path) {
128
44
  return null;
129
- } else if (existsSync$1(path)) {
45
+ } else if (existsSync(path)) {
130
46
  return path;
131
47
  }
132
48
  const extension = extensions.find(
133
- (extension2) => existsSync$1(`${path}.${extension2}`)
49
+ (extension2) => existsSync(`${path}.${extension2}`)
134
50
  );
135
51
  return extension ? `${path}.${extension}` : null;
136
52
  };
137
53
 
138
- function groupOperationsByType(ops) {
139
- const result = {
140
- query: {},
141
- mutation: {},
142
- subscription: {}
143
- };
144
- for (const op of ops) {
145
- result[op.operationType][op.graphqlName] = {
146
- hasVariables: op.hasVariables,
147
- variablesOptional: !op.needsVariables
148
- };
149
- }
150
- return result;
151
- }
152
- function buildOperationTypeCode(operationMetadata, typeName, serverApiPrefix) {
153
- const imports = [];
154
- const resultTypes = [];
155
- let code = "";
156
- let nitroCode = "";
157
- const operationNames = Object.keys(operationMetadata);
158
- if (operationNames.length === 0) {
159
- return { code, nitroCode, imports, resultTypes };
160
- }
161
- const lines = [];
162
- const nitroLines = [];
163
- for (const name of operationNames) {
164
- const nameResult = pascalCase(`${name}${typeName}`);
165
- const nameVariables = pascalCase(`${name}${typeName}Variables`);
166
- resultTypes.push(nameResult);
167
- imports.push(nameResult);
168
- const { hasVariables, variablesOptional } = operationMetadata[name];
169
- if (hasVariables) {
170
- imports.push(nameVariables);
171
- }
172
- const variablesType = hasVariables ? nameVariables : "null";
173
- lines.push(
174
- ` ${name}: [${variablesType}, ${variablesOptional ? "true" : "false"}, ${nameResult}]`
175
- );
176
- nitroLines.push(`
177
- '${serverApiPrefix}/${typeName.toLowerCase()}/${name}': {
178
- 'default': GraphqlResponse<${nameResult}>
179
- }`);
180
- }
181
- code += ` export type GraphqlMiddleware${typeName} = {
182
- ${lines.join(",\n")}
183
- }
184
- `;
185
- nitroCode += nitroLines.join("\n");
186
- return { code, nitroCode, imports, resultTypes };
187
- }
188
- function generateContextTemplate(collectedOperations, serverApiPrefix) {
189
- const grouped = groupOperationsByType(collectedOperations);
190
- const queryResult = buildOperationTypeCode(
191
- grouped.query,
192
- "Query",
193
- serverApiPrefix
194
- );
195
- const mutationResult = buildOperationTypeCode(
196
- grouped.mutation,
197
- "Mutation",
198
- serverApiPrefix
199
- );
200
- const subscriptionResult = buildOperationTypeCode(
201
- grouped.subscription,
202
- "Subscription",
203
- serverApiPrefix
204
- );
205
- const allImports = [
206
- ...queryResult.imports,
207
- ...mutationResult.imports,
208
- ...subscriptionResult.imports
209
- ];
210
- const allResultTypes = [
211
- ...queryResult.resultTypes,
212
- ...mutationResult.resultTypes,
213
- ...subscriptionResult.resultTypes
214
- ];
215
- const combinedCode = [
216
- queryResult.code,
217
- mutationResult.code,
218
- subscriptionResult.code
219
- ].join("\n");
220
- const combinedNitroCode = [
221
- queryResult.nitroCode,
222
- mutationResult.nitroCode,
223
- subscriptionResult.nitroCode
224
- ].join("\n");
225
- return `
226
- import type { GraphqlResponse } from '#graphql-middleware-server-options-build'
227
- import type {
228
- ${allImports.join(",\n ")}
229
- } from './../graphql-operations'
230
-
231
- declare module '#nuxt-graphql-middleware/generated-types' {
232
- export type GraphqlMiddlewareResponseUnion = ${allResultTypes.join(" | ")}
233
- ${combinedCode}
234
- }
235
-
236
- declare module 'nitropack' {
237
- interface InternalApi {
238
- ${combinedNitroCode}
239
- }
240
- }
241
- `;
242
- }
243
-
54
+ const SYMBOL_CROSS = "x";
55
+ const SYMBOL_CHECK = "\u2714";
244
56
  function getMaxLengths(entries) {
245
57
  let name = 0;
246
58
  let path = 0;
@@ -263,11 +75,11 @@ function logAllEntries(entries) {
263
75
  let prevHadError = false;
264
76
  for (const entry of entries) {
265
77
  const hasErrors = entry.errors.length > 0;
266
- const icon = hasErrors ? colors.red("x") : colors.green("\u2714");
78
+ const icon = hasErrors ? color.red(SYMBOL_CROSS) : color.green(SYMBOL_CHECK);
267
79
  const type = entry.type.padEnd(lengths.type);
268
- const namePadded = colors.bold(entry.name.padEnd(lengths.name));
269
- const name = hasErrors ? colors.red(namePadded) : colors.green(namePadded);
270
- const path = colors.dim(entry.path);
80
+ const namePadded = color.bold(entry.name.padEnd(lengths.name));
81
+ const name = hasErrors ? color.red(namePadded) : color.green(namePadded);
82
+ const path = color.dim(entry.path);
271
83
  const parts = [icon, type, name, path];
272
84
  if (hasErrors && !prevHadError) {
273
85
  process.stdout.write("-".repeat(process.stdout.columns) + "\n");
@@ -276,10 +88,10 @@ function logAllEntries(entries) {
276
88
  if (hasErrors) {
277
89
  const errorLines = [];
278
90
  entry.errors.forEach((error) => {
279
- let output = colors.red(error.message);
91
+ let output = color.red(error.message);
280
92
  if (error.source && error.locations) {
281
93
  for (const location of error.locations) {
282
- output += "\n\n" + colors.red(printSourceLocation(error.source, location));
94
+ output += "\n\n" + color.red(printSourceLocation(error.source, location));
283
95
  }
284
96
  }
285
97
  errorLines.push(output);
@@ -293,6 +105,7 @@ function logAllEntries(entries) {
293
105
  }
294
106
  logger.restoreStd();
295
107
  }
108
+
296
109
  class CollectedFile {
297
110
  filePath;
298
111
  fileContents;
@@ -306,6 +119,9 @@ class CollectedFile {
306
119
  }
307
120
  static async fromFilePath(filePath) {
308
121
  const content = (await promises.readFile(filePath)).toString();
122
+ if (!content) {
123
+ return null;
124
+ }
309
125
  return new CollectedFile(filePath, content, true);
310
126
  }
311
127
  /**
@@ -324,17 +140,37 @@ class CollectedFile {
324
140
  return false;
325
141
  }
326
142
  }
143
+
327
144
  class Collector {
328
- constructor(schema, context, nuxtConfigDocuments = [], generatorOptions) {
145
+ constructor(schema, helper) {
329
146
  this.schema = schema;
330
- this.context = context;
331
- this.nuxtConfigDocuments = nuxtConfigDocuments;
332
- this.generator = new Generator(schema, generatorOptions);
147
+ this.helper = helper;
148
+ const mappedOptions = { ...helper.options.codegenConfig };
149
+ if (!mappedOptions.output) {
150
+ mappedOptions.output = {};
151
+ }
152
+ if (!mappedOptions.output.buildTypeDocFilePath) {
153
+ mappedOptions.output.buildTypeDocFilePath = (filePath) => {
154
+ if (filePath.startsWith("/")) {
155
+ return this.filePathToBuildRelative(filePath);
156
+ }
157
+ return filePath;
158
+ };
159
+ }
160
+ this.generator = new Generator(schema, mappedOptions);
333
161
  }
334
162
  /**
335
163
  * All collected files.
336
164
  */
337
165
  files = /* @__PURE__ */ new Map();
166
+ /**
167
+ * All documents provided by hooks.
168
+ */
169
+ hookDocuments = /* @__PURE__ */ new Map();
170
+ /**
171
+ * All file paths provided by hooks.
172
+ */
173
+ hookFiles = /* @__PURE__ */ new Set();
338
174
  /**
339
175
  * The code generator.
340
176
  */
@@ -348,45 +184,78 @@ class Collector {
348
184
  */
349
185
  rpcItems = /* @__PURE__ */ new Map();
350
186
  /**
351
- * The generated TypeScript type template output.
352
- */
353
- outputTypes = "";
354
- /**
355
- * The generated oeprations file.
356
- */
357
- outputOperations = "";
358
- /**
359
- * The generated context template file.
187
+ * The registered templates.
360
188
  */
361
- outputContext = "";
189
+ templates = [];
362
190
  /**
363
- * Whether we need to rebuild the Generator state.
191
+ * The generated template contents.
364
192
  */
365
- needsRebuild = false;
366
- filePathToRelative(filePath) {
367
- return "./" + relative(this.context.buildDir, filePath);
193
+ templateResult = /* @__PURE__ */ new Map();
194
+ isInitialised = false;
195
+ async reset() {
196
+ this.files.clear();
197
+ this.generator.reset();
198
+ this.operationTimestamps.clear();
199
+ this.rpcItems.clear();
200
+ }
201
+ async updateSchema(schema) {
202
+ this.schema = schema;
203
+ this.generator.updateSchema(schema);
204
+ await this.reset();
205
+ await this.initDocuments();
206
+ }
207
+ filePathToBuildRelative(filePath) {
208
+ return "./" + this.helper.toModuleBuildRelative(filePath);
209
+ }
210
+ filePathToSourceRelative(filePath) {
211
+ if (filePath.startsWith("/")) {
212
+ return "./" + relative(process.cwd(), filePath);
213
+ }
214
+ return filePath;
368
215
  }
369
216
  operationToLogEntry(operation, errors) {
370
217
  return {
371
218
  name: operation.graphqlName,
372
219
  type: operation.operationType,
373
- path: operation.filePath,
220
+ path: this.filePathToSourceRelative(operation.filePath),
374
221
  errors
375
222
  };
376
223
  }
224
+ getTemplate(template, type) {
225
+ const content = this.templateResult.get(template + "-" + type);
226
+ if (content === void 0) {
227
+ throw new Error(`Missing template content: ${template}`);
228
+ }
229
+ return content;
230
+ }
377
231
  /**
378
232
  * Executes code gen and performs validation for operations.
379
233
  */
380
- buildState() {
234
+ async buildState() {
381
235
  const output = this.generator.build();
382
236
  const operations = output.getCollectedOperations();
383
237
  const generatedCode = output.getGeneratedCode();
384
- this.outputOperations = output.getOperationsFile();
385
- this.outputTypes = output.getEverything();
386
- this.outputContext = generateContextTemplate(
387
- operations,
388
- this.context.serverApiPrefix
389
- );
238
+ this.templates.forEach((template) => {
239
+ const path = template.options.path;
240
+ if (template.build) {
241
+ this.templateResult.set(
242
+ path + "-default",
243
+ this.helper.processTemplate(
244
+ template.options.path,
245
+ template.build(output, this.helper, this)
246
+ )
247
+ );
248
+ }
249
+ if (template.buildTypes) {
250
+ this.templateResult.set(
251
+ template.options.path + "-types",
252
+ this.helper.processTemplate(
253
+ template.options.path,
254
+ template.buildTypes(output, this.helper, this)
255
+ )
256
+ );
257
+ }
258
+ });
390
259
  const fragmentMap = /* @__PURE__ */ new Map();
391
260
  const operationSourceMap = /* @__PURE__ */ new Map();
392
261
  for (const code of generatedCode) {
@@ -415,90 +284,191 @@ class Collector {
415
284
  } else {
416
285
  this.operationTimestamps.set(operation.graphqlName, operation.timestamp);
417
286
  }
418
- logEntries.push(this.operationToLogEntry(operation, errors));
287
+ const shouldLog = errors.length || !this.helper.options.logOnlyErrors;
288
+ if (shouldLog) {
289
+ logEntries.push(this.operationToLogEntry(operation, errors));
290
+ }
419
291
  }
420
- logAllEntries(logEntries);
292
+ logAllEntries(
293
+ logEntries.sort((a, b) => {
294
+ return a.type.localeCompare(b.type) || a.name.localeCompare(b.name);
295
+ })
296
+ );
421
297
  if (hasErrors) {
422
298
  throw new Error("GraphQL errors");
423
299
  }
424
- for (const code of generatedCode) {
425
- const id = `${code.identifier}_${code.graphqlName}`;
426
- if (code.identifier === "fragment" || code.identifier === "mutation" || code.identifier === "query") {
427
- if (this.rpcItems.get(id)?.timestamp === code.timestamp) {
428
- continue;
300
+ await this.helper.nuxt.callHook("nuxt-graphql-middleware:build", { output });
301
+ if (this.helper.isDev) {
302
+ for (const code of generatedCode) {
303
+ const id = `${code.identifier}_${code.graphqlName}`;
304
+ if (code.identifier === "fragment" || code.identifier === "mutation" || code.identifier === "query") {
305
+ if (this.rpcItems.get(id)?.timestamp === code.timestamp) {
306
+ continue;
307
+ }
308
+ const fragmentDepdendencies = code.getGraphQLFragmentDependencies().map((name) => fragmentMap.get(name) || "").join("\n\n");
309
+ this.rpcItems.set(id, {
310
+ id,
311
+ timestamp: code.timestamp,
312
+ source: code.source + "\n\n" + fragmentDepdendencies,
313
+ name: code.graphqlName,
314
+ filePath: code.filePath,
315
+ identifier: code.identifier
316
+ });
429
317
  }
430
- const fragmentDepdendencies = code.getGraphQLFragmentDependencies().map((name) => fragmentMap.get(name) || "").join("\n\n");
431
- this.rpcItems.set(id, {
432
- id,
433
- timestamp: code.timestamp,
434
- source: code.source + "\n\n" + fragmentDepdendencies,
435
- name: code.graphqlName,
436
- filePath: code.filePath,
437
- identifier: code.identifier
438
- });
439
318
  }
440
319
  }
441
320
  }
321
+ buildErrorMessage(error) {
322
+ let output = "";
323
+ if (error instanceof FieldNotFoundError || error instanceof TypeNotFoundError || error instanceof FragmentNotFoundError) {
324
+ const filePath = error.context?.filePath;
325
+ const file = filePath ? this.files.get(filePath) : null;
326
+ if (filePath) {
327
+ output += ` | ${this.filePathToSourceRelative(filePath)}
328
+ `;
329
+ }
330
+ output += "\n" + error.message + "\n\n";
331
+ if (file) {
332
+ output += file.fileContents;
333
+ }
334
+ } else if (error instanceof Error) {
335
+ output += "\n" + error.message;
336
+ }
337
+ return output;
338
+ }
339
+ logError(error) {
340
+ let output = `${SYMBOL_CROSS}`;
341
+ output += this.buildErrorMessage(error);
342
+ logger.error(color.red(output));
343
+ }
442
344
  /**
443
- * Get all file paths that match the import patterns.
345
+ * Initialise the collector.
346
+ *
347
+ * In dev mode, the method will call itself recursively until all documents
348
+ * are valid.
349
+ *
350
+ * If not in dev mode the method will throw an error when documents are not
351
+ * valid.
444
352
  */
445
- async getImportPatternFiles() {
446
- if (this.context.patterns.length) {
447
- return resolveFiles(this.context.srcDir, this.context.patterns, {
448
- followSymbolicLinks: false
353
+ async init() {
354
+ try {
355
+ await this.initDocuments();
356
+ } catch {
357
+ if (this.helper.isDev) {
358
+ const shouldRevalidate = await this.helper.prompt.confirm(
359
+ "Do you want to revalidate the GraphQL documents?"
360
+ );
361
+ if (shouldRevalidate === "yes") {
362
+ await this.reset();
363
+ await this.init();
364
+ this.isInitialised = true;
365
+ return;
366
+ }
367
+ }
368
+ throw new Error("Graphql document validation failed.");
369
+ }
370
+ }
371
+ addHookDocument(identifier, source) {
372
+ this.hookDocuments.set("hook:" + identifier, source);
373
+ }
374
+ async addOrUpdateHookDocument(identifier, source) {
375
+ const fullIdentifier = "hook:" + identifier;
376
+ const exists = this.hookDocuments.has(fullIdentifier);
377
+ this.hookDocuments.set(fullIdentifier, source);
378
+ if (exists && this.isInitialised) {
379
+ this.generator.update({
380
+ filePath: fullIdentifier,
381
+ document: source
449
382
  });
383
+ await this.buildState();
450
384
  }
451
- return [];
385
+ }
386
+ addHookFile(filePath) {
387
+ this.hookFiles.add(filePath);
452
388
  }
453
389
  /**
454
390
  * Initialise the collector.
455
391
  */
456
- async init() {
457
- const files = await this.getImportPatternFiles();
458
- for (const filePath of files) {
459
- await this.addFile(filePath);
460
- }
461
- this.nuxtConfigDocuments.forEach((docString, i) => {
462
- const pseudoPath = `nuxt.config.ts[${i}]`;
463
- const file = new CollectedFile(pseudoPath, docString, false);
464
- this.files.set(pseudoPath, file);
465
- this.generator.add({
466
- filePath: this.filePathToRelative("nuxt.config.ts"),
467
- documentNode: file.parsed
392
+ async initDocuments() {
393
+ try {
394
+ const files = await this.helper.getImportPatternFiles();
395
+ for (const filePath of files) {
396
+ await this.addFile(filePath);
397
+ }
398
+ const nuxtConfigDocuments = this.helper.options.documents.join("\n\n");
399
+ if (nuxtConfigDocuments.length) {
400
+ const filePath = this.helper.paths.nuxtConfig;
401
+ const file = new CollectedFile(filePath, nuxtConfigDocuments, false);
402
+ this.files.set(filePath, file);
403
+ this.generator.add({
404
+ filePath,
405
+ documentNode: file.parsed
406
+ });
407
+ }
408
+ const hookDocuments = [...this.hookDocuments.entries()];
409
+ hookDocuments.forEach(([identifier, source]) => {
410
+ const file = new CollectedFile(identifier, source, false);
411
+ this.files.set(identifier, file);
412
+ this.generator.add({
413
+ filePath: identifier,
414
+ documentNode: file.parsed
415
+ });
468
416
  });
469
- });
470
- this.buildState();
471
- logger.success("All GraphQL documents are valid.");
417
+ for (const filePath of this.hookFiles) {
418
+ await this.addFile(filePath);
419
+ }
420
+ await this.buildState();
421
+ logger.success("All GraphQL documents are valid.");
422
+ } catch (e) {
423
+ this.logError(e);
424
+ throw new Error("GraphQL document validation failed.");
425
+ }
472
426
  }
473
427
  /**
474
428
  * Add a file.
475
429
  */
476
430
  async addFile(filePath) {
477
431
  const file = await CollectedFile.fromFilePath(filePath);
432
+ if (!file?.fileContents) {
433
+ return null;
434
+ }
478
435
  this.files.set(filePath, file);
479
436
  this.generator.add({
480
- filePath: this.filePathToRelative(filePath),
437
+ filePath,
481
438
  documentNode: file.parsed
482
439
  });
483
440
  return file;
484
441
  }
442
+ matchesPatternOrExists(filePath) {
443
+ return this.files.has(filePath) || this.hookFiles.has(filePath) || this.hookDocuments.has(filePath) || this.helper.matchesImportPattern(filePath);
444
+ }
485
445
  async handleAdd(filePath) {
486
- await this.addFile(filePath);
487
- return true;
446
+ if (!this.matchesPatternOrExists(filePath)) {
447
+ return false;
448
+ }
449
+ const result = await this.addFile(filePath);
450
+ return !!result;
488
451
  }
489
452
  async handleChange(filePath) {
453
+ if (!this.matchesPatternOrExists(filePath)) {
454
+ return false;
455
+ }
490
456
  const file = this.files.get(filePath);
491
457
  if (!file) {
492
- return false;
458
+ return this.handleAdd(filePath);
493
459
  }
494
- const needsUpdate = await file.update();
495
- if (!needsUpdate) {
496
- return false;
460
+ try {
461
+ const needsUpdate = await file.update();
462
+ if (!needsUpdate) {
463
+ return false;
464
+ }
465
+ this.generator.update({
466
+ filePath,
467
+ documentNode: file.parsed
468
+ });
469
+ } catch {
470
+ return this.handleUnlink(filePath);
497
471
  }
498
- this.generator.update({
499
- filePath: this.filePathToRelative(filePath),
500
- documentNode: file.parsed
501
- });
502
472
  return true;
503
473
  }
504
474
  handleUnlink(filePath) {
@@ -507,7 +477,7 @@ class Collector {
507
477
  return false;
508
478
  }
509
479
  this.files.delete(filePath);
510
- this.generator.remove(this.filePathToRelative(filePath));
480
+ this.generator.remove(filePath);
511
481
  return true;
512
482
  }
513
483
  handleUnlinkDir(folderPath) {
@@ -527,6 +497,7 @@ class Collector {
527
497
  */
528
498
  async handleWatchEvent(event, filePath) {
529
499
  let hasChanged = false;
500
+ const oldOperationTimestamps = new Map(this.operationTimestamps);
530
501
  try {
531
502
  if (event === "add") {
532
503
  hasChanged = await this.handleAdd(filePath);
@@ -536,357 +507,1181 @@ class Collector {
536
507
  hasChanged = this.handleUnlink(filePath);
537
508
  } else if (event === "unlinkDir") {
538
509
  hasChanged = this.handleUnlinkDir(filePath);
539
- } else if (event === "addDir") {
540
510
  }
541
511
  if (hasChanged) {
542
- this.buildState();
512
+ await this.buildState();
543
513
  }
544
514
  } catch (e) {
545
515
  this.generator.resetCaches();
546
516
  logger.error("Failed to update GraphQL code.");
547
- logger.error(e);
548
- return false;
517
+ this.logError(e);
518
+ return {
519
+ hasChanged: false,
520
+ affectedOperations: [],
521
+ error: { message: this.buildErrorMessage(e) }
522
+ };
549
523
  }
524
+ const affectedOperations = [];
550
525
  if (hasChanged) {
551
- logger.success("Finished GraphQL code update.");
526
+ logger.success("Finished GraphQL code update successfully.");
527
+ for (const [name, newTimestamp] of this.operationTimestamps) {
528
+ const oldTimestamp = oldOperationTimestamps.get(name);
529
+ if (!oldTimestamp || oldTimestamp !== newTimestamp) {
530
+ affectedOperations.push(name);
531
+ }
532
+ }
552
533
  }
553
- return hasChanged;
534
+ return { hasChanged, affectedOperations };
535
+ }
536
+ /**
537
+ * Adds a virtual template (not written to disk) for both Nuxt and Nitro.
538
+ *
539
+ * For some reason a template written to disk works for both Nuxt and Nitro,
540
+ * but a virtual template requires adding two templates.
541
+ */
542
+ addVirtualTemplate(template) {
543
+ const filename = template.options.path + ".js";
544
+ const getContents = () => this.getTemplate(template.options.path, "default");
545
+ addTemplate({
546
+ filename,
547
+ getContents
548
+ });
549
+ addServerTemplate({
550
+ // Since this is a virtual template, the name must match the final
551
+ // alias, example:
552
+ // - nuxt-graphql-middleware/foobar.mjs => #nuxt-graphql-middleware/foobar
553
+ //
554
+ // That way we can reference the same template using the alias in both
555
+ // Nuxt and Nitro environments.
556
+ filename: "#" + template.options.path,
557
+ getContents
558
+ });
554
559
  }
555
560
  /**
556
- * Get the TypeScript types template contents.
561
+ * Adds a template that dependes on Collector state.
557
562
  */
558
- getTemplateTypes() {
559
- return this.outputTypes;
563
+ addTemplate(template) {
564
+ this.templates.push(template);
565
+ if (template.build) {
566
+ if (template.options.virtual) {
567
+ this.addVirtualTemplate(template);
568
+ } else {
569
+ const path = template.options.path;
570
+ const filename = template.options.isFullPath ? path : path + ".js";
571
+ addTemplate({
572
+ filename,
573
+ write: true,
574
+ getContents: () => this.getTemplate(path, "default")
575
+ });
576
+ }
577
+ }
578
+ if (template.buildTypes) {
579
+ const path = template.options.path;
580
+ const filename = template.options.path + ".d.ts";
581
+ addTypeTemplate(
582
+ {
583
+ filename,
584
+ write: true,
585
+ getContents: () => this.getTemplate(path, "types")
586
+ },
587
+ {
588
+ nuxt: true,
589
+ nitro: true
590
+ }
591
+ );
592
+ }
560
593
  }
561
594
  /**
562
- * Get the context template contents.
595
+ * Get the hook documents.
563
596
  */
564
- getTemplateContext() {
565
- return this.outputContext;
597
+ getHookDocuments() {
598
+ return [...this.hookDocuments.entries()].map(([identifier, source]) => {
599
+ return {
600
+ identifier,
601
+ source
602
+ };
603
+ });
566
604
  }
567
605
  /**
568
- * Get the operations template contents.
606
+ * Get the hook documents.
569
607
  */
570
- getTemplateOperations() {
571
- return this.outputOperations;
608
+ getHookFiles() {
609
+ return [...this.hookFiles.values()];
572
610
  }
573
611
  }
574
612
 
575
- const RPC_NAMESPACE = "nuxt-graphql-middleware";
576
- const module = defineNuxtModule({
577
- meta: {
578
- name,
579
- configKey: "graphqlMiddleware",
580
- version,
581
- compatibility: {
582
- nuxt: ">=3.13.0"
583
- }
584
- },
585
- defaults: defaultOptions,
586
- async setup(passedOptions, nuxt) {
587
- const options = defu({}, passedOptions, defaultOptions);
588
- function addAlias(name2, aliasPath) {
589
- nuxt.options.alias[name2] = aliasPath;
590
- }
591
- const isModuleBuild = process.env.MODULE_BUILD === "true" && nuxt.options._prepare;
592
- if (isModuleBuild) {
593
- options.graphqlEndpoint = "http://localhost";
594
- options.downloadSchema = false;
595
- options.schemaPath = "~~/schema.graphql";
596
- options.autoImportPatterns = [
597
- "~~/playground/**/*.{gql,graphql}",
598
- "!node_modules"
599
- ];
600
- }
601
- if (!passedOptions.autoImportPatterns) {
602
- options.autoImportPatterns = ["~~/**/*.{gql,graphql}", "!node_modules"];
603
- }
604
- options.autoImportPatterns = (options.autoImportPatterns || []).map(
605
- (pattern) => {
606
- return resolveAlias(pattern);
613
+ class SchemaProvider {
614
+ constructor(helper) {
615
+ this.helper = helper;
616
+ }
617
+ /**
618
+ * The raw schema content.
619
+ */
620
+ schemaContent = "";
621
+ /**
622
+ * The parsed schema object.
623
+ */
624
+ schema = null;
625
+ async init() {
626
+ try {
627
+ await this.loadSchema();
628
+ } catch (error) {
629
+ logger.error(error);
630
+ const hasLoaded = await this.loadFromDiskFallback();
631
+ if (!hasLoaded) {
632
+ throw new Error("Failed to load GraphQL schema.");
607
633
  }
608
- );
609
- if (!nuxt.options._prepare) {
610
- validateOptions(options);
611
- }
612
- const moduleResolver = createResolver(import.meta.url);
613
- const serverResolver = createResolver(nuxt.options.serverDir);
614
- const srcResolver = createResolver(nuxt.options.srcDir);
615
- const appResolver = createResolver(nuxt.options.dir.app);
616
- const rootDir = nuxt.options.rootDir;
617
- const rootResolver = createResolver(rootDir);
618
- const { schemaPath, schemaContent } = await getSchemaPath(
619
- resolveAlias(options.schemaPath),
620
- options,
621
- rootResolver.resolve,
622
- options.downloadSchema
623
- );
624
- const schema = await loadSchema(schemaContent, {
625
- loaders: []
626
- });
627
- const runtimeDir = fileURLToPath(new URL("./runtime", import.meta.url));
628
- nuxt.options.build.transpile.push(runtimeDir);
629
- const context = {
630
- patterns: options.autoImportPatterns || [],
631
- srcDir: nuxt.options.srcDir,
632
- buildDir: srcResolver.resolve(nuxt.options.buildDir),
633
- schemaPath,
634
- serverApiPrefix: options.serverApiPrefix
635
- };
636
- const collector = new Collector(
637
- schema,
638
- context,
639
- options.documents,
640
- options.codegenConfig
641
- );
642
- await collector.init();
643
- const isDevToolsEnabled = nuxt.options.dev && options.devtools;
644
- let rpc;
645
- if (isDevToolsEnabled) {
646
- const clientPath = moduleResolver.resolve("./client");
647
- setupDevToolsUI(nuxt, clientPath);
648
- onDevToolsInitialized(() => {
649
- rpc = extendServerRpc(RPC_NAMESPACE, {
650
- // register server RPC functions
651
- getModuleOptions() {
652
- return options;
653
- },
654
- getDocuments() {
655
- return [...collector.rpcItems.values()];
656
- }
657
- });
658
- });
659
634
  }
660
- nuxt.options.runtimeConfig.public["nuxt-graphql-middleware"] = {
661
- serverApiPrefix: options.serverApiPrefix
662
- };
663
- nuxt.options.appConfig.graphqlMiddleware = {
664
- clientCacheEnabled: !!options.clientCache?.enabled,
665
- clientCacheMaxSize: options.clientCache?.maxSize || 100
666
- };
667
- nuxt.options.runtimeConfig.graphqlMiddleware = {
668
- graphqlEndpoint: options.graphqlEndpoint || ""
669
- };
670
- if (options.includeComposables) {
671
- const nuxtComposables = [
672
- "useGraphqlQuery",
673
- "useGraphqlMutation",
674
- "useGraphqlState",
675
- "useAsyncGraphqlQuery"
676
- ];
677
- if (options.enableFileUploads) {
678
- nuxtComposables.push("useGraphqlUploadMutation");
635
+ }
636
+ async loadFromDiskFallback() {
637
+ const hasSchemaOnDisk = await this.hasSchemaOnDisk();
638
+ if (this.helper.isDev && hasSchemaOnDisk && this.helper.options.downloadSchema) {
639
+ const shouldUseFromDisk = await this.helper.prompt.confirm(
640
+ "Do you want to continue with the previously downloaded schema from disk?"
641
+ );
642
+ if (shouldUseFromDisk === "yes") {
643
+ await this.loadSchema({ forceDisk: true });
644
+ return true;
679
645
  }
680
- nuxtComposables.forEach((name2) => {
681
- addImports({
682
- from: moduleResolver.resolve("./runtime/composables/" + name2),
683
- name: name2
684
- });
685
- });
686
- const serverUtils = ["useGraphqlQuery", "useGraphqlMutation"].map(
687
- (name2) => {
688
- return {
689
- from: moduleResolver.resolve("./runtime/server/utils/" + name2),
690
- name: name2
691
- };
692
- }
646
+ }
647
+ return false;
648
+ }
649
+ /**
650
+ * Loads the schema from disk.
651
+ *
652
+ * @returns The schema contents from disk.
653
+ */
654
+ async loadSchemaFromDisk() {
655
+ const fileExists = await this.hasSchemaOnDisk();
656
+ if (!fileExists) {
657
+ logger.error(
658
+ '"downloadSchema" is set to false but no schema exists at ' + this.helper.paths.schema
693
659
  );
694
- addServerImports(serverUtils);
660
+ throw new Error("Missing GraphQL schema.");
695
661
  }
696
- const templateTypescript = addTemplate({
697
- filename: GraphqlMiddlewareTemplate.OperationTypes,
698
- write: true,
699
- getContents: () => collector.getTemplateTypes()
700
- });
701
- addAlias("#graphql-operations", templateTypescript.dst);
702
- const templateDocuments = addTemplate({
703
- filename: GraphqlMiddlewareTemplate.Documents,
704
- write: true,
705
- getContents: () => collector.getTemplateOperations()
706
- });
707
- addAlias("#graphql-documents", templateDocuments.dst);
708
- const templateContext = addTemplate({
709
- filename: GraphqlMiddlewareTemplate.ComposableContext,
710
- write: true,
711
- getContents: () => collector.getTemplateContext()
712
- });
713
- addAlias("#nuxt-graphql-middleware/generated-types", templateContext.dst);
714
- addTemplate({
715
- write: true,
716
- filename: "nuxt-graphql-middleware/graphql-documents.d.ts",
717
- getContents: () => {
718
- return `
719
- import type {
720
- GraphqlMiddlewareQuery,
721
- GraphqlMiddlewareMutation,
722
- } from '#nuxt-graphql-middleware/generated-types'
723
-
724
- declare module '#graphql-documents' {
725
- type Operations = {
726
- query: GraphqlMiddlewareQuery
727
- mutation: GraphqlMiddlewareMutation
662
+ logger.info(`Loading GraphQL schema from disk: ${this.helper.paths.schema}`);
663
+ return await fs.readFile(this.helper.paths.schema).then((v) => v.toString());
728
664
  }
729
- const operations: Operations
730
- export { operations, Operations }
731
- }
732
- `;
733
- }
734
- });
735
- const findServerOptions = () => {
736
- const newPath = serverResolver.resolve("graphqlMiddleware.serverOptions");
737
- const serverPath = fileExists(newPath);
738
- if (serverPath) {
739
- return serverPath;
740
- }
741
- const candidates = [
742
- rootResolver.resolve("graphqlMiddleware.serverOptions"),
743
- rootResolver.resolve("app/graphqlMiddleware.serverOptions"),
744
- srcResolver.resolve("graphqlMiddleware.serverOptions")
745
- ];
746
- for (let i = 0; i < candidates.length; i++) {
747
- const path = candidates[i];
748
- const filePath = fileExists(path);
749
- if (filePath) {
750
- logger.warn(
751
- `The graphqlMiddleware.serverOptions file should be placed in Nuxt's <serverDir> ("${nuxt.options.serverDir}/graphqlMiddleware.serverOptions.ts"). The new path will be enforced in the next major release.`
752
- );
753
- return filePath;
665
+ /**
666
+ * Downloads the schema and saves it to disk.
667
+ *
668
+ * @returns The schema contents.
669
+ */
670
+ downloadSchema() {
671
+ const endpoint = this.helper.options.graphqlEndpoint;
672
+ if (!endpoint) {
673
+ throw new Error("Missing graphqlEndpoint config.");
674
+ }
675
+ const pluginConfig = this.helper.options.codegenSchemaConfig?.urlSchemaOptions;
676
+ const schemaAstConfig = this.helper.options.codegenSchemaConfig?.schemaAstConfig || {
677
+ sort: true
678
+ };
679
+ const config = {
680
+ schema: endpoint,
681
+ pluginLoader: (name) => {
682
+ switch (name) {
683
+ case "@graphql-codegen/schema-ast":
684
+ return Promise.resolve(PluginSchemaAst);
685
+ }
686
+ throw new Error(`graphql-codegen plugin not found: ${name}`);
687
+ },
688
+ silent: true,
689
+ errorsOnly: true,
690
+ config: pluginConfig,
691
+ generates: {
692
+ [this.helper.paths.schema]: {
693
+ plugins: ["schema-ast"],
694
+ config: schemaAstConfig
754
695
  }
755
696
  }
756
- logger.info("No graphqlMiddleware.serverOptions file found.");
757
697
  };
758
- const resolvedPath = findServerOptions();
759
- const moduleTypesPath = relative(
760
- nuxt.options.buildDir,
761
- moduleResolver.resolve("./types")
762
- );
763
- const resolvedPathRelative = resolvedPath ? relative(nuxt.options.buildDir, resolvedPath) : null;
764
- const template = addTemplate({
765
- filename: "graphqlMiddleware.serverOptions.mjs",
766
- write: true,
767
- getContents: () => {
768
- const serverOptionsLine = resolvedPathRelative ? `import serverOptions from '${resolvedPathRelative}'` : `const serverOptions = {}`;
769
- return `
770
- ${serverOptionsLine}
771
- export { serverOptions }
772
- `;
773
- }
698
+ logger.info(`Downloading GraphQL schema from "${endpoint}".`);
699
+ return generate(config, true).then((v) => v[0]?.content);
700
+ }
701
+ /**
702
+ * Determine if the schema exists on disk.
703
+ *
704
+ * @returns True if the schema file exists on disk.
705
+ */
706
+ hasSchemaOnDisk() {
707
+ return fs.access(this.helper.paths.schema).then(() => true).catch(() => false);
708
+ }
709
+ /**
710
+ * Load the schema either from disk or by downloading it.
711
+ *
712
+ * @param forceDownload - Forces downloading the schema.
713
+ */
714
+ async loadSchema(opts) {
715
+ if (opts?.forceDisk) {
716
+ this.schemaContent = await this.loadSchemaFromDisk();
717
+ } else if (this.helper.options.downloadSchema || opts?.forceDownload) {
718
+ this.schemaContent = await this.downloadSchema();
719
+ } else {
720
+ this.schemaContent = await this.loadSchemaFromDisk();
721
+ }
722
+ this.schema = await loadSchema(this.schemaContent, {
723
+ loaders: []
774
724
  });
775
- addTemplate({
776
- filename: "graphqlMiddleware.serverOptions.d.ts",
777
- write: true,
778
- getContents: () => {
779
- const serverOptionsLineTypes = resolvedPathRelative ? `import serverOptions from '${resolvedPathRelative}'` : `const serverOptions: GraphqlMiddlewareServerOptions = {}`;
780
- return `
781
- import type { GraphqlMiddlewareServerOptions } from '${moduleTypesPath}'
782
- ${serverOptionsLineTypes}
783
- import type { GraphqlServerResponse } from '${runtimeTypesPath}'
784
- import type { GraphqlMiddlewareResponseUnion } from '#nuxt-graphql-middleware/generated-types'
785
-
786
- type GraphqlResponseAdditions =
787
- typeof serverOptions extends GraphqlMiddlewareServerOptions<infer R, any, any> ? R : {}
788
-
789
- export type GraphqlResponse<T> = GraphqlServerResponse<T> & GraphqlResponseAdditions
790
-
791
- export type GraphqlResponseTyped = GraphqlResponse<GraphqlMiddlewareResponseUnion>
725
+ }
726
+ /**
727
+ * Get the schema.
728
+ *
729
+ * @returns The parsed GraphQL schema object.
730
+ */
731
+ getSchema() {
732
+ if (!this.schema) {
733
+ throw new Error("Failed to load schema.");
734
+ }
735
+ return this.schema;
736
+ }
737
+ }
792
738
 
793
- export { serverOptions }
739
+ const unicode = isUnicodeSupported();
740
+ const s = (c, fallback) => unicode ? c : fallback;
741
+ const S_BAR = s("\u2502", "|");
742
+ const S_STEP_ACTIVE = s("\u25C6", "*");
743
+ const S_STEP_CANCEL = s("\u25A0", "x");
744
+ const S_STEP_ERROR = s("\u25B2", "x");
745
+ const S_STEP_SUBMIT = s("\u25C7", "o");
746
+ const S_RADIO_ACTIVE = s("\u25CF", ">");
747
+ const S_RADIO_INACTIVE = s("\u25CB", " ");
748
+ const S_BAR_END = s("\u2514", "\u2014");
749
+ const symbol = (state) => {
750
+ switch (state) {
751
+ case "initial":
752
+ case "active":
753
+ return color.cyan(S_STEP_ACTIVE);
754
+ case "cancel":
755
+ return color.red(S_STEP_CANCEL);
756
+ case "error":
757
+ return color.yellow(S_STEP_ERROR);
758
+ case "submit":
759
+ return color.green(S_STEP_SUBMIT);
760
+ }
761
+ };
762
+ class ConsolePrompt {
763
+ abortController = null;
764
+ confirm(message) {
765
+ this.abort();
766
+ this.abortController = new AbortController();
767
+ const active = "Yes";
768
+ const inactive = "No";
769
+ return new ConfirmPrompt({
770
+ active,
771
+ inactive,
772
+ initialValue: true,
773
+ signal: this.abortController.signal,
774
+ render() {
775
+ const title = `${color.gray(S_BAR)}
776
+ ${symbol(this.state)} ${message}
794
777
  `;
778
+ const value = this.value ? active : inactive;
779
+ switch (this.state) {
780
+ case "submit":
781
+ return `${title}${color.gray(S_BAR)} ${color.dim(value)}`;
782
+ case "cancel":
783
+ return `${title}${color.gray(S_BAR)} ${color.strikethrough(
784
+ color.dim(value)
785
+ )}
786
+ ${color.gray(S_BAR)}`;
787
+ default: {
788
+ return `${title}${color.cyan(S_BAR)} ${this.value ? `${color.green(S_RADIO_ACTIVE)} ${active}` : `${color.dim(S_RADIO_INACTIVE)} ${color.dim(active)}`} ${color.dim("/")} ${!this.value ? `${color.green(S_RADIO_ACTIVE)} ${inactive}` : `${color.dim(S_RADIO_INACTIVE)} ${color.dim(inactive)}`}
789
+ ${color.cyan(S_BAR_END)}
790
+ `;
791
+ }
792
+ }
795
793
  }
794
+ }).prompt().then((v) => {
795
+ const result = v;
796
+ if (result === true) {
797
+ return "yes";
798
+ } else if (result === false) {
799
+ return "no";
800
+ }
801
+ return "cancel";
796
802
  });
797
- const getClientOptionsImport = () => {
798
- const clientOptionsPath = appResolver.resolve(
799
- "graphqlMiddleware.clientOptions"
800
- );
801
- if (fileExists(clientOptionsPath)) {
802
- const pathRelative = relative(nuxt.options.buildDir, clientOptionsPath);
803
- return `import clientOptions from '${pathRelative}'`;
803
+ }
804
+ abort() {
805
+ if (this.abortController) {
806
+ this.abortController.abort();
807
+ this.abortController = null;
808
+ }
809
+ }
810
+ }
811
+
812
+ class ModuleHelper {
813
+ constructor(nuxt, moduleUrl, options) {
814
+ this.nuxt = nuxt;
815
+ const mergedOptions = defu({}, options, defaultOptions);
816
+ if (!mergedOptions.autoImportPatterns) {
817
+ mergedOptions.autoImportPatterns = [
818
+ "~~/**/*.{gql,graphql}",
819
+ "!node_modules"
820
+ ];
821
+ }
822
+ const layerAliases = nuxt.options._layers.map((layer) => {
823
+ return {
824
+ "~~": layer.config.rootDir,
825
+ "@@": layer.config.rootDir,
826
+ "~": layer.config.srcDir,
827
+ "@": layer.config.srcDir,
828
+ // Merge any additional aliases defined by the layer.
829
+ // Must be last so that the layer may override the "default" aliases.
830
+ ...layer.config.alias || {}
831
+ };
832
+ });
833
+ const srcResolver = createResolver(nuxt.options.srcDir);
834
+ const rootResolver = createResolver(nuxt.options.rootDir);
835
+ mergedOptions.autoImportPatterns = (mergedOptions.autoImportPatterns || []).flatMap((pattern) => {
836
+ if (pattern.startsWith("!") || pattern.startsWith("/")) {
837
+ return pattern;
838
+ } else if (pattern.startsWith("~") || pattern.startsWith("@")) {
839
+ return layerAliases.map((aliases) => resolveAlias(pattern, aliases));
804
840
  }
841
+ return rootResolver.resolve(pattern);
842
+ }).map((pattern) => {
843
+ return pattern.replace(".{graphql}", ".graphql").replace(".{gql}", ".gql");
844
+ });
845
+ this.options = mergedOptions;
846
+ if (!nuxt.options._prepare) {
847
+ validateOptions(this.options);
848
+ }
849
+ this.isDev = nuxt.options.dev;
850
+ this.resolvers = {
851
+ module: createResolver(moduleUrl),
852
+ server: createResolver(nuxt.options.serverDir),
853
+ src: srcResolver,
854
+ app: createResolver(nuxt.options.dir.app),
855
+ root: rootResolver
805
856
  };
806
- const clientOptionsImport = getClientOptionsImport();
807
- const clientOptionsTemplate = addTemplate({
808
- filename: "graphqlMiddleware.clientOptions.mjs",
809
- write: true,
810
- getContents: () => {
811
- if (clientOptionsImport) {
812
- return `${clientOptionsImport}
813
- export { clientOptions }`;
814
- }
815
- return `export const clientOptions = {}`;
857
+ this.paths = {
858
+ runtimeTypes: "",
859
+ root: nuxt.options.rootDir,
860
+ nuxtConfig: this.resolvers.root.resolve("nuxt.config.ts"),
861
+ serverDir: nuxt.options.serverDir,
862
+ schema: this.resolvers.root.resolve(
863
+ resolveAlias(this.options.schemaPath)
864
+ ),
865
+ serverOptions: "",
866
+ clientOptions: this.findClientOptions(),
867
+ moduleBuildDir: nuxt.options.buildDir + "/nuxt-graphql-middleware",
868
+ moduleTypesDir: nuxt.options.buildDir + "/graphql-operations"
869
+ };
870
+ this.paths.runtimeTypes = this.toModuleBuildRelative(
871
+ this.resolvers.module.resolve("./runtime/types.ts")
872
+ );
873
+ this.paths.serverOptions = this.findServerOptions();
874
+ }
875
+ resolvers;
876
+ paths;
877
+ isDev;
878
+ options;
879
+ prompt = new ConsolePrompt();
880
+ nitroExternals = [];
881
+ tsPaths = {};
882
+ /**
883
+ * Find the path to the graphqlMiddleware.serverOptions.ts file.
884
+ */
885
+ findServerOptions() {
886
+ const newPath = this.resolvers.server.resolve(
887
+ "graphqlMiddleware.serverOptions"
888
+ );
889
+ const serverPath = fileExists(newPath);
890
+ if (serverPath) {
891
+ return serverPath;
892
+ }
893
+ const candidates = [
894
+ this.resolvers.root.resolve("graphqlMiddleware.serverOptions"),
895
+ this.resolvers.root.resolve("app/graphqlMiddleware.serverOptions"),
896
+ this.resolvers.src.resolve("graphqlMiddleware.serverOptions")
897
+ ];
898
+ for (let i = 0; i < candidates.length; i++) {
899
+ const path = candidates[i];
900
+ const filePath = fileExists(path);
901
+ if (filePath) {
902
+ throw new Error(
903
+ `The graphqlMiddleware.serverOptions file should be placed in Nuxt's <serverDir> ("${this.paths.serverDir}/graphqlMiddleware.serverOptions.ts").`
904
+ );
816
905
  }
817
- });
818
- const runtimeTypesPath = relative(
819
- nuxt.options.buildDir,
820
- moduleResolver.resolve("./runtime/types.ts")
906
+ }
907
+ logger.info("No graphqlMiddleware.serverOptions file found.");
908
+ return null;
909
+ }
910
+ findClientOptions() {
911
+ const clientOptionsPath = this.resolvers.app.resolve(
912
+ "graphqlMiddleware.clientOptions"
821
913
  );
822
- addTemplate({
823
- filename: "graphqlMiddleware.clientOptions.d.ts",
824
- write: true,
825
- getContents: () => {
826
- if (clientOptionsImport) {
827
- return `import type { GraphqlClientOptions } from '${runtimeTypesPath}'
828
- ${clientOptionsImport}
914
+ if (fileExists(clientOptionsPath)) {
915
+ return clientOptionsPath;
916
+ }
917
+ return null;
918
+ }
919
+ /**
920
+ * Transform the path relative to the module's build directory.
921
+ *
922
+ * @param path - The absolute path.
923
+ *
924
+ * @returns The path relative to the module's build directory.
925
+ */
926
+ toModuleBuildRelative(path) {
927
+ return relative(this.paths.moduleBuildDir, path);
928
+ }
929
+ /**
930
+ * Transform the path relative to the Nuxt build directory.
931
+ *
932
+ * @param path - The absolute path.
933
+ *
934
+ * @returns The path relative to the module's build directory.
935
+ */
936
+ toBuildRelative(path) {
937
+ return relative(this.nuxt.options.buildDir, path);
938
+ }
939
+ /**
940
+ * Get all file paths that match the import patterns.
941
+ */
942
+ async getImportPatternFiles() {
943
+ return resolveFiles(
944
+ this.nuxt.options.srcDir,
945
+ this.options.autoImportPatterns
946
+ );
947
+ }
948
+ matchesImportPattern(filePath) {
949
+ return micromatch.isMatch(filePath, this.options.autoImportPatterns) || this.options.autoImportPatterns.includes(filePath);
950
+ }
951
+ addAlias(name, path) {
952
+ this.nuxt.options.alias[name] = path;
953
+ const pathFromName = `./${name.substring(1)}`;
954
+ this.tsPaths[name] = pathFromName;
955
+ this.tsPaths[name + "/*"] = pathFromName + "/*";
956
+ this.inlineNitroExternals(name);
957
+ }
958
+ inlineNitroExternals(arg) {
959
+ const path = typeof arg === "string" ? arg : arg.dst;
960
+ this.nitroExternals.push(path);
961
+ this.transpile(path);
962
+ }
963
+ transpile(path) {
964
+ this.nuxt.options.build.transpile.push(path);
965
+ }
966
+ applyBuildConfig() {
967
+ this.nuxt.options.nitro.externals ||= {};
968
+ this.nuxt.options.nitro.externals.inline ||= [];
969
+ this.nuxt.options.nitro.externals.inline.push(...this.nitroExternals);
970
+ this.nuxt.options.nitro.typescript ||= {};
971
+ this.nuxt.options.nitro.typescript.tsConfig ||= {};
972
+ this.nuxt.options.nitro.typescript.tsConfig.compilerOptions ||= {};
973
+ this.nuxt.options.nitro.typescript.tsConfig.compilerOptions.paths ||= {};
974
+ this.nuxt.options.typescript.tsConfig ||= {};
975
+ this.nuxt.options.typescript.tsConfig.compilerOptions ||= {};
976
+ this.nuxt.options.typescript.tsConfig.compilerOptions.paths ||= {};
977
+ for (const [name, path] of Object.entries(this.tsPaths)) {
978
+ this.nuxt.options.nitro.typescript.tsConfig.compilerOptions.paths[name] = [path];
979
+ this.nuxt.options.typescript.tsConfig.compilerOptions.paths[name] = [path];
980
+ }
981
+ }
982
+ processTemplate(path, content) {
983
+ if (path.includes("graphql-operations/") || path.endsWith(".graphql")) {
984
+ return content.trim();
985
+ }
986
+ const name = path.split("/")[1];
987
+ return `/*
988
+ * @see [Documentation](https://nuxt-graphql-middleware.dulnan.net/advanced/templates#${name})
989
+ */
990
+ ${content.trim()}`;
991
+ }
992
+ addTemplate(template) {
993
+ if (template.build) {
994
+ const content = this.processTemplate(
995
+ template.options.path,
996
+ template.build(this)
997
+ );
998
+ addTemplate({
999
+ filename: template.options.path + ".js",
1000
+ write: true,
1001
+ getContents: () => content
1002
+ });
1003
+ }
1004
+ if (template.buildTypes) {
1005
+ const content = this.processTemplate(
1006
+ template.options.path,
1007
+ template.buildTypes(this)
1008
+ );
1009
+ const filename = template.options.path + ".d.ts";
1010
+ addTypeTemplate({
1011
+ filename,
1012
+ write: true,
1013
+ getContents: () => content
1014
+ });
1015
+ }
1016
+ }
1017
+ addPlugin(name) {
1018
+ addPlugin(this.resolvers.module.resolve("./runtime/plugins/" + name), {
1019
+ append: false
1020
+ });
1021
+ }
1022
+ addServerHandler(name, path, method) {
1023
+ addServerHandler({
1024
+ handler: this.resolvers.module.resolve("./runtime/server/api/" + name),
1025
+ route: this.options.serverApiPrefix + path,
1026
+ method
1027
+ });
1028
+ }
1029
+ addComposable(name) {
1030
+ addImports({
1031
+ from: this.resolvers.module.resolve("./runtime/composables/" + name),
1032
+ name
1033
+ });
1034
+ }
1035
+ addServerUtil(name) {
1036
+ addServerImports([
1037
+ {
1038
+ from: this.resolvers.module.resolve("./runtime/server/utils/" + name),
1039
+ name
1040
+ }
1041
+ ]);
1042
+ }
1043
+ }
1044
+
1045
+ function defineGeneratorTemplate(options, build, buildTypes) {
1046
+ return {
1047
+ type: "generator",
1048
+ options,
1049
+ build,
1050
+ buildTypes
1051
+ };
1052
+ }
1053
+ function defineStaticTemplate(options, build, buildTypes) {
1054
+ return {
1055
+ type: "static",
1056
+ options,
1057
+ build,
1058
+ buildTypes
1059
+ };
1060
+ }
829
1061
 
830
- export type GraphqlClientContext = typeof clientOptions extends GraphqlClientOptions<infer R> ? R : {}
1062
+ const ClientOptions = defineStaticTemplate(
1063
+ { path: "nuxt-graphql-middleware/client-options" },
1064
+ (helper) => {
1065
+ if (helper.paths.clientOptions) {
1066
+ const pathRelative = helper.toModuleBuildRelative(
1067
+ helper.paths.clientOptions
1068
+ );
1069
+ return `import clientOptions from '${pathRelative}'
1070
+ export { clientOptions }
1071
+ `;
1072
+ }
1073
+ return `export const clientOptions = {}`;
1074
+ },
1075
+ (helper) => {
1076
+ if (helper.paths.clientOptions) {
1077
+ const pathRelative = helper.toModuleBuildRelative(
1078
+ helper.paths.clientOptions
1079
+ );
1080
+ return `import type { GraphqlClientOptions } from '${helper.paths.runtimeTypes}'
1081
+ import clientOptionsImport from '${pathRelative}'
831
1082
 
832
- export { clientOptions }`;
833
- }
834
- return `import type { GraphqlClientOptions } from '${runtimeTypesPath}'
835
- export const clientOptions: GraphqlClientOptions
1083
+ declare export const clientOptions: GraphqlClientOptions
1084
+ export type GraphqlClientContext = typeof clientOptionsImport extends GraphqlClientOptions<infer R> ? R : {}
1085
+ `;
1086
+ }
1087
+ return `
1088
+ import type { GraphqlClientOptions } from '${helper.paths.runtimeTypes}'
836
1089
 
1090
+ declare export const clientOptions: GraphqlClientOptions
837
1091
  export type GraphqlClientContext = {}
838
1092
  `;
839
- }
840
- });
841
- addAlias("#graphql-middleware-client-options", clientOptionsTemplate.dst);
842
- nuxt.options.nitro.externals = nuxt.options.nitro.externals || {};
843
- nuxt.options.nitro.externals.inline = nuxt.options.nitro.externals.inline || [];
844
- nuxt.options.nitro.externals.inline.push(template.dst);
845
- nuxt.options.nitro.externals.inline.push(templateDocuments.dst);
846
- addAlias("#graphql-middleware-server-options-build", template.dst);
847
- addAlias(
848
- "#graphql-middleware/types",
849
- moduleResolver.resolve("./runtime/types.ts")
1093
+ }
1094
+ );
1095
+
1096
+ const Documents = defineGeneratorTemplate(
1097
+ { path: "nuxt-graphql-middleware/documents", virtual: true },
1098
+ (output, helper) => {
1099
+ return output.getOperationsFile({
1100
+ exportName: "documents",
1101
+ minify: !helper.isDev
1102
+ }).getSource();
1103
+ },
1104
+ () => {
1105
+ return `
1106
+ import type { Query, Mutation } from './operation-types'
1107
+
1108
+ declare module '#nuxt-graphql-middleware/documents' {
1109
+ export type Documents = {
1110
+ query: Record<keyof Query, string>
1111
+ mutation: Record<keyof Mutation, string>
1112
+ }
1113
+ export const documents: Documents
1114
+ }`;
1115
+ }
1116
+ );
1117
+
1118
+ const GraphqlConfig = defineStaticTemplate(
1119
+ { path: "nuxt-graphql-middleware/graphql.config" },
1120
+ (helper) => {
1121
+ const patterns = helper.options.autoImportPatterns || [];
1122
+ const configPath = helper.resolvers.root.resolve(
1123
+ (helper.options.graphqlConfigFilePath || "").replace(
1124
+ "/graphql.config.ts",
1125
+ ""
1126
+ )
850
1127
  );
851
- addServerHandler({
852
- handler: moduleResolver.resolve("./runtime/serverHandler/index"),
853
- route: options.serverApiPrefix + "/:operation/:name"
1128
+ const schemaPath = "./" + relative(configPath, helper.paths.schema);
1129
+ const documents = patterns.filter((v) => !v.includes("!")).map((pattern) => {
1130
+ return "./" + relative(configPath, helper.resolvers.root.resolve(pattern));
854
1131
  });
855
- if (options.enableFileUploads) {
856
- addServerHandler({
857
- handler: moduleResolver.resolve("./runtime/serverHandler/upload"),
858
- route: options.serverApiPrefix + "/upload/:name"
859
- });
1132
+ documents.push(
1133
+ "./" + relative(
1134
+ configPath,
1135
+ join(helper.paths.moduleBuildDir, "hook-documents.graphql")
1136
+ )
1137
+ );
1138
+ return `
1139
+ import { hookFiles } from './hook-files'
1140
+
1141
+ const schema = ${JSON.stringify(schemaPath)}
1142
+
1143
+ const documents = ${JSON.stringify(documents, null, 2)};
1144
+
1145
+ const config = {
1146
+ schema,
1147
+ documents: [
1148
+ ...documents,
1149
+ ...hookFiles
1150
+ ]
1151
+ }
1152
+
1153
+ export default config
1154
+ `;
1155
+ },
1156
+ () => {
1157
+ return `
1158
+ import type { IGraphQLProject } from 'graphql-config'
1159
+
1160
+ type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };
1161
+
1162
+ const config: WithRequired<IGraphQLProject, 'schema' | 'documents'>;
1163
+
1164
+ export default config;
1165
+ `;
1166
+ }
1167
+ );
1168
+
1169
+ const Helpers = defineStaticTemplate(
1170
+ { path: "nuxt-graphql-middleware/helpers" },
1171
+ (helper) => {
1172
+ return `export const serverApiPrefix = '${helper.options.serverApiPrefix}'
1173
+ export function getEndpoint(operation, operationName) {
1174
+ return serverApiPrefix + '/' + operation + '/' + operationName
1175
+ }
1176
+ `;
1177
+ },
1178
+ () => {
1179
+ return `export const serverApiPrefix: string;
1180
+ export function getEndpoint(operation: string, operationName: string): string`;
1181
+ }
1182
+ );
1183
+
1184
+ const NitroTypes = defineGeneratorTemplate(
1185
+ { path: "nuxt-graphql-middleware/nitro" },
1186
+ null,
1187
+ (output, helper) => {
1188
+ const operations = output.getCollectedOperations();
1189
+ const serverApiPrefix = helper.options.serverApiPrefix;
1190
+ const endpoints = [];
1191
+ const imports = [];
1192
+ for (const operation of operations) {
1193
+ imports.push(operation.typeName);
1194
+ const method = operation.operationType === OperationTypeNode.QUERY ? "get" : "post";
1195
+ endpoints.push(
1196
+ ` '${serverApiPrefix}/${operation.operationType}/${operation.graphqlName}': {
1197
+ '${method}': GraphqlResponse<${operation.typeName}>
1198
+ }`
1199
+ );
860
1200
  }
861
- addPlugin(moduleResolver.resolve("./runtime/plugins/provideState"), {
862
- append: false
1201
+ return `import type { GraphqlResponse } from './response'
1202
+ import type {
1203
+ ${imports.sort().join(",\n ")}
1204
+ } from './../graphql-operations'
1205
+
1206
+ declare module 'nitropack/types' {
1207
+ interface InternalApi {
1208
+ ${endpoints.sort().join("\n")}
1209
+ }
1210
+ }`;
1211
+ }
1212
+ );
1213
+
1214
+ const OperationTypesAll = defineGeneratorTemplate(
1215
+ { path: "nuxt-graphql-middleware/operation-types" },
1216
+ () => `export {}`,
1217
+ (output) => {
1218
+ return output.getOperationTypesFile({
1219
+ importFrom: "./../graphql-operations"
1220
+ }).getSource();
1221
+ }
1222
+ );
1223
+
1224
+ const Operations = defineGeneratorTemplate(
1225
+ { path: "graphql-operations/index" },
1226
+ (output) => {
1227
+ const typesFile = output.getOperations("js");
1228
+ return typesFile.getSource();
1229
+ },
1230
+ (output) => {
1231
+ const typesFile = output.getOperations("d.ts");
1232
+ return typesFile.getSource();
1233
+ }
1234
+ );
1235
+
1236
+ const Response = defineGeneratorTemplate(
1237
+ { path: "nuxt-graphql-middleware/response" },
1238
+ null,
1239
+ (output, helper) => {
1240
+ const operations = output.getCollectedOperations();
1241
+ const allTypes = operations.map((v) => v.typeName).sort();
1242
+ return `import type {
1243
+ ${allTypes.join(",\n ")}
1244
+ } from './../graphql-operations'
1245
+ import type { GraphqlResponseAdditions } from './server-options'
1246
+ import type { GraphqlServerResponse } from '${helper.paths.runtimeTypes}'
1247
+
1248
+ declare module '#nuxt-graphql-middleware/response' {
1249
+ export type GraphqlMiddlewareResponseUnion =
1250
+ | ${allTypes.join("\n | ") || "never"}
1251
+
1252
+ export type GraphqlResponse<T> = GraphqlServerResponse<T> & GraphqlResponseAdditions
1253
+ export type GraphqlResponseTyped = GraphqlResponse<GraphqlMiddlewareResponseUnion>
1254
+ }`;
1255
+ }
1256
+ );
1257
+
1258
+ const ServerOptions = defineStaticTemplate(
1259
+ { path: "nuxt-graphql-middleware/server-options" },
1260
+ (helper) => {
1261
+ const resolvedPathRelative = helper.paths.serverOptions ? helper.toModuleBuildRelative(helper.paths.serverOptions) : null;
1262
+ const serverOptionsLine = resolvedPathRelative ? `import serverOptions from '${resolvedPathRelative}'` : `const serverOptions = {}`;
1263
+ return `
1264
+ ${serverOptionsLine}
1265
+ export { serverOptions }
1266
+ `;
1267
+ },
1268
+ (helper) => {
1269
+ if (helper.paths.serverOptions) {
1270
+ const resolvedPathRelative = helper.toModuleBuildRelative(
1271
+ helper.paths.serverOptions
1272
+ );
1273
+ return `
1274
+ import type { GraphqlMiddlewareServerOptions } from '${helper.paths.runtimeTypes}'
1275
+ import serverOptionsImport from '${resolvedPathRelative}'
1276
+
1277
+ export type GraphqlResponseAdditions =
1278
+ typeof serverOptionsImport extends GraphqlMiddlewareServerOptions<infer R, any, any> ? R : {}
1279
+
1280
+ declare export const serverOptions: GraphqlMiddlewareServerOptions
1281
+ `;
1282
+ }
1283
+ return `
1284
+ import type { GraphqlMiddlewareServerOptions } from '${helper.paths.runtimeTypes}'
1285
+
1286
+ declare export const serverOptions: GraphqlMiddlewareServerOptions
1287
+
1288
+ export type GraphqlResponseAdditions = object
1289
+ `;
1290
+ }
1291
+ );
1292
+
1293
+ const Sources = defineGeneratorTemplate(
1294
+ { path: "nuxt-graphql-middleware/sources" },
1295
+ (output, helper) => {
1296
+ const operations = output.getCollectedOperations();
1297
+ const srcDir = helper.paths.root;
1298
+ const lines = [];
1299
+ for (const operation of operations) {
1300
+ const filePath = operation.filePath.startsWith("/") ? relative(srcDir, operation.filePath) : operation.filePath;
1301
+ lines.push(
1302
+ `${operation.operationType}_${operation.graphqlName}: '${filePath}',`
1303
+ );
1304
+ }
1305
+ return `
1306
+ export const operationSources = {
1307
+ ${lines.join("\n ")}
1308
+ }
1309
+ `;
1310
+ },
1311
+ () => {
1312
+ return `export const operationSources: Record<string, string>`;
1313
+ }
1314
+ );
1315
+
1316
+ const HookDocuments = defineGeneratorTemplate(
1317
+ {
1318
+ path: "nuxt-graphql-middleware/hook-documents.graphql",
1319
+ virtual: false,
1320
+ isFullPath: true
1321
+ },
1322
+ (_output, _helper, collector) => {
1323
+ return collector.getHookDocuments().map((v) => {
1324
+ return `
1325
+ # ${v.identifier}
1326
+ ${v.source}
1327
+ `;
1328
+ }).join("\n\n");
1329
+ },
1330
+ null
1331
+ );
1332
+
1333
+ const HookFiles = defineGeneratorTemplate(
1334
+ {
1335
+ path: "nuxt-graphql-middleware/hook-files",
1336
+ virtual: false
1337
+ },
1338
+ (_output, helper, collector) => {
1339
+ const configPath = helper.resolvers.root.resolve(
1340
+ (helper.options.graphqlConfigFilePath || "").replace(
1341
+ "/graphql.config.ts",
1342
+ ""
1343
+ )
1344
+ );
1345
+ const files = collector.getHookFiles().map((filePath) => {
1346
+ return "./" + relative(configPath, filePath);
863
1347
  });
864
- nuxt.hook("nitro:config", (nitroConfig) => {
865
- nitroConfig.externals = defu(
866
- typeof nitroConfig.externals === "object" ? nitroConfig.externals : {},
867
- {
868
- inline: [moduleResolver.resolve("./runtime")]
869
- }
1348
+ return `export const hookFiles = ${JSON.stringify(files, null, 2)}`;
1349
+ },
1350
+ null
1351
+ );
1352
+
1353
+ const TEMPLATES = [
1354
+ ClientOptions,
1355
+ Documents,
1356
+ GraphqlConfig,
1357
+ Helpers,
1358
+ NitroTypes,
1359
+ OperationTypesAll,
1360
+ Operations,
1361
+ Response,
1362
+ ServerOptions,
1363
+ Sources,
1364
+ HookDocuments,
1365
+ HookFiles
1366
+ ];
1367
+
1368
+ const DEVTOOLS_UI_ROUTE = "/__nuxt-graphql-middleware";
1369
+ const DEVTOOLS_UI_LOCAL_PORT = 3300;
1370
+ function setupDevToolsUI(nuxt, clientPath) {
1371
+ const isProductionBuild = existsSync$1(clientPath);
1372
+ if (isProductionBuild) {
1373
+ nuxt.hook("vite:serverCreated", async (server) => {
1374
+ const sirv = await import('sirv').then((r) => r.default || r);
1375
+ server.middlewares.use(
1376
+ DEVTOOLS_UI_ROUTE,
1377
+ sirv(clientPath, { dev: true, single: true })
870
1378
  );
871
1379
  });
872
- if (nuxt.options.dev || nuxt.options._prepare) {
873
- addServerHandler({
874
- handler: moduleResolver.resolve("./runtime/serverHandler/debug"),
875
- route: options.serverApiPrefix + "/debug"
1380
+ } else {
1381
+ nuxt.hook("vite:extendConfig", (config) => {
1382
+ config.server = config.server || {};
1383
+ config.server.proxy = config.server.proxy || {};
1384
+ config.server.proxy[DEVTOOLS_UI_ROUTE] = {
1385
+ target: "http://localhost:" + DEVTOOLS_UI_LOCAL_PORT + DEVTOOLS_UI_ROUTE,
1386
+ changeOrigin: true,
1387
+ followRedirects: true,
1388
+ rewrite: (path) => path.replace(DEVTOOLS_UI_ROUTE, "")
1389
+ };
1390
+ });
1391
+ }
1392
+ nuxt.hook("devtools:customTabs", (tabs) => {
1393
+ tabs.push({
1394
+ // unique identifier
1395
+ name: "nuxt-graphql-middleware",
1396
+ // title to display in the tab
1397
+ title: "GraphQL Middleware",
1398
+ // any icon from Iconify, or a URL to an image
1399
+ icon: "akar-icons:graphql-fill",
1400
+ // iframe view
1401
+ view: {
1402
+ type: "iframe",
1403
+ src: DEVTOOLS_UI_ROUTE
1404
+ }
1405
+ });
1406
+ });
1407
+ }
1408
+
1409
+ const RPC_NAMESPACE = "nuxt-graphql-middleware";
1410
+ class DevModeHandler {
1411
+ constructor(nuxt, schemaProvider, collector, helper) {
1412
+ this.nuxt = nuxt;
1413
+ this.schemaProvider = schemaProvider;
1414
+ this.collector = collector;
1415
+ this.helper = helper;
1416
+ }
1417
+ devToolsRpc = null;
1418
+ nitro = null;
1419
+ viteWebSocket = null;
1420
+ operationsToReload = /* @__PURE__ */ new Set();
1421
+ init() {
1422
+ this.nuxt.hooks.hookOnce("ready", this.onReady.bind(this));
1423
+ this.nuxt.hooks.hookOnce(
1424
+ "vite:serverCreated",
1425
+ this.onViteServerCreated.bind(this)
1426
+ );
1427
+ this.nuxt.hook("builder:watch", this.onBuilderWatch.bind(this));
1428
+ if (this.helper.options.devtools) {
1429
+ const clientPath = this.helper.resolvers.module.resolve("./client");
1430
+ setupDevToolsUI(this.nuxt, clientPath);
1431
+ onDevToolsInitialized(() => {
1432
+ this.devToolsRpc = extendServerRpc(
1433
+ RPC_NAMESPACE,
1434
+ {
1435
+ // register server RPC functions
1436
+ getModuleOptions: () => {
1437
+ return this.helper.options;
1438
+ },
1439
+ getDocuments: () => {
1440
+ return [...this.collector.rpcItems.values()];
1441
+ }
1442
+ }
1443
+ );
876
1444
  });
877
- nuxt.hook("builder:watch", async (event, pathAbsolute) => {
878
- if (pathAbsolute === schemaPath) {
879
- return;
880
- }
881
- if (!pathAbsolute.match(/\.(gql|graphql)$/)) {
1445
+ }
1446
+ }
1447
+ onReady() {
1448
+ this.nitro = useNitro();
1449
+ this.nitro.hooks.hook("compiled", this.onNitroCompiled.bind(this));
1450
+ }
1451
+ async onBuilderWatch(event, providedFilePath) {
1452
+ if (!providedFilePath.endsWith(".graphql") && !providedFilePath.endsWith(".gql")) {
1453
+ return;
1454
+ }
1455
+ const pathAbsolute = providedFilePath.startsWith("/") ? providedFilePath : this.helper.resolvers.src.resolve(providedFilePath);
1456
+ if (pathAbsolute === this.helper.paths.schema) {
1457
+ return;
1458
+ }
1459
+ this.helper.prompt.abort();
1460
+ const { hasChanged, affectedOperations, error } = await this.collector.handleWatchEvent(event, pathAbsolute);
1461
+ if (error) {
1462
+ this.sendError(error);
1463
+ await this.helper.prompt.confirm("Do you want to download and update the GraphQL schema?").then(async (shouldReload) => {
1464
+ if (shouldReload !== "yes") {
882
1465
  return;
883
1466
  }
884
- const hasChanged = await collector.handleWatchEvent(event, pathAbsolute);
885
- if (hasChanged && rpc) {
886
- rpc.broadcast.documentsUpdated([...collector.rpcItems.values()]);
1467
+ try {
1468
+ await this.schemaProvider.loadSchema({ forceDownload: true });
1469
+ await this.collector.updateSchema(this.schemaProvider.getSchema());
1470
+ } catch (e) {
1471
+ logger.error(e);
887
1472
  }
888
1473
  });
1474
+ return;
1475
+ }
1476
+ if (!hasChanged) {
1477
+ return;
1478
+ }
1479
+ if (this.nitro) {
1480
+ await this.nitro.hooks.callHook("rollup:reload");
1481
+ }
1482
+ if (affectedOperations.length) {
1483
+ affectedOperations.forEach(
1484
+ (operation) => this.operationsToReload.add(operation)
1485
+ );
1486
+ }
1487
+ if (this.devToolsRpc) {
1488
+ try {
1489
+ this.devToolsRpc.broadcast.documentsUpdated([
1490
+ ...this.collector.rpcItems.values()
1491
+ ]);
1492
+ } catch {
1493
+ logger.info(
1494
+ "Failed to update GraphQL documents in dev tools. The documents might be stale."
1495
+ );
1496
+ }
1497
+ }
1498
+ }
1499
+ onViteServerCreated(server) {
1500
+ this.viteWebSocket = server.ws;
1501
+ }
1502
+ sendError(error) {
1503
+ if (!this.viteWebSocket) {
1504
+ return;
1505
+ }
1506
+ this.viteWebSocket.send({
1507
+ type: "error",
1508
+ err: {
1509
+ message: error.message,
1510
+ stack: ""
1511
+ }
1512
+ });
1513
+ }
1514
+ onNitroCompiled() {
1515
+ if (!this.operationsToReload.size) {
1516
+ return;
1517
+ }
1518
+ const operations = [...this.operationsToReload.values()];
1519
+ this.operationsToReload.clear();
1520
+ if (!this.viteWebSocket) {
1521
+ return;
1522
+ }
1523
+ this.viteWebSocket.send({
1524
+ type: "custom",
1525
+ event: "nuxt-graphql-middleware:reload",
1526
+ data: { operations }
1527
+ });
1528
+ }
1529
+ }
1530
+
1531
+ class ModuleContext {
1532
+ constructor(schemaProvider, collector) {
1533
+ this.schemaProvider = schemaProvider;
1534
+ this.collector = collector;
1535
+ }
1536
+ /**
1537
+ * Return the GraphQL schema.
1538
+ *
1539
+ * Note that the schema may be updated during development, so it can become
1540
+ * stale. Prefer using methods like `schemaHasType()` to query the schema.
1541
+ *
1542
+ * @returns The GraphQL schema.
1543
+ */
1544
+ getSchema() {
1545
+ return this.schemaProvider.getSchema();
1546
+ }
1547
+ /**
1548
+ * Check if the given GraphQL type (interface, concrete type, enum, input type)
1549
+ * exists in the schema.
1550
+ *
1551
+ * @param name - The name of the type.
1552
+ *
1553
+ * @returns True if the type exists in the schema.
1554
+ */
1555
+ schemaHasType(name) {
1556
+ return !!this.schemaProvider.getSchema().getType(name);
1557
+ }
1558
+ /**
1559
+ * Get a type from the schema.
1560
+ *
1561
+ * @param name - The name of the type.
1562
+ *
1563
+ * @returns The type.
1564
+ */
1565
+ schemaGetType(name) {
1566
+ return this.schemaProvider.getSchema().getType(name);
1567
+ }
1568
+ /**
1569
+ * Add an additional static document.
1570
+ *
1571
+ * @param identifier - The unique identifier for your document.
1572
+ * @param source - The document source.
1573
+ */
1574
+ addDocument(identifier, source) {
1575
+ this.collector.addHookDocument(identifier, source);
1576
+ return this;
1577
+ }
1578
+ /**
1579
+ * Add or update an additional static document.
1580
+ *
1581
+ * @param identifier - The unique identifier for your document.
1582
+ * @param source - The document source.
1583
+ */
1584
+ async addOrUpdateDocument(identifier, source) {
1585
+ await this.collector.addOrUpdateHookDocument(identifier, source);
1586
+ return this;
1587
+ }
1588
+ /**
1589
+ * Add an additional GraphQL file to import.
1590
+ *
1591
+ * @param filePath - The absolute path to the file.
1592
+ */
1593
+ addImportFile(filePath) {
1594
+ if (!filePath.startsWith("/")) {
1595
+ throw new Error(
1596
+ `The provided file path "${filePath}" must be an absolute path.`
1597
+ );
1598
+ }
1599
+ if (!filePath.endsWith(".graphql") && !filePath.endsWith(".gql")) {
1600
+ throw new Error(
1601
+ `The provided file path "${filePath}" should have a .graphql or .gql extension.`
1602
+ );
889
1603
  }
1604
+ this.collector.addHookFile(filePath);
1605
+ return this;
1606
+ }
1607
+ }
1608
+
1609
+ const module = defineNuxtModule({
1610
+ meta: {
1611
+ name,
1612
+ configKey: "graphqlMiddleware",
1613
+ version,
1614
+ compatibility: {
1615
+ nuxt: ">=3.15.0"
1616
+ }
1617
+ },
1618
+ defaults: defaultOptions,
1619
+ async setup(passedOptions, nuxt) {
1620
+ const helper = new ModuleHelper(nuxt, import.meta.url, passedOptions);
1621
+ const schemaProvider = new SchemaProvider(helper);
1622
+ await schemaProvider.init();
1623
+ const collector = new Collector(schemaProvider.getSchema(), helper);
1624
+ const moduleContext = new ModuleContext(schemaProvider, collector);
1625
+ nuxt._nuxt_graphql_middleware = moduleContext;
1626
+ nuxt.options.appConfig.graphqlMiddleware = {
1627
+ clientCacheEnabled: !!helper.options.clientCache?.enabled,
1628
+ clientCacheMaxSize: helper.options.clientCache?.maxSize ?? 100
1629
+ };
1630
+ nuxt.options.runtimeConfig.graphqlMiddleware = {
1631
+ graphqlEndpoint: helper.options.graphqlEndpoint || ""
1632
+ };
1633
+ helper.transpile(fileURLToPath(new URL("./runtime", import.meta.url)));
1634
+ helper.inlineNitroExternals(helper.resolvers.module.resolve("./runtime"));
1635
+ helper.inlineNitroExternals(helper.paths.moduleBuildDir);
1636
+ helper.inlineNitroExternals(helper.paths.moduleTypesDir);
1637
+ helper.addAlias("#nuxt-graphql-middleware", helper.paths.moduleBuildDir);
1638
+ helper.addAlias("#graphql-operations", helper.paths.moduleTypesDir);
1639
+ helper.addPlugin("provideState");
1640
+ if (helper.isDev && helper.options.errorOverlay) {
1641
+ helper.addPlugin("devMode");
1642
+ }
1643
+ helper.addServerHandler("query", "/query/:name", "get");
1644
+ helper.addServerHandler("mutation", "/mutation/:name", "post");
1645
+ if (helper.options.enableFileUploads) {
1646
+ helper.addServerHandler("upload", "/upload/:name", "post");
1647
+ }
1648
+ if (helper.isDev) {
1649
+ helper.addServerHandler("debug", "/debug", "get");
1650
+ }
1651
+ if (helper.options.includeComposables) {
1652
+ helper.addComposable("useGraphqlQuery");
1653
+ helper.addComposable("useGraphqlMutation");
1654
+ helper.addComposable("useGraphqlState");
1655
+ helper.addComposable("useAsyncGraphqlQuery");
1656
+ if (helper.options.enableFileUploads) {
1657
+ helper.addComposable("useGraphqlUploadMutation");
1658
+ }
1659
+ helper.addServerUtil("useGraphqlQuery");
1660
+ helper.addServerUtil("useGraphqlMutation");
1661
+ helper.addServerUtil("doGraphqlRequest");
1662
+ }
1663
+ TEMPLATES.forEach((template) => {
1664
+ if (template.type === "static") {
1665
+ helper.addTemplate(template);
1666
+ } else {
1667
+ collector.addTemplate(template);
1668
+ }
1669
+ });
1670
+ helper.applyBuildConfig();
1671
+ nuxt.hooks.hookOnce("modules:done", async () => {
1672
+ await nuxt.hooks.callHook("nuxt-graphql-middleware:init", moduleContext);
1673
+ await collector.init();
1674
+ });
1675
+ if (!helper.isDev) {
1676
+ return;
1677
+ }
1678
+ const devModeHandler = new DevModeHandler(
1679
+ nuxt,
1680
+ schemaProvider,
1681
+ collector,
1682
+ helper
1683
+ );
1684
+ devModeHandler.init();
890
1685
  }
891
1686
  });
892
1687