nuxt-graphql-middleware 5.0.0-alpha.5 → 5.0.0-alpha.6

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 (37) hide show
  1. package/dist/client/200.html +7 -7
  2. package/dist/client/404.html +7 -7
  3. package/dist/client/_nuxt/{CZ2Qwgdk.js → B4KMzhZo.js} +1 -1
  4. package/dist/client/_nuxt/BawWjxPx.js +25 -0
  5. package/dist/client/_nuxt/BtHrwWER.js +1 -0
  6. package/dist/client/_nuxt/BvMfLM9s.js +1 -0
  7. package/dist/client/_nuxt/{GOrnHr4p.js → DkAo05uu.js} +1 -1
  8. package/dist/client/_nuxt/builds/latest.json +1 -1
  9. package/dist/client/_nuxt/builds/meta/9b9c571e-ce30-465b-8174-06afbdca446b.json +1 -0
  10. package/dist/client/index.html +7 -7
  11. package/dist/module.d.mts +192 -162
  12. package/dist/module.d.ts +192 -162
  13. package/dist/module.json +2 -2
  14. package/dist/module.mjs +889 -563
  15. package/dist/runtime/components/CodeFrame.vue +1 -1
  16. package/dist/runtime/composables/useAsyncGraphqlQuery.js +9 -1
  17. package/dist/runtime/server/api/mutation.js +28 -0
  18. package/dist/runtime/server/api/query.js +29 -0
  19. package/dist/runtime/server/api/upload.d.ts +2 -0
  20. package/dist/runtime/{serverHandler → server/api}/upload.js +11 -9
  21. package/dist/runtime/{serverHandler → server}/helpers/index.d.ts +8 -10
  22. package/dist/runtime/{serverHandler → server}/helpers/index.js +8 -25
  23. package/dist/runtime/server/utils/doGraphqlRequest.d.ts +18 -0
  24. package/dist/runtime/server/utils/doGraphqlRequest.js +67 -0
  25. package/dist/runtime/settings/index.d.ts +26 -2
  26. package/dist/runtime/settings/index.js +19 -13
  27. package/package.json +8 -6
  28. package/dist/client/_nuxt/BS583yk8.js +0 -25
  29. package/dist/client/_nuxt/DpxjPVZy.js +0 -1
  30. package/dist/client/_nuxt/builds/meta/c22c2916-33e9-427d-b6fe-10f11766c207.json +0 -1
  31. package/dist/client/_nuxt/exxdaCPN.js +0 -1
  32. package/dist/runtime/serverHandler/index.js +0 -78
  33. package/dist/runtime/serverHandler/tsconfig.json +0 -3
  34. /package/dist/runtime/{serverHandler → server/api}/debug.d.ts +0 -0
  35. /package/dist/runtime/{serverHandler → server/api}/debug.js +0 -0
  36. /package/dist/runtime/{serverHandler/index.d.ts → server/api/mutation.d.ts} +0 -0
  37. /package/dist/runtime/{serverHandler/upload.d.ts → server/api/query.d.ts} +0 -0
package/dist/module.mjs CHANGED
@@ -1,92 +1,26 @@
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, addTypeTemplate, 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';
2
+ import { useLogger, addTemplate, addServerTemplate, addTypeTemplate, resolveAlias, createResolver, resolveFiles, addPlugin, addServerHandler, addImports, addServerImports, useNitro, defineNuxtModule } from '@nuxt/kit';
3
+ import { Template } from '../dist/runtime/settings/index.js';
9
4
  import fs from 'node:fs/promises';
10
- import { existsSync as existsSync$1, promises } from 'node:fs';
11
- import { generate } from '@graphql-codegen/cli';
12
- import * as PluginSchemaAst from '@graphql-codegen/schema-ast';
5
+ import { existsSync, promises } from 'node:fs';
6
+ import { relative } from 'pathe';
13
7
  import { basename } from 'node:path';
14
8
  import { printSourceLocation, parse, OperationTypeNode, Source } from 'graphql';
15
9
  import { Generator, FieldNotFoundError, TypeNotFoundError, FragmentNotFoundError } from 'graphql-typescript-deluxe';
16
- import colors from 'picocolors';
10
+ import color from 'picocolors';
17
11
  import { validateGraphQlDocuments } from '@graphql-tools/utils';
12
+ import { generate } from '@graphql-codegen/cli';
13
+ import * as PluginSchemaAst from '@graphql-codegen/schema-ast';
14
+ import { loadSchema } from '@graphql-tools/load';
15
+ import { defu } from 'defu';
16
+ import * as micromatch from 'micromatch';
17
+ import { ConfirmPrompt } from '@clack/core';
18
+ import isUnicodeSupported from 'is-unicode-supported';
19
+ import { existsSync as existsSync$1 } from 'fs';
20
+ import { onDevToolsInitialized, extendServerRpc } from '@nuxt/devtools-kit';
18
21
 
19
22
  const name = "nuxt-graphql-middleware";
20
- const version = "5.0.0-alpha.5";
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
- }
23
+ const version = "5.0.0-alpha.6";
90
24
 
91
25
  const logger = useLogger(name);
92
26
  const defaultOptions = {
@@ -98,59 +32,26 @@ const defaultOptions = {
98
32
  includeComposables: true,
99
33
  documents: [],
100
34
  devtools: true,
101
- errorOverlay: true
35
+ errorOverlay: true,
36
+ graphqlConfigFilePath: "./graphql.config.ts"
102
37
  };
103
38
  function validateOptions(options) {
104
39
  if (!options.graphqlEndpoint) {
105
40
  throw new Error("Missing graphqlEndpoint.");
106
41
  }
107
42
  }
108
- async function getSchemaPath(schemaPath, options, resolver, writeToDisk = false) {
109
- const dest = resolver(schemaPath);
110
- if (!options.downloadSchema) {
111
- const fileExists2 = await fs.access(dest).then(() => true).catch(() => false);
112
- if (!fileExists2) {
113
- logger.error(
114
- '"downloadSchema" is set to false but no schema exists at ' + dest
115
- );
116
- throw new Error("Missing GraphQL schema.");
117
- }
118
- const schemaContent = await fs.readFile(dest).then((v) => v.toString());
119
- return { schemaPath, schemaContent };
120
- }
121
- if (!options.graphqlEndpoint) {
122
- throw new Error("Missing graphqlEndpoint config.");
123
- }
124
- const result = await generateSchema(options, dest, writeToDisk);
125
- return { schemaPath, schemaContent: result.content };
126
- }
127
43
  const fileExists = (path, extensions = ["js", "ts", "mjs"]) => {
128
44
  if (!path) {
129
45
  return null;
130
- } else if (existsSync$1(path)) {
46
+ } else if (existsSync(path)) {
131
47
  return path;
132
48
  }
133
49
  const extension = extensions.find(
134
- (extension2) => existsSync$1(`${path}.${extension2}`)
50
+ (extension2) => existsSync(`${path}.${extension2}`)
135
51
  );
136
52
  return extension ? `${path}.${extension}` : null;
137
53
  };
138
54
 
139
- function generateResponseTypeTemplate(operations, context) {
140
- const allTypes = operations.map((v) => v.typeName).sort();
141
- return `import type {
142
- ${allTypes.join(",\n ")}
143
- } from './../graphql-operations'
144
- import type { GraphqlResponseAdditions } from './server-options'
145
- import type { GraphqlServerResponse } from '${context.runtimeTypesPath}'
146
-
147
- export type GraphqlMiddlewareResponseUnion =
148
- | ${allTypes.join("\n | ") || "never"}
149
-
150
- export type GraphqlResponse<T> = GraphqlServerResponse<T> & GraphqlResponseAdditions
151
- export type GraphqlResponseTyped = GraphqlResponse<GraphqlMiddlewareResponseUnion>`;
152
- }
153
-
154
55
  const SYMBOL_CROSS = "x";
155
56
  const SYMBOL_CHECK = "\u2714";
156
57
  function getMaxLengths(entries) {
@@ -175,11 +76,11 @@ function logAllEntries(entries) {
175
76
  let prevHadError = false;
176
77
  for (const entry of entries) {
177
78
  const hasErrors = entry.errors.length > 0;
178
- const icon = hasErrors ? colors.red(SYMBOL_CROSS) : colors.green(SYMBOL_CHECK);
79
+ const icon = hasErrors ? color.red(SYMBOL_CROSS) : color.green(SYMBOL_CHECK);
179
80
  const type = entry.type.padEnd(lengths.type);
180
- const namePadded = colors.bold(entry.name.padEnd(lengths.name));
181
- const name = hasErrors ? colors.red(namePadded) : colors.green(namePadded);
182
- const path = colors.dim(entry.path);
81
+ const namePadded = color.bold(entry.name.padEnd(lengths.name));
82
+ const name = hasErrors ? color.red(namePadded) : color.green(namePadded);
83
+ const path = color.dim(entry.path);
183
84
  const parts = [icon, type, name, path];
184
85
  if (hasErrors && !prevHadError) {
185
86
  process.stdout.write("-".repeat(process.stdout.columns) + "\n");
@@ -188,10 +89,10 @@ function logAllEntries(entries) {
188
89
  if (hasErrors) {
189
90
  const errorLines = [];
190
91
  entry.errors.forEach((error) => {
191
- let output = colors.red(error.message);
92
+ let output = color.red(error.message);
192
93
  if (error.source && error.locations) {
193
94
  for (const location of error.locations) {
194
- output += "\n\n" + colors.red(printSourceLocation(error.source, location));
95
+ output += "\n\n" + color.red(printSourceLocation(error.source, location));
195
96
  }
196
97
  }
197
98
  errorLines.push(output);
@@ -238,7 +139,24 @@ class CollectedFile {
238
139
  }
239
140
  }
240
141
 
241
- function generateNitroTypes(operations, serverApiPrefix) {
142
+ function ResponseTypes(operations, helper) {
143
+ const allTypes = operations.map((v) => v.typeName).sort();
144
+ return `import type {
145
+ ${allTypes.join(",\n ")}
146
+ } from './../graphql-operations'
147
+ import type { GraphqlResponseAdditions } from './server-options'
148
+ import type { GraphqlServerResponse } from '${helper.paths.runtimeTypes}'
149
+
150
+ declare module '#nuxt-graphql-middleware/response' {
151
+ export type GraphqlMiddlewareResponseUnion =
152
+ | ${allTypes.join("\n | ") || "never"}
153
+
154
+ export type GraphqlResponse<T> = GraphqlServerResponse<T> & GraphqlResponseAdditions
155
+ export type GraphqlResponseTyped = GraphqlResponse<GraphqlMiddlewareResponseUnion>
156
+ }`;
157
+ }
158
+
159
+ function NitroTypes(operations, serverApiPrefix) {
242
160
  const endpoints = [];
243
161
  const imports = [];
244
162
  for (const operation of operations) {
@@ -262,7 +180,7 @@ ${endpoints.sort().join("\n")}
262
180
  }`;
263
181
  }
264
182
 
265
- function generateSourcesTemplate(operations, srcDir) {
183
+ function OperationSources(operations, srcDir) {
266
184
  const lines = [];
267
185
  for (const operation of operations) {
268
186
  const filePath = relative(srcDir, operation.filePath);
@@ -277,12 +195,24 @@ export const operationSources = {
277
195
  `;
278
196
  }
279
197
 
198
+ function OperationTypes(generatorOutput) {
199
+ const typesFile = generatorOutput.getTypes();
200
+ let output = "";
201
+ const enumImports = typesFile.getTypeScriptEnumDependencies();
202
+ if (enumImports.length) {
203
+ output += `import type { ${enumImports.join(", ")} } from './enums'
204
+
205
+ `;
206
+ }
207
+ output += typesFile.getSource();
208
+ return output;
209
+ }
210
+
280
211
  class Collector {
281
- constructor(schema, context, nuxtConfigDocuments = [], generatorOptions = {}) {
212
+ constructor(schema, helper) {
282
213
  this.schema = schema;
283
- this.context = context;
284
- this.nuxtConfigDocuments = nuxtConfigDocuments;
285
- const mappedOptions = { ...generatorOptions };
214
+ this.helper = helper;
215
+ const mappedOptions = { ...helper.options.codegenConfig };
286
216
  if (!mappedOptions.output) {
287
217
  mappedOptions.output = {};
288
218
  }
@@ -310,35 +240,23 @@ class Collector {
310
240
  */
311
241
  rpcItems = /* @__PURE__ */ new Map();
312
242
  /**
313
- * The generated TypeScript type template output.
314
- */
315
- outputTypes = "";
316
- /**
317
- * The generated TypeScript enum template output.
318
- */
319
- outputEnums = "";
320
- /**
321
- * The generated oeprations file.
322
- */
323
- outputOperations = "";
324
- /**
325
- * The generated oepration types file.
243
+ * The generated templates.
326
244
  */
327
- outputOperationTypes = "";
328
- /**
329
- * The generated context template file.
330
- */
331
- outputResponseTypes = "";
332
- /**
333
- * The generated nitro template file.
334
- */
335
- outputNitroTypes = "";
336
- /**
337
- * The generated nitro template file.
338
- */
339
- outputSources = "";
245
+ templates = /* @__PURE__ */ new Map();
246
+ async reset() {
247
+ this.files.clear();
248
+ this.generator.reset();
249
+ this.operationTimestamps.clear();
250
+ this.rpcItems.clear();
251
+ }
252
+ async updateSchema(schema) {
253
+ this.schema = schema;
254
+ this.generator.updateSchema(schema);
255
+ await this.reset();
256
+ await this.initDocuments();
257
+ }
340
258
  filePathToBuildRelative(filePath) {
341
- return "./" + relative(this.context.buildDir, filePath);
259
+ return "./" + this.helper.toBuildRelative(filePath);
342
260
  }
343
261
  filePathToSourceRelative(filePath) {
344
262
  return "./" + relative(process.cwd(), filePath);
@@ -351,16 +269,15 @@ class Collector {
351
269
  errors
352
270
  };
353
271
  }
354
- buildOutputTypes(file) {
355
- let output = "";
356
- const enumImports = file.getTypeScriptEnumDependencies();
357
- if (enumImports.length) {
358
- output += `import type { ${enumImports.join(", ")} } from './enums'
359
-
360
- `;
272
+ updateTemplate(template, content) {
273
+ this.templates.set(template, content);
274
+ }
275
+ getTemplate(template) {
276
+ const content = this.templates.get(template);
277
+ if (content === void 0) {
278
+ throw new Error(`Missing template content: ${template}`);
361
279
  }
362
- output += file.getSource();
363
- return output;
280
+ return content;
364
281
  }
365
282
  /**
366
283
  * Executes code gen and performs validation for operations.
@@ -369,26 +286,32 @@ class Collector {
369
286
  const output = this.generator.build();
370
287
  const operations = output.getCollectedOperations();
371
288
  const generatedCode = output.getGeneratedCode();
372
- this.outputOperations = output.getOperationsFile({
373
- exportName: "documents",
374
- minify: !this.context.isDev
375
- }).getSource();
376
- this.outputOperationTypes = output.getOperationTypesFile({
377
- importFrom: "./../graphql-operations"
378
- }).getSource();
379
- this.outputEnums = output.buildFile(["enum"]).getSource();
380
- this.outputTypes = this.buildOutputTypes(output.getTypes());
381
- this.outputResponseTypes = generateResponseTypeTemplate(
382
- operations,
383
- this.context
289
+ this.updateTemplate(
290
+ Template.Documents,
291
+ output.getOperationsFile({
292
+ exportName: "documents",
293
+ minify: !this.helper.isDev
294
+ }).getSource()
295
+ );
296
+ this.updateTemplate(
297
+ Template.OperationTypesAll,
298
+ output.getOperationTypesFile({
299
+ importFrom: "./../graphql-operations"
300
+ }).getSource()
384
301
  );
385
- this.outputNitroTypes = generateNitroTypes(
386
- operations,
387
- this.context.serverApiPrefix
302
+ this.updateTemplate(
303
+ Template.NitroTypes,
304
+ NitroTypes(operations, this.helper.options.serverApiPrefix)
388
305
  );
389
- this.outputSources = generateSourcesTemplate(
390
- operations,
391
- this.context.rootDir
306
+ this.updateTemplate(Template.OperationTypes, OperationTypes(output));
307
+ this.updateTemplate(
308
+ Template.ResponseTypes,
309
+ ResponseTypes(operations, this.helper)
310
+ );
311
+ this.updateTemplate(Template.Enums, output.buildFile(["enum"]).getSource());
312
+ this.updateTemplate(
313
+ Template.OperationSources,
314
+ OperationSources(operations, this.helper.paths.root)
392
315
  );
393
316
  const fragmentMap = /* @__PURE__ */ new Map();
394
317
  const operationSourceMap = /* @__PURE__ */ new Map();
@@ -418,7 +341,7 @@ class Collector {
418
341
  } else {
419
342
  this.operationTimestamps.set(operation.graphqlName, operation.timestamp);
420
343
  }
421
- const shouldLog = errors.length || !this.context.logOnlyErrors;
344
+ const shouldLog = errors.length || !this.helper.options.logOnlyErrors;
422
345
  if (shouldLog) {
423
346
  logEntries.push(this.operationToLogEntry(operation, errors));
424
347
  }
@@ -466,31 +389,45 @@ class Collector {
466
389
  logError(error) {
467
390
  let output = `${SYMBOL_CROSS}`;
468
391
  output += this.buildErrorMessage(error);
469
- logger.error(colors.red(output));
392
+ logger.error(color.red(output));
470
393
  }
471
394
  /**
472
- * Get all file paths that match the import patterns.
395
+ * Initialise the collector.
396
+ *
397
+ * In dev mode, the method will call itself recursively until all documents
398
+ * are valid.
399
+ *
400
+ * If not in dev mode the method will throw an error when documents are not
401
+ * valid.
473
402
  */
474
- async getImportPatternFiles() {
475
- if (this.context.patterns.length) {
476
- return resolveFiles(this.context.srcDir, this.context.patterns, {
477
- followSymbolicLinks: false
478
- });
403
+ async init() {
404
+ try {
405
+ await this.initDocuments();
406
+ } catch (e) {
407
+ if (this.helper.isDev) {
408
+ const shouldRevalidate = await this.helper.prompt.confirm(
409
+ "Do you want to revalidate the GraphQL documents?"
410
+ );
411
+ if (shouldRevalidate === "yes") {
412
+ await this.reset();
413
+ return this.init();
414
+ }
415
+ }
416
+ throw new Error("Graphql document validation failed.");
479
417
  }
480
- return [];
481
418
  }
482
419
  /**
483
420
  * Initialise the collector.
484
421
  */
485
- async init() {
422
+ async initDocuments() {
486
423
  try {
487
- const files = await this.getImportPatternFiles();
424
+ const files = await this.helper.getImportPatternFiles();
488
425
  for (const filePath of files) {
489
426
  await this.addFile(filePath);
490
427
  }
491
- const nuxtConfigDocuments = this.nuxtConfigDocuments.join("\n\n");
428
+ const nuxtConfigDocuments = this.helper.options.documents.join("\n\n");
492
429
  if (nuxtConfigDocuments.length) {
493
- const filePath = this.context.nuxtConfigPath;
430
+ const filePath = this.helper.paths.nuxtConfig;
494
431
  const file = new CollectedFile(filePath, nuxtConfigDocuments, false);
495
432
  this.files.set(filePath, file);
496
433
  this.generator.add({
@@ -510,6 +447,9 @@ class Collector {
510
447
  */
511
448
  async addFile(filePath) {
512
449
  const file = await CollectedFile.fromFilePath(filePath);
450
+ if (!file.fileContents) {
451
+ return null;
452
+ }
513
453
  this.files.set(filePath, file);
514
454
  this.generator.add({
515
455
  filePath,
@@ -518,26 +458,32 @@ class Collector {
518
458
  return file;
519
459
  }
520
460
  async handleAdd(filePath) {
521
- const matching = await this.getImportPatternFiles();
522
- if (!matching.includes(filePath)) {
461
+ if (!this.helper.matchesImportPattern(filePath)) {
523
462
  return false;
524
463
  }
525
- await this.addFile(filePath);
526
- return true;
464
+ const result = await this.addFile(filePath);
465
+ return !!result;
527
466
  }
528
467
  async handleChange(filePath) {
468
+ if (!this.helper.matchesImportPattern(filePath)) {
469
+ return false;
470
+ }
529
471
  const file = this.files.get(filePath);
530
472
  if (!file) {
531
- return false;
473
+ return this.handleAdd(filePath);
532
474
  }
533
- const needsUpdate = await file.update();
534
- if (!needsUpdate) {
535
- return false;
475
+ try {
476
+ const needsUpdate = await file.update();
477
+ if (!needsUpdate) {
478
+ return false;
479
+ }
480
+ this.generator.update({
481
+ filePath,
482
+ documentNode: file.parsed
483
+ });
484
+ } catch {
485
+ return this.handleUnlink(filePath);
536
486
  }
537
- this.generator.update({
538
- filePath,
539
- documentNode: file.parsed
540
- });
541
487
  return true;
542
488
  }
543
489
  handleUnlink(filePath) {
@@ -566,6 +512,7 @@ class Collector {
566
512
  */
567
513
  async handleWatchEvent(event, filePath) {
568
514
  let hasChanged = false;
515
+ const oldOperationTimestamps = new Map(this.operationTimestamps);
569
516
  try {
570
517
  if (event === "add") {
571
518
  hasChanged = await this.handleAdd(filePath);
@@ -575,7 +522,6 @@ class Collector {
575
522
  hasChanged = this.handleUnlink(filePath);
576
523
  } else if (event === "unlinkDir") {
577
524
  hasChanged = this.handleUnlinkDir(filePath);
578
- } else if (event === "addDir") {
579
525
  }
580
526
  if (hasChanged) {
581
527
  this.buildState();
@@ -586,327 +532,537 @@ class Collector {
586
532
  this.logError(e);
587
533
  return {
588
534
  hasChanged: false,
535
+ affectedOperations: [],
589
536
  error: { message: this.buildErrorMessage(e) }
590
537
  };
591
538
  }
539
+ const affectedOperations = [];
592
540
  if (hasChanged) {
593
541
  logger.success("Finished GraphQL code update successfully.");
542
+ for (const [name, newTimestamp] of this.operationTimestamps) {
543
+ const oldTimestamp = oldOperationTimestamps.get(name);
544
+ if (!oldTimestamp || oldTimestamp !== newTimestamp) {
545
+ affectedOperations.push(name);
546
+ }
547
+ }
594
548
  }
595
- return { hasChanged };
549
+ return { hasChanged, affectedOperations };
596
550
  }
597
551
  /**
598
- * Get the TypeScript types template contents.
552
+ * Adds a virtual template (not written to disk) for both Nuxt and Nitro.
553
+ *
554
+ * For some reason a template written to disk works for both Nuxt and Nitro,
555
+ * but a virtual template requires adding two templates.
599
556
  */
600
- getTemplateTypes() {
601
- return this.outputTypes;
557
+ addVirtualTemplate(template) {
558
+ const getContents = () => this.getTemplate(template);
559
+ addTemplate({
560
+ filename: template,
561
+ getContents
562
+ });
563
+ addServerTemplate({
564
+ // Since this is a virtual template, the name must match the final
565
+ // alias, example:
566
+ // - nuxt-graphql-middleware/foobar.mjs => #nuxt-graphql-middleware/foobar
567
+ //
568
+ // That way we can reference the same template using the alias in both
569
+ // Nuxt and Nitro environments.
570
+ filename: "#" + template.replace(".mjs", ""),
571
+ getContents
572
+ });
602
573
  }
603
574
  /**
604
- * Get the TypeScript Enums template contents.
575
+ * Adds a template that dependes on Collector state.
605
576
  */
606
- getTemplateEnums() {
607
- return this.outputEnums;
577
+ addTemplate(template) {
578
+ if (template.endsWith(".d.ts")) {
579
+ addTypeTemplate(
580
+ {
581
+ filename: template,
582
+ write: true,
583
+ getContents: () => this.getTemplate(template)
584
+ },
585
+ {
586
+ nuxt: true,
587
+ nitro: true
588
+ }
589
+ );
590
+ } else {
591
+ addTemplate({
592
+ filename: template,
593
+ write: true,
594
+ getContents: () => this.getTemplate(template)
595
+ });
596
+ }
608
597
  }
598
+ }
599
+
600
+ class SchemaProvider {
601
+ constructor(helper) {
602
+ this.helper = helper;
603
+ }
604
+ /**
605
+ * The raw schema content.
606
+ */
607
+ schemaContent = "";
609
608
  /**
610
- * Get the context template contents.
609
+ * The parsed schema object.
611
610
  */
612
- getTemplateResponseTypes() {
613
- return this.outputResponseTypes;
611
+ schema = null;
612
+ async init() {
613
+ try {
614
+ await this.loadSchema();
615
+ } catch (error) {
616
+ logger.error(error);
617
+ const hasLoaded = await this.loadFromDiskFallback();
618
+ if (!hasLoaded) {
619
+ throw new Error("Failed to load GraphQL schema.");
620
+ }
621
+ }
622
+ }
623
+ async loadFromDiskFallback() {
624
+ const hasSchemaOnDisk = await this.hasSchemaOnDisk();
625
+ if (this.helper.isDev && hasSchemaOnDisk && this.helper.options.downloadSchema) {
626
+ const shouldUseFromDisk = await this.helper.prompt.confirm(
627
+ "Do you want to continue with the previously downloaded schema from disk?"
628
+ );
629
+ if (shouldUseFromDisk === "yes") {
630
+ await this.loadSchema({ forceDisk: true });
631
+ return true;
632
+ }
633
+ }
634
+ return false;
614
635
  }
615
636
  /**
616
- * Get the operations template contents.
637
+ * Loads the schema from disk.
638
+ *
639
+ * @returns The schema contents from disk.
617
640
  */
618
- getTemplateOperations() {
619
- return this.outputOperations;
641
+ async loadSchemaFromDisk() {
642
+ const fileExists = await this.hasSchemaOnDisk();
643
+ if (!fileExists) {
644
+ logger.error(
645
+ '"downloadSchema" is set to false but no schema exists at ' + this.helper.paths.schema
646
+ );
647
+ throw new Error("Missing GraphQL schema.");
648
+ }
649
+ logger.info(`Loading GraphQL schema from disk: ${this.helper.paths.schema}`);
650
+ return await fs.readFile(this.helper.paths.schema).then((v) => v.toString());
620
651
  }
621
652
  /**
622
- * Get the operation types template contents.
653
+ * Downloads the schema and saves it to disk.
654
+ *
655
+ * @returns The schema contents.
623
656
  */
624
- getTemplateOperationTypes() {
625
- return this.outputOperationTypes;
657
+ downloadSchema() {
658
+ const endpoint = this.helper.options.graphqlEndpoint;
659
+ if (!endpoint) {
660
+ throw new Error("Missing graphqlEndpoint config.");
661
+ }
662
+ const pluginConfig = this.helper.options.codegenSchemaConfig?.urlSchemaOptions;
663
+ const schemaAstConfig = this.helper.options.codegenSchemaConfig?.schemaAstConfig || {
664
+ sort: true
665
+ };
666
+ const config = {
667
+ schema: endpoint,
668
+ pluginLoader: (name) => {
669
+ switch (name) {
670
+ case "@graphql-codegen/schema-ast":
671
+ return Promise.resolve(PluginSchemaAst);
672
+ }
673
+ throw new Error(`graphql-codegen plugin not found: ${name}`);
674
+ },
675
+ silent: true,
676
+ errorsOnly: true,
677
+ config: pluginConfig,
678
+ generates: {
679
+ [this.helper.paths.schema]: {
680
+ plugins: ["schema-ast"],
681
+ config: schemaAstConfig
682
+ }
683
+ }
684
+ };
685
+ logger.info(`Downloading GraphQL schema from "${endpoint}".`);
686
+ return generate(config, true).then((v) => v[0]?.content);
626
687
  }
627
688
  /**
628
- * Get the nitro types template contents.
689
+ * Determine if the schema exists on disk.
690
+ *
691
+ * @returns True if the schema file exists on disk.
629
692
  */
630
- getTemplateNitroTypes() {
631
- return this.outputNitroTypes;
693
+ hasSchemaOnDisk() {
694
+ return fs.access(this.helper.paths.schema).then(() => true).catch(() => false);
632
695
  }
633
696
  /**
634
- * Get the nitro types template contents.
697
+ * Load the schema either from disk or by downloading it.
698
+ *
699
+ * @param forceDownload - Forces downloading the schema.
635
700
  */
636
- getTemplateSources() {
637
- return this.outputSources;
701
+ async loadSchema(opts) {
702
+ if (opts?.forceDisk) {
703
+ this.schemaContent = await this.loadSchemaFromDisk();
704
+ } else if (this.helper.options.downloadSchema || opts?.forceDownload) {
705
+ this.schemaContent = await this.downloadSchema();
706
+ } else {
707
+ this.schemaContent = await this.loadSchemaFromDisk();
708
+ }
709
+ this.schema = await loadSchema(this.schemaContent, {
710
+ loaders: []
711
+ });
638
712
  }
639
- }
640
-
641
- function generateDocumentTypesTemplate() {
642
- return `
643
- import type { Query, Mutation } from './operations'
644
-
645
- declare module '#nuxt-graphql-middleware/documents' {
646
- export type Documents = {
647
- query: Record<keyof Query, string>
648
- mutation: Record<keyof Mutation, string>
713
+ /**
714
+ * Get the schema.
715
+ *
716
+ * @returns The parsed GraphQL schema object.
717
+ */
718
+ getSchema() {
719
+ if (!this.schema) {
720
+ throw new Error("Failed to load schema.");
721
+ }
722
+ return this.schema;
649
723
  }
650
- export const documents: Documents
651
- }`;
652
724
  }
653
725
 
654
- function useViteWebSocket(nuxt) {
655
- return new Promise((resolve) => {
656
- nuxt.hooks.hook("vite:serverCreated", (viteServer) => {
657
- resolve(viteServer.ws);
726
+ const unicode = isUnicodeSupported();
727
+ const s = (c, fallback) => unicode ? c : fallback;
728
+ const S_BAR = s("\u2502", "|");
729
+ const S_STEP_ACTIVE = s("\u25C6", "*");
730
+ const S_STEP_CANCEL = s("\u25A0", "x");
731
+ const S_STEP_ERROR = s("\u25B2", "x");
732
+ const S_STEP_SUBMIT = s("\u25C7", "o");
733
+ const S_RADIO_ACTIVE = s("\u25CF", ">");
734
+ const S_RADIO_INACTIVE = s("\u25CB", " ");
735
+ const S_BAR_END = s("\u2514", "\u2014");
736
+ const symbol = (state) => {
737
+ switch (state) {
738
+ case "initial":
739
+ case "active":
740
+ return color.cyan(S_STEP_ACTIVE);
741
+ case "cancel":
742
+ return color.red(S_STEP_CANCEL);
743
+ case "error":
744
+ return color.yellow(S_STEP_ERROR);
745
+ case "submit":
746
+ return color.green(S_STEP_SUBMIT);
747
+ }
748
+ };
749
+ class ConsolePrompt {
750
+ abortController = null;
751
+ confirm(message) {
752
+ this.abort();
753
+ this.abortController = new AbortController();
754
+ const active = "Yes";
755
+ const inactive = "No";
756
+ return new ConfirmPrompt({
757
+ active,
758
+ inactive,
759
+ initialValue: true,
760
+ signal: this.abortController.signal,
761
+ render() {
762
+ const title = `${color.gray(S_BAR)}
763
+ ${symbol(this.state)} ${message}
764
+ `;
765
+ const value = this.value ? active : inactive;
766
+ switch (this.state) {
767
+ case "submit":
768
+ return `${title}${color.gray(S_BAR)} ${color.dim(value)}`;
769
+ case "cancel":
770
+ return `${title}${color.gray(S_BAR)} ${color.strikethrough(
771
+ color.dim(value)
772
+ )}
773
+ ${color.gray(S_BAR)}`;
774
+ default: {
775
+ 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)}`}
776
+ ${color.cyan(S_BAR_END)}
777
+ `;
778
+ }
779
+ }
780
+ }
781
+ }).prompt().then((v) => {
782
+ const result = v;
783
+ if (result === true) {
784
+ return "yes";
785
+ } else if (result === false) {
786
+ return "no";
787
+ }
788
+ return "cancel";
658
789
  });
659
- });
660
- }
661
- const RPC_NAMESPACE = "nuxt-graphql-middleware";
662
- const module = defineNuxtModule({
663
- meta: {
664
- name,
665
- configKey: "graphqlMiddleware",
666
- version,
667
- compatibility: {
668
- nuxt: ">=3.15.0"
669
- }
670
- },
671
- defaults: defaultOptions,
672
- async setup(passedOptions, nuxt) {
673
- const options = defu({}, passedOptions, defaultOptions);
674
- function addAlias(name2, path) {
675
- nuxt.options.alias[name2] = path;
676
- }
677
- function inlineNitroExternals(path) {
678
- nuxt.options.nitro.externals = nuxt.options.nitro.externals || {};
679
- nuxt.options.nitro.externals.inline = nuxt.options.nitro.externals.inline || [];
680
- nuxt.options.nitro.externals.inline.push(path);
790
+ }
791
+ abort() {
792
+ if (this.abortController) {
793
+ this.abortController.abort();
794
+ this.abortController = null;
681
795
  }
796
+ }
797
+ }
798
+
799
+ class ModuleHelper {
800
+ constructor(nuxt, moduleUrl, options) {
801
+ this.nuxt = nuxt;
682
802
  const isModuleBuild = process.env.MODULE_BUILD === "true" && nuxt.options._prepare;
803
+ const mergedOptions = defu({}, options, defaultOptions);
804
+ if (!mergedOptions.autoImportPatterns) {
805
+ mergedOptions.autoImportPatterns = [
806
+ "~~/**/*.{gql,graphql}",
807
+ "!node_modules"
808
+ ];
809
+ }
683
810
  if (isModuleBuild) {
684
- options.graphqlEndpoint = "http://localhost";
685
- options.downloadSchema = false;
686
- options.schemaPath = "~~/schema.graphql";
687
- options.autoImportPatterns = [
811
+ mergedOptions.graphqlEndpoint = "http://localhost";
812
+ mergedOptions.downloadSchema = false;
813
+ mergedOptions.schemaPath = "~~/schema.graphql";
814
+ mergedOptions.autoImportPatterns = [
688
815
  "~~/playground/**/*.{gql,graphql}",
689
816
  "!node_modules"
690
817
  ];
691
818
  }
692
- if (!passedOptions.autoImportPatterns) {
693
- options.autoImportPatterns = ["~~/**/*.{gql,graphql}", "!node_modules"];
694
- }
695
- options.autoImportPatterns = (options.autoImportPatterns || []).map(
696
- (pattern) => {
697
- return resolveAlias(pattern);
698
- }
699
- );
819
+ mergedOptions.autoImportPatterns = (mergedOptions.autoImportPatterns || []).map((pattern) => {
820
+ return resolveAlias(pattern);
821
+ });
822
+ this.options = mergedOptions;
700
823
  if (!nuxt.options._prepare) {
701
- validateOptions(options);
824
+ validateOptions(this.options);
702
825
  }
703
- const moduleResolver = createResolver(import.meta.url);
704
- const serverResolver = createResolver(nuxt.options.serverDir);
705
- const srcResolver = createResolver(nuxt.options.srcDir);
706
- const appResolver = createResolver(nuxt.options.dir.app);
707
- const rootDir = nuxt.options.rootDir;
708
- const rootResolver = createResolver(rootDir);
709
- const { schemaPath, schemaContent } = await getSchemaPath(
710
- resolveAlias(options.schemaPath),
711
- options,
712
- rootResolver.resolve,
713
- options.downloadSchema
714
- );
715
- const schema = await loadSchema(schemaContent, {
716
- loaders: []
717
- });
718
- const runtimeDir = fileURLToPath(new URL("./runtime", import.meta.url));
719
- nuxt.options.build.transpile.push(runtimeDir);
720
- const nuxtGraphqlMiddlewareBuildDir = nuxt.options.buildDir + "/nuxt-graphql-middleware";
721
- const operationTypesBuildDir = nuxt.options.buildDir + "/graphql-operations";
722
- const toBuildRelative = (path) => {
723
- return relative(nuxtGraphqlMiddlewareBuildDir, path);
826
+ this.isDev = nuxt.options.dev;
827
+ this.resolvers = {
828
+ module: createResolver(moduleUrl),
829
+ server: createResolver(nuxt.options.serverDir),
830
+ src: createResolver(nuxt.options.srcDir),
831
+ app: createResolver(nuxt.options.dir.app),
832
+ root: createResolver(nuxt.options.rootDir)
724
833
  };
725
- addAlias("#nuxt-graphql-middleware", nuxtGraphqlMiddlewareBuildDir);
726
- addAlias("#graphql-operations", operationTypesBuildDir);
727
- const context = {
728
- isDev: nuxt.options.dev,
729
- patterns: options.autoImportPatterns || [],
730
- srcDir: nuxt.options.srcDir,
731
- rootDir: nuxt.options.rootDir,
732
- buildDir: srcResolver.resolve(nuxt.options.buildDir),
733
- nuxtConfigPath: rootResolver.resolve("nuxt.config.ts"),
734
- schemaPath,
735
- serverApiPrefix: options.serverApiPrefix,
736
- logOnlyErrors: !!options.logOnlyErrors,
737
- runtimeTypesPath: toBuildRelative(
738
- moduleResolver.resolve("./runtime/types.ts")
739
- )
834
+ this.paths = {
835
+ runtimeTypes: "",
836
+ root: nuxt.options.rootDir,
837
+ nuxtConfig: this.resolvers.root.resolve("nuxt.config.ts"),
838
+ serverDir: nuxt.options.serverDir,
839
+ schema: this.resolvers.root.resolve(
840
+ resolveAlias(this.options.schemaPath)
841
+ ),
842
+ serverOptions: this.findServerOptions(),
843
+ clientOptions: this.findClientOptions(),
844
+ moduleBuildDir: nuxt.options.buildDir + "/nuxt-graphql-middleware",
845
+ moduleTypesDir: nuxt.options.buildDir + "/graphql-operations"
740
846
  };
741
- const collector = new Collector(
742
- schema,
743
- context,
744
- options.documents,
745
- options.codegenConfig
847
+ this.paths.runtimeTypes = this.toModuleBuildRelative(
848
+ this.resolvers.module.resolve("./runtime/types.ts")
746
849
  );
747
- await collector.init();
748
- const isDevToolsEnabled = nuxt.options.dev && options.devtools;
749
- let rpc;
750
- if (isDevToolsEnabled) {
751
- const clientPath = moduleResolver.resolve("./client");
752
- setupDevToolsUI(nuxt, clientPath);
753
- onDevToolsInitialized(() => {
754
- rpc = extendServerRpc(RPC_NAMESPACE, {
755
- // register server RPC functions
756
- getModuleOptions() {
757
- return options;
758
- },
759
- getDocuments() {
760
- return [...collector.rpcItems.values()];
761
- }
762
- });
763
- });
850
+ }
851
+ resolvers;
852
+ paths;
853
+ isDev;
854
+ options;
855
+ prompt = new ConsolePrompt();
856
+ /**
857
+ * Find the path to the graphqlMiddleware.serverOptions.ts file.
858
+ */
859
+ findServerOptions() {
860
+ const newPath = this.resolvers.server.resolve(
861
+ "graphqlMiddleware.serverOptions"
862
+ );
863
+ const serverPath = fileExists(newPath);
864
+ if (serverPath) {
865
+ return serverPath;
764
866
  }
765
- nuxt.options.appConfig.graphqlMiddleware = {
766
- clientCacheEnabled: !!options.clientCache?.enabled,
767
- clientCacheMaxSize: options.clientCache?.maxSize || 100
768
- };
769
- nuxt.options.runtimeConfig.graphqlMiddleware = {
770
- graphqlEndpoint: options.graphqlEndpoint || ""
771
- };
772
- if (options.includeComposables) {
773
- const nuxtComposables = [
774
- "useGraphqlQuery",
775
- "useGraphqlMutation",
776
- "useGraphqlState",
777
- "useAsyncGraphqlQuery"
778
- ];
779
- if (options.enableFileUploads) {
780
- nuxtComposables.push("useGraphqlUploadMutation");
867
+ const candidates = [
868
+ this.resolvers.root.resolve("graphqlMiddleware.serverOptions"),
869
+ this.resolvers.root.resolve("app/graphqlMiddleware.serverOptions"),
870
+ this.resolvers.src.resolve("graphqlMiddleware.serverOptions")
871
+ ];
872
+ for (let i = 0; i < candidates.length; i++) {
873
+ const path = candidates[i];
874
+ const filePath = fileExists(path);
875
+ if (filePath) {
876
+ throw new Error(
877
+ `The graphqlMiddleware.serverOptions file should be placed in Nuxt's <serverDir> ("${this.paths.serverDir}/graphqlMiddleware.serverOptions.ts").`
878
+ );
781
879
  }
782
- nuxtComposables.forEach((name2) => {
783
- addImports({
784
- from: moduleResolver.resolve("./runtime/composables/" + name2),
785
- name: name2
786
- });
787
- });
788
- const serverUtils = ["useGraphqlQuery", "useGraphqlMutation"].map(
789
- (name2) => {
790
- return {
791
- from: moduleResolver.resolve("./runtime/server/utils/" + name2),
792
- name: name2
793
- };
794
- }
795
- );
796
- addServerImports(serverUtils);
797
880
  }
798
- addTypeTemplate({
799
- filename: GraphqlMiddlewareTemplate.OperationTypes,
800
- write: true,
801
- getContents: () => collector.getTemplateTypes()
802
- });
803
- addTypeTemplate({
804
- filename: GraphqlMiddlewareTemplate.Types,
805
- write: true,
806
- getContents: () => {
807
- return `
808
- declare module '#nuxt-graphql-middleware/sources' {
809
- export const operationSources: Record<string, string>
810
- }
811
- `;
881
+ logger.info("No graphqlMiddleware.serverOptions file found.");
882
+ return null;
883
+ }
884
+ findClientOptions() {
885
+ const clientOptionsPath = this.resolvers.app.resolve(
886
+ "graphqlMiddleware.clientOptions"
887
+ );
888
+ if (fileExists(clientOptionsPath)) {
889
+ return clientOptionsPath;
890
+ }
891
+ return null;
892
+ }
893
+ /**
894
+ * Transform the path relative to the module's build directory.
895
+ *
896
+ * @param path - The absolute path.
897
+ *
898
+ * @returns The path relative to the module's build directory.
899
+ */
900
+ toModuleBuildRelative(path) {
901
+ return relative(this.paths.moduleBuildDir, path);
902
+ }
903
+ /**
904
+ * Transform the path relative to the Nuxt build directory.
905
+ *
906
+ * @param path - The absolute path.
907
+ *
908
+ * @returns The path relative to the module's build directory.
909
+ */
910
+ toBuildRelative(path) {
911
+ return relative(this.nuxt.options.buildDir, path);
912
+ }
913
+ /**
914
+ * Get all file paths that match the import patterns.
915
+ */
916
+ async getImportPatternFiles() {
917
+ return resolveFiles(
918
+ this.nuxt.options.srcDir,
919
+ this.options.autoImportPatterns,
920
+ {
921
+ followSymbolicLinks: false
812
922
  }
923
+ );
924
+ }
925
+ matchesImportPattern(filePath) {
926
+ return micromatch.isMatch(filePath, this.options.autoImportPatterns);
927
+ }
928
+ addAlias(name, path) {
929
+ this.nuxt.options.alias[name] = path;
930
+ const pathFromName = `./${name.substring(1)}`;
931
+ this.nuxt.options.nitro.typescript ||= {};
932
+ this.nuxt.options.nitro.typescript.tsConfig ||= {};
933
+ this.nuxt.options.nitro.typescript.tsConfig.compilerOptions ||= {};
934
+ this.nuxt.options.nitro.typescript.tsConfig.compilerOptions.paths ||= {};
935
+ this.nuxt.options.nitro.typescript.tsConfig.compilerOptions.paths[name] = [
936
+ pathFromName
937
+ ];
938
+ this.nuxt.options.nitro.typescript.tsConfig.compilerOptions.paths[name + "/*"] = [pathFromName + "/*"];
939
+ }
940
+ inlineNitroExternals(arg) {
941
+ const path = typeof arg === "string" ? arg : arg.dst;
942
+ this.nuxt.options.nitro.externals = this.nuxt.options.nitro.externals || {};
943
+ this.nuxt.options.nitro.externals.inline = this.nuxt.options.nitro.externals.inline || [];
944
+ this.nuxt.options.nitro.externals.inline.push(path);
945
+ }
946
+ addTemplate(template, cb) {
947
+ const content = cb(this);
948
+ if (template.endsWith("d.ts")) {
949
+ addTypeTemplate({
950
+ filename: template,
951
+ write: true,
952
+ getContents: () => content
953
+ });
954
+ } else {
955
+ addTemplate({
956
+ filename: template,
957
+ write: true,
958
+ getContents: () => content
959
+ });
960
+ }
961
+ }
962
+ addPlugin(path) {
963
+ addPlugin(this.resolvers.module.resolve(path), {
964
+ append: false
813
965
  });
814
- addTemplate({
815
- filename: GraphqlMiddlewareTemplate.Enums,
816
- write: true,
817
- getContents: () => collector.getTemplateEnums()
966
+ }
967
+ addServerHandler(name, path, method) {
968
+ addServerHandler({
969
+ handler: this.resolvers.module.resolve("./runtime/server/api/" + name),
970
+ route: this.options.serverApiPrefix + path,
971
+ method
818
972
  });
819
- addTemplate({
820
- filename: GraphqlMiddlewareTemplate.OperationSources,
821
- write: true,
822
- getContents: () => collector.getTemplateSources()
973
+ }
974
+ addComposable(name) {
975
+ addImports({
976
+ from: this.resolvers.module.resolve("./runtime/composables/" + name),
977
+ name
823
978
  });
824
- addTemplate({
825
- filename: GraphqlMiddlewareTemplate.Helpers,
826
- write: true,
827
- getContents: () => `export const serverApiPrefix = '${context.serverApiPrefix}'
979
+ }
980
+ addServerUtil(name) {
981
+ addServerImports([
982
+ {
983
+ from: this.resolvers.module.resolve("./runtime/server/utils/" + name),
984
+ name
985
+ }
986
+ ]);
987
+ }
988
+ }
989
+
990
+ function GraphqlConfig(helper) {
991
+ const patterns = helper.options.autoImportPatterns || [];
992
+ const configPath = helper.resolvers.root.resolve(
993
+ (helper.options.graphqlConfigFilePath || "").replace(
994
+ "/graphql.config.ts",
995
+ ""
996
+ )
997
+ );
998
+ const schemaPath = "./" + relative(configPath, helper.paths.schema);
999
+ const documents = patterns.filter((v) => !v.includes("!")).map((pattern) => {
1000
+ return "./" + relative(configPath, helper.resolvers.root.resolve(pattern));
1001
+ });
1002
+ return `
1003
+ import type { IGraphQLConfig } from 'graphql-config'
1004
+
1005
+ const schema = ${JSON.stringify(schemaPath)}
1006
+
1007
+ const documents: string[] = ${JSON.stringify(documents, null, 2)};
1008
+
1009
+ const config: IGraphQLConfig = {
1010
+ schema,
1011
+ documents,
1012
+ }
1013
+
1014
+ export default config
1015
+ `;
1016
+ }
1017
+
1018
+ function DocumentTypes() {
1019
+ return `
1020
+ import type { Query, Mutation } from './operations'
1021
+
1022
+ declare module '#nuxt-graphql-middleware/documents' {
1023
+ export type Documents = {
1024
+ query: Record<keyof Query, string>
1025
+ mutation: Record<keyof Mutation, string>
1026
+ }
1027
+ export const documents: Documents
1028
+ }`;
1029
+ }
1030
+
1031
+ function Types() {
1032
+ return `declare module '#nuxt-graphql-middleware/sources' {
1033
+ export const operationSources: Record<string, string>
1034
+ }`;
1035
+ }
1036
+
1037
+ function HelpersTypes() {
1038
+ return `export const serverApiPrefix: string;
1039
+ export function getEndpoint(operation: string, operationName: string): string`;
1040
+ }
1041
+
1042
+ function Helpers(helper) {
1043
+ return `export const serverApiPrefix = '${helper.options.serverApiPrefix}'
828
1044
  export function getEndpoint(operation, operationName) {
829
- return '${context.serverApiPrefix}' + '/' + operation + '/' + operationName
1045
+ return '${helper.options.serverApiPrefix}' + '/' + operation + '/' + operationName
830
1046
  }
831
- `
832
- });
833
- addTypeTemplate({
834
- filename: GraphqlMiddlewareTemplate.HelpersTypes,
835
- write: true,
836
- getContents: () => `export const serverApiPrefix: string;
837
- export function getEndpoint(operation: string, operationName: string): string
838
- `
839
- });
840
- addTypeTemplate({
841
- filename: GraphqlMiddlewareTemplate.NitroTypes,
842
- write: true,
843
- getContents: () => collector.getTemplateNitroTypes()
844
- });
845
- addTypeTemplate({
846
- filename: GraphqlMiddlewareTemplate.OperationTypesAll,
847
- write: true,
848
- getContents: () => collector.getTemplateOperationTypes()
849
- });
850
- const templateDocuments = addTemplate({
851
- filename: GraphqlMiddlewareTemplate.Documents,
852
- write: true,
853
- getContents: () => collector.getTemplateOperations()
854
- });
855
- inlineNitroExternals(templateDocuments.dst);
856
- addTypeTemplate({
857
- filename: GraphqlMiddlewareTemplate.ResponseTypes,
858
- write: true,
859
- getContents: () => collector.getTemplateResponseTypes()
860
- });
861
- addTypeTemplate({
862
- write: true,
863
- filename: "nuxt-graphql-middleware/documents.d.ts",
864
- getContents: () => generateDocumentTypesTemplate()
865
- });
866
- const findServerOptions = () => {
867
- const newPath = serverResolver.resolve("graphqlMiddleware.serverOptions");
868
- const serverPath = fileExists(newPath);
869
- if (serverPath) {
870
- return serverPath;
871
- }
872
- const candidates = [
873
- rootResolver.resolve("graphqlMiddleware.serverOptions"),
874
- rootResolver.resolve("app/graphqlMiddleware.serverOptions"),
875
- srcResolver.resolve("graphqlMiddleware.serverOptions")
876
- ];
877
- for (let i = 0; i < candidates.length; i++) {
878
- const path = candidates[i];
879
- const filePath = fileExists(path);
880
- if (filePath) {
881
- logger.warn(
882
- `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.`
883
- );
884
- return filePath;
885
- }
886
- }
887
- logger.info("No graphqlMiddleware.serverOptions file found.");
888
- };
889
- const resolvedServerOptionsPath = findServerOptions();
890
- const moduleTypesPath = toBuildRelative(moduleResolver.resolve("./types"));
891
- const resolvedPathRelative = resolvedServerOptionsPath ? toBuildRelative(resolvedServerOptionsPath) : null;
892
- const template = addTemplate({
893
- filename: "nuxt-graphql-middleware/server-options.mjs",
894
- write: true,
895
- getContents: () => {
896
- const serverOptionsLine = resolvedPathRelative ? `import serverOptions from '${resolvedPathRelative}'` : `const serverOptions = {}`;
897
- return `
1047
+ `;
1048
+ }
1049
+
1050
+ function ServerOptions(helper) {
1051
+ const resolvedPathRelative = helper.paths.serverOptions ? helper.toModuleBuildRelative(helper.paths.serverOptions) : null;
1052
+ const serverOptionsLine = resolvedPathRelative ? `import serverOptions from '${resolvedPathRelative}'` : `const serverOptions = {}`;
1053
+ return `
898
1054
  ${serverOptionsLine}
899
1055
  export { serverOptions }
900
1056
  `;
901
- }
902
- });
903
- inlineNitroExternals(template.dst);
904
- addTemplate({
905
- filename: "nuxt-graphql-middleware/server-options.d.ts",
906
- write: true,
907
- getContents: () => {
908
- const serverOptionsLineTypes = resolvedPathRelative ? `import serverOptions from '${resolvedPathRelative}'` : `const serverOptions: GraphqlMiddlewareServerOptions = {}`;
909
- return `
1057
+ }
1058
+
1059
+ function ServerOptionsTypes(helper) {
1060
+ const resolvedPathRelative = helper.paths.serverOptions ? helper.toModuleBuildRelative(helper.paths.serverOptions) : null;
1061
+ const serverOptionsLineTypes = resolvedPathRelative ? `import serverOptions from '${resolvedPathRelative}'` : `const serverOptions: GraphqlMiddlewareServerOptions = {}`;
1062
+ const moduleTypesPath = helper.toModuleBuildRelative(
1063
+ helper.resolvers.module.resolve("./types")
1064
+ );
1065
+ return `
910
1066
  import type { GraphqlMiddlewareServerOptions } from '${moduleTypesPath}'
911
1067
  ${serverOptionsLineTypes}
912
1068
 
@@ -914,112 +1070,282 @@ export type GraphqlResponseAdditions =
914
1070
  typeof serverOptions extends GraphqlMiddlewareServerOptions<infer R, any, any> ? R : {}
915
1071
 
916
1072
  export { serverOptions }`;
917
- }
918
- });
919
- const getClientOptionsImport = () => {
920
- const clientOptionsPath = appResolver.resolve(
921
- "graphqlMiddleware.clientOptions"
922
- );
923
- if (fileExists(clientOptionsPath)) {
924
- const pathRelative = toBuildRelative(clientOptionsPath);
925
- return `import clientOptions from '${pathRelative}'`;
926
- }
927
- };
928
- const clientOptionsImport = getClientOptionsImport();
929
- addTemplate({
930
- filename: "nuxt-graphql-middleware/client-options.mjs",
931
- write: true,
932
- getContents: () => {
933
- if (clientOptionsImport) {
934
- return `${clientOptionsImport}
935
- export { clientOptions }`;
936
- }
937
- return `export const clientOptions = {}`;
938
- }
939
- });
940
- addTemplate({
941
- filename: "nuxt-graphql-middleware/client-options.d.ts",
942
- write: true,
943
- getContents: () => {
944
- if (clientOptionsImport) {
945
- return `import type { GraphqlClientOptions } from '${context.runtimeTypesPath}'
946
- ${clientOptionsImport}
1073
+ }
1074
+
1075
+ function ClientOptions(helper) {
1076
+ if (helper.paths.clientOptions) {
1077
+ const pathRelative = helper.toModuleBuildRelative(
1078
+ helper.paths.clientOptions
1079
+ );
1080
+ return `import clientOptions from '${pathRelative}'
1081
+ export { clientOptions }
1082
+ `;
1083
+ }
1084
+ return `export const clientOptions = {}`;
1085
+ }
1086
+
1087
+ function ClientOptionsTypes(helper) {
1088
+ if (helper.paths.clientOptions) {
1089
+ const pathRelative = helper.toModuleBuildRelative(
1090
+ helper.paths.clientOptions
1091
+ );
1092
+ return `import type { GraphqlClientOptions } from '${helper.paths.runtimeTypes}'
1093
+ import { clientOptions } from '${pathRelative}'
947
1094
 
948
1095
  export type GraphqlClientContext = typeof clientOptions extends GraphqlClientOptions<infer R> ? R : {}
949
1096
 
950
1097
  export { clientOptions }`;
951
- }
952
- return `import type { GraphqlClientOptions } from '${context.runtimeTypesPath}'
1098
+ }
1099
+ return `
1100
+ import type { GraphqlClientOptions } from '${helper.paths.runtimeTypes}'
953
1101
  export const clientOptions: GraphqlClientOptions
954
1102
 
955
1103
  export type GraphqlClientContext = {}
956
1104
  `;
957
- }
958
- });
959
- addServerHandler({
960
- handler: moduleResolver.resolve("./runtime/serverHandler/index"),
961
- route: options.serverApiPrefix + "/:operation/:name"
1105
+ }
1106
+
1107
+ const DEVTOOLS_UI_ROUTE = "/__nuxt-graphql-middleware";
1108
+ const DEVTOOLS_UI_LOCAL_PORT = 3300;
1109
+ function setupDevToolsUI(nuxt, clientPath) {
1110
+ const isProductionBuild = existsSync$1(clientPath);
1111
+ if (isProductionBuild) {
1112
+ nuxt.hook("vite:serverCreated", async (server) => {
1113
+ const sirv = await import('sirv').then((r) => r.default || r);
1114
+ server.middlewares.use(
1115
+ DEVTOOLS_UI_ROUTE,
1116
+ sirv(clientPath, { dev: true, single: true })
1117
+ );
962
1118
  });
963
- if (options.enableFileUploads) {
964
- addServerHandler({
965
- handler: moduleResolver.resolve("./runtime/serverHandler/upload"),
966
- route: options.serverApiPrefix + "/upload/:name"
967
- });
968
- }
969
- addPlugin(moduleResolver.resolve("./runtime/plugins/provideState"), {
970
- append: false
1119
+ } else {
1120
+ nuxt.hook("vite:extendConfig", (config) => {
1121
+ config.server = config.server || {};
1122
+ config.server.proxy = config.server.proxy || {};
1123
+ config.server.proxy[DEVTOOLS_UI_ROUTE] = {
1124
+ target: "http://localhost:" + DEVTOOLS_UI_LOCAL_PORT + DEVTOOLS_UI_ROUTE,
1125
+ changeOrigin: true,
1126
+ followRedirects: true,
1127
+ rewrite: (path) => path.replace(DEVTOOLS_UI_ROUTE, "")
1128
+ };
971
1129
  });
972
- if (context.isDev && options.errorOverlay) {
973
- addPlugin(moduleResolver.resolve("./runtime/plugins/devMode"), {
974
- append: false
975
- });
976
- }
977
- nuxt.hook("nitro:config", (nitroConfig) => {
978
- nitroConfig.externals = defu(
979
- typeof nitroConfig.externals === "object" ? nitroConfig.externals : {},
980
- {
981
- inline: [moduleResolver.resolve("./runtime")]
982
- }
983
- );
1130
+ }
1131
+ nuxt.hook("devtools:customTabs", (tabs) => {
1132
+ tabs.push({
1133
+ // unique identifier
1134
+ name: "nuxt-graphql-middleware",
1135
+ // title to display in the tab
1136
+ title: "GraphQL Middleware",
1137
+ // any icon from Iconify, or a URL to an image
1138
+ icon: "akar-icons:graphql-fill",
1139
+ // iframe view
1140
+ view: {
1141
+ type: "iframe",
1142
+ src: DEVTOOLS_UI_ROUTE
1143
+ }
984
1144
  });
985
- if (nuxt.options.dev || nuxt.options._prepare) {
986
- let sendError = function(error) {
987
- wsPromise.then((ws) => {
988
- ws.send({
989
- type: "error",
990
- err: {
991
- message: error.message,
992
- stack: ""
1145
+ });
1146
+ }
1147
+
1148
+ const RPC_NAMESPACE = "nuxt-graphql-middleware";
1149
+ class DevModeHandler {
1150
+ constructor(nuxt, schemaProvider, collector, helper) {
1151
+ this.nuxt = nuxt;
1152
+ this.schemaProvider = schemaProvider;
1153
+ this.collector = collector;
1154
+ this.helper = helper;
1155
+ }
1156
+ devToolsRpc = null;
1157
+ nitro = null;
1158
+ viteWebSocket = null;
1159
+ operationsToReload = /* @__PURE__ */ new Set();
1160
+ init() {
1161
+ this.nuxt.hooks.hookOnce("ready", this.onReady.bind(this));
1162
+ this.nuxt.hooks.hookOnce(
1163
+ "vite:serverCreated",
1164
+ this.onViteServerCreated.bind(this)
1165
+ );
1166
+ this.nuxt.hook("builder:watch", this.onBuilderWatch.bind(this));
1167
+ if (this.helper.options.devtools) {
1168
+ const clientPath = this.helper.resolvers.module.resolve("./client");
1169
+ setupDevToolsUI(this.nuxt, clientPath);
1170
+ onDevToolsInitialized(() => {
1171
+ this.devToolsRpc = extendServerRpc(
1172
+ RPC_NAMESPACE,
1173
+ {
1174
+ // register server RPC functions
1175
+ getModuleOptions: () => {
1176
+ return this.helper.options;
1177
+ },
1178
+ getDocuments: () => {
1179
+ return [...this.collector.rpcItems.values()];
993
1180
  }
994
- });
995
- });
996
- };
997
- addServerHandler({
998
- handler: moduleResolver.resolve("./runtime/serverHandler/debug"),
999
- route: options.serverApiPrefix + "/debug"
1181
+ }
1182
+ );
1000
1183
  });
1001
- const wsPromise = useViteWebSocket(nuxt);
1002
- nuxt.hook("builder:watch", async (event, pathAbsolute) => {
1003
- if (pathAbsolute === schemaPath) {
1004
- return;
1005
- }
1006
- if (!pathAbsolute.match(/\.(gql|graphql)$/)) {
1184
+ }
1185
+ }
1186
+ onReady() {
1187
+ this.nitro = useNitro();
1188
+ this.nitro.hooks.hook("compiled", this.onNitroCompiled.bind(this));
1189
+ }
1190
+ async onBuilderWatch(event, pathAbsolute) {
1191
+ if (pathAbsolute === this.helper.paths.schema) {
1192
+ return;
1193
+ }
1194
+ if (!pathAbsolute.match(/\.(gql|graphql)$/)) {
1195
+ return;
1196
+ }
1197
+ this.helper.prompt.abort();
1198
+ const { hasChanged, affectedOperations, error } = await this.collector.handleWatchEvent(event, pathAbsolute);
1199
+ if (error) {
1200
+ this.sendError(error);
1201
+ await this.helper.prompt.confirm("Do you want to download and update the GraphQL schema?").then(async (shouldReload) => {
1202
+ if (shouldReload !== "yes") {
1007
1203
  return;
1008
1204
  }
1009
- const { hasChanged, error } = await collector.handleWatchEvent(
1010
- event,
1011
- pathAbsolute
1012
- );
1013
- if (error) {
1014
- sendError(error);
1015
- }
1016
- if (hasChanged) {
1017
- if (rpc) {
1018
- rpc.broadcast.documentsUpdated([...collector.rpcItems.values()]);
1019
- }
1205
+ try {
1206
+ await this.schemaProvider.loadSchema({ forceDownload: true });
1207
+ await this.collector.updateSchema(this.schemaProvider.getSchema());
1208
+ } catch (e) {
1209
+ logger.error(e);
1020
1210
  }
1021
1211
  });
1212
+ return;
1213
+ }
1214
+ if (!hasChanged) {
1215
+ return;
1216
+ }
1217
+ if (this.nitro) {
1218
+ await this.nitro.hooks.callHook("rollup:reload");
1022
1219
  }
1220
+ if (affectedOperations.length) {
1221
+ affectedOperations.forEach(
1222
+ (operation) => this.operationsToReload.add(operation)
1223
+ );
1224
+ }
1225
+ if (this.devToolsRpc) {
1226
+ try {
1227
+ this.devToolsRpc.broadcast.documentsUpdated([
1228
+ ...this.collector.rpcItems.values()
1229
+ ]);
1230
+ } catch {
1231
+ logger.info(
1232
+ "Failed to update GraphQL documents in dev tools. The documents might be stale."
1233
+ );
1234
+ }
1235
+ }
1236
+ }
1237
+ onViteServerCreated(server) {
1238
+ this.viteWebSocket = server.ws;
1239
+ }
1240
+ sendError(error) {
1241
+ if (!this.viteWebSocket) {
1242
+ return;
1243
+ }
1244
+ this.viteWebSocket.send({
1245
+ type: "error",
1246
+ err: {
1247
+ message: error.message,
1248
+ stack: ""
1249
+ }
1250
+ });
1251
+ }
1252
+ onNitroCompiled() {
1253
+ if (!this.operationsToReload.size) {
1254
+ return;
1255
+ }
1256
+ const operations = [...this.operationsToReload.values()];
1257
+ this.operationsToReload.clear();
1258
+ if (!this.viteWebSocket) {
1259
+ return;
1260
+ }
1261
+ this.viteWebSocket.send({
1262
+ type: "custom",
1263
+ event: "nuxt-graphql-middleware:reload",
1264
+ data: { operations }
1265
+ });
1266
+ }
1267
+ }
1268
+
1269
+ const module = defineNuxtModule({
1270
+ meta: {
1271
+ name,
1272
+ configKey: "graphqlMiddleware",
1273
+ version,
1274
+ compatibility: {
1275
+ nuxt: ">=3.16.0"
1276
+ }
1277
+ },
1278
+ defaults: defaultOptions,
1279
+ async setup(passedOptions, nuxt) {
1280
+ const helper = new ModuleHelper(nuxt, import.meta.url, passedOptions);
1281
+ const schemaProvider = new SchemaProvider(helper);
1282
+ await schemaProvider.init();
1283
+ const collector = new Collector(schemaProvider.getSchema(), helper);
1284
+ await collector.init();
1285
+ nuxt.options.appConfig.graphqlMiddleware = {
1286
+ clientCacheEnabled: !!helper.options.clientCache?.enabled,
1287
+ clientCacheMaxSize: helper.options.clientCache?.maxSize ?? 100
1288
+ };
1289
+ nuxt.options.runtimeConfig.graphqlMiddleware = {
1290
+ graphqlEndpoint: helper.options.graphqlEndpoint || ""
1291
+ };
1292
+ nuxt.options.build.transpile.push(
1293
+ fileURLToPath(new URL("./runtime", import.meta.url))
1294
+ );
1295
+ helper.inlineNitroExternals(helper.resolvers.module.resolve("./runtime"));
1296
+ helper.inlineNitroExternals(helper.paths.moduleBuildDir);
1297
+ helper.addAlias("#nuxt-graphql-middleware", helper.paths.moduleBuildDir);
1298
+ helper.addAlias("#graphql-operations", helper.paths.moduleTypesDir);
1299
+ helper.addPlugin("./runtime/plugins/provideState");
1300
+ if (helper.isDev && helper.options.errorOverlay) {
1301
+ helper.addPlugin("./runtime/plugins/devMode");
1302
+ }
1303
+ helper.addServerHandler("query", "/query/:name", "get");
1304
+ helper.addServerHandler("mutation", "/mutation/:name", "post");
1305
+ if (helper.options.enableFileUploads) {
1306
+ helper.addServerHandler("upload", "/upload/:name", "post");
1307
+ }
1308
+ if (helper.isDev) {
1309
+ helper.addServerHandler("debug", "/debug", "get");
1310
+ }
1311
+ if (helper.options.includeComposables) {
1312
+ helper.addComposable("useGraphqlQuery");
1313
+ helper.addComposable("useGraphqlMutation");
1314
+ helper.addComposable("useGraphqlState");
1315
+ helper.addComposable("useAsyncGraphqlQuery");
1316
+ if (helper.options.enableFileUploads) {
1317
+ helper.addComposable("useGraphqlUploadMutation");
1318
+ }
1319
+ helper.addServerUtil("useGraphqlQuery");
1320
+ helper.addServerUtil("useGraphqlMutation");
1321
+ helper.addServerUtil("doGraphqlRequest");
1322
+ }
1323
+ helper.addTemplate(Template.ClientOptions, ClientOptions);
1324
+ helper.addTemplate(Template.ClientOptionsTypes, ClientOptionsTypes);
1325
+ helper.addTemplate(Template.DocumentTypes, DocumentTypes);
1326
+ helper.addTemplate(Template.GraphqlConfig, GraphqlConfig);
1327
+ helper.addTemplate(Template.Helpers, Helpers);
1328
+ helper.addTemplate(Template.HelpersTypes, HelpersTypes);
1329
+ helper.addTemplate(Template.ServerOptions, ServerOptions);
1330
+ helper.addTemplate(Template.ServerOptionsTypes, ServerOptionsTypes);
1331
+ helper.addTemplate(Template.Types, Types);
1332
+ collector.addTemplate(Template.Enums);
1333
+ collector.addTemplate(Template.NitroTypes);
1334
+ collector.addTemplate(Template.OperationSources);
1335
+ collector.addTemplate(Template.OperationTypes);
1336
+ collector.addTemplate(Template.OperationTypesAll);
1337
+ collector.addTemplate(Template.ResponseTypes);
1338
+ collector.addVirtualTemplate(Template.Documents);
1339
+ if (!helper.isDev) {
1340
+ return;
1341
+ }
1342
+ const devModeHandler = new DevModeHandler(
1343
+ nuxt,
1344
+ schemaProvider,
1345
+ collector,
1346
+ helper
1347
+ );
1348
+ devModeHandler.init();
1023
1349
  }
1024
1350
  });
1025
1351