@lewebsimple/nuxt-graphql 0.1.10 → 0.1.11

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,10 +1,17 @@
1
1
  import * as _nuxt_schema from '@nuxt/schema';
2
+ import { GraphQLCacheConfig } from '../dist/runtime/utils/graphql-cache.js';
2
3
 
3
4
  interface ModuleOptions {
4
5
  endpoint?: string;
6
+ headers?: Record<string, string>;
7
+ cache?: Partial<GraphQLCacheConfig>;
5
8
  codegen?: {
6
9
  pattern?: string;
7
10
  schemaOutput?: string;
11
+ scalars?: Record<string, string | {
12
+ input: string;
13
+ output: string;
14
+ }>;
8
15
  };
9
16
  }
10
17
  declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lewebsimple/nuxt-graphql",
3
3
  "configKey": "graphql",
4
- "version": "0.1.10",
4
+ "version": "0.1.11",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -175,36 +175,65 @@ function formatDefinitions(defs) {
175
175
  return defs.map((def) => `${colorOf(def)}${def.name}${reset}`).join(`${dim} / ${reset}`);
176
176
  }
177
177
  async function runCodegen(options) {
178
- const { sdl, documents, operationsFile } = options;
178
+ const { sdl, documents, operationsFile, schemasFile, scalars } = options;
179
179
  if (documents.length === 0) {
180
180
  logger.warn("No GraphQL documents found");
181
181
  return;
182
182
  }
183
+ const zodScalars = {};
184
+ if (scalars) {
185
+ for (const [name, config] of Object.entries(scalars)) {
186
+ const inputType = typeof config === "string" ? config : config.input;
187
+ switch (inputType) {
188
+ case "Date":
189
+ zodScalars[name] = "z.coerce.date()";
190
+ break;
191
+ case "number":
192
+ zodScalars[name] = "z.coerce.number()";
193
+ break;
194
+ case "boolean":
195
+ zodScalars[name] = "z.coerce.boolean()";
196
+ break;
197
+ default:
198
+ zodScalars[name] = "z.string()";
199
+ }
200
+ }
201
+ }
183
202
  try {
184
- await generate(
185
- {
186
- schema: sdl,
187
- documents,
188
- generates: {
189
- [operationsFile]: {
190
- plugins: ["typescript", "typescript-operations", "typed-document-node"],
191
- config: {
192
- useTypeImports: true,
193
- enumsAsTypes: true,
194
- skipTypename: true,
195
- documentVariableSuffix: "Document",
196
- documentMode: "documentNode",
197
- strictScalars: true,
198
- defaultScalarType: "never"
199
- // TODO: Make codegen config customizable, e.g. for custom scalars
203
+ const generates = {
204
+ [operationsFile]: {
205
+ plugins: ["typescript", "typescript-operations", "typed-document-node"],
206
+ config: {
207
+ useTypeImports: true,
208
+ enumsAsTypes: true,
209
+ skipTypename: true,
210
+ documentVariableSuffix: "Document",
211
+ documentMode: "documentNode",
212
+ strictScalars: true,
213
+ defaultScalarType: "never",
214
+ scalars
215
+ }
216
+ }
217
+ };
218
+ if (schemasFile) {
219
+ generates[schemasFile] = {
220
+ plugins: ["typescript-validation-schema"],
221
+ config: {
222
+ schema: "zodv4",
223
+ importFrom: "#graphql/operations",
224
+ useTypeImports: true,
225
+ directives: {
226
+ constraint: {
227
+ minLength: "min",
228
+ maxLength: "max",
229
+ pattern: "regex"
200
230
  }
201
- }
202
- },
203
- silent: true,
204
- errorsOnly: true
205
- },
206
- true
207
- );
231
+ },
232
+ scalarSchemas: zodScalars
233
+ }
234
+ };
235
+ }
236
+ await generate({ schema: sdl, documents, generates, silent: true, errorsOnly: true }, true);
208
237
  logger.success(`Generated types for ${documents.length} document(s)`);
209
238
  } catch (error) {
210
239
  logger.error("GraphQL codegen failed:", error instanceof Error ? error.message : error);
@@ -253,13 +282,23 @@ const module$1 = defineNuxtModule({
253
282
  nuxt.hook("listen", (_, { url }) => {
254
283
  logger.success(`GraphQL Yoga ready at ${cyan}${url.replace(/\/$/, "")}${endpoint}${reset}`);
255
284
  });
256
- nuxt.options.runtimeConfig.public.graphql = { endpoint };
285
+ nuxt.options.runtimeConfig.public.graphql = {
286
+ endpoint,
287
+ headers: options.headers || {},
288
+ cache: {
289
+ enabled: options.cache?.enabled ?? false,
290
+ ttl: options.cache?.ttl ?? 6e4,
291
+ storage: options.cache?.storage ?? "memory"
292
+ }
293
+ };
257
294
  const codegenPattern = options.codegen?.pattern ?? "**/*.gql";
258
295
  const graphqlrcFile = join(rootDir, ".graphqlrc");
259
296
  const operationsFile = join(nuxt.options.buildDir, "graphql/operations.ts");
260
297
  const registryFile = join(nuxt.options.buildDir, "graphql/registry.ts");
298
+ const schemasFile = join(nuxt.options.buildDir, "graphql/schemas.ts");
261
299
  nuxt.options.alias["#graphql/operations"] = operationsFile;
262
300
  nuxt.options.alias["#graphql/registry"] = registryFile;
301
+ nuxt.options.alias["#graphql/schemas"] = schemasFile;
263
302
  const schemaOutput = options.codegen?.schemaOutput ?? "server/graphql/schema.graphql";
264
303
  if (schemaOutput) {
265
304
  if (!schemaOutput.endsWith(".graphql")) {
@@ -279,12 +318,24 @@ const module$1 = defineNuxtModule({
279
318
  const defs = analysis.byFile.get(doc.path) ?? [];
280
319
  logger.info(`${cyan}${relativePath}${reset} [${formatDefinitions(defs)}]`);
281
320
  }
282
- await runCodegen({ sdl, documents, operationsFile });
321
+ await runCodegen({
322
+ sdl,
323
+ documents,
324
+ operationsFile,
325
+ schemasFile,
326
+ scalars: options.codegen?.scalars
327
+ });
283
328
  if (writeFileIfChanged(schemaFile, sdl)) {
284
329
  logger.info(`GraphQL schema saved to ${cyan}${schemaOutput}${reset}`);
285
330
  }
286
- const config = JSON.stringify({ schema: relative(rootDir, schemaFile), documents: codegenPattern }, null, 2);
287
- if (writeFileIfChanged(graphqlrcFile, config)) {
331
+ const graphqlrc = {
332
+ schema: relative(rootDir, schemaFile),
333
+ documents: codegenPattern
334
+ };
335
+ if (options.codegen?.scalars) {
336
+ graphqlrc.scalars = options.codegen.scalars;
337
+ }
338
+ if (writeFileIfChanged(graphqlrcFile, JSON.stringify(graphqlrc, null, 2))) {
288
339
  logger.info(`GraphQL config saved to ${cyan}.graphqlrc${reset}`);
289
340
  }
290
341
  if (writeFileIfChanged(registryFile, generateRegistryByTypeSource(analysis.operationsByType))) {
@@ -295,6 +346,7 @@ const module$1 = defineNuxtModule({
295
346
  await generate();
296
347
  if (existsSync(operationsFile)) references.push({ path: operationsFile });
297
348
  if (existsSync(registryFile)) references.push({ path: registryFile });
349
+ if (existsSync(schemasFile)) references.push({ path: schemasFile });
298
350
  });
299
351
  if (nuxt.options.dev) {
300
352
  nuxt.hook("builder:watch", async (event, path) => {
@@ -0,0 +1,5 @@
1
+ import type { QueryName, QueryVariables } from "#graphql/registry";
2
+ export declare function useGraphQLCache(): {
3
+ enabled: any;
4
+ invalidate: <N extends QueryName>(operationName?: N, variables?: QueryVariables<N>) => Promise<void>;
5
+ };
@@ -0,0 +1,15 @@
1
+ import { useRuntimeConfig } from "#imports";
2
+ import { cacheInvalidate, initCache } from "../utils/graphql-cache.js";
3
+ export function useGraphQLCache() {
4
+ const { public: { graphql: { cache: cacheConfig } } } = useRuntimeConfig();
5
+ if (import.meta.client && cacheConfig.enabled) {
6
+ initCache(cacheConfig.storage);
7
+ }
8
+ async function invalidate(operationName, variables) {
9
+ await cacheInvalidate(operationName, variables);
10
+ }
11
+ return {
12
+ enabled: cacheConfig.enabled,
13
+ invalidate
14
+ };
15
+ }
@@ -1,6 +1,7 @@
1
1
  import { ref } from "vue";
2
2
  import { useNuxtApp } from "#imports";
3
3
  import { mutations } from "#graphql/registry";
4
+ import { wrapError } from "../utils/graphql-error.js";
4
5
  export function useGraphQLMutation(operationName) {
5
6
  const document = mutations[operationName];
6
7
  const { $graphql } = useNuxtApp();
@@ -11,9 +12,8 @@ export function useGraphQLMutation(operationName) {
11
12
  const [variables, headers] = args;
12
13
  const result = await $graphql().request(document, variables, headers);
13
14
  return { data: result, error: null };
14
- } catch (e) {
15
- const error = e instanceof Error ? e : new Error(String(e));
16
- return { data: null, error };
15
+ } catch (error) {
16
+ return { data: null, error: wrapError(error) };
17
17
  } finally {
18
18
  pending.value = false;
19
19
  }
@@ -1,4 +1,9 @@
1
1
  import type { AsyncData, AsyncDataOptions } from "#app";
2
2
  import { type QueryName, type QueryResult, type QueryVariables } from "#graphql/registry";
3
+ import { type CacheOptions } from "../utils/graphql-cache.js";
3
4
  import type { IsEmptyObject } from "../utils/helpers.js";
4
- export declare function useGraphQLQuery<N extends QueryName>(operationName: N, ...args: IsEmptyObject<QueryVariables<N>> extends true ? [variables?: QueryVariables<N>, options?: AsyncDataOptions<QueryResult<N>>, headers?: HeadersInit] : [variables: QueryVariables<N>, options?: AsyncDataOptions<QueryResult<N>>, headers?: HeadersInit]): AsyncData<QueryResult<N>, Error | null>;
5
+ export interface UseGraphQLQueryOptions<T> extends AsyncDataOptions<T> {
6
+ cache?: CacheOptions | false;
7
+ headers?: HeadersInit;
8
+ }
9
+ export declare function useGraphQLQuery<N extends QueryName>(operationName: N, ...args: IsEmptyObject<QueryVariables<N>> extends true ? [variables?: QueryVariables<N>, options?: UseGraphQLQueryOptions<QueryResult<N>>] : [variables: QueryVariables<N>, options?: UseGraphQLQueryOptions<QueryResult<N>>]): AsyncData<QueryResult<N>, Error | null>;
@@ -1,10 +1,37 @@
1
- import { useAsyncData, useNuxtApp } from "#imports";
2
- import { hash } from "ohash";
1
+ import { useAsyncData, useNuxtApp, useRuntimeConfig, onScopeDispose } from "#imports";
3
2
  import { queries } from "#graphql/registry";
3
+ import { cacheGet, cacheSet, dedupeGet, dedupeSet, registerRefresh, initCache, getCacheKey } from "../utils/graphql-cache.js";
4
4
  export function useGraphQLQuery(operationName, ...args) {
5
5
  const { $graphql } = useNuxtApp();
6
+ const { public: { graphql: { cache: globalCache } } } = useRuntimeConfig();
6
7
  const document = queries[operationName];
7
- const [variables, options, headers] = args;
8
- const key = `graphql:query:${operationName}:${hash(variables ?? {})}`;
9
- return useAsyncData(key, () => $graphql().request(document, variables, headers), options);
8
+ const [variables, options] = args;
9
+ const cacheEnabled = options?.cache !== false && globalCache.enabled;
10
+ const cacheTtl = options?.cache === false ? 0 : options?.cache?.ttl ?? globalCache.ttl;
11
+ if (import.meta.client && cacheEnabled) {
12
+ initCache(globalCache.storage);
13
+ }
14
+ const key = getCacheKey(operationName, variables);
15
+ const fetcher = async () => {
16
+ if (import.meta.client && cacheEnabled) {
17
+ const cached = await cacheGet(operationName, variables);
18
+ if (cached) return cached;
19
+ }
20
+ const inFlight = dedupeGet(operationName, variables);
21
+ if (inFlight) return inFlight;
22
+ const promise = $graphql().request(document, variables, options?.headers);
23
+ dedupeSet(operationName, variables, promise);
24
+ const result = await promise;
25
+ if (import.meta.client && cacheEnabled && cacheTtl) {
26
+ await cacheSet(operationName, variables, result, cacheTtl);
27
+ }
28
+ return result;
29
+ };
30
+ const { cache: _cache, headers: _headers, ...asyncDataOptions } = options ?? {};
31
+ const asyncData = useAsyncData(key, fetcher, asyncDataOptions);
32
+ if (import.meta.client) {
33
+ const unregister = registerRefresh(operationName, variables, () => asyncData.refresh());
34
+ onScopeDispose(unregister);
35
+ }
36
+ return asyncData;
10
37
  }
@@ -1,9 +1,10 @@
1
1
  import { type MaybeRefOrGetter, type Ref } from "vue";
2
2
  import { type SubscriptionName, type SubscriptionResult, type SubscriptionVariables } from "#graphql/registry";
3
+ import { type GraphQLClientError } from "../utils/graphql-error.js";
3
4
  import type { IsEmptyObject } from "../utils/helpers.js";
4
5
  export type UseGraphQLSubscriptionReturn<N extends SubscriptionName> = {
5
6
  data: Ref<SubscriptionResult<N> | null>;
6
- error: Ref<Error | null>;
7
+ error: Ref<GraphQLClientError | null>;
7
8
  start: () => void;
8
9
  stop: () => void;
9
10
  };
@@ -2,6 +2,7 @@ import { ref, onScopeDispose, toValue } from "vue";
2
2
  import { print } from "graphql";
3
3
  import { useNuxtApp } from "#imports";
4
4
  import { subscriptions } from "#graphql/registry";
5
+ import { wrapError } from "../utils/graphql-error.js";
5
6
  export function useGraphQLSubscription(operationName, ...args) {
6
7
  const { $graphqlSSE } = useNuxtApp();
7
8
  const [variables] = args;
@@ -19,13 +20,13 @@ export function useGraphQLSubscription(operationName, ...args) {
19
20
  {
20
21
  next: (result) => {
21
22
  if (result.errors?.length) {
22
- error.value = new Error(result.errors.map((e) => e.message).join(", "));
23
+ error.value = wrapError({ errors: result.errors });
23
24
  } else if (result.data) {
24
25
  data.value = result.data;
25
26
  }
26
27
  },
27
28
  error: (e) => {
28
- error.value = e instanceof Error ? e : new Error(String(e));
29
+ error.value = wrapError(e);
29
30
  },
30
31
  complete: () => {
31
32
  unsubscribe = null;
@@ -1,19 +1,25 @@
1
1
  import { GraphQLClient } from "graphql-request";
2
2
  import { createClient } from "graphql-sse";
3
3
  import { defineNuxtPlugin, useRequestHeaders, useRequestURL, useRuntimeConfig } from "#imports";
4
- export default defineNuxtPlugin(() => {
5
- const { public: { graphql: { endpoint } } } = useRuntimeConfig();
4
+ import { wrapError } from "../utils/graphql-error.js";
5
+ export default defineNuxtPlugin((nuxtApp) => {
6
+ const { public: { graphql: { endpoint, headers: staticHeaders } } } = useRuntimeConfig();
6
7
  const { origin } = useRequestURL();
7
8
  const url = `${origin}${endpoint}`;
8
- let client = null;
9
9
  const getClient = () => {
10
- if (!client) {
11
- client = new GraphQLClient(url);
12
- }
10
+ const headers = { ...staticHeaders };
13
11
  if (import.meta.server) {
14
- const headers = useRequestHeaders(["cookie", "authorization"]);
15
- client.setHeaders(headers);
12
+ const ssrHeaders = useRequestHeaders(["cookie", "authorization"]);
13
+ Object.assign(headers, ssrHeaders);
16
14
  }
15
+ const client = new GraphQLClient(url, {
16
+ headers,
17
+ responseMiddleware: (response) => {
18
+ if (response instanceof Error) {
19
+ nuxtApp.callHook("graphql:error", wrapError(response));
20
+ }
21
+ }
22
+ });
17
23
  return client;
18
24
  };
19
25
  let sseClient = null;
@@ -22,7 +28,7 @@ export default defineNuxtPlugin(() => {
22
28
  throw new Error("SSE subscriptions are not available on the server");
23
29
  }
24
30
  if (!sseClient) {
25
- sseClient = createClient({ url });
31
+ sseClient = createClient({ url, headers: staticHeaders });
26
32
  }
27
33
  return sseClient;
28
34
  };
@@ -1,17 +1,24 @@
1
1
  import type { GraphQLClient } from "graphql-request";
2
2
  import type { Client as SSEClient } from "graphql-sse";
3
+ import type { GraphQLClientError } from "../utils/graphql-error";
3
4
 
4
5
  declare module "#app" {
5
6
  interface NuxtApp {
6
7
  $graphql: () => GraphQLClient;
7
8
  $graphqlSSE: () => SSEClient;
8
9
  }
10
+ interface RuntimeNuxtHooks {
11
+ "graphql:headers": (headers: Record<string, string>) => void | Promise<void>;
12
+ "graphql:error": (error: GraphQLClientError) => void;
13
+ }
9
14
  }
10
15
 
11
16
  declare module "nuxt/schema" {
12
17
  interface PublicRuntimeConfig {
13
18
  graphql: {
14
19
  endpoint: string;
20
+ headers: Record<string, string>;
21
+ cache: GraphQLCacheConfig;
15
22
  };
16
23
  }
17
24
  }
@@ -0,0 +1,26 @@
1
+ export interface GraphQLCacheConfig {
2
+ enabled: boolean;
3
+ ttl: number;
4
+ storage: "memory" | "localStorage";
5
+ }
6
+ export interface CacheOptions {
7
+ enabled?: boolean;
8
+ ttl?: number;
9
+ }
10
+ export declare function initCache(type: "memory" | "localStorage"): void;
11
+ export declare function getCacheKey(operationName: string, variables: unknown): string;
12
+ export declare function cacheGet<T>(operationName: string, variables: unknown): Promise<T | null>;
13
+ export declare function cacheSet<T>(operationName: string, variables: unknown, data: T, ttl: number): Promise<void>;
14
+ export declare function dedupeGet(operationName: string, variables: unknown): Promise<unknown> | null;
15
+ export declare function dedupeSet(operationName: string, variables: unknown, promise: Promise<unknown>): void;
16
+ /**
17
+ * Register a refresh callback for a query
18
+ */
19
+ export declare function registerRefresh(operationName: string, variables: unknown, refresh: () => void): () => void;
20
+ /**
21
+ * Invalidate cached queries and trigger refreshes
22
+ * - No args: invalidate all
23
+ * - operationName only: invalidate all queries with that name
24
+ * - operationName + variables: invalidate specific query
25
+ */
26
+ export declare function cacheInvalidate(operationName?: string, variables?: unknown): Promise<void>;
@@ -0,0 +1,65 @@
1
+ import { createStorage } from "unstorage";
2
+ import memoryDriver from "unstorage/drivers/memory";
3
+ import localStorageDriver from "unstorage/drivers/localstorage";
4
+ import { hash } from "ohash";
5
+ const inFlight = /* @__PURE__ */ new Map();
6
+ const refreshCallbacks = /* @__PURE__ */ new Map();
7
+ let storage = null;
8
+ export function initCache(type) {
9
+ if (storage) return;
10
+ storage = createStorage({
11
+ driver: type === "localStorage" ? localStorageDriver({ base: "graphql:" }) : memoryDriver()
12
+ });
13
+ }
14
+ export function getCacheKey(operationName, variables) {
15
+ return `${operationName}:${hash(variables ?? {})}`;
16
+ }
17
+ export async function cacheGet(operationName, variables) {
18
+ if (!storage) return null;
19
+ const key = getCacheKey(operationName, variables);
20
+ const entry = await storage.getItem(key);
21
+ if (!entry) return null;
22
+ if (Date.now() > entry.expires) {
23
+ await storage.removeItem(key);
24
+ return null;
25
+ }
26
+ return entry.data;
27
+ }
28
+ export async function cacheSet(operationName, variables, data, ttl) {
29
+ if (!storage) return;
30
+ const key = getCacheKey(operationName, variables);
31
+ const entry = { data, expires: Date.now() + ttl };
32
+ await storage.setItem(key, entry);
33
+ }
34
+ export function dedupeGet(operationName, variables) {
35
+ const key = getCacheKey(operationName, variables);
36
+ return inFlight.get(key) ?? null;
37
+ }
38
+ export function dedupeSet(operationName, variables, promise) {
39
+ const key = getCacheKey(operationName, variables);
40
+ inFlight.set(key, promise);
41
+ promise.finally(() => inFlight.delete(key));
42
+ }
43
+ export function registerRefresh(operationName, variables, refresh) {
44
+ const key = getCacheKey(operationName, variables);
45
+ if (!refreshCallbacks.has(key)) {
46
+ refreshCallbacks.set(key, /* @__PURE__ */ new Set());
47
+ }
48
+ refreshCallbacks.get(key).add(refresh);
49
+ return () => refreshCallbacks.get(key)?.delete(refresh);
50
+ }
51
+ export async function cacheInvalidate(operationName, variables) {
52
+ if (!storage) return;
53
+ if (operationName && variables !== void 0) {
54
+ const key = getCacheKey(operationName, variables);
55
+ await storage.removeItem(key);
56
+ refreshCallbacks.get(key)?.forEach((cb) => cb());
57
+ } else {
58
+ const keys = await storage.getKeys();
59
+ const toRemove = operationName ? keys.filter((k) => k.startsWith(`${operationName}:`)) : keys;
60
+ await Promise.all(toRemove.map((k) => storage.removeItem(k)));
61
+ for (const key of toRemove) {
62
+ refreshCallbacks.get(key)?.forEach((cb) => cb());
63
+ }
64
+ }
65
+ }
@@ -0,0 +1,13 @@
1
+ import type { GraphQLError } from "graphql";
2
+ export declare class GraphQLClientError extends Error {
3
+ readonly errors: GraphQLError[];
4
+ constructor(message: string, errors?: GraphQLError[]);
5
+ }
6
+ /**
7
+ * Wrap a generic error into a GraphQLClientError
8
+ *
9
+ * @param error Generic error
10
+ *
11
+ * @returns Wrapped GraphQLClientError
12
+ */
13
+ export declare function wrapError(error: unknown): GraphQLClientError;
@@ -0,0 +1,24 @@
1
+ export class GraphQLClientError extends Error {
2
+ errors;
3
+ constructor(message, errors = []) {
4
+ super(message);
5
+ this.name = "GraphQLClientError";
6
+ this.errors = errors;
7
+ }
8
+ }
9
+ export function wrapError(error) {
10
+ if (error instanceof GraphQLClientError) {
11
+ return error;
12
+ }
13
+ if (error && typeof error === "object" && "response" in error) {
14
+ const clientError = error;
15
+ return new GraphQLClientError(clientError.message, clientError.response?.errors);
16
+ }
17
+ if (error && typeof error === "object" && "errors" in error) {
18
+ const { errors } = error;
19
+ const message2 = errors.map((e) => e.message).join(", ");
20
+ return new GraphQLClientError(message2, errors);
21
+ }
22
+ const message = error instanceof Error ? error.message : String(error);
23
+ return new GraphQLClientError(message);
24
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lewebsimple/nuxt-graphql",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "Opinionated Nuxt module for using GraphQL",
5
5
  "repository": "lewebsimple/nuxt-graphql",
6
6
  "license": "MIT",
@@ -41,12 +41,14 @@
41
41
  "@graphql-typed-document-node/core": "^3.2.0",
42
42
  "@nuxt/kit": "^4.2.2",
43
43
  "graphql": "^16.12.0",
44
+ "graphql-codegen-typescript-validation-schema": "^0.18.1",
44
45
  "graphql-request": "^7.4.0",
45
46
  "graphql-sse": "^2.6.0",
46
47
  "graphql-yoga": "^5.18.0",
47
48
  "jiti": "^2.6.1",
48
49
  "ohash": "^2.0.11",
49
- "tinyglobby": "^0.2.15"
50
+ "tinyglobby": "^0.2.15",
51
+ "unstorage": "^1.17.3"
50
52
  },
51
53
  "devDependencies": {
52
54
  "@nuxt/devtools": "^3.1.1",