nuxt-graphql-middleware 5.0.0-alpha.9 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +101 -19
  2. package/dist/client/200.html +8 -8
  3. package/dist/client/404.html +8 -8
  4. package/dist/client/_nuxt/{C9pb_2rp.js → CPiV13Ng.js} +2 -2
  5. package/dist/client/_nuxt/CQCiRbEL.js +1 -0
  6. package/dist/client/_nuxt/CYI2eNUl.js +1 -0
  7. package/dist/client/_nuxt/UAIVpYSK.js +25 -0
  8. package/dist/client/_nuxt/builds/latest.json +1 -1
  9. package/dist/client/_nuxt/builds/meta/48332277-c4ad-4d6a-8f09-7a58a109c32d.json +1 -0
  10. package/dist/client/_nuxt/error-404.Bbd2eCoc.css +1 -0
  11. package/dist/client/_nuxt/error-500.Cd2cwFc3.css +1 -0
  12. package/dist/client/_nuxt/{BLvMh1Ga.js → u1b7LyOw.js} +1 -1
  13. package/dist/client/index.html +8 -8
  14. package/dist/client-options.d.mts +6 -0
  15. package/dist/client-options.mjs +5 -0
  16. package/dist/module.d.mts +63 -181
  17. package/dist/module.json +4 -4
  18. package/dist/module.mjs +435 -92
  19. package/dist/runtime/components/CodeFrame.vue +19 -28
  20. package/dist/runtime/components/CodeFrame.vue.d.ts +7 -0
  21. package/dist/runtime/components/DevModeOverlay.vue +25 -33
  22. package/dist/runtime/components/DevModeOverlay.vue.d.ts +3 -0
  23. package/dist/runtime/components/ErrorExtensions.vue +9 -11
  24. package/dist/runtime/components/ErrorExtensions.vue.d.ts +5 -0
  25. package/dist/runtime/components/ErrorGroup.vue +28 -39
  26. package/dist/runtime/components/ErrorGroup.vue.d.ts +9 -0
  27. package/dist/runtime/composables/nuxtApp.d.ts +30 -2
  28. package/dist/runtime/composables/nuxtApp.js +66 -24
  29. package/dist/runtime/composables/useAsyncGraphqlQuery.js +28 -25
  30. package/dist/runtime/composables/useGraphqlMutation.d.ts +1 -1
  31. package/dist/runtime/composables/useGraphqlMutation.js +11 -16
  32. package/dist/runtime/composables/useGraphqlQuery.d.ts +1 -1
  33. package/dist/runtime/composables/useGraphqlQuery.js +7 -25
  34. package/dist/runtime/composables/useGraphqlUploadMutation.js +1 -1
  35. package/dist/runtime/helpers/ClientCache.d.ts +1 -0
  36. package/dist/runtime/helpers/ClientCache.js +12 -0
  37. package/dist/runtime/helpers/composables.d.ts +8 -0
  38. package/dist/runtime/helpers/composables.js +27 -0
  39. package/dist/runtime/helpers/index.d.ts +0 -4
  40. package/dist/runtime/helpers/index.js +0 -13
  41. package/dist/runtime/helpers/queryEncoding.d.ts +11 -0
  42. package/dist/runtime/helpers/queryEncoding.js +89 -0
  43. package/dist/runtime/plugins/devMode.js +2 -1
  44. package/dist/runtime/server/api/mutation.js +2 -1
  45. package/dist/runtime/server/api/query.js +7 -8
  46. package/dist/runtime/server/utils/doGraphqlRequest.js +5 -4
  47. package/dist/runtime/server/utils/useGraphqlQuery.js +2 -2
  48. package/dist/runtime/settings/index.d.ts +1 -0
  49. package/dist/runtime/settings/index.js +1 -0
  50. package/dist/runtime/types.d.ts +2 -2
  51. package/dist/server-options.d.mts +8 -0
  52. package/dist/server-options.mjs +5 -0
  53. package/dist/shared/nuxt-graphql-middleware.-BeiPV4H.d.mts +566 -0
  54. package/dist/types.d.mts +1 -7
  55. package/dist/utils.d.mts +15 -0
  56. package/dist/utils.mjs +18 -0
  57. package/package.json +36 -32
  58. package/dist/client/_nuxt/CBwfSTyQ.js +0 -1
  59. package/dist/client/_nuxt/CPyoLiCY.js +0 -1
  60. package/dist/client/_nuxt/VpkRx2_e.js +0 -25
  61. package/dist/client/_nuxt/builds/meta/826a43da-d42c-4fbf-8dfd-2572141eaf8f.json +0 -1
  62. package/dist/client/_nuxt/error-404.BJkSn6RI.css +0 -1
  63. package/dist/client/_nuxt/error-500.TOCKLquH.css +0 -1
  64. package/dist/module.cjs +0 -5
  65. package/dist/module.d.ts +0 -210
  66. package/dist/runtime/clientOptions/index.d.ts +0 -2
  67. package/dist/runtime/clientOptions/index.js +0 -3
  68. package/dist/runtime/serverOptions/defineGraphqlServerOptions.d.ts +0 -4
  69. package/dist/runtime/serverOptions/defineGraphqlServerOptions.js +0 -3
  70. package/dist/runtime/serverOptions/index.d.ts +0 -2
  71. package/dist/runtime/serverOptions/index.js +0 -2
  72. package/dist/types.d.ts +0 -7
package/dist/module.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  import { fileURLToPath } from 'url';
2
- import { useLogger, addTemplate, addServerTemplate, addTypeTemplate, resolveAlias, createResolver, resolveFiles, addPlugin, addServerHandler, addImports, addServerImports, useNitro, defineNuxtModule } from '@nuxt/kit';
2
+ import { useLogger, addTemplate, addServerTemplate, addTypeTemplate, createResolver, resolveAlias, resolveFiles, addPlugin, addServerHandler, addImports, addServerImports, useNitro, defineNuxtModule } from '@nuxt/kit';
3
3
  import { existsSync, promises } from 'node:fs';
4
- import { relative } from 'pathe';
5
4
  import { basename } from 'node:path';
5
+ import { relative, join } from 'pathe';
6
6
  import { printSourceLocation, parse, Source, OperationTypeNode } from 'graphql';
7
7
  import { Generator, FieldNotFoundError, TypeNotFoundError, FragmentNotFoundError } from 'graphql-typescript-deluxe';
8
8
  import color from 'picocolors';
@@ -12,16 +12,17 @@ import { generate } from '@graphql-codegen/cli';
12
12
  import * as PluginSchemaAst from '@graphql-codegen/schema-ast';
13
13
  import { loadSchema } from '@graphql-tools/load';
14
14
  import { defu } from 'defu';
15
- import * as micromatch from 'micromatch';
15
+ import micromatch from 'micromatch';
16
16
  import { ConfirmPrompt } from '@clack/core';
17
17
  import isUnicodeSupported from 'is-unicode-supported';
18
+ import { hash } from 'ohash';
18
19
  import { existsSync as existsSync$1 } from 'fs';
19
20
  import { onDevToolsInitialized, extendServerRpc } from '@nuxt/devtools-kit';
20
21
 
21
22
  const name = "nuxt-graphql-middleware";
22
- const version = "5.0.0-alpha.9";
23
+ const version = "5.1.0";
23
24
 
24
- const logger = useLogger(name);
25
+ const logger = useLogger("nuxt-graphql-middleware");
25
26
  const defaultOptions = {
26
27
  downloadSchema: true,
27
28
  schemaPath: "~~/schema.graphql",
@@ -32,7 +33,14 @@ const defaultOptions = {
32
33
  documents: [],
33
34
  devtools: true,
34
35
  errorOverlay: true,
35
- graphqlConfigFilePath: "./graphql.config.ts"
36
+ graphqlConfigFilePath: "./graphql.config.ts",
37
+ experimental: {
38
+ improvedQueryParamEncoding: false
39
+ },
40
+ clientCache: {
41
+ enabled: false,
42
+ maxSize: 100
43
+ }
36
44
  };
37
45
  function validateOptions(options) {
38
46
  if (!options.graphqlEndpoint) {
@@ -119,6 +127,9 @@ class CollectedFile {
119
127
  }
120
128
  static async fromFilePath(filePath) {
121
129
  const content = (await promises.readFile(filePath)).toString();
130
+ if (!content) {
131
+ return null;
132
+ }
122
133
  return new CollectedFile(filePath, content, true);
123
134
  }
124
135
  /**
@@ -148,7 +159,10 @@ class Collector {
148
159
  }
149
160
  if (!mappedOptions.output.buildTypeDocFilePath) {
150
161
  mappedOptions.output.buildTypeDocFilePath = (filePath) => {
151
- return this.filePathToBuildRelative(filePath);
162
+ if (filePath.startsWith("/")) {
163
+ return this.filePathToBuildRelative(filePath);
164
+ }
165
+ return filePath;
152
166
  };
153
167
  }
154
168
  this.generator = new Generator(schema, mappedOptions);
@@ -157,6 +171,14 @@ class Collector {
157
171
  * All collected files.
158
172
  */
159
173
  files = /* @__PURE__ */ new Map();
174
+ /**
175
+ * All documents provided by hooks.
176
+ */
177
+ hookDocuments = /* @__PURE__ */ new Map();
178
+ /**
179
+ * All file paths provided by hooks.
180
+ */
181
+ hookFiles = /* @__PURE__ */ new Set();
160
182
  /**
161
183
  * The code generator.
162
184
  */
@@ -177,6 +199,7 @@ class Collector {
177
199
  * The generated template contents.
178
200
  */
179
201
  templateResult = /* @__PURE__ */ new Map();
202
+ isInitialised = false;
180
203
  async reset() {
181
204
  this.files.clear();
182
205
  this.generator.reset();
@@ -190,10 +213,13 @@ class Collector {
190
213
  await this.initDocuments();
191
214
  }
192
215
  filePathToBuildRelative(filePath) {
193
- return "./" + this.helper.toBuildRelative(filePath);
216
+ return "./" + this.helper.toModuleBuildRelative(filePath);
194
217
  }
195
218
  filePathToSourceRelative(filePath) {
196
- return "./" + relative(process.cwd(), filePath);
219
+ if (filePath.startsWith("/")) {
220
+ return "./" + relative(process.cwd(), filePath);
221
+ }
222
+ return filePath;
197
223
  }
198
224
  operationToLogEntry(operation, errors) {
199
225
  return {
@@ -203,8 +229,8 @@ class Collector {
203
229
  errors
204
230
  };
205
231
  }
206
- getTemplate(template) {
207
- const content = this.templateResult.get(template);
232
+ getTemplate(template, type) {
233
+ const content = this.templateResult.get(template + "-" + type);
208
234
  if (content === void 0) {
209
235
  throw new Error(`Missing template content: ${template}`);
210
236
  }
@@ -213,23 +239,28 @@ class Collector {
213
239
  /**
214
240
  * Executes code gen and performs validation for operations.
215
241
  */
216
- buildState() {
242
+ async buildState() {
217
243
  const output = this.generator.build();
218
244
  const operations = output.getCollectedOperations();
219
245
  const generatedCode = output.getGeneratedCode();
220
246
  this.templates.forEach((template) => {
247
+ const path = template.options.path;
221
248
  if (template.build) {
222
- const filename = template.options.path + ".js";
223
249
  this.templateResult.set(
224
- filename,
225
- template.build(output, this.helper).trim()
250
+ path + "-default",
251
+ this.helper.processTemplate(
252
+ template.options.path,
253
+ template.build(output, this.helper, this)
254
+ )
226
255
  );
227
256
  }
228
257
  if (template.buildTypes) {
229
- const filename = template.options.path + ".d.ts";
230
258
  this.templateResult.set(
231
- filename,
232
- template.buildTypes(output, this.helper).trim()
259
+ template.options.path + "-types",
260
+ this.helper.processTemplate(
261
+ template.options.path,
262
+ template.buildTypes(output, this.helper, this)
263
+ )
233
264
  );
234
265
  }
235
266
  });
@@ -266,10 +297,15 @@ class Collector {
266
297
  logEntries.push(this.operationToLogEntry(operation, errors));
267
298
  }
268
299
  }
269
- logAllEntries(logEntries);
300
+ logAllEntries(
301
+ logEntries.sort((a, b) => {
302
+ return a.type.localeCompare(b.type) || a.name.localeCompare(b.name);
303
+ })
304
+ );
270
305
  if (hasErrors) {
271
306
  throw new Error("GraphQL errors");
272
307
  }
308
+ await this.helper.nuxt.callHook("nuxt-graphql-middleware:build", { output });
273
309
  if (this.helper.isDev) {
274
310
  for (const code of generatedCode) {
275
311
  const id = `${code.identifier}_${code.graphqlName}`;
@@ -332,12 +368,32 @@ class Collector {
332
368
  );
333
369
  if (shouldRevalidate === "yes") {
334
370
  await this.reset();
335
- return this.init();
371
+ await this.init();
372
+ this.isInitialised = true;
373
+ return;
336
374
  }
337
375
  }
338
376
  throw new Error("Graphql document validation failed.");
339
377
  }
340
378
  }
379
+ addHookDocument(identifier, source) {
380
+ this.hookDocuments.set("hook:" + identifier, source);
381
+ }
382
+ async addOrUpdateHookDocument(identifier, source) {
383
+ const fullIdentifier = "hook:" + identifier;
384
+ const exists = this.hookDocuments.has(fullIdentifier);
385
+ this.hookDocuments.set(fullIdentifier, source);
386
+ if (exists && this.isInitialised) {
387
+ this.generator.update({
388
+ filePath: fullIdentifier,
389
+ document: source
390
+ });
391
+ await this.buildState();
392
+ }
393
+ }
394
+ addHookFile(filePath) {
395
+ this.hookFiles.add(filePath);
396
+ }
341
397
  /**
342
398
  * Initialise the collector.
343
399
  */
@@ -357,7 +413,19 @@ class Collector {
357
413
  documentNode: file.parsed
358
414
  });
359
415
  }
360
- this.buildState();
416
+ const hookDocuments = [...this.hookDocuments.entries()];
417
+ hookDocuments.forEach(([identifier, source]) => {
418
+ const file = new CollectedFile(identifier, source, false);
419
+ this.files.set(identifier, file);
420
+ this.generator.add({
421
+ filePath: identifier,
422
+ documentNode: file.parsed
423
+ });
424
+ });
425
+ for (const filePath of this.hookFiles) {
426
+ await this.addFile(filePath);
427
+ }
428
+ await this.buildState();
361
429
  logger.success("All GraphQL documents are valid.");
362
430
  } catch (e) {
363
431
  this.logError(e);
@@ -369,7 +437,7 @@ class Collector {
369
437
  */
370
438
  async addFile(filePath) {
371
439
  const file = await CollectedFile.fromFilePath(filePath);
372
- if (!file.fileContents) {
440
+ if (!file?.fileContents) {
373
441
  return null;
374
442
  }
375
443
  this.files.set(filePath, file);
@@ -379,15 +447,18 @@ class Collector {
379
447
  });
380
448
  return file;
381
449
  }
450
+ matchesPatternOrExists(filePath) {
451
+ return this.files.has(filePath) || this.hookFiles.has(filePath) || this.hookDocuments.has(filePath) || this.helper.matchesImportPattern(filePath);
452
+ }
382
453
  async handleAdd(filePath) {
383
- if (!this.helper.matchesImportPattern(filePath)) {
454
+ if (!this.matchesPatternOrExists(filePath)) {
384
455
  return false;
385
456
  }
386
457
  const result = await this.addFile(filePath);
387
458
  return !!result;
388
459
  }
389
460
  async handleChange(filePath) {
390
- if (!this.helper.matchesImportPattern(filePath)) {
461
+ if (!this.matchesPatternOrExists(filePath)) {
391
462
  return false;
392
463
  }
393
464
  const file = this.files.get(filePath);
@@ -446,7 +517,7 @@ class Collector {
446
517
  hasChanged = this.handleUnlinkDir(filePath);
447
518
  }
448
519
  if (hasChanged) {
449
- this.buildState();
520
+ await this.buildState();
450
521
  }
451
522
  } catch (e) {
452
523
  this.generator.resetCaches();
@@ -478,7 +549,7 @@ class Collector {
478
549
  */
479
550
  addVirtualTemplate(template) {
480
551
  const filename = template.options.path + ".js";
481
- const getContents = () => this.getTemplate(filename);
552
+ const getContents = () => this.getTemplate(template.options.path, "default");
482
553
  addTemplate({
483
554
  filename,
484
555
  getContents
@@ -503,21 +574,23 @@ class Collector {
503
574
  if (template.options.virtual) {
504
575
  this.addVirtualTemplate(template);
505
576
  } else {
506
- const filename = template.options.path + ".js";
577
+ const path = template.options.path;
578
+ const filename = template.options.isFullPath ? path : path + ".js";
507
579
  addTemplate({
508
580
  filename,
509
581
  write: true,
510
- getContents: () => this.getTemplate(filename)
582
+ getContents: () => this.getTemplate(path, "default")
511
583
  });
512
584
  }
513
585
  }
514
586
  if (template.buildTypes) {
587
+ const path = template.options.path;
515
588
  const filename = template.options.path + ".d.ts";
516
589
  addTypeTemplate(
517
590
  {
518
591
  filename,
519
592
  write: true,
520
- getContents: () => this.getTemplate(filename)
593
+ getContents: () => this.getTemplate(path, "types")
521
594
  },
522
595
  {
523
596
  nuxt: true,
@@ -526,6 +599,23 @@ class Collector {
526
599
  );
527
600
  }
528
601
  }
602
+ /**
603
+ * Get the hook documents.
604
+ */
605
+ getHookDocuments() {
606
+ return [...this.hookDocuments.entries()].map(([identifier, source]) => {
607
+ return {
608
+ identifier,
609
+ source
610
+ };
611
+ });
612
+ }
613
+ /**
614
+ * Get the hook documents.
615
+ */
616
+ getHookFiles() {
617
+ return [...this.hookFiles.values()];
618
+ }
529
619
  }
530
620
 
531
621
  class SchemaProvider {
@@ -730,25 +820,36 @@ ${color.cyan(S_BAR_END)}
730
820
  class ModuleHelper {
731
821
  constructor(nuxt, moduleUrl, options) {
732
822
  this.nuxt = nuxt;
733
- const isModuleBuild = process.env.MODULE_BUILD === "true" && nuxt.options._prepare;
734
823
  const mergedOptions = defu({}, options, defaultOptions);
735
824
  if (!mergedOptions.autoImportPatterns) {
736
825
  mergedOptions.autoImportPatterns = [
737
826
  "~~/**/*.{gql,graphql}",
827
+ "~/**/*.{gql,graphql}",
738
828
  "!node_modules"
739
829
  ];
740
830
  }
741
- if (isModuleBuild) {
742
- mergedOptions.graphqlEndpoint = "http://localhost";
743
- mergedOptions.downloadSchema = false;
744
- mergedOptions.schemaPath = "~~/schema.graphql";
745
- mergedOptions.autoImportPatterns = [
746
- "~~/playground/**/*.{gql,graphql}",
747
- "!node_modules"
748
- ];
749
- }
750
- mergedOptions.autoImportPatterns = (mergedOptions.autoImportPatterns || []).map((pattern) => {
751
- return resolveAlias(pattern);
831
+ const layerAliases = nuxt.options._layers.map((layer) => {
832
+ return {
833
+ "~~": layer.config.rootDir,
834
+ "@@": layer.config.rootDir,
835
+ "~": layer.config.srcDir,
836
+ "@": layer.config.srcDir,
837
+ // Merge any additional aliases defined by the layer.
838
+ // Must be last so that the layer may override the "default" aliases.
839
+ ...layer.config.alias || {}
840
+ };
841
+ });
842
+ const srcResolver = createResolver(nuxt.options.srcDir);
843
+ const rootResolver = createResolver(nuxt.options.rootDir);
844
+ mergedOptions.autoImportPatterns = (mergedOptions.autoImportPatterns || []).flatMap((pattern) => {
845
+ if (pattern.startsWith("!") || pattern.startsWith("/")) {
846
+ return pattern;
847
+ } else if (pattern.startsWith("~") || pattern.startsWith("@")) {
848
+ return layerAliases.map((aliases) => resolveAlias(pattern, aliases));
849
+ }
850
+ return rootResolver.resolve(pattern);
851
+ }).map((pattern) => {
852
+ return pattern.replace(".{graphql}", ".graphql").replace(".{gql}", ".gql");
752
853
  });
753
854
  this.options = mergedOptions;
754
855
  if (!nuxt.options._prepare) {
@@ -758,9 +859,9 @@ class ModuleHelper {
758
859
  this.resolvers = {
759
860
  module: createResolver(moduleUrl),
760
861
  server: createResolver(nuxt.options.serverDir),
761
- src: createResolver(nuxt.options.srcDir),
862
+ src: srcResolver,
762
863
  app: createResolver(nuxt.options.dir.app),
763
- root: createResolver(nuxt.options.rootDir)
864
+ root: rootResolver
764
865
  };
765
866
  this.paths = {
766
867
  runtimeTypes: "",
@@ -786,6 +887,7 @@ class ModuleHelper {
786
887
  options;
787
888
  prompt = new ConsolePrompt();
788
889
  nitroExternals = [];
890
+ tsPaths = {};
789
891
  /**
790
892
  * Find the path to the graphqlMiddleware.serverOptions.ts file.
791
893
  */
@@ -849,35 +951,17 @@ class ModuleHelper {
849
951
  async getImportPatternFiles() {
850
952
  return resolveFiles(
851
953
  this.nuxt.options.srcDir,
852
- this.options.autoImportPatterns,
853
- {
854
- followSymbolicLinks: false
855
- }
954
+ this.options.autoImportPatterns
856
955
  );
857
956
  }
858
957
  matchesImportPattern(filePath) {
859
- return micromatch.isMatch(filePath, this.options.autoImportPatterns);
958
+ return micromatch.isMatch(filePath, this.options.autoImportPatterns) || this.options.autoImportPatterns.includes(filePath);
860
959
  }
861
960
  addAlias(name, path) {
862
961
  this.nuxt.options.alias[name] = path;
863
962
  const pathFromName = `./${name.substring(1)}`;
864
- this.nuxt.options.nitro.typescript ||= {};
865
- this.nuxt.options.nitro.typescript.tsConfig ||= {};
866
- this.nuxt.options.nitro.typescript.tsConfig.compilerOptions ||= {};
867
- this.nuxt.options.nitro.typescript.tsConfig.compilerOptions.paths ||= {};
868
- this.nuxt.options.nitro.typescript.tsConfig.compilerOptions.paths[name] = [
869
- pathFromName
870
- ];
871
- this.nuxt.options.nitro.typescript.tsConfig.compilerOptions.paths[name + "/*"] = [pathFromName + "/*"];
872
- this.nuxt.options.typescript.tsConfig ||= {};
873
- this.nuxt.options.typescript.tsConfig.compilerOptions ||= {};
874
- this.nuxt.options.typescript.tsConfig.compilerOptions.paths ||= {};
875
- this.nuxt.options.typescript.tsConfig.compilerOptions.paths[name] = [
876
- pathFromName
877
- ];
878
- this.nuxt.options.typescript.tsConfig.compilerOptions.paths[name + "/*"] = [
879
- pathFromName + "/*"
880
- ];
963
+ this.tsPaths[name] = pathFromName;
964
+ this.tsPaths[name + "/*"] = pathFromName + "/*";
881
965
  this.inlineNitroExternals(name);
882
966
  }
883
967
  inlineNitroExternals(arg) {
@@ -892,10 +976,34 @@ class ModuleHelper {
892
976
  this.nuxt.options.nitro.externals ||= {};
893
977
  this.nuxt.options.nitro.externals.inline ||= [];
894
978
  this.nuxt.options.nitro.externals.inline.push(...this.nitroExternals);
979
+ this.nuxt.options.nitro.typescript ||= {};
980
+ this.nuxt.options.nitro.typescript.tsConfig ||= {};
981
+ this.nuxt.options.nitro.typescript.tsConfig.compilerOptions ||= {};
982
+ this.nuxt.options.nitro.typescript.tsConfig.compilerOptions.paths ||= {};
983
+ this.nuxt.options.typescript.tsConfig ||= {};
984
+ this.nuxt.options.typescript.tsConfig.compilerOptions ||= {};
985
+ this.nuxt.options.typescript.tsConfig.compilerOptions.paths ||= {};
986
+ for (const [name, path] of Object.entries(this.tsPaths)) {
987
+ this.nuxt.options.nitro.typescript.tsConfig.compilerOptions.paths[name] = [path];
988
+ this.nuxt.options.typescript.tsConfig.compilerOptions.paths[name] = [path];
989
+ }
990
+ }
991
+ processTemplate(path, content) {
992
+ if (path.includes("graphql-operations/") || path.endsWith(".graphql")) {
993
+ return content.trim();
994
+ }
995
+ const name = path.split("/")[1];
996
+ return `/*
997
+ * @see [Documentation](https://nuxt-graphql-middleware.dulnan.net/advanced/templates#${name})
998
+ */
999
+ ${content.trim()}`;
895
1000
  }
896
1001
  addTemplate(template) {
897
1002
  if (template.build) {
898
- const content = template.build(this).trim();
1003
+ const content = this.processTemplate(
1004
+ template.options.path,
1005
+ template.build(this)
1006
+ );
899
1007
  addTemplate({
900
1008
  filename: template.options.path + ".js",
901
1009
  write: true,
@@ -903,7 +1011,10 @@ class ModuleHelper {
903
1011
  });
904
1012
  }
905
1013
  if (template.buildTypes) {
906
- const content = template.buildTypes(this).trim();
1014
+ const content = this.processTemplate(
1015
+ template.options.path,
1016
+ template.buildTypes(this)
1017
+ );
907
1018
  const filename = template.options.path + ".d.ts";
908
1019
  addTypeTemplate({
909
1020
  filename,
@@ -912,8 +1023,8 @@ class ModuleHelper {
912
1023
  });
913
1024
  }
914
1025
  }
915
- addPlugin(path) {
916
- addPlugin(this.resolvers.module.resolve(path), {
1026
+ addPlugin(name) {
1027
+ addPlugin(this.resolvers.module.resolve("./runtime/plugins/" + name), {
917
1028
  append: false
918
1029
  });
919
1030
  }
@@ -968,7 +1079,7 @@ const ClientOptions = defineStaticTemplate(
968
1079
  export { clientOptions }
969
1080
  `;
970
1081
  }
971
- return `export const clientOptions = {}`;
1082
+ return `export const clientOptions = undefined`;
972
1083
  },
973
1084
  (helper) => {
974
1085
  if (helper.paths.clientOptions) {
@@ -976,21 +1087,43 @@ export { clientOptions }
976
1087
  helper.paths.clientOptions
977
1088
  );
978
1089
  return `import type { GraphqlClientOptions } from '${helper.paths.runtimeTypes}'
979
- import { clientOptions } from '${pathRelative}'
980
-
981
- export type GraphqlClientContext = typeof clientOptions extends GraphqlClientOptions<infer R> ? R : {}
1090
+ import clientOptionsImport from '${pathRelative}'
982
1091
 
983
- export { clientOptions }`;
1092
+ declare export const clientOptions: GraphqlClientOptions|undefined
1093
+ export type GraphqlClientContext = typeof clientOptionsImport extends GraphqlClientOptions<infer R> ? R : {}
1094
+ `;
984
1095
  }
985
1096
  return `
986
1097
  import type { GraphqlClientOptions } from '${helper.paths.runtimeTypes}'
987
- export const clientOptions: GraphqlClientOptions
988
1098
 
1099
+ declare export const clientOptions: GraphqlClientOptions|undefined
989
1100
  export type GraphqlClientContext = {}
990
1101
  `;
991
1102
  }
992
1103
  );
993
1104
 
1105
+ const Config = defineStaticTemplate(
1106
+ { path: "nuxt-graphql-middleware/config" },
1107
+ (helper) => {
1108
+ const experimentalQueryParamEncoding = !!helper.options.experimental.improvedQueryParamEncoding;
1109
+ const clientCacheEnabledAtBuild = !!helper.options.clientCache.enabled;
1110
+ return `
1111
+ export const experimentalQueryParamEncoding = ${JSON.stringify(experimentalQueryParamEncoding)}
1112
+ export const clientCacheEnabledAtBuild = ${JSON.stringify(clientCacheEnabledAtBuild)}
1113
+ export const importMetaServer = import.meta.server
1114
+ export const importMetaClient = import.meta.client
1115
+ `;
1116
+ },
1117
+ () => {
1118
+ return `
1119
+ declare export const experimentalQueryParamEncoding: boolean
1120
+ declare export const clientCacheEnabledAtBuild: boolean
1121
+ declare export const importMetaServer: boolean
1122
+ declare export const importMetaClient: boolean
1123
+ `;
1124
+ }
1125
+ );
1126
+
994
1127
  const Documents = defineGeneratorTemplate(
995
1128
  { path: "nuxt-graphql-middleware/documents", virtual: true },
996
1129
  (output, helper) => {
@@ -1027,13 +1160,25 @@ const GraphqlConfig = defineStaticTemplate(
1027
1160
  const documents = patterns.filter((v) => !v.includes("!")).map((pattern) => {
1028
1161
  return "./" + relative(configPath, helper.resolvers.root.resolve(pattern));
1029
1162
  });
1030
- return `const schema = ${JSON.stringify(schemaPath)}
1163
+ documents.push(
1164
+ "./" + relative(
1165
+ configPath,
1166
+ join(helper.paths.moduleBuildDir, "hook-documents.graphql")
1167
+ )
1168
+ );
1169
+ return `
1170
+ import { hookFiles } from './hook-files'
1171
+
1172
+ const schema = ${JSON.stringify(schemaPath)}
1031
1173
 
1032
1174
  const documents = ${JSON.stringify(documents, null, 2)};
1033
1175
 
1034
1176
  const config = {
1035
1177
  schema,
1036
- documents,
1178
+ documents: [
1179
+ ...documents,
1180
+ ...hookFiles
1181
+ ]
1037
1182
  }
1038
1183
 
1039
1184
  export default config
@@ -1067,6 +1212,43 @@ export function getEndpoint(operation: string, operationName: string): string`;
1067
1212
  }
1068
1213
  );
1069
1214
 
1215
+ const HookDocuments = defineGeneratorTemplate(
1216
+ {
1217
+ path: "nuxt-graphql-middleware/hook-documents.graphql",
1218
+ virtual: false,
1219
+ isFullPath: true
1220
+ },
1221
+ (_output, _helper, collector) => {
1222
+ return collector.getHookDocuments().map((v) => {
1223
+ return `
1224
+ # ${v.identifier}
1225
+ ${v.source}
1226
+ `;
1227
+ }).join("\n\n");
1228
+ },
1229
+ null
1230
+ );
1231
+
1232
+ const HookFiles = defineGeneratorTemplate(
1233
+ {
1234
+ path: "nuxt-graphql-middleware/hook-files",
1235
+ virtual: false
1236
+ },
1237
+ (_output, helper, collector) => {
1238
+ const configPath = helper.resolvers.root.resolve(
1239
+ (helper.options.graphqlConfigFilePath || "").replace(
1240
+ "/graphql.config.ts",
1241
+ ""
1242
+ )
1243
+ );
1244
+ const files = collector.getHookFiles().map((filePath) => {
1245
+ return "./" + relative(configPath, filePath);
1246
+ });
1247
+ return `export const hookFiles = ${JSON.stringify(files, null, 2)}`;
1248
+ },
1249
+ null
1250
+ );
1251
+
1070
1252
  const NitroTypes = defineGeneratorTemplate(
1071
1253
  { path: "nuxt-graphql-middleware/nitro" },
1072
1254
  null,
@@ -1097,6 +1279,46 @@ ${endpoints.sort().join("\n")}
1097
1279
  }
1098
1280
  );
1099
1281
 
1282
+ const OperationHashes = defineGeneratorTemplate(
1283
+ { path: "nuxt-graphql-middleware/operation-hashes", virtual: true },
1284
+ (output, helper) => {
1285
+ if (helper.isDev) {
1286
+ return `export const operationHashes = {}`;
1287
+ }
1288
+ const fragmentHashMap = {};
1289
+ const generatedCode = output.getGeneratedCode();
1290
+ generatedCode.forEach((code) => {
1291
+ if (code.type === "fragment") {
1292
+ const fragmentHash = hash(code.source);
1293
+ if (code.graphqlName) {
1294
+ fragmentHashMap[code.graphqlName] = fragmentHash;
1295
+ }
1296
+ }
1297
+ });
1298
+ const lines = [];
1299
+ generatedCode.forEach((code) => {
1300
+ if (code.type === "operation" && code.graphqlName && code.source) {
1301
+ const source = code.source;
1302
+ const fragments = code.getGraphQLFragmentDependencies().map((fragmentName) => {
1303
+ return fragmentHashMap[fragmentName];
1304
+ }).join("");
1305
+ const sourceHash = hash(source + fragments);
1306
+ lines.push(`"${code.graphqlName}": "${sourceHash.substring(0, 10)}"`);
1307
+ }
1308
+ });
1309
+ return `export const operationHashes = {
1310
+ ${lines.sort().join(",\n ")}
1311
+ }`;
1312
+ },
1313
+ () => {
1314
+ return `
1315
+ declare module '#nuxt-graphql-middleware/operation-hashes' {
1316
+ export const operationHashes: Record<string, string>;
1317
+ }
1318
+ `;
1319
+ }
1320
+ );
1321
+
1100
1322
  const OperationTypesAll = defineGeneratorTemplate(
1101
1323
  { path: "nuxt-graphql-middleware/operation-types" },
1102
1324
  () => `export {}`,
@@ -1119,6 +1341,27 @@ const Operations = defineGeneratorTemplate(
1119
1341
  }
1120
1342
  );
1121
1343
 
1344
+ const OperationVariables = defineGeneratorTemplate(
1345
+ { path: "nuxt-graphql-middleware/operation-variables" },
1346
+ (output) => {
1347
+ const operations = output.getCollectedOperations().reduce((acc, collectedOperation) => {
1348
+ const node = collectedOperation.node;
1349
+ const operationName = collectedOperation.graphqlName;
1350
+ const variables = (node.variableDefinitions || []).map(
1351
+ (v) => v.variable.name.value
1352
+ );
1353
+ acc[operationName] = variables;
1354
+ return acc;
1355
+ }, {});
1356
+ return `export const operationVariables = ${JSON.stringify(operations, null, 2)}`;
1357
+ },
1358
+ () => {
1359
+ return `
1360
+ declare export const operationVariables: Record<string, string[]>
1361
+ `;
1362
+ }
1363
+ );
1364
+
1122
1365
  const Response = defineGeneratorTemplate(
1123
1366
  { path: "nuxt-graphql-middleware/response" },
1124
1367
  null,
@@ -1133,7 +1376,7 @@ import type { GraphqlServerResponse } from '${helper.paths.runtimeTypes}'
1133
1376
 
1134
1377
  declare module '#nuxt-graphql-middleware/response' {
1135
1378
  export type GraphqlMiddlewareResponseUnion =
1136
- | ${allTypes.join("\n | ") || "never"}
1379
+ | ${allTypes.join("\n | ") || "never"}
1137
1380
 
1138
1381
  export type GraphqlResponse<T> = GraphqlServerResponse<T> & GraphqlResponseAdditions
1139
1382
  export type GraphqlResponseTyped = GraphqlResponse<GraphqlMiddlewareResponseUnion>
@@ -1152,16 +1395,27 @@ export { serverOptions }
1152
1395
  `;
1153
1396
  },
1154
1397
  (helper) => {
1155
- const resolvedPathRelative = helper.paths.serverOptions ? helper.toModuleBuildRelative(helper.paths.serverOptions) : null;
1156
- const serverOptionsLineTypes = resolvedPathRelative ? `import serverOptions from '${resolvedPathRelative}'` : `const serverOptions: GraphqlMiddlewareServerOptions = {}`;
1157
- return `
1398
+ if (helper.paths.serverOptions) {
1399
+ const resolvedPathRelative = helper.toModuleBuildRelative(
1400
+ helper.paths.serverOptions
1401
+ );
1402
+ return `
1158
1403
  import type { GraphqlMiddlewareServerOptions } from '${helper.paths.runtimeTypes}'
1159
- ${serverOptionsLineTypes}
1404
+ import serverOptionsImport from '${resolvedPathRelative}'
1160
1405
 
1161
1406
  export type GraphqlResponseAdditions =
1162
- typeof serverOptions extends GraphqlMiddlewareServerOptions<infer R, any, any> ? R : {}
1407
+ typeof serverOptionsImport extends GraphqlMiddlewareServerOptions<infer R, any, any> ? R : {}
1163
1408
 
1164
- export { serverOptions }`;
1409
+ declare export const serverOptions: GraphqlMiddlewareServerOptions
1410
+ `;
1411
+ }
1412
+ return `
1413
+ import type { GraphqlMiddlewareServerOptions } from '${helper.paths.runtimeTypes}'
1414
+
1415
+ declare export const serverOptions: GraphqlMiddlewareServerOptions
1416
+
1417
+ export type GraphqlResponseAdditions = object
1418
+ `;
1165
1419
  }
1166
1420
  );
1167
1421
 
@@ -1172,7 +1426,7 @@ const Sources = defineGeneratorTemplate(
1172
1426
  const srcDir = helper.paths.root;
1173
1427
  const lines = [];
1174
1428
  for (const operation of operations) {
1175
- const filePath = relative(srcDir, operation.filePath);
1429
+ const filePath = operation.filePath.startsWith("/") ? relative(srcDir, operation.filePath) : operation.filePath;
1176
1430
  lines.push(
1177
1431
  `${operation.operationType}_${operation.graphqlName}: '${filePath}',`
1178
1432
  );
@@ -1190,12 +1444,17 @@ export const operationSources = {
1190
1444
 
1191
1445
  const TEMPLATES = [
1192
1446
  ClientOptions,
1447
+ Config,
1193
1448
  Documents,
1194
1449
  GraphqlConfig,
1195
1450
  Helpers,
1451
+ HookDocuments,
1452
+ HookFiles,
1196
1453
  NitroTypes,
1454
+ OperationHashes,
1197
1455
  OperationTypesAll,
1198
1456
  Operations,
1457
+ OperationVariables,
1199
1458
  Response,
1200
1459
  ServerOptions,
1201
1460
  Sources
@@ -1284,11 +1543,12 @@ class DevModeHandler {
1284
1543
  this.nitro = useNitro();
1285
1544
  this.nitro.hooks.hook("compiled", this.onNitroCompiled.bind(this));
1286
1545
  }
1287
- async onBuilderWatch(event, pathAbsolute) {
1288
- if (pathAbsolute === this.helper.paths.schema) {
1546
+ async onBuilderWatch(event, providedFilePath) {
1547
+ if (!providedFilePath.endsWith(".graphql") && !providedFilePath.endsWith(".gql")) {
1289
1548
  return;
1290
1549
  }
1291
- if (!pathAbsolute.match(/\.(gql|graphql)$/)) {
1550
+ const pathAbsolute = providedFilePath.startsWith("/") ? providedFilePath : this.helper.resolvers.src.resolve(providedFilePath);
1551
+ if (pathAbsolute === this.helper.paths.schema) {
1292
1552
  return;
1293
1553
  }
1294
1554
  this.helper.prompt.abort();
@@ -1363,13 +1623,91 @@ class DevModeHandler {
1363
1623
  }
1364
1624
  }
1365
1625
 
1626
+ class ModuleContext {
1627
+ constructor(schemaProvider, collector) {
1628
+ this.schemaProvider = schemaProvider;
1629
+ this.collector = collector;
1630
+ }
1631
+ /**
1632
+ * Return the GraphQL schema.
1633
+ *
1634
+ * Note that the schema may be updated during development, so it can become
1635
+ * stale. Prefer using methods like `schemaHasType()` to query the schema.
1636
+ *
1637
+ * @returns The GraphQL schema.
1638
+ */
1639
+ getSchema() {
1640
+ return this.schemaProvider.getSchema();
1641
+ }
1642
+ /**
1643
+ * Check if the given GraphQL type (interface, concrete type, enum, input type)
1644
+ * exists in the schema.
1645
+ *
1646
+ * @param name - The name of the type.
1647
+ *
1648
+ * @returns True if the type exists in the schema.
1649
+ */
1650
+ schemaHasType(name) {
1651
+ return !!this.schemaProvider.getSchema().getType(name);
1652
+ }
1653
+ /**
1654
+ * Get a type from the schema.
1655
+ *
1656
+ * @param name - The name of the type.
1657
+ *
1658
+ * @returns The type.
1659
+ */
1660
+ schemaGetType(name) {
1661
+ return this.schemaProvider.getSchema().getType(name);
1662
+ }
1663
+ /**
1664
+ * Add an additional static document.
1665
+ *
1666
+ * @param identifier - The unique identifier for your document.
1667
+ * @param source - The document source.
1668
+ */
1669
+ addDocument(identifier, source) {
1670
+ this.collector.addHookDocument(identifier, source);
1671
+ return this;
1672
+ }
1673
+ /**
1674
+ * Add or update an additional static document.
1675
+ *
1676
+ * @param identifier - The unique identifier for your document.
1677
+ * @param source - The document source.
1678
+ */
1679
+ async addOrUpdateDocument(identifier, source) {
1680
+ await this.collector.addOrUpdateHookDocument(identifier, source);
1681
+ return this;
1682
+ }
1683
+ /**
1684
+ * Add an additional GraphQL file to import.
1685
+ *
1686
+ * @param filePath - The absolute path to the file.
1687
+ */
1688
+ addImportFile(filePath) {
1689
+ if (!filePath.startsWith("/")) {
1690
+ throw new Error(
1691
+ `The provided file path "${filePath}" must be an absolute path.`
1692
+ );
1693
+ }
1694
+ if (!filePath.endsWith(".graphql") && !filePath.endsWith(".gql")) {
1695
+ throw new Error(
1696
+ `The provided file path "${filePath}" should have a .graphql or .gql extension.`
1697
+ );
1698
+ }
1699
+ this.collector.addHookFile(filePath);
1700
+ return this;
1701
+ }
1702
+ }
1703
+
1366
1704
  const module = defineNuxtModule({
1367
1705
  meta: {
1368
1706
  name,
1369
1707
  configKey: "graphqlMiddleware",
1370
1708
  version,
1371
1709
  compatibility: {
1372
- nuxt: ">=3.15.0"
1710
+ nuxt: ">=3.17.0"
1373
1711
  }
1374
1712
  },
1375
1713
  defaults: defaultOptions,
@@ -1378,6 +1716,8 @@ const module = defineNuxtModule({
1378
1716
  const schemaProvider = new SchemaProvider(helper);
1379
1717
  await schemaProvider.init();
1380
1718
  const collector = new Collector(schemaProvider.getSchema(), helper);
1719
+ const moduleContext = new ModuleContext(schemaProvider, collector);
1720
+ nuxt._nuxt_graphql_middleware = moduleContext;
1381
1721
  nuxt.options.appConfig.graphqlMiddleware = {
1382
1722
  clientCacheEnabled: !!helper.options.clientCache?.enabled,
1383
1723
  clientCacheMaxSize: helper.options.clientCache?.maxSize ?? 100
@@ -1391,9 +1731,9 @@ const module = defineNuxtModule({
1391
1731
  helper.inlineNitroExternals(helper.paths.moduleTypesDir);
1392
1732
  helper.addAlias("#nuxt-graphql-middleware", helper.paths.moduleBuildDir);
1393
1733
  helper.addAlias("#graphql-operations", helper.paths.moduleTypesDir);
1394
- helper.addPlugin("./runtime/plugins/provideState");
1734
+ helper.addPlugin("provideState");
1395
1735
  if (helper.isDev && helper.options.errorOverlay) {
1396
- helper.addPlugin("./runtime/plugins/devMode");
1736
+ helper.addPlugin("devMode");
1397
1737
  }
1398
1738
  helper.addServerHandler("query", "/query/:name", "get");
1399
1739
  helper.addServerHandler("mutation", "/mutation/:name", "post");
@@ -1423,7 +1763,10 @@ const module = defineNuxtModule({
1423
1763
  }
1424
1764
  });
1425
1765
  helper.applyBuildConfig();
1426
- await collector.init();
1766
+ nuxt.hooks.hookOnce("modules:done", async () => {
1767
+ await nuxt.hooks.callHook("nuxt-graphql-middleware:init", moduleContext);
1768
+ await collector.init();
1769
+ });
1427
1770
  if (!helper.isDev) {
1428
1771
  return;
1429
1772
  }