@lewebsimple/nuxt-graphql 0.5.4 → 0.5.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.
package/dist/module.d.mts CHANGED
@@ -1,6 +1,5 @@
1
1
  import * as _nuxt_schema from '@nuxt/schema';
2
2
  import { HeadersInput } from '../dist/runtime/shared/lib/headers.js';
3
- import { CacheConfig } from '../dist/runtime/shared/lib/cache-config.js';
4
3
 
5
4
  interface LocalSchemaDef {
6
5
  type: "local";
@@ -39,7 +38,7 @@ interface NuxtGraphQLModuleOptions {
39
38
  /**
40
39
  * Global cache configuration for queries.
41
40
  */
42
- cache?: Partial<CacheConfig>;
41
+ cache?: Partial<GraphQLCacheConfig>;
43
42
  /**
44
43
  * GraphQL documents glob pattern.
45
44
  * Default: "**\/*.gql"
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-graphql",
3
3
  "configKey": "graphql",
4
- "version": "0.5.4",
4
+ "version": "0.5.6",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -1,89 +1,49 @@
1
1
  import { mkdirSync, writeFileSync } from 'node:fs';
2
2
  import { dirname, relative, resolve } from 'node:path';
3
- import { defineNuxtModule, useLogger, createResolver, addTemplate, addServerTemplate, addTypeTemplate, addServerHandler, addPlugin, addImportsDir, addServerImportsDir } from '@nuxt/kit';
4
3
  import { defu } from 'defu';
5
- import { hash } from 'ohash';
6
- import { loadDocuments as loadDocuments$1 } from '@graphql-tools/load';
7
4
  import { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader';
8
- import { Kind, getIntrospectionQuery, buildClientSchema, GraphQLSchema } from 'graphql';
5
+ import { loadDocuments } from '@graphql-tools/load';
6
+ import { stitchSchemas } from '@graphql-tools/stitch';
7
+ import { defineNuxtModule, useLogger, createResolver, addTemplate, addServerTemplate, addTypeTemplate, addServerHandler, addPlugin, addImportsDir, addServerImportsDir } from '@nuxt/kit';
9
8
  import { codegen } from '@graphql-codegen/core';
10
9
  import * as typescriptPlugin from '@graphql-codegen/typescript';
11
10
  import * as typescriptOperationsPlugin from '@graphql-codegen/typescript-operations';
12
11
  import * as typedDocumentNodePlugin from '@graphql-codegen/typed-document-node';
12
+ import { Kind, getIntrospectionQuery, buildClientSchema, GraphQLSchema } from 'graphql';
13
13
  import { mergeHeaders } from '../dist/runtime/shared/lib/headers.js';
14
- import { stitchSchemas } from '@graphql-tools/stitch';
15
- import { resolveCacheConfig } from '../dist/runtime/shared/lib/cache-config.js';
14
+ import { resolveCacheConfig } from '../dist/runtime/shared/lib/cache.js';
15
+ import { hash } from 'ohash';
16
16
 
17
17
  const cyan = "\x1B[36m";
18
18
  const reset = "\x1B[0m";
19
19
 
20
20
  function renderContextTemplate({ contextModules }) {
21
- const imports = contextModules.map((module, index) => `import context${index} from ${JSON.stringify(module)};`);
22
- const types = ["{}", ...contextModules.map((_, index) => `Awaited<ReturnType<typeof context${index}>>`)];
23
- return [
24
- `import type { H3Event } from "h3";`,
25
- ...imports,
26
- "",
27
- `export type GraphQLContext = ${types.join(" & ")};`,
28
- "",
29
- "export async function createContext(event: H3Event): Promise<GraphQLContext> {",
30
- " const parts = await Promise.all([",
31
- " () => ({}),",
32
- ...contextModules.map((_, index) => ` context${index}(event),`),
33
- " ]);",
34
- " return Object.assign({}, ...parts) as GraphQLContext;",
35
- "}"
36
- ].join("\n");
37
- }
21
+ const contextImports = contextModules.map((contextModule, index) => `import { createContext as createContext${index} } from '${contextModule}';`);
22
+ const contexts = contextModules.map((_, index) => `createContext${index}(event)`);
23
+ return `
24
+ ${contextImports.join("\n")}
38
25
 
39
- async function loadDocuments(documents) {
40
- try {
41
- return await loadDocuments$1([
42
- documents,
43
- "!**/.cache/**",
44
- "!**/.nuxt/**",
45
- "!**/.output/**",
46
- "!**/dist/**",
47
- "!**/node_modules/**"
48
- ], { loaders: [new GraphQLFileLoader()] });
49
- } catch {
50
- return [];
51
- }
26
+ export async function createContext(event) {
27
+ const parts = await Promise.all([${contexts.join(", ")}]);
28
+ return Object.assign({}, ...parts);
29
+ }`.trim();
52
30
  }
31
+ function renderContextTypesTemplate({ contextModules }) {
32
+ const contextImports = contextModules.map((module, index) => `import createContext${index} from ${JSON.stringify(module)};`);
33
+ const contextTypes = ["{}", ...contextModules.map((_, index) => `Awaited<ReturnType<typeof createContext${index}>>`)];
34
+ return `
35
+ import type { H3Event } from "h3";
36
+ ${contextImports.join("\n")}
53
37
 
54
- async function renderFragmentsTemplate({ documents }) {
55
- const fragments = collectFragments(documents);
56
- if (fragments.length === 0) {
57
- return `export { }`;
58
- }
59
- return [
60
- `export type {`,
61
- ...fragments.map((name) => ` ${name}Fragment,`),
62
- `} from "./operations";`
63
- ].join("\n");
64
- }
65
- function collectFragments(documents) {
66
- const fragments = /* @__PURE__ */ new Set();
67
- for (const source of documents) {
68
- const doc = source.document;
69
- if (!doc) continue;
70
- for (const def of doc.definitions) {
71
- if (def.kind !== Kind.FRAGMENT_DEFINITION) continue;
72
- const name = def.name?.value;
73
- if (!name) continue;
74
- if (fragments.has(name)) {
75
- throw new Error(`Duplicate GraphQL fragment name "${name}"`);
76
- }
77
- fragments.add(name);
78
- }
79
- }
80
- return [...fragments.values()];
38
+ declare module "#graphql/context" {
39
+ export type GraphQLContext = ${contextTypes.join(" & ")};
40
+ export function createContext(event: H3Event): Promise<GraphQLContext>;
41
+ }`.trim();
81
42
  }
82
43
 
83
44
  async function renderOperationsTemplate({ schema, documents }) {
84
- const output = await codegen({
45
+ const content = await codegen({
85
46
  filename: "operations.ts",
86
- // @graphql-codegen/core codegen supports GraphQLSchema at runtime, but types expect DocumentNode
87
47
  schema,
88
48
  documents,
89
49
  plugins: [
@@ -133,51 +93,72 @@ async function renderOperationsTemplate({ schema, documents }) {
133
93
  },
134
94
  config: {}
135
95
  });
136
- return output;
96
+ const docs = splitDocuments(content);
97
+ const module = docs.map(({ name, object }) => `export const ${name} = ${object};`).join("\n");
98
+ const types = `${content.replace(/export const \w+ = [\s\S]*?;\n?/g, "")}
99
+ declare module "#graphql/operations" {
100
+ ${docs.map(({ name, type }) => `export type ${name} = ${type};`).join("\n ")}
101
+ }`.trim();
102
+ return { module, types };
103
+ }
104
+ function splitDocuments(content) {
105
+ const documents = [];
106
+ const documentRegex = /export const (\w+) = ([\s\S]*?) as unknown as (DocumentNode<[^>]+>);/g;
107
+ let match;
108
+ while (match = documentRegex.exec(content)) {
109
+ const [, name, object, type] = match;
110
+ if (!name || !object || !type) {
111
+ throw new Error("Invalid typed document matched while splitting documents.");
112
+ }
113
+ documents.push({ name, object: object.trim(), type });
114
+ }
115
+ return documents;
137
116
  }
138
117
 
139
118
  async function renderRegistryTemplate({ documents }) {
140
119
  const operations = collectOperations(documents);
141
- return [
142
- `import type { DocumentNode } from "graphql";`,
143
- `import {`,
144
- ...operations.map(
145
- ({ name }) => ` ${name}Document, type ${name}QueryVariables, type ${name}QueryResult,`
146
- ),
147
- `} from "./operations";`,
148
- ``,
149
- // Operation entry
150
- `export interface OperationEntry<TVariables, TResult, TKind extends "query" | "mutation" | "subscription"> {`,
151
- ` kind: TKind;`,
152
- ` variables: TVariables;`,
153
- ` result: TResult;`,
154
- ` document: DocumentNode;`,
155
- `}`,
156
- ``,
157
- // Operation registry type
158
- `export type OperationRegistry = {`,
159
- ...operations.map(
160
- ({ name, kind }) => ` ${name}: OperationEntry<${name}QueryVariables, ${name}QueryResult, "${kind}">;`
161
- ),
162
- `};`,
163
- ``,
164
- // Operation name types
165
- `export type QueryName = { [K in keyof OperationRegistry]: OperationRegistry[K]["kind"] extends "query" ? K : never }[keyof OperationRegistry];`,
166
- `export type MutationName = { [K in keyof OperationRegistry]: OperationRegistry[K]["kind"] extends "mutation" ? K : never }[keyof OperationRegistry];`,
167
- `export type SubscriptionName = { [K in keyof OperationRegistry]: OperationRegistry[K]["kind"] extends "subscription" ? K : never }[keyof OperationRegistry];`,
168
- ``,
169
- // Projection helpers (variables / result)
170
- `export type VariablesOf<TName extends keyof OperationRegistry> = OperationRegistry[TName]["variables"];`,
171
- `export type ResultOf<TName extends keyof OperationRegistry> = OperationRegistry[TName]["result"];`,
172
- ``,
173
- // Runtime registry (document + kind only)
174
- `export const registry: { [K in keyof OperationRegistry]: { kind: OperationRegistry[K]["kind"]; document: DocumentNode; }; } = {`,
175
- ...operations.map(
176
- ({ name, kind }) => ` ${name}: { kind: "${kind}", document: ${name}Document },`
177
- ),
178
- `};`,
179
- ``
180
- ].join("\n");
120
+ const module = `
121
+ import {
122
+ ${operations.map(({ name }) => `${name}Document`).join(",\n ")}
123
+ } from "./operations";
124
+
125
+ export const registry = {
126
+ ${operations.map(({ name, kind }) => `${name}: { kind: "${kind}", document: ${name}Document },`).join("\n ")}
127
+ };`.trim();
128
+ const types = `
129
+ import type { DocumentNode } from "graphql";
130
+ import type {
131
+ ${operations.map(({ name, kind }) => `${name}${capitalize(kind)}Variables, ${name}${capitalize(kind)}Result,`).join("\n ")}
132
+ } from "./operations";
133
+
134
+ // Operation entry
135
+ export interface OperationEntry<TVariables, TResult, TKind extends "query" | "mutation" | "subscription"> {
136
+ kind: TKind;
137
+ variables: TVariables;
138
+ result: TResult;
139
+ document: DocumentNode;
140
+ }
141
+
142
+ // Operation registry type
143
+ export type OperationRegistry = {
144
+ ${operations.map(({ name, kind }) => `${name}: OperationEntry<${name}${capitalize(kind)}Variables, ${name}${capitalize(kind)}Result, "${kind}">;`).join("\n ")}
145
+ };
146
+
147
+ // Operation name types
148
+ export type OperationName = keyof OperationRegistry;
149
+ export type QueryName = { [K in keyof OperationRegistry]: OperationRegistry[K]["kind"] extends "query" ? K : never }[keyof OperationRegistry];
150
+ export type MutationName = { [K in keyof OperationRegistry]: OperationRegistry[K]["kind"] extends "mutation" ? K : never }[keyof OperationRegistry];
151
+ export type SubscriptionName = { [K in keyof OperationRegistry]: OperationRegistry[K]["kind"] extends "subscription" ? K : never }[keyof OperationRegistry];
152
+
153
+ // Projection helpers (variables / result)
154
+ export type VariablesOf<TName extends keyof OperationRegistry> = OperationRegistry[TName]["variables"];
155
+ export type ResultOf<TName extends keyof OperationRegistry> = OperationRegistry[TName]["result"];
156
+
157
+ declare module "#graphql/registry" {
158
+ export const registry: { [K in keyof OperationRegistry]: { kind: OperationRegistry[K]["kind"]; document: DocumentNode; }; };
159
+ export type { OperationRegistry, OperationName, QueryName, MutationName, SubscriptionName, VariablesOf, ResultOf };
160
+ }`.trim();
161
+ return { module, types };
181
162
  }
182
163
  function collectOperations(documents) {
183
164
  const operations = /* @__PURE__ */ new Map();
@@ -193,50 +174,71 @@ function collectOperations(documents) {
193
174
  if (operations.has(name)) {
194
175
  throw new Error(`Duplicate GraphQL operation name "${name}"`);
195
176
  }
196
- operations.set(name, {
197
- name,
198
- kind: def.operation
199
- });
177
+ operations.set(name, { name, kind: def.operation });
200
178
  }
201
179
  }
202
- return [...operations.values()];
180
+ return Array.from(operations.values());
181
+ }
182
+ function capitalize(value) {
183
+ if (!value) return value;
184
+ return value.charAt(0).toUpperCase() + value.slice(1);
185
+ }
186
+
187
+ function renderLocalSchemaTemplate({ schemaModule }) {
188
+ return `export { schema } from ${JSON.stringify(schemaModule)};`;
189
+ }
190
+ async function renderRemoteSchemaTemplate({ remoteExecutorModule, hooksModules, schemaDef, schemaLoader }) {
191
+ const importHooks = hooksModules.map((hookPath, index) => `import hooks${index} from ${JSON.stringify(hookPath)};`);
192
+ const hooks = hooksModules.map((_, index) => `hooks${index}`);
193
+ const { url, headers } = schemaDef;
194
+ const schema = await schemaLoader();
195
+ const sdl = await printSchemaSDL(schema);
196
+ return `
197
+ import { buildSchema } from "graphql";
198
+ import { createRemoteExecutor } from ${JSON.stringify(remoteExecutorModule)};
199
+ ${importHooks.join("\n")}
200
+
201
+ const sdl = /* GraphQL */ \`${sdl.replace(/`/g, "\\`")}\`;
202
+
203
+ export const schema = {
204
+ schema: buildSchema(sdl),
205
+ executor: createRemoteExecutor({
206
+ url: ${JSON.stringify(url)},
207
+ headers: ${JSON.stringify(headers || {})},
208
+ hooks: [${hooks.join(", ")}]
209
+ }),
210
+ };
211
+ `.trim();
212
+ }
213
+ function renderSchemaTemplate({ schemaNames }) {
214
+ const importSchemas = schemaNames.map((name) => `import { schema as ${name}Schema } from ${JSON.stringify(`./schemas/${name}`)};`);
215
+ const schemas = schemaNames.map((name) => `${name}Schema`);
216
+ return `
217
+ import { stitchSchemas } from "@graphql-tools/stitch";
218
+ ${importSchemas.join("\n")}
219
+
220
+ export const schema = stitchSchemas({
221
+ subschemas: [${schemas.join(", ")}],
222
+ });`.trim();
203
223
  }
224
+ function renderSchemaTypesTemplate() {
225
+ return `
226
+ import type { GraphQLSchema } from "graphql";
204
227
 
205
- function renderLocalSchemaTemplate({ path }) {
206
- return `export { schema } from ${JSON.stringify(path)};`;
228
+ declare module "#graphql/schema" {
229
+ export const schema: GraphQLSchema;
230
+ }
231
+ `.trim();
207
232
  }
208
- async function loadLocalSchema({ path }) {
233
+ async function loadLocalSchema({ schemaModule }) {
209
234
  const { createJiti } = await import('jiti');
210
235
  const jiti = createJiti(import.meta.url, { interopDefault: true });
211
- const module = await jiti.import(path);
236
+ const module = await jiti.import(schemaModule);
212
237
  if (!module.schema || !(module.schema instanceof Object) || typeof module.schema.getQueryType !== "function") {
213
- throw new Error(`${path} must export a valid 'schema' of type GraphQLSchema.`);
238
+ throw new Error(`${schemaModule} must export a valid 'schema' of type GraphQLSchema.`);
214
239
  }
215
240
  return module.schema;
216
241
  }
217
- async function renderRemoteSchemaTemplate({ remoteExecutorModule, type, hooks, ...schemaDef }) {
218
- const schema = await introspectRemoteSchema(schemaDef);
219
- const sdl = await printSchemaSDL(schema);
220
- const imports = (hooks || []).map((hookPath, index) => `import hooks${index} from ${JSON.stringify(hookPath)};`);
221
- return [
222
- `import { buildSchema } from "graphql";`,
223
- `import type { SubschemaConfig } from "@graphql-tools/delegate";`,
224
- `import { createRemoteExecutor } from ${JSON.stringify(remoteExecutorModule)};`,
225
- ...imports,
226
- ``,
227
- `const sdl = /* GraphQL */ \`${sdl.replace(/`/g, "\\`")}\`;`,
228
- ``,
229
- `export const schema: SubschemaConfig = {`,
230
- ` schema: buildSchema(sdl),`,
231
- ` executor: createRemoteExecutor({`,
232
- ` ...${JSON.stringify(schemaDef)},`,
233
- ` hooks: [`,
234
- ...(hooks || []).map((_, index) => ` hooks${index},`),
235
- ` ]`,
236
- ` }),`,
237
- `};`
238
- ].join("\n");
239
- }
240
242
  async function introspectRemoteSchema({ url, headers }) {
241
243
  const response = await fetch(url, {
242
244
  method: "POST",
@@ -266,34 +268,9 @@ async function printSchemaSDL(schema) {
266
268
  const { printSchema, lexicographicSortSchema } = await import('graphql');
267
269
  return printSchema(lexicographicSortSchema(schema));
268
270
  }
269
- function renderStitchedSchemaTemplate({ schemaNames }) {
270
- return [
271
- `import type { GraphQLSchema } from "graphql"; `,
272
- `import type { SubschemaConfig } from "@graphql-tools/delegate"; `,
273
- `import { stitchSchemas } from "@graphql-tools/stitch"; `,
274
- ...schemaNames.map((name) => `import { schema as ${name}Schema } from ${JSON.stringify(`./schemas/${name}`)}; `),
275
- ``,
276
- `const subschemas: Array<GraphQLSchema | SubschemaConfig> = [`,
277
- ...schemaNames.map((name) => ` ${name}Schema, `),
278
- `]; `,
279
- ``,
280
- `export const schema = stitchSchemas({ subschemas }); `
281
- ].join("\n");
282
- }
283
- async function loadStitchedSchema(schemaDefs) {
284
- const subschemas = [];
285
- for (const schemaDef of Object.values(schemaDefs)) {
286
- if (schemaDef.type === "local") {
287
- subschemas.push(await loadLocalSchema(schemaDef));
288
- } else if (schemaDef.type === "remote") {
289
- subschemas.push(await introspectRemoteSchema(schemaDef));
290
- }
291
- }
292
- return stitchSchemas({ subschemas });
293
- }
294
271
 
295
272
  function renderAppTypesTemplate() {
296
- return `// Nuxt GraphQL types (app)
273
+ return `
297
274
  import type { GraphQLClient } from "graphql-request";
298
275
  import type { Client as SSEClient } from "graphql-sse";
299
276
 
@@ -312,20 +289,10 @@ declare module "#app" {
312
289
  }
313
290
 
314
291
  export {};
315
- `;
292
+ `.trim();
316
293
  }
317
294
  function renderServerTypesTemplate() {
318
- return `// Nuxt GraphQL types (server)
319
- import type { GraphQLSchema } from "graphql";
320
-
321
- declare module "#graphql/context" {
322
- export type { GraphQLContext };
323
- }
324
-
325
- declare module "#graphql/schema" {
326
- export const schema: GraphQLSchema;
327
- }
328
-
295
+ return `
329
296
  declare module "h3" {
330
297
  interface H3EventContext {
331
298
  _graphqlInFlightRequestsMap?: Map<string, Promise<unknown>>;
@@ -333,24 +300,34 @@ declare module "h3" {
333
300
  }
334
301
 
335
302
  export {};
336
- `;
303
+ `.trim();
337
304
  }
338
305
  function renderSharedTypesTemplate() {
339
- return `// Nuxt GraphQL types (shared)
306
+ return `
340
307
  import type { DocumentNode } from "graphql";
341
308
  import type { CacheConfig } from "nuxt-graphql/runtime/shared/lib/cache-config";
342
309
 
310
+ declare global {
311
+ type GraphQLCacheConfig = {
312
+ policy: "no-cache" | "cache-first" | "network-first" | "swr";
313
+ ttl?: number;
314
+ keyPrefix: string;
315
+ keyVersion: string | number;
316
+ };;
317
+ }
318
+
343
319
  declare module "nuxt/schema" {
344
320
  interface PublicRuntimeConfig {
345
321
  graphql: {
346
- cacheConfig?: CacheConfig;
322
+ cacheConfig: GraphQLCacheConfig;
347
323
  ssrForwardHeaders: string[];
348
324
  };
349
325
  }
350
326
  }
351
327
 
352
- export {};
353
- `;
328
+ export { };
329
+
330
+ `.trim();
354
331
  }
355
332
 
356
333
  const module$1 = defineNuxtModule({
@@ -384,87 +361,107 @@ const module$1 = defineNuxtModule({
384
361
  }
385
362
  nuxt.options.alias ||= {};
386
363
  nuxt.options.alias["#graphql"] ||= resolveBuild("graphql");
387
- const contextModules = [
388
- ...await Promise.all((options.yoga?.context || []).map((path) => resolveRootPath(path, true)))
389
- ];
390
- addTemplate({ filename: "graphql/context.ts", getContents: () => renderContextTemplate({ contextModules }), write: true });
391
- addServerTemplate({ filename: "#graphql/#context.ts", getContents: () => renderContextTemplate({ contextModules }) });
392
- const schemaDefs = {};
364
+ const buildCache = /* @__PURE__ */ new Map();
365
+ function cachedLoader(baseKey, loader) {
366
+ return async (...args) => {
367
+ const key = `${baseKey}:${hash(args)}`;
368
+ const cached = buildCache.get(key);
369
+ if (cached?.key === key) {
370
+ return cached.data;
371
+ }
372
+ const data = await loader(...args);
373
+ buildCache.set(key, { key, data });
374
+ return data;
375
+ };
376
+ }
377
+ const contextModules = await Promise.all((options.yoga?.context || []).map((path) => resolveRootPath(path, true)));
378
+ addTemplate({ filename: "graphql/context.mjs", getContents: () => renderContextTemplate({ contextModules }), write: true });
379
+ addTemplate({ filename: "graphql/context.d.ts", getContents: () => renderContextTypesTemplate({ contextModules }), write: true });
380
+ addServerTemplate({ filename: "#graphql/context.mjs", getContents: () => renderContextTemplate({ contextModules }) });
381
+ const remoteExecutorModule = resolveModule("./runtime/server/lib/remote-executor");
382
+ const schemaCachedLoaders = {};
393
383
  for (const [schemaName, schemaDef] of Object.entries(options.yoga?.schemas || {})) {
394
384
  if (schemaDef.type === "local") {
395
- const localSchemaDef = {
396
- ...schemaDef,
397
- path: await resolveRootPath(schemaDef.path, true)
398
- };
399
- schemaDefs[schemaName] = localSchemaDef;
400
- addTemplate({ filename: `graphql/schemas/${schemaName}.ts`, getContents: async () => renderLocalSchemaTemplate({ ...localSchemaDef }), write: true });
401
- addServerTemplate({ filename: `#graphql/schemas/${schemaName}.ts`, getContents: async () => renderLocalSchemaTemplate({ ...localSchemaDef }) });
385
+ const schemaModule = await resolveRootPath(schemaDef.path, true);
386
+ schemaCachedLoaders[schemaName] = cachedLoader(`schema:local:${schemaName}`, async () => await loadLocalSchema({ schemaModule }));
387
+ addTemplate({ filename: `graphql/schemas/${schemaName}.mjs`, getContents: async () => renderLocalSchemaTemplate({ schemaModule }), write: true });
388
+ addServerTemplate({ filename: `#graphql/schemas/${schemaName}.mjs`, getContents: async () => renderLocalSchemaTemplate({ schemaModule }) });
402
389
  } else if (schemaDef.type === "remote") {
403
- const remoteSchemaDef = {
404
- ...schemaDef,
405
- hooks: await Promise.all((schemaDef.hooks || []).map((hookPath) => resolveRootPath(hookPath, true))),
406
- remoteExecutorModule: resolveModule("./runtime/server/lib/remote-executor")
390
+ schemaCachedLoaders[schemaName] = cachedLoader(`schema:remote:${schemaName}`, async () => await introspectRemoteSchema(schemaDef));
391
+ const input = {
392
+ remoteExecutorModule,
393
+ hooksModules: await Promise.all((schemaDef.hooks || []).map((hookPath) => resolveRootPath(hookPath, true))),
394
+ schemaDef,
395
+ schemaLoader: schemaCachedLoaders[schemaName]
407
396
  };
408
- schemaDefs[schemaName] = remoteSchemaDef;
409
- addTemplate({ filename: `graphql/schemas/${schemaName}.ts`, getContents: async () => await renderRemoteSchemaTemplate({ ...remoteSchemaDef }), write: true });
410
- addServerTemplate({ filename: `#graphql/schemas/${schemaName}.ts`, getContents: async () => await renderRemoteSchemaTemplate({ ...remoteSchemaDef }) });
397
+ addTemplate({ filename: `graphql/schemas/${schemaName}.mjs`, getContents: async () => await renderRemoteSchemaTemplate(input), write: true });
398
+ addServerTemplate({ filename: `#graphql/schemas/${schemaName}.mjs`, getContents: async () => await renderRemoteSchemaTemplate(input) });
411
399
  } else {
412
400
  throw new Error(`Unknown schema type for schema "${schemaName}"`);
413
401
  }
414
402
  }
415
- addTemplate({ filename: "graphql/schema.ts", getContents: () => renderStitchedSchemaTemplate({ schemaNames: Object.keys(options.yoga?.schemas || {}) }), write: true });
416
- addServerTemplate({ filename: "#graphql/schema.ts", getContents: () => renderStitchedSchemaTemplate({ schemaNames: Object.keys(options.yoga?.schemas || {}) }) });
417
- let documentsCache = null;
418
- async function getDocuments(glob) {
419
- const key = `documents:${glob}`;
420
- if (documentsCache?.key === key) return documentsCache.data;
421
- const documents = await loadDocuments(glob);
422
- documentsCache = { key, data: documents };
423
- return documents;
424
- }
425
403
  const sdlPath = resolveRoot(options.saveSDL || "server/graphql/schema.graphql");
426
- let schemaCache = null;
427
- async function getStitchedSchema(schemaDefs2) {
428
- const key = `schema:${hash(schemaDefs2)}`;
429
- if (schemaCache?.key === key) return schemaCache.data;
430
- const schema = await loadStitchedSchema(schemaDefs2);
431
- schemaCache = { key, data: schema };
404
+ const loadCachedSchema = cachedLoader("schema:stitched", async () => {
405
+ const schema = stitchSchemas({
406
+ subschemas: await Promise.all(Object.values(schemaCachedLoaders).map((loader) => loader()))
407
+ });
432
408
  const sdl = await printSchemaSDL(schema);
433
409
  mkdirSync(dirname(sdlPath), { recursive: true });
434
410
  writeFileSync(sdlPath, sdl, { encoding: "utf-8" });
435
411
  logger.info(`GraphQL SDL saved to: ${cyan}${getRelativePath(sdlPath)}${reset}`);
436
412
  return schema;
437
- }
438
- addTemplate({
439
- filename: "graphql/operations.ts",
440
- getContents: async () => await renderOperationsTemplate({
441
- schema: await getStitchedSchema(schemaDefs),
442
- documents: await getDocuments(options.client?.documents || "**/*.gql")
443
- }),
444
- write: true
413
+ });
414
+ addTemplate({ filename: "graphql/schema.mjs", getContents: () => renderSchemaTemplate({ schemaNames: Object.keys(options.yoga?.schemas || {}) }), write: true });
415
+ addTemplate({ filename: "graphql/schema.d.ts", getContents: () => renderSchemaTypesTemplate(), write: true });
416
+ addServerTemplate({ filename: "#graphql/schema.mjs", getContents: () => renderSchemaTemplate({ schemaNames: Object.keys(options.yoga?.schemas || {}) }) });
417
+ const loadCachedDocuments = cachedLoader("documents", async (documentsGlob) => {
418
+ try {
419
+ return await loadDocuments([
420
+ documentsGlob,
421
+ "!**/.cache/**",
422
+ "!**/.nuxt/**",
423
+ "!**/.output/**",
424
+ "!**/dist/**",
425
+ "!**/node_modules/**"
426
+ ], { loaders: [new GraphQLFileLoader()] });
427
+ } catch {
428
+ return [];
429
+ }
430
+ });
431
+ const loadCachedOperations = cachedLoader("operations", async (documentsGlob) => {
432
+ const schema = await loadCachedSchema();
433
+ const documents = await loadCachedDocuments(documentsGlob);
434
+ return await renderOperationsTemplate({ schema, documents });
435
+ });
436
+ addTemplate({ filename: "graphql/operations.mjs", getContents: async () => (await loadCachedOperations(options.client?.documents || "**/*.gql")).module, write: true });
437
+ addServerTemplate({ filename: "#graphql/operations.mjs", getContents: async () => (await loadCachedOperations(options.client?.documents || "**/*.gql")).module });
438
+ addTemplate({ filename: "graphql/operations.d.ts", getContents: async () => (await loadCachedOperations(options.client?.documents || "**/*.gql")).types, write: true });
439
+ const loadCachedRegistry = cachedLoader("registry", async (documentsGlob) => {
440
+ const documents = await loadCachedDocuments(documentsGlob);
441
+ return await renderRegistryTemplate({ documents });
445
442
  });
446
443
  addTemplate({
447
- filename: "graphql/fragments.ts",
448
- getContents: async () => await renderFragmentsTemplate({
449
- documents: await getDocuments(options.client?.documents || "**/*.gql")
450
- }),
444
+ filename: "graphql/registry.mjs",
445
+ getContents: async () => (await loadCachedRegistry(options.client?.documents || "**/*.gql")).module,
451
446
  write: true
452
447
  });
448
+ addServerTemplate({
449
+ filename: "#graphql/registry.mjs",
450
+ getContents: async () => (await loadCachedRegistry(options.client?.documents || "**/*.gql")).module
451
+ });
453
452
  addTemplate({
454
- filename: "graphql/registry.ts",
455
- getContents: async () => await renderRegistryTemplate({
456
- documents: await getDocuments(options.client?.documents || "**/*.gql")
457
- }),
453
+ filename: "graphql/registry.d.ts",
454
+ getContents: async () => (await loadCachedRegistry(options.client?.documents || "**/*.gql")).types,
458
455
  write: true
459
456
  });
460
- addTypeTemplate({ filename: "types/nuxt-graphql.app.d.ts", getContents: () => renderAppTypesTemplate() }, { nuxt: true });
461
- addTypeTemplate({ filename: "types/nuxt-graphql.server.d.ts", getContents: () => renderServerTypesTemplate() }, { nitro: true });
462
- addTypeTemplate({ filename: "types/nuxt-graphql.shared.d.ts", getContents: () => renderSharedTypesTemplate() }, { nuxt: true, nitro: true });
463
457
  const configPath = resolveRoot(options.saveConfig || "graphql.config.json");
464
458
  const config = { schema: getRelativePath(sdlPath), documents: options.client?.documents || "**/*.gql" };
465
459
  mkdirSync(dirname(configPath), { recursive: true });
466
460
  writeFileSync(configPath, JSON.stringify(config, null, 2), { encoding: "utf-8" });
467
461
  logger.info(`GraphQL config saved to: ${cyan}${getRelativePath(configPath)}${reset}`);
462
+ addTypeTemplate({ filename: "types/nuxt-graphql.app.d.ts", getContents: () => renderAppTypesTemplate() }, { nuxt: true });
463
+ addTypeTemplate({ filename: "types/nuxt-graphql.server.d.ts", getContents: () => renderServerTypesTemplate() }, { nitro: true, node: true });
464
+ addTypeTemplate({ filename: "types/nuxt-graphql.shared.d.ts", getContents: () => renderSharedTypesTemplate() }, { nuxt: true, nitro: true, node: true });
468
465
  nuxt.options.runtimeConfig.public.graphql = defu(nuxt.options.runtimeConfig.public.graphql, {
469
466
  cacheConfig: resolveCacheConfig(options.client?.cache),
470
467
  ssrForwardHeaders: options.client?.ssrForwardHeaders || ["authorization", "cookie"]
@@ -473,7 +470,8 @@ const module$1 = defineNuxtModule({
473
470
  nuxt.hook("builder:watch", async (_event, changedPath) => {
474
471
  if (changedPath.endsWith(".gql")) {
475
472
  logger.info(`Documents change detected: ${cyan}${getRelativePath(changedPath)}${reset}`);
476
- documentsCache = null;
473
+ buildCache.delete("documents");
474
+ buildCache.delete("operations");
477
475
  }
478
476
  });
479
477
  }
@@ -2,10 +2,9 @@ import { useAsyncData, type AsyncDataOptions } from "#app";
2
2
  import type { QueryName, ResultOf, VariablesOf } from "#graphql/registry";
3
3
  import { type MaybeRefOrGetter } from "#imports";
4
4
  import { type ExecuteGraphQLHTTPOptions } from "../lib/execute-http.js";
5
- import { type CacheConfig } from "../../shared/lib/cache-config.js";
6
5
  import type { IsEmptyObject } from "../../shared/lib/utils.js";
7
6
  type UseAsyncGraphQLQueryOptions<TName extends QueryName> = ExecuteGraphQLHTTPOptions & {
8
- cache?: Partial<CacheConfig>;
7
+ cache?: Partial<GraphQLCacheConfig>;
9
8
  } & AsyncDataOptions<ResultOf<TName>>;
10
9
  /**
11
10
  * Async GraphQL query composable with caching support.
@@ -1,10 +1,9 @@
1
1
  import { useAsyncData, useNuxtData, useRuntimeConfig } from "#app";
2
2
  import { computed, toValue } from "#imports";
3
- import { getCacheKeyParts } from "../lib/cache.js";
4
3
  import { executeGraphQLHTTP } from "../lib/execute-http.js";
5
4
  import { getInFlightRequests } from "../lib/in-flight.js";
6
5
  import { getPersistedEntry, setPersistedEntry } from "../lib/persisted.js";
7
- import { resolveCacheConfig } from "../../shared/lib/cache-config.js";
6
+ import { getCacheKeyParts, resolveCacheConfig } from "../../shared/lib/cache.js";
8
7
  export function useAsyncGraphQLQuery(operationName, ...args) {
9
8
  const [variables, options] = args;
10
9
  const isClient = import.meta.client;
@@ -1,5 +1,5 @@
1
1
  import { clearNuxtData, useRuntimeConfig } from "#imports";
2
- import { getCacheKeyParts } from "../lib/cache.js";
2
+ import { getCacheKeyParts } from "../../shared/lib/cache.js";
3
3
  import { deletePersistedByPrefix, deletePersistedEntry } from "../lib/persisted.js";
4
4
  export function useGraphQLCache() {
5
5
  const { public: { graphql: { cacheConfig } } } = useRuntimeConfig();
@@ -1,9 +1,7 @@
1
1
  import type { Executor } from "@graphql-tools/utils";
2
- import { type HeadersInput } from "../../shared/lib/headers.js";
3
2
  import type { GraphQLRemoteExecHooks } from "../utils/defineRemoteExecutorHooks.js";
4
- type CreateRemoteExecutorInput = {
5
- url: string;
6
- headers?: HeadersInput;
3
+ import type { RemoteSchemaDef } from "../../../lib/schemas.js";
4
+ type CreateRemoteExecutorInput = Pick<RemoteSchemaDef, "url" | "headers"> & {
7
5
  hooks: GraphQLRemoteExecHooks[];
8
6
  };
9
7
  /**
@@ -1,3 +1,3 @@
1
1
  {
2
- "extends": "../../../.nuxt/tsconfig.server.json",
3
- }
2
+ "extends": "../../../.nuxt/tsconfig.server.json"
3
+ }
@@ -1,4 +1,10 @@
1
- import type { CacheConfig } from "../../shared/lib/cache-config.js";
1
+ /**
2
+ * Merge the default cache config with user overrides.
3
+ *
4
+ * @param overrides Partial cache config overrides.
5
+ * @returns Resolved cache configuration.
6
+ */
7
+ export declare function resolveCacheConfig(...overrides: Array<Partial<GraphQLCacheConfig> | undefined>): GraphQLCacheConfig;
2
8
  type CacheKeyParts = {
3
9
  key: string;
4
10
  opPrefix: string;
@@ -6,7 +12,7 @@ type CacheKeyParts = {
6
12
  /**
7
13
  * Build cache key parts from config, operation name, and variables.
8
14
  *
9
- * @param {CacheConfig} options Cache configuration.
15
+ * @param {GraphQLCacheConfig} options Cache configuration.
10
16
  * @param options.keyPrefix Cache key prefix.
11
17
  * @param options.keyVersion Cache key version.
12
18
  * @param operationName Operation name.
@@ -14,5 +20,5 @@ type CacheKeyParts = {
14
20
  * @param scope Optional cache scope segment.
15
21
  * @returns Key parts including full key and operation prefix.
16
22
  */
17
- export declare function getCacheKeyParts({ keyPrefix, keyVersion }: CacheConfig, operationName: string, variables: unknown, scope?: string): CacheKeyParts;
23
+ export declare function getCacheKeyParts({ keyPrefix, keyVersion }: GraphQLCacheConfig, operationName: string, variables: unknown, scope?: string): CacheKeyParts;
18
24
  export {};
@@ -1,4 +1,13 @@
1
1
  import { hash } from "ohash";
2
+ const defaultCacheConfig = {
3
+ keyPrefix: "gql",
4
+ keyVersion: "1",
5
+ policy: "no-cache",
6
+ ttl: void 0
7
+ };
8
+ export function resolveCacheConfig(...overrides) {
9
+ return Object.assign({}, defaultCacheConfig, ...overrides);
10
+ }
2
11
  export function getCacheKeyParts({ keyPrefix, keyVersion }, operationName, variables, scope) {
3
12
  const parts = [keyPrefix, keyVersion];
4
13
  if (scope) parts.push(scope);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lewebsimple/nuxt-graphql",
3
- "version": "0.5.4",
3
+ "version": "0.5.6",
4
4
  "description": "Opinionated Nuxt module for using GraphQL",
5
5
  "repository": "lewebsimple/nuxt-graphql",
6
6
  "license": "AGPL-3.0-only",
@@ -1,42 +0,0 @@
1
- type GraphQLCachePolicy = "no-cache" | "cache-first" | "network-first" | "swr";
2
- export type CacheConfig = {
3
- /**
4
- * Prefix used for all persisted cache keys.
5
- *
6
- * Used for namespacing and bulk invalidation.
7
- * Default: 'graphql'
8
- */
9
- keyPrefix: string;
10
- /**
11
- * Version included in cache keys.
12
- *
13
- * Changing this value invalidates all existing cache entries.
14
- * Default: '1'
15
- */
16
- keyVersion: string | number;
17
- /**
18
- * Cache strategy used by useAsyncGraphQLQuery.
19
- *
20
- * - 'no-cache' → always fetch, never read/write cache
21
- * - 'cache-first' → return cache if valid, otherwise fetch
22
- * - 'network-first' → fetch first, fallback to cache on failure
23
- * - 'swr' → return cache immediately, revalidate in background
24
- */
25
- policy: GraphQLCachePolicy;
26
- /**
27
- * Time-to-live in seconds.
28
- *
29
- * - undefined → inherit from higher-level config
30
- * - 0 → never expires
31
- * - > 0 → expires after TTL
32
- */
33
- ttl?: number;
34
- };
35
- /**
36
- * Merge the default cache config with user overrides.
37
- *
38
- * @param overrides Partial cache config overrides.
39
- * @returns Resolved cache configuration.
40
- */
41
- export declare function resolveCacheConfig(...overrides: Array<Partial<CacheConfig> | undefined>): CacheConfig;
42
- export {};
@@ -1,9 +0,0 @@
1
- const defaultCacheConfig = {
2
- keyPrefix: "gql",
3
- keyVersion: "1",
4
- policy: "no-cache",
5
- ttl: void 0
6
- };
7
- export function resolveCacheConfig(...overrides) {
8
- return Object.assign({}, defaultCacheConfig, ...overrides);
9
- }