@lewebsimple/nuxt-graphql 0.6.19 → 0.7.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 (63) hide show
  1. package/README.md +61 -74
  2. package/dist/module.d.mts +31 -5
  3. package/dist/module.json +3 -3
  4. package/dist/module.mjs +442 -341
  5. package/dist/runtime/app/composables/useAsyncGraphQLQuery.d.ts +14 -10
  6. package/dist/runtime/app/composables/useAsyncGraphQLQuery.js +71 -80
  7. package/dist/runtime/app/composables/useGraphQLCache.client.d.ts +9 -14
  8. package/dist/runtime/app/composables/useGraphQLCache.client.js +40 -61
  9. package/dist/runtime/app/composables/useGraphQLLoadMore.d.ts +16 -9
  10. package/dist/runtime/app/composables/useGraphQLLoadMore.js +36 -40
  11. package/dist/runtime/app/composables/useGraphQLMutation.d.ts +29 -31
  12. package/dist/runtime/app/composables/useGraphQLMutation.js +17 -32
  13. package/dist/runtime/app/composables/useGraphQLQuery.d.ts +10 -3
  14. package/dist/runtime/app/composables/useGraphQLQuery.js +4 -14
  15. package/dist/runtime/app/composables/useGraphQLSubscription.client.d.ts +4 -5
  16. package/dist/runtime/app/composables/useGraphQLSubscription.client.js +7 -5
  17. package/dist/runtime/app/lib/cache-config.d.ts +18 -0
  18. package/dist/runtime/app/lib/cache-config.js +9 -0
  19. package/dist/runtime/app/lib/cache.d.ts +81 -49
  20. package/dist/runtime/app/lib/cache.js +65 -55
  21. package/dist/runtime/app/lib/persisted.d.ts +18 -12
  22. package/dist/runtime/app/lib/persisted.js +42 -45
  23. package/dist/runtime/app/plugins/graphql-sse.client.js +1 -2
  24. package/dist/runtime/app/plugins/graphql.d.ts +24 -0
  25. package/dist/runtime/app/plugins/graphql.js +16 -0
  26. package/dist/runtime/server/api/graphql.d.ts +6 -0
  27. package/dist/runtime/server/api/graphql.js +1 -1
  28. package/dist/runtime/server/{utils/defineGraphQLContext.d.ts → lib/context.d.ts} +10 -1
  29. package/dist/runtime/server/lib/remote-executor.d.ts +42 -19
  30. package/dist/runtime/server/lib/remote-executor.js +11 -13
  31. package/dist/runtime/server/lib/yoga.d.ts +0 -1
  32. package/dist/runtime/server/lib/yoga.js +1 -2
  33. package/dist/runtime/server/tsconfig.json +1 -1
  34. package/dist/runtime/server/utils/execute-schema.d.ts +11 -0
  35. package/dist/runtime/server/utils/execute-schema.js +24 -0
  36. package/dist/runtime/shared/lib/headers.d.ts +14 -2
  37. package/dist/runtime/shared/lib/headers.js +18 -1
  38. package/dist/runtime/shared/utils/error.d.ts +39 -0
  39. package/dist/runtime/shared/utils/error.js +67 -0
  40. package/dist/runtime/shared/utils/execute.d.ts +33 -0
  41. package/dist/runtime/shared/utils/execute.js +26 -0
  42. package/dist/runtime/shared/utils/registry.d.ts +72 -0
  43. package/dist/runtime/shared/utils/registry.js +37 -0
  44. package/package.json +48 -36
  45. package/dist/runtime/app/lib/in-flight.d.ts +0 -14
  46. package/dist/runtime/app/lib/in-flight.js +0 -19
  47. package/dist/runtime/app/plugins/execute-graphql.d.ts +0 -18
  48. package/dist/runtime/app/plugins/execute-graphql.js +0 -25
  49. package/dist/runtime/server/lib/execute-graphql-schema.d.ts +0 -3
  50. package/dist/runtime/server/lib/execute-graphql-schema.js +0 -22
  51. package/dist/runtime/server/utils/defineRemoteExecutorHooks.d.ts +0 -8
  52. package/dist/runtime/server/utils/defineRemoteExecutorHooks.js +0 -3
  53. package/dist/runtime/server/utils/useGraphQLOperation.d.ts +0 -16
  54. package/dist/runtime/server/utils/useGraphQLOperation.js +0 -12
  55. package/dist/runtime/shared/lib/error.d.ts +0 -42
  56. package/dist/runtime/shared/lib/error.js +0 -52
  57. package/dist/runtime/shared/lib/registry.d.ts +0 -12
  58. package/dist/runtime/shared/lib/registry.js +0 -8
  59. package/dist/runtime/shared/lib/types.d.ts +0 -30
  60. package/dist/runtime/shared/lib/types.js +0 -0
  61. package/dist/runtime/shared/utils/execute-graphql-http.d.ts +0 -7
  62. package/dist/runtime/shared/utils/execute-graphql-http.js +0 -31
  63. /package/dist/runtime/server/{utils/defineGraphQLContext.js → lib/context.js} +0 -0
@@ -1,41 +1,26 @@
1
1
  import { useNuxtApp } from "#app";
2
- import { ref } from "vue";
3
- import { getOperationDocument } from "../../shared/lib/registry.js";
4
- import { normalizeError } from "../../shared/lib/error.js";
5
- export function useGraphQLMutation(operationName, options) {
6
- const { $executeGraphQL } = useNuxtApp();
7
- const document = getOperationDocument(operationName);
8
- const pending = ref(false);
9
- async function mutate(...args) {
10
- const [variables] = args;
11
- let context;
12
- if (options?.onMutate) {
13
- try {
14
- context = await options.onMutate(variables);
15
- } catch (error) {
16
- const normalizedError = normalizeError(error);
17
- options?.onError?.(normalizedError, variables, context);
18
- return { data: null, error: normalizedError };
19
- }
20
- }
21
- pending.value = true;
2
+ import { computed, ref } from "vue";
3
+ export function useGraphQLMutation(operationName, hooks) {
4
+ const { $executeOperation } = useNuxtApp();
5
+ const inFlightCount = ref(0);
6
+ const pending = computed(() => inFlightCount.value > 0);
7
+ async function mutate(variables) {
8
+ let context = void 0;
9
+ inFlightCount.value += 1;
22
10
  try {
23
- const result = await $executeGraphQL({ query: document, variables, operationName });
11
+ if (hooks?.onMutate) {
12
+ context = await hooks.onMutate(variables);
13
+ }
14
+ const result = await $executeOperation({ operationName, variables });
24
15
  if (result.error) {
25
- options?.onError?.(result.error, variables, context);
26
- } else if (result.data) {
27
- options?.onSuccess?.(result.data, variables, context);
16
+ hooks?.onError?.(result.error, variables, context);
17
+ } else {
18
+ hooks?.onSuccess?.(result.data, variables, context);
28
19
  }
29
- options?.onSettled?.(result, variables, context);
20
+ hooks?.onSettled?.(result, variables, context);
30
21
  return result;
31
- } catch (error) {
32
- const normalizedError = normalizeError(error);
33
- const errorResult = { data: null, error: normalizedError };
34
- options?.onError?.(normalizedError, variables, context);
35
- options?.onSettled?.(errorResult, variables, context);
36
- return errorResult;
37
22
  } finally {
38
- pending.value = false;
23
+ inFlightCount.value = Math.max(0, inFlightCount.value - 1);
39
24
  }
40
25
  }
41
26
  return { pending, mutate };
@@ -1,3 +1,10 @@
1
- import type { QueryName, ResultOf, VariablesOf } from "#graphql/registry";
2
- import type { ExecuteGraphQLResult, IsEmptyObject } from "../../shared/lib/types.js";
3
- export declare function useGraphQLQuery<TName extends QueryName>(operationName: TName, ...args: IsEmptyObject<VariablesOf<TName>> extends true ? [variables?: VariablesOf<TName>] : [variables: VariablesOf<TName>]): Promise<ExecuteGraphQLResult<ResultOf<TName>>>;
1
+ import type { ExecuteGraphQLResult } from "../../shared/utils/execute.js";
2
+ import type { QueryName, VariablesInputOf } from "../../shared/utils/registry.js";
3
+ /**
4
+ * Execute a GraphQL query operation once.
5
+ *
6
+ * @param operationName Query operation name.
7
+ * @param variables Query variables.
8
+ * @returns Query execution result.
9
+ */
10
+ export declare function useGraphQLQuery<TName extends QueryName>(operationName: TName, variables: VariablesInputOf<TName>): Promise<ExecuteGraphQLResult<TName>>;
@@ -1,15 +1,5 @@
1
- import { useNuxtApp } from "#imports";
2
- import { normalizeError } from "../../shared/lib/error.js";
3
- import { getOperationDocument } from "../../shared/lib/registry.js";
4
- export async function useGraphQLQuery(operationName, ...args) {
5
- const { $executeGraphQL } = useNuxtApp();
6
- const [variables] = args;
7
- const document = getOperationDocument(operationName);
8
- try {
9
- return await $executeGraphQL(
10
- { query: document, variables, operationName }
11
- );
12
- } catch (error) {
13
- return { data: null, error: normalizeError(error) };
14
- }
1
+ import { useNuxtApp } from "#app";
2
+ export async function useGraphQLQuery(operationName, variables) {
3
+ const { $executeOperation } = useNuxtApp();
4
+ return $executeOperation({ operationName, variables });
15
5
  }
@@ -1,7 +1,6 @@
1
- import { type Ref } from "#imports";
2
- import type { ResultOf, SubscriptionName, VariablesOf } from "#graphql/registry";
3
- import { type NormalizedError } from "../../shared/lib/error.js";
4
- import type { IsEmptyObject } from "../../shared/lib/types.js";
1
+ import { type Ref } from "vue";
2
+ import { type NormalizedError } from "../../shared/utils/error.js";
3
+ import { type ResultOf, type SubscriptionName, type VariablesInputOf } from "../../shared/utils/registry.js";
5
4
  type UseGraphQLSubscriptionReturn<TName extends SubscriptionName> = {
6
5
  data: Readonly<Ref<ResultOf<TName> | null>>;
7
6
  error: Readonly<Ref<NormalizedError | null>>;
@@ -15,5 +14,5 @@ type UseGraphQLSubscriptionReturn<TName extends SubscriptionName> = {
15
14
  * @param args Operation variables (if any).
16
15
  * @returns Object with reactive data, error, and start/stop helpers.
17
16
  */
18
- export declare function useGraphQLSubscription<TName extends SubscriptionName>(operationName: TName, ...args: IsEmptyObject<VariablesOf<TName>> extends true ? [variables?: VariablesOf<TName>] : [variables: VariablesOf<TName>]): UseGraphQLSubscriptionReturn<TName>;
17
+ export declare function useGraphQLSubscription<TName extends SubscriptionName>(operationName: TName, variables: VariablesInputOf<TName>): UseGraphQLSubscriptionReturn<TName>;
19
18
  export {};
@@ -1,10 +1,12 @@
1
+ import { useNuxtApp } from "#app";
1
2
  import { print } from "graphql";
2
- import { onScopeDispose, shallowRef, useNuxtApp } from "#imports";
3
- import { normalizeError } from "../../shared/lib/error.js";
4
- import { getOperationDocument } from "../../shared/lib/registry.js";
5
- export function useGraphQLSubscription(operationName, ...args) {
3
+ import { onScopeDispose, shallowRef } from "vue";
4
+ import { normalizeError } from "../../shared/utils/error.js";
5
+ import {
6
+ getOperationDocument
7
+ } from "../../shared/utils/registry.js";
8
+ export function useGraphQLSubscription(operationName, variables) {
6
9
  const { $getGraphQLSSEClient } = useNuxtApp();
7
- const [variables] = args;
8
10
  const document = getOperationDocument(operationName);
9
11
  const query = print(document);
10
12
  const data = shallowRef(null);
@@ -0,0 +1,18 @@
1
+ /** Cache configuration. */
2
+ export type CacheConfig = {
3
+ /** Cache strategy. */
4
+ policy: "no-cache" | "cache-first" | "network-first" | "swr";
5
+ /** Optional cache time-to-live in seconds. */
6
+ ttl?: number;
7
+ /** Cache key prefix. */
8
+ keyPrefix: string;
9
+ /** Cache key version. */
10
+ keyVersion: string | number;
11
+ };
12
+ /**
13
+ * Merge cache configuration overrides with defaults.
14
+ *
15
+ * @param overrides Partial cache config overrides.
16
+ * @returns Resolved cache configuration.
17
+ */
18
+ export declare function resolveCacheConfig(...overrides: Array<Partial<CacheConfig> | undefined>): CacheConfig;
@@ -0,0 +1,9 @@
1
+ const DEFAULT_CACHE_CONFIG = {
2
+ policy: "no-cache",
3
+ ttl: void 0,
4
+ keyPrefix: "gql",
5
+ keyVersion: "1"
6
+ };
7
+ export function resolveCacheConfig(...overrides) {
8
+ return Object.assign({}, DEFAULT_CACHE_CONFIG, ...overrides);
9
+ }
@@ -1,82 +1,114 @@
1
- import type { CacheConfig } from "../../shared/lib/types.js";
1
+ import type { CacheConfig } from "./cache-config.js";
2
2
  /**
3
- * Resolve cache config from default value with user overrides.
3
+ * Build the root cache key prefix shared by all entries.
4
4
  *
5
- * @param overrides Partial cache config overrides.
6
- * @returns Resolved cache configuration.
5
+ * @param config Cache configuration.
6
+ * @returns Root cache prefix.
7
7
  */
8
- export declare function resolveCacheConfig(...overrides: Array<Partial<CacheConfig> | undefined>): CacheConfig;
9
- type CacheKeyParts = {
10
- rootPrefix: string;
11
- scopePrefix: string;
12
- opPrefix: string;
13
- key: string;
14
- };
8
+ export declare function getCacheRootPrefix({ keyPrefix, keyVersion }: CacheConfig): string;
15
9
  /**
16
- * Build cache key parts from config, operation name, and variables.
10
+ * Build the cache key prefix for a specific scope.
17
11
  *
18
- * @param {GraphQLCacheConfig} config Cache configuration.
19
- * @param config.keyPrefix Cache key prefix.
20
- * @param config.keyVersion Cache key version.
21
- * @param scope Cache scope segment.
22
- * @param operationName Operation name.
23
- * @param variables Operation variables.
24
- * @returns Key parts including full key and operation prefix.
12
+ * @param config Cache configuration.
13
+ * @param scope Cache scope.
14
+ * @returns Scope cache prefix.
25
15
  */
26
- export declare function getCacheKeyParts({ keyPrefix, keyVersion }: CacheConfig, scope: string, operationName: string, variables: unknown): CacheKeyParts;
16
+ export declare function getCacheScopePrefix(config: CacheConfig, scope: string): string;
27
17
  /**
28
- * Register a cache key seen by async GraphQL queries.
18
+ * Build the cache key prefix for a specific operation in a scope.
29
19
  *
30
- * @param key Cache key.
20
+ * @param config Cache configuration.
21
+ * @param scope Cache scope.
22
+ * @param operationName GraphQL operation name.
23
+ * @returns Operation cache prefix.
31
24
  */
32
- export declare function registerCacheKey(key: string): void;
25
+ export declare function getCacheOperationPrefix(config: CacheConfig, scope: string, operationName: string): string;
33
26
  /**
34
- * Get known cache keys by prefix.
27
+ * Build the full cache key for a scoped operation and variables.
35
28
  *
36
- * @param prefix Cache key prefix.
37
- * @returns Matching cache keys.
29
+ * @param config Cache configuration.
30
+ * @param scope Cache scope.
31
+ * @param operationName GraphQL operation name.
32
+ * @param variables Operation variables.
33
+ * @returns Full cache key.
38
34
  */
39
- export declare function getCacheKeysByPrefix(prefix: string): string[];
35
+ export declare function getCacheKey(config: CacheConfig, scope: string, operationName: string, variables: unknown): string;
40
36
  /**
41
- * Mark a single cache key as invalidated.
37
+ * Invalidate cache entries matching a specific key prefix.
42
38
  *
43
- * @param key Cache key.
39
+ * @param prefix Cache key prefix to invalidate.
44
40
  */
45
- export declare function invalidateCacheKey(key: string): void;
41
+ export declare function invalidateCachePrefix(prefix: string): void;
42
+ /** Invalidate all cache entries regardless of prefix. */
43
+ export declare function invalidateAllCache(): void;
46
44
  /**
47
- * Mark all cache keys matching a prefix as invalidated.
45
+ * Determine whether a cache entry should be bypassed due to invalidation.
48
46
  *
49
- * @param prefix Cache key prefix.
47
+ * @param key Full cache key.
48
+ * @param createdAt Entry creation timestamp in milliseconds.
49
+ * @returns `true` when the entry was invalidated by global or prefix invalidation.
50
50
  */
51
- export declare function invalidateCachePrefix(prefix: string): void;
51
+ export declare function shouldBypassCache(key: string, createdAt?: number): boolean;
52
+ /** Metadata stored for a cache entry. */
53
+ export type CacheMeta<T> = {
54
+ /** In-flight promise for the current cache request. */
55
+ promise?: Promise<T>;
56
+ /** Timestamp when this metadata entry was created. */
57
+ createdAt: number;
58
+ /** Expiration timestamp in milliseconds, or `null` when it does not expire. */
59
+ expiresAt: number | null;
60
+ };
52
61
  /**
53
- * Mark all cache keys as invalidated.
62
+ * Returns metadata for a cache key.
63
+ *
64
+ * @param key Cache key used to look up metadata.
65
+ * @returns Metadata for the key when present, otherwise `undefined`.
54
66
  */
55
- export declare function invalidateAllCacheKeys(): void;
67
+ export declare function getCacheMeta<T>(key: string): CacheMeta<T> | undefined;
56
68
  /**
57
- * Mark a cache key as refreshed from network.
69
+ * Resolves a cache entry and updates both metadata and Nuxt data state.
58
70
  *
59
- * @param key Cache key.
71
+ * @param key Cache key to resolve.
72
+ * @param value Resolved data to store in Nuxt state.
73
+ * @param ttl Optional time-to-live in milliseconds. When omitted or `null`, the entry does not expire.
74
+ * @returns The resolved value.
60
75
  */
61
- export declare function markCacheKeyRefreshed(key: string): void;
76
+ export declare function resolveCacheEntry<T>(key: string, value: T, ttl?: number | null): T;
62
77
  /**
63
- * Determine whether cache should be bypassed for this key.
78
+ * Returns whether the provided cache metadata is expired.
64
79
  *
65
- * @param key Cache key.
66
- * @returns True when invalidation is newer than the last successful network refresh.
80
+ * @param meta Metadata object to evaluate.
81
+ * @returns `true` when metadata exists and is past its expiration time; otherwise `false`.
67
82
  */
68
- export declare function shouldBypassCache(key: string): boolean;
83
+ export declare function isExpired(meta?: CacheMeta<unknown>): boolean;
69
84
  /**
70
- * Remove a single cache key from all internal registries.
85
+ * Returns a promise for a cache key, creating it if it doesn't exist.
86
+ *
87
+ * @param key Cache key to resolve.
88
+ * @param create Function to create a new promise if none exists.
89
+ * @param ttl Optional time-to-live in milliseconds. When omitted or `null`, the entry does not expire.
90
+ * @returns A promise for the cache key.
71
91
  */
72
- export declare function forgetCacheKey(key: string): void;
92
+ export declare function getOrCreatePromise<T>(key: string, create: () => Promise<T>, ttl?: number | null): Promise<T>;
73
93
  /**
74
- * Remove all cache keys and related metadata matching a prefix.
94
+ * Determines whether a cache entry should be used based on its presence, invalidation status, and expiration.
95
+ *
96
+ * @param key Cache key to evaluate.
97
+ * @param cached Cached value to check.
98
+ * @param meta Metadata associated with the cache entry.
99
+ * @returns `true` if the cache entry should be used; otherwise `false`.
75
100
  */
76
- export declare function forgetCacheByPrefix(prefix: string): void;
101
+ export declare function shouldUseCached<T>(key: string, cached: T | undefined, meta?: CacheMeta<unknown>): cached is T;
102
+ type SWRParams<T> = {
103
+ cached: T | undefined;
104
+ fetch: () => Promise<T>;
105
+ inFlight?: Promise<T>;
106
+ };
77
107
  /**
78
- * Clear all in-memory cache tracking metadata.
79
- * Does NOT delete persisted storage.
108
+ * Return cached data immediately and refresh in background when possible.
109
+ *
110
+ * @param params SWR inputs including cached value, fetcher, and optional in-flight promise.
111
+ * @returns Cached value when available, otherwise a promise resolving fresh data.
80
112
  */
81
- export declare function forgetCacheAll(): void;
113
+ export declare function staleWhileRevalidate<T>({ cached, fetch, inFlight }: SWRParams<T>): Promise<T> | T;
82
114
  export {};
@@ -1,74 +1,84 @@
1
1
  import { hash } from "ohash";
2
- const defaultCacheConfig = {
3
- policy: "no-cache",
4
- ttl: void 0,
5
- keyPrefix: "gql",
6
- keyVersion: "1"
7
- };
8
- export function resolveCacheConfig(...overrides) {
9
- return Object.assign({}, defaultCacheConfig, ...overrides);
2
+ export function getCacheRootPrefix({ keyPrefix, keyVersion }) {
3
+ return `${keyPrefix}:${keyVersion}:`;
10
4
  }
11
- export function getCacheKeyParts({ keyPrefix, keyVersion }, scope, operationName, variables) {
12
- const rootPrefix = `${keyPrefix}:${keyVersion}:`;
13
- const scopePrefix = `${rootPrefix}${scope}:`;
14
- const opPrefix = `${scopePrefix}${operationName}:`;
15
- const key = `${opPrefix}${hash(variables || {})}`;
16
- return { rootPrefix, scopePrefix, opPrefix, key };
5
+ export function getCacheScopePrefix(config, scope) {
6
+ return `${getCacheRootPrefix(config)}${scope}:`;
17
7
  }
18
- const knownCacheKeys = /* @__PURE__ */ new Set();
19
- export function registerCacheKey(key) {
20
- knownCacheKeys.add(key);
8
+ export function getCacheOperationPrefix(config, scope, operationName) {
9
+ return `${getCacheScopePrefix(config, scope)}${operationName}:`;
21
10
  }
22
- export function getCacheKeysByPrefix(prefix) {
23
- return [...knownCacheKeys].filter((key) => key.startsWith(prefix));
11
+ export function getCacheKey(config, scope, operationName, variables) {
12
+ return `${getCacheOperationPrefix(config, scope, operationName)}${hash(variables ?? {})}`;
24
13
  }
25
- const invalidatedExactAt = /* @__PURE__ */ new Map();
26
14
  const invalidatedPrefixAt = /* @__PURE__ */ new Map();
27
- const refreshedAt = /* @__PURE__ */ new Map();
28
15
  let invalidatedAllAt = 0;
29
- export function invalidateCacheKey(key) {
30
- invalidatedExactAt.set(key, Date.now());
31
- }
32
16
  export function invalidateCachePrefix(prefix) {
33
17
  invalidatedPrefixAt.set(prefix, Date.now());
34
18
  }
35
- export function invalidateAllCacheKeys() {
19
+ export function invalidateAllCache() {
36
20
  invalidatedAllAt = Date.now();
37
21
  }
38
- export function markCacheKeyRefreshed(key) {
39
- refreshedAt.set(key, Date.now());
22
+ export function shouldBypassCache(key, createdAt) {
23
+ if (!createdAt) return false;
24
+ if (createdAt < invalidatedAllAt) return true;
25
+ let newest = 0;
26
+ let idx = key.indexOf(":");
27
+ while (idx !== -1) {
28
+ const prefix = key.slice(0, idx + 1);
29
+ const ts = invalidatedPrefixAt.get(prefix);
30
+ if (ts && ts > newest) newest = ts;
31
+ idx = key.indexOf(":", idx + 1);
32
+ }
33
+ return createdAt < newest;
34
+ }
35
+ const metaCache = /* @__PURE__ */ new Map();
36
+ export function getCacheMeta(key) {
37
+ return metaCache.get(key);
40
38
  }
41
- export function shouldBypassCache(key) {
42
- const lastRefreshAt = refreshedAt.get(key) ?? 0;
43
- const exactInvalidatedAt = invalidatedExactAt.get(key) ?? 0;
44
- let prefixInvalidatedAt = 0;
45
- for (const [prefix, timestamp] of invalidatedPrefixAt) {
46
- if (key.startsWith(prefix) && timestamp > prefixInvalidatedAt) {
47
- prefixInvalidatedAt = timestamp;
48
- }
39
+ export function resolveCacheEntry(key, value, ttl) {
40
+ const now = Date.now();
41
+ const meta = metaCache.get(key);
42
+ const next = meta ?? { createdAt: now, expiresAt: null };
43
+ next.createdAt = now;
44
+ next.expiresAt = ttl ? now + ttl : null;
45
+ next.promise = void 0;
46
+ metaCache.set(key, next);
47
+ return value;
48
+ }
49
+ export function isExpired(meta) {
50
+ if (!meta) return false;
51
+ if (meta.expiresAt === null) return false;
52
+ return Date.now() > meta.expiresAt;
53
+ }
54
+ export function getOrCreatePromise(key, create, ttl) {
55
+ let meta = metaCache.get(key);
56
+ if (meta?.promise) return meta.promise;
57
+ const promise = (async () => {
58
+ const value = await create();
59
+ resolveCacheEntry(key, value, ttl);
60
+ return value;
61
+ })();
62
+ if (!meta) {
63
+ meta = { createdAt: Date.now(), expiresAt: null };
64
+ metaCache.set(key, meta);
49
65
  }
50
- const latestInvalidationAt = Math.max(invalidatedAllAt, exactInvalidatedAt, prefixInvalidatedAt);
51
- return latestInvalidationAt > lastRefreshAt;
66
+ meta.promise = promise;
67
+ return promise;
52
68
  }
53
- export function forgetCacheKey(key) {
54
- knownCacheKeys.delete(key);
55
- invalidatedExactAt.delete(key);
56
- refreshedAt.delete(key);
69
+ export function shouldUseCached(key, cached, meta) {
70
+ if (cached === void 0) return false;
71
+ if (shouldBypassCache(key, meta?.createdAt)) return false;
72
+ if (isExpired(meta)) return false;
73
+ return true;
57
74
  }
58
- export function forgetCacheByPrefix(prefix) {
59
- for (const key of knownCacheKeys) {
60
- if (key.startsWith(prefix)) {
61
- knownCacheKeys.delete(key);
62
- invalidatedExactAt.delete(key);
63
- refreshedAt.delete(key);
75
+ export function staleWhileRevalidate({ cached, fetch, inFlight }) {
76
+ if (cached !== void 0) {
77
+ if (!inFlight) {
78
+ fetch().catch(() => {
79
+ });
64
80
  }
81
+ return cached;
65
82
  }
66
- invalidatedPrefixAt.delete(prefix);
67
- }
68
- export function forgetCacheAll() {
69
- knownCacheKeys.clear();
70
- invalidatedExactAt.clear();
71
- invalidatedPrefixAt.clear();
72
- refreshedAt.clear();
73
- invalidatedAllAt = 0;
83
+ return inFlight ?? fetch();
74
84
  }
@@ -1,30 +1,36 @@
1
+ type PersistedEntry<T> = {
2
+ value: T;
3
+ createdAt: number;
4
+ expiresAt: number | null;
5
+ };
1
6
  /**
2
- * Retrieve a persisted cache entry.
7
+ * Read a persisted cache entry from local storage.
3
8
  *
4
9
  * @param key Cache key.
5
- * @returns Cached value or undefined when missing/expired.
10
+ * @returns Persisted entry when present and not expired.
6
11
  */
7
- export declare function getPersistedEntry<T>(key: string): Promise<T | undefined>;
12
+ export declare function getPersistedEntry<T>(key: string): Promise<PersistedEntry<T> | undefined>;
8
13
  /**
9
- * Persist a cache entry with TTL.
14
+ * Persist a cache entry to local storage.
10
15
  *
11
16
  * @param key Cache key.
12
- * @param value Value to store.
13
- * @param ttl Time to live in seconds (0 = never expires).
14
- * @returns Resolves when the entry is stored.
17
+ * @param value Value to persist.
18
+ * @param ttl Optional time-to-live in milliseconds.
19
+ * @returns Nothing.
15
20
  */
16
- export declare function setPersistedEntry<T>(key: string, value: T, ttl: number): Promise<void>;
21
+ export declare function setPersistedEntry<T>(key: string, value: T, ttl?: number | null): Promise<void>;
17
22
  /**
18
- * Delete a persisted cache entry by key.
23
+ * Remove a single persisted cache entry.
19
24
  *
20
25
  * @param key Cache key.
21
- * @returns Resolves when the entry is removed.
26
+ * @returns Nothing.
22
27
  */
23
28
  export declare function deletePersistedEntry(key: string): Promise<void>;
24
29
  /**
25
- * Delete all persisted cache entries by key prefix.
30
+ * Remove persisted cache entries matching a key prefix.
26
31
  *
27
32
  * @param prefix Cache key prefix.
28
- * @returns Resolves when matching entries are removed.
33
+ * @returns Nothing.
29
34
  */
30
35
  export declare function deletePersistedByPrefix(prefix: string): Promise<void>;
36
+ export {};
@@ -1,70 +1,67 @@
1
- import { createStorage } from "unstorage";
2
- import localStorageDriver from "unstorage/drivers/localstorage";
3
- function isPersistedStorageAvailable() {
4
- return import.meta.client && typeof window.localStorage !== "undefined";
5
- }
6
- let storage = null;
7
- function getPersistedStorage() {
8
- if (!storage && isPersistedStorageAvailable()) {
9
- try {
10
- storage = createStorage({
11
- driver: localStorageDriver({ base: "nuxt-graphql:" })
12
- });
13
- } catch {
14
- storage = null;
15
- }
1
+ function getStorage() {
2
+ if (import.meta.server) {
3
+ return null;
16
4
  }
17
- return storage;
5
+ try {
6
+ return window.localStorage;
7
+ } catch {
8
+ return null;
9
+ }
10
+ }
11
+ function buildKey(key) {
12
+ return `cache:${key}`;
18
13
  }
19
14
  export async function getPersistedEntry(key) {
20
- const ps = getPersistedStorage();
21
- if (!ps) {
22
- return void 0;
23
- }
15
+ const storage = getStorage();
16
+ if (!storage) return void 0;
24
17
  try {
25
- const payload = await ps.getItem(key);
26
- if (!payload) {
27
- return void 0;
28
- }
29
- if (payload.expiresAt && payload.expiresAt < Date.now()) {
30
- await ps.removeItem(key);
18
+ const storageKey = buildKey(key);
19
+ const raw = storage.getItem(storageKey);
20
+ if (!raw) return void 0;
21
+ const entry = JSON.parse(raw);
22
+ if (entry.expiresAt !== null && Date.now() > entry.expiresAt) {
23
+ storage.removeItem(storageKey);
31
24
  return void 0;
32
25
  }
33
- return payload.value;
26
+ return entry;
34
27
  } catch {
35
28
  return void 0;
36
29
  }
37
30
  }
38
31
  export async function setPersistedEntry(key, value, ttl) {
39
- const ps = getPersistedStorage();
40
- if (!ps) {
41
- return;
42
- }
43
- const expiresAt = ttl > 0 ? Date.now() + ttl * 1e3 : null;
32
+ const storage = getStorage();
33
+ if (!storage) return;
44
34
  try {
45
- const payload = { value, expiresAt };
46
- await ps.setItem(key, payload);
35
+ const now = Date.now();
36
+ const entry = {
37
+ value,
38
+ createdAt: now,
39
+ expiresAt: ttl ? now + ttl : null
40
+ };
41
+ storage.setItem(buildKey(key), JSON.stringify(entry));
47
42
  } catch {
48
43
  }
49
44
  }
50
45
  export async function deletePersistedEntry(key) {
51
- const ps = getPersistedStorage();
52
- if (!ps) {
53
- return;
54
- }
46
+ const storage = getStorage();
47
+ if (!storage) return;
55
48
  try {
56
- await ps.removeItem(key);
49
+ storage.removeItem(buildKey(key));
57
50
  } catch {
58
51
  }
59
52
  }
60
53
  export async function deletePersistedByPrefix(prefix) {
61
- const ps = getPersistedStorage();
62
- if (!ps) {
63
- return;
64
- }
54
+ const storage = getStorage();
55
+ if (!storage) return;
65
56
  try {
66
- const keys = await ps.getKeys(prefix);
67
- await Promise.all(keys.map((key) => ps.removeItem(key)));
57
+ const fullPrefix = buildKey(prefix);
58
+ for (let i = storage.length - 1; i >= 0; i--) {
59
+ const k = storage.key(i);
60
+ if (!k) continue;
61
+ if (k.startsWith(fullPrefix)) {
62
+ storage.removeItem(k);
63
+ }
64
+ }
68
65
  } catch {
69
66
  }
70
67
  }
@@ -1,6 +1,6 @@
1
1
  import { defineNuxtPlugin, useRequestURL } from "#app";
2
2
  import { createClient } from "graphql-sse";
3
- export default defineNuxtPlugin((_nuxtApp) => {
3
+ export default defineNuxtPlugin(() => {
4
4
  const { origin } = useRequestURL();
5
5
  let sseClient;
6
6
  function getGraphQLSSEClient() {
@@ -10,6 +10,5 @@ export default defineNuxtPlugin((_nuxtApp) => {
10
10
  if (sseClient) return sseClient;
11
11
  return sseClient = createClient({ url: `${origin}/api/graphql` });
12
12
  }
13
- ;
14
13
  return { provide: { getGraphQLSSEClient } };
15
14
  });