@wpnuxt/core 2.0.0-alpha.4 → 2.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.
package/dist/module.json CHANGED
@@ -1,7 +1,10 @@
1
1
  {
2
- "name": "wpnuxt",
2
+ "name": "@wpnuxt/core",
3
3
  "configKey": "wpNuxt",
4
- "version": "2.0.0-alpha.3",
4
+ "compatibility": {
5
+ "nuxt": ">=3.0.0"
6
+ },
7
+ "version": "2.0.0-alpha.5",
5
8
  "builder": {
6
9
  "@nuxt/module-builder": "1.0.2",
7
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -8,6 +8,13 @@ import { ref } from 'vue';
8
8
  import { parse, GraphQLError } from 'graphql';
9
9
  import { execSync } from 'node:child_process';
10
10
 
11
+ function createModuleError(module, message) {
12
+ return new Error(formatErrorMessage(module, message));
13
+ }
14
+ function formatErrorMessage(module, message) {
15
+ return `[wpnuxt:${module}] ${message}`;
16
+ }
17
+
11
18
  function randHashGenerator(length = 12) {
12
19
  return Math.random().toString(36).substring(2, 2 + length).toUpperCase().padEnd(length, "0");
13
20
  }
@@ -38,14 +45,14 @@ async function mergeQueries(nuxt, wpNuxtConfig, resolver) {
38
45
 
39
46
  const _parseDoc = async (doc) => {
40
47
  if (!doc || typeof doc !== "string" || doc.trim().length === 0) {
41
- throw new Error("WPNuxt: Invalid GraphQL document - document is empty or not a string");
48
+ throw createModuleError("core", "Invalid GraphQL document - document is empty or not a string");
42
49
  }
43
50
  try {
44
51
  const { definitions } = parse(doc);
45
52
  const operations = definitions.filter(({ kind }) => kind === "OperationDefinition").map((definition) => {
46
53
  const operationDefinition = definition;
47
54
  if (!operationDefinition.name?.value) {
48
- throw new Error("WPNuxt: GraphQL operation is missing a name. All queries and mutations must have a name.");
55
+ throw createModuleError("core", "GraphQL operation is missing a name. All queries and mutations must have a name.");
49
56
  }
50
57
  const query = {
51
58
  name: operationDefinition.name.value.trim(),
@@ -60,7 +67,7 @@ const _parseDoc = async (doc) => {
60
67
  return operations;
61
68
  } catch (error) {
62
69
  if (error instanceof GraphQLError) {
63
- throw new TypeError(`WPNuxt: Failed to parse GraphQL document - ${error.message}`);
70
+ throw createModuleError("core", `Failed to parse GraphQL document - ${error.message}`);
64
71
  }
65
72
  throw error;
66
73
  }
@@ -83,6 +90,31 @@ function processSelections(selections, level, query, canExtract = true) {
83
90
  const parseDoc = _parseDoc;
84
91
 
85
92
  const SCHEMA_PATTERN = /schema\.(?:gql|graphql)$/i;
93
+ const COMPLEXITY_THRESHOLDS = {
94
+ /** Maximum recommended extraction depth */
95
+ maxDepth: 5,
96
+ /** Maximum recommended number of fragments */
97
+ maxFragments: 4
98
+ };
99
+ function analyzeQueryComplexity(queries) {
100
+ const logger = getLogger();
101
+ for (const query of queries) {
102
+ const warnings = [];
103
+ const depth = query.nodes?.length ?? 0;
104
+ if (depth > COMPLEXITY_THRESHOLDS.maxDepth) {
105
+ warnings.push(`deep extraction path (${depth} levels)`);
106
+ }
107
+ const fragmentCount = query.fragments?.length ?? 0;
108
+ if (fragmentCount > COMPLEXITY_THRESHOLDS.maxFragments) {
109
+ warnings.push(`many fragments (${fragmentCount})`);
110
+ }
111
+ if (warnings.length > 0) {
112
+ logger.warn(
113
+ `Query "${query.name}" may be expensive: ${warnings.join(", ")}. Consider splitting into smaller queries or reducing data fetched.`
114
+ );
115
+ }
116
+ }
117
+ }
86
118
  const allowDocument = (f, resolver) => {
87
119
  if (SCHEMA_PATTERN.test(f)) return false;
88
120
  try {
@@ -106,6 +138,7 @@ async function prepareContext(ctx) {
106
138
  }
107
139
  const queries = ctx.fns.filter((f) => f.operation === "query");
108
140
  const mutations = ctx.fns.filter((f) => f.operation === "mutation");
141
+ analyzeQueryComplexity(queries);
109
142
  const fnName = (fn) => ctx.composablesPrefix + upperFirst(fn);
110
143
  const mutationFnName = (fn) => `useMutation${upperFirst(fn)}`;
111
144
  const formatNodes = (nodes) => nodes?.map((n) => `'${n}'`).join(",") ?? "";
@@ -246,7 +279,7 @@ async function validateWordPressEndpoint(wordpressUrl, graphqlEndpoint = "/graph
246
279
  clearTimeout(timeout);
247
280
  if (!response.ok) {
248
281
  throw new Error(
249
- `[WPNuxt] WordPress GraphQL endpoint returned HTTP ${response.status}.
282
+ `[wpnuxt:core] WordPress GraphQL endpoint returned HTTP ${response.status}.
250
283
 
251
284
  URL: ${fullUrl}
252
285
 
@@ -261,7 +294,7 @@ Check your wpNuxt.wordpressUrl configuration in nuxt.config.ts`
261
294
  const data = await response.json();
262
295
  if (!data || typeof data !== "object") {
263
296
  throw new Error(
264
- `[WPNuxt] WordPress GraphQL endpoint returned invalid response.
297
+ `[wpnuxt:core] WordPress GraphQL endpoint returned invalid response.
265
298
 
266
299
  URL: ${fullUrl}
267
300
 
@@ -271,7 +304,7 @@ The endpoint did not return valid JSON. Make sure WPGraphQL plugin is installed
271
304
  if (data.errors && !data.data) {
272
305
  const errorMessage = data.errors[0]?.message || "Unknown error";
273
306
  throw new Error(
274
- `[WPNuxt] WordPress GraphQL endpoint returned an error: ${errorMessage}
307
+ `[wpnuxt:core] WordPress GraphQL endpoint returned an error: ${errorMessage}
275
308
 
276
309
  URL: ${fullUrl}
277
310
 
@@ -289,7 +322,7 @@ Make sure WPGraphQL plugin is installed and activated on your WordPress site.`
289
322
  } catch (err) {
290
323
  const error = err;
291
324
  throw new Error(
292
- `[WPNuxt] Failed to download GraphQL schema.
325
+ `[wpnuxt:core] Failed to download GraphQL schema.
293
326
 
294
327
  URL: ${fullUrl}
295
328
  Error: ${error.stderr?.toString() || error.message}
@@ -299,14 +332,14 @@ Make sure WPGraphQL plugin is installed and activated on your WordPress site.`
299
332
  }
300
333
  }
301
334
  } catch (error) {
302
- if (error instanceof Error && error.message.startsWith("[WPNuxt]")) {
335
+ if (error instanceof Error && error.message.startsWith("[wpnuxt:core]")) {
303
336
  throw error;
304
337
  }
305
338
  const err = error;
306
339
  const errorCode = err.code || err.cause?.code || "";
307
340
  if (err.name === "AbortError") {
308
341
  throw new Error(
309
- `[WPNuxt] WordPress GraphQL endpoint timed out after 10 seconds.
342
+ `[wpnuxt:core] WordPress GraphQL endpoint timed out after 10 seconds.
310
343
 
311
344
  URL: ${fullUrl}
312
345
 
@@ -315,7 +348,7 @@ The server did not respond in time. Check if the WordPress site is accessible.`
315
348
  }
316
349
  if (errorCode === "ENOTFOUND" || err.message?.includes("getaddrinfo")) {
317
350
  throw new Error(
318
- `[WPNuxt] WordPress site not found - DNS lookup failed.
351
+ `[wpnuxt:core] WordPress site not found - DNS lookup failed.
319
352
 
320
353
  URL: ${fullUrl}
321
354
 
@@ -329,7 +362,7 @@ Check your wpNuxt.wordpressUrl configuration in nuxt.config.ts`
329
362
  }
330
363
  if (errorCode === "ECONNREFUSED") {
331
364
  throw new Error(
332
- `[WPNuxt] Connection refused to WordPress site.
365
+ `[wpnuxt:core] Connection refused to WordPress site.
333
366
 
334
367
  URL: ${fullUrl}
335
368
 
@@ -337,7 +370,7 @@ The server is not accepting connections. Check if the WordPress site is running.
337
370
  );
338
371
  }
339
372
  throw new Error(
340
- `[WPNuxt] Failed to connect to WordPress GraphQL endpoint.
373
+ `[wpnuxt:core] Failed to connect to WordPress GraphQL endpoint.
341
374
 
342
375
  URL: ${fullUrl}
343
376
  Error: ${err.message || "Unknown error"}
@@ -373,8 +406,11 @@ function patchWPGraphQLSchema(schemaPath) {
373
406
 
374
407
  const module$1 = defineNuxtModule({
375
408
  meta: {
376
- name: "wpnuxt",
377
- configKey: "wpNuxt"
409
+ name: "@wpnuxt/core",
410
+ configKey: "wpNuxt",
411
+ compatibility: {
412
+ nuxt: ">=3.0.0"
413
+ }
378
414
  },
379
415
  defaults: {
380
416
  wordpressUrl: void 0,
@@ -404,15 +440,32 @@ const module$1 = defineNuxtModule({
404
440
  const mergedQueriesFolder = await mergeQueries(nuxt, wpNuxtConfig, resolver);
405
441
  await setupServerOptions(nuxt, resolver, logger);
406
442
  await setupClientOptions(nuxt, resolver, logger);
443
+ const schemaPath = join(nuxt.options.rootDir, "schema.graphql");
444
+ const schemaExists = existsSync(schemaPath);
407
445
  if (wpNuxtConfig.downloadSchema) {
408
- const schemaPath = join(nuxt.options.rootDir, "schema.graphql");
409
- logger.debug(`Validating WordPress endpoint: ${wpNuxtConfig.wordpressUrl}${wpNuxtConfig.graphqlEndpoint}`);
410
- await validateWordPressEndpoint(
411
- wpNuxtConfig.wordpressUrl,
412
- wpNuxtConfig.graphqlEndpoint,
413
- { schemaPath }
414
- );
415
- logger.debug("WordPress endpoint validation passed");
446
+ if (!schemaExists) {
447
+ logger.debug(`Downloading schema from: ${wpNuxtConfig.wordpressUrl}${wpNuxtConfig.graphqlEndpoint}`);
448
+ await validateWordPressEndpoint(
449
+ wpNuxtConfig.wordpressUrl,
450
+ wpNuxtConfig.graphqlEndpoint,
451
+ { schemaPath }
452
+ );
453
+ logger.debug("Schema downloaded successfully");
454
+ } else {
455
+ nuxt.hook("ready", async () => {
456
+ try {
457
+ await validateWordPressEndpoint(
458
+ wpNuxtConfig.wordpressUrl,
459
+ wpNuxtConfig.graphqlEndpoint
460
+ );
461
+ logger.debug("WordPress endpoint validation passed");
462
+ } catch (error) {
463
+ const message = error instanceof Error ? error.message : String(error);
464
+ logger.warn(`WordPress endpoint validation failed: ${message.split("\n")[0]}`);
465
+ logger.warn("App will continue with existing schema.graphql file");
466
+ }
467
+ });
468
+ }
416
469
  }
417
470
  await registerModules(nuxt, resolver, wpNuxtConfig, mergedQueriesFolder);
418
471
  nuxt.hook("devtools:customTabs", (tabs) => {
@@ -500,10 +553,10 @@ function loadConfig(options, nuxt) {
500
553
  }
501
554
  };
502
555
  if (!config.wordpressUrl?.trim()) {
503
- throw new Error("WPNuxt error: WordPress url is missing");
556
+ throw createModuleError("core", "WordPress URL is required. Set it in nuxt.config.ts or via WPNUXT_WORDPRESS_URL environment variable.");
504
557
  }
505
558
  if (config.wordpressUrl.endsWith("/")) {
506
- throw new Error(`WPNuxt error: WordPress url should not have a trailing slash: ${config.wordpressUrl}`);
559
+ throw createModuleError("core", `WordPress URL should not have a trailing slash: ${config.wordpressUrl}`);
507
560
  }
508
561
  return config;
509
562
  }
@@ -1,36 +1,102 @@
1
1
  import { getRelativeImagePath } from "../util/images.js";
2
- import { computed, useAsyncGraphqlQuery } from "#imports";
3
- function defaultGetCachedData(key, nuxtApp, ctx) {
4
- if (nuxtApp.isHydrating) {
5
- return nuxtApp.payload.data[key];
6
- }
7
- if (ctx.cause === "refresh:manual" || ctx.cause === "refresh:hook") {
8
- return void 0;
9
- }
10
- return nuxtApp.payload.data[key] ?? nuxtApp.static.data[key];
11
- }
2
+ import { computed, ref, watch as vueWatch, useAsyncGraphqlQuery } from "#imports";
12
3
  export const useWPContent = (queryName, nodes, fixImagePaths, params, options) => {
13
- const { clientCache, cacheKey, getCachedData: userGetCachedData, ...restOptions } = options ?? {};
4
+ const {
5
+ clientCache,
6
+ cacheKey,
7
+ getCachedData: userGetCachedData,
8
+ retry: retryOption,
9
+ retryDelay: retryDelayOption,
10
+ timeout: timeoutOption,
11
+ ...restOptions
12
+ } = options ?? {};
13
+ const maxRetries = retryOption === false ? 0 : retryOption ?? 0;
14
+ const baseRetryDelay = retryDelayOption ?? 1e3;
15
+ const retryCount = ref(0);
16
+ const isRetrying = ref(false);
17
+ const timeoutMs = timeoutOption ?? 0;
14
18
  const graphqlCaching = clientCache === false ? { client: false } : restOptions.graphqlCaching ?? { client: true };
15
19
  const paramsKey = params ? JSON.stringify(params) : "{}";
16
20
  const baseKey = `wpnuxt-${String(queryName)}-${paramsKey}`;
17
21
  const finalKey = cacheKey ? `${baseKey}-${cacheKey}` : baseKey;
18
- const effectiveGetCachedData = userGetCachedData ?? (clientCache === false ? () => void 0 : defaultGetCachedData);
22
+ const effectiveGetCachedData = userGetCachedData ?? (clientCache === false ? () => void 0 : void 0);
23
+ let abortController;
24
+ let timeoutId;
25
+ if (timeoutMs > 0) {
26
+ abortController = new AbortController();
27
+ timeoutId = setTimeout(() => {
28
+ abortController?.abort();
29
+ if (import.meta.dev) {
30
+ console.warn(`[wpnuxt] Query "${String(queryName)}" timed out after ${timeoutMs}ms`);
31
+ }
32
+ }, timeoutMs);
33
+ }
19
34
  const mergedOptions = {
20
35
  ...restOptions,
21
36
  graphqlCaching,
22
37
  // Explicit key ensures consistent cache lookups
23
38
  key: finalKey,
24
- getCachedData: effectiveGetCachedData
39
+ // Only set getCachedData if user provided one or wants to disable caching
40
+ // Otherwise let nuxt-graphql-middleware handle it with $graphqlCache
41
+ ...effectiveGetCachedData && { getCachedData: effectiveGetCachedData },
42
+ // Pass abort signal for timeout support
43
+ ...abortController && {
44
+ fetchOptions: {
45
+ ...restOptions.fetchOptions ?? {},
46
+ signal: abortController.signal
47
+ }
48
+ }
25
49
  };
26
50
  const { data, pending, refresh, execute, clear, error, status } = useAsyncGraphqlQuery(
27
51
  queryName,
28
52
  params ?? {},
29
53
  mergedOptions
30
54
  );
55
+ const transformError = ref(null);
56
+ if (timeoutId !== void 0) {
57
+ vueWatch(pending, (isPending) => {
58
+ if (!isPending && timeoutId !== void 0) {
59
+ clearTimeout(timeoutId);
60
+ timeoutId = void 0;
61
+ }
62
+ }, { immediate: true });
63
+ }
64
+ if (maxRetries > 0) {
65
+ vueWatch(error, async (newError) => {
66
+ if (newError && !isRetrying.value && retryCount.value < maxRetries && import.meta.client) {
67
+ isRetrying.value = true;
68
+ retryCount.value++;
69
+ const delay = baseRetryDelay * Math.pow(2, retryCount.value - 1);
70
+ if (import.meta.dev) {
71
+ console.warn(`[wpnuxt] Query "${String(queryName)}" failed, retrying in ${delay}ms (attempt ${retryCount.value}/${maxRetries})`);
72
+ }
73
+ await new Promise((resolve) => setTimeout(resolve, delay));
74
+ try {
75
+ await refresh();
76
+ } finally {
77
+ isRetrying.value = false;
78
+ }
79
+ }
80
+ });
81
+ vueWatch(data, (newData) => {
82
+ if (newData && retryCount.value > 0) {
83
+ retryCount.value = 0;
84
+ }
85
+ });
86
+ }
31
87
  const transformedData = computed(() => {
32
- const queryResult = data.value && typeof data.value === "object" && data.value !== null && "data" in data.value ? data.value.data : void 0;
33
- return queryResult ? transformData(queryResult, nodes, fixImagePaths) : void 0;
88
+ transformError.value = null;
89
+ try {
90
+ const queryResult = data.value && typeof data.value === "object" && data.value !== null && "data" in data.value ? data.value.data : void 0;
91
+ if (!queryResult) return void 0;
92
+ return transformData(queryResult, nodes, fixImagePaths);
93
+ } catch (err) {
94
+ if (import.meta.dev) {
95
+ console.warn(`[wpnuxt] Data transformation error for "${String(queryName)}":`, err);
96
+ }
97
+ transformError.value = err instanceof Error ? err : new Error("Failed to transform query response");
98
+ return void 0;
99
+ }
34
100
  });
35
101
  return {
36
102
  data: transformedData,
@@ -39,7 +105,13 @@ export const useWPContent = (queryName, nodes, fixImagePaths, params, options) =
39
105
  execute,
40
106
  clear,
41
107
  error,
42
- status
108
+ status,
109
+ /** Error from data transformation (separate from fetch error) */
110
+ transformError,
111
+ /** Current retry attempt count (0 if no retries or retries disabled) */
112
+ retryCount,
113
+ /** Whether a retry is currently in progress */
114
+ isRetrying
43
115
  };
44
116
  };
45
117
  const transformData = (data, nodes, fixImagePaths) => {
File without changes
@@ -0,0 +1,32 @@
1
+ export function createWPNuxtError(code, message, options) {
2
+ return {
3
+ code,
4
+ message,
5
+ details: options?.details,
6
+ query: options?.query,
7
+ timestamp: Date.now()
8
+ };
9
+ }
10
+ export function isWPNuxtError(error) {
11
+ return typeof error === "object" && error !== null && "code" in error && "message" in error && "timestamp" in error && typeof error.code === "string" && typeof error.message === "string" && typeof error.timestamp === "number";
12
+ }
13
+ export function success(data) {
14
+ return { success: true, data, error: null };
15
+ }
16
+ export function failure(error) {
17
+ return { success: false, data: null, error };
18
+ }
19
+ export function fromError(error, code = "NETWORK_ERROR", query) {
20
+ let errorCode = code;
21
+ if (error.name === "AbortError") {
22
+ errorCode = "TIMEOUT_ERROR";
23
+ } else if (error.message?.includes("GraphQL")) {
24
+ errorCode = "GRAPHQL_ERROR";
25
+ } else if (error.message?.includes("401") || error.message?.includes("403")) {
26
+ errorCode = "AUTH_ERROR";
27
+ }
28
+ return createWPNuxtError(errorCode, error.message, {
29
+ details: error.stack,
30
+ query
31
+ });
32
+ }
@@ -8,6 +8,8 @@
8
8
 
9
9
  // Stub for #imports
10
10
  export const computed: any
11
+ export const ref: any
12
+ export const watch: any
11
13
  export const defineNuxtPlugin: any
12
14
  export const useAsyncGraphqlQuery: any
13
15
  export const useGraphqlState: any
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wpnuxt/core",
3
- "version": "2.0.0-alpha.4",
3
+ "version": "2.0.0-alpha.6",
4
4
  "description": "Nuxt module for WordPress integration via GraphQL (WPGraphQL)",
5
5
  "keywords": [
6
6
  "nuxt",
@@ -47,18 +47,18 @@
47
47
  "@radya/nuxt-dompurify": "^1.0.5",
48
48
  "defu": "^6.1.4",
49
49
  "graphql": "^16.12.0",
50
- "nuxt-graphql-middleware": "5.3.1",
50
+ "nuxt-graphql-middleware": "5.3.2",
51
51
  "scule": "^1.3.0"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@nuxt/devtools": "^3.1.1",
55
55
  "@nuxt/module-builder": "^1.0.2",
56
56
  "@nuxt/schema": "4.2.2",
57
- "@nuxt/test-utils": "^3.21.0",
58
- "@types/node": "^25.0.3",
57
+ "@nuxt/test-utils": "^3.23.0",
58
+ "@types/node": "^25.0.9",
59
59
  "nuxt": "4.2.2",
60
- "vitest": "^4.0.16",
61
- "vue-tsc": "^3.2.1"
60
+ "vitest": "^4.0.17",
61
+ "vue-tsc": "^3.2.2"
62
62
  },
63
63
  "peerDependencies": {
64
64
  "nuxt": "^4.0.0"