@lewebsimple/nuxt-graphql 0.1.9 → 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 +7 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +83 -30
- package/dist/runtime/composables/useGraphQLCache.d.ts +5 -0
- package/dist/runtime/composables/useGraphQLCache.js +15 -0
- package/dist/runtime/composables/useGraphQLMutation.d.ts +1 -1
- package/dist/runtime/composables/useGraphQLMutation.js +7 -6
- package/dist/runtime/composables/useGraphQLQuery.d.ts +6 -1
- package/dist/runtime/composables/useGraphQLQuery.js +33 -7
- package/dist/runtime/composables/useGraphQLSubscription.d.ts +11 -0
- package/dist/runtime/composables/useGraphQLSubscription.js +46 -0
- package/dist/runtime/plugins/graphql.d.ts +5 -2
- package/dist/runtime/plugins/graphql.js +34 -5
- package/dist/runtime/server/utils/graphql-client.d.ts +15 -0
- package/dist/runtime/server/utils/graphql-client.js +14 -0
- package/dist/runtime/server/utils/useGraphQLMutation.d.ts +9 -0
- package/dist/runtime/server/utils/useGraphQLMutation.js +16 -0
- package/dist/runtime/server/utils/useGraphQLQuery.d.ts +4 -0
- package/dist/runtime/server/utils/useGraphQLQuery.js +7 -0
- package/dist/runtime/types/graphql-client.d.ts +10 -1
- package/dist/runtime/utils/graphql-cache.d.ts +26 -0
- package/dist/runtime/utils/graphql-cache.js +65 -0
- package/dist/runtime/utils/graphql-error.d.ts +13 -0
- package/dist/runtime/utils/graphql-error.js +24 -0
- package/dist/templates/yoga-handler.mjs +1 -0
- package/package.json +16 -12
- package/dist/runtime/composables/useGraphQL.d.ts +0 -4
- package/dist/runtime/composables/useGraphQL.js +0 -8
- /package/dist/runtime/server/{default-context.d.ts → graphql/default-context.d.ts} +0 -0
- /package/dist/runtime/server/{default-context.js → graphql/default-context.js} +0 -0
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
package/dist/module.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useLogger, defineNuxtModule, createResolver, getLayerDirectories, addServerTemplate, addServerHandler, addImportsDir, addPlugin, addTypeTemplate } from '@nuxt/kit';
|
|
1
|
+
import { useLogger, defineNuxtModule, createResolver, getLayerDirectories, addServerTemplate, addServerHandler, addImportsDir, addServerImportsDir, addPlugin, addTypeTemplate } from '@nuxt/kit';
|
|
2
2
|
import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { join, dirname, relative } from 'node:path';
|
|
4
4
|
import { glob } from 'tinyglobby';
|
|
@@ -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
|
-
|
|
185
|
-
{
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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);
|
|
@@ -238,7 +267,7 @@ const module$1 = defineNuxtModule({
|
|
|
238
267
|
const layerServerDirs = layerDirs.map(({ server }) => server);
|
|
239
268
|
const layerRootDirs = layerDirs.map(({ root }) => root);
|
|
240
269
|
const schemaPath = await findSingleFile(layerServerDirs, "graphql/schema.{ts,mjs}", true);
|
|
241
|
-
const contextPath = await findSingleFile(layerServerDirs, "graphql/context.{ts,mjs}") || resolve("./runtime/server/default-context.ts");
|
|
270
|
+
const contextPath = await findSingleFile(layerServerDirs, "graphql/context.{ts,mjs}") || resolve("./runtime/server/graphql/default-context.ts");
|
|
242
271
|
nuxt.hook("nitro:config", (config) => {
|
|
243
272
|
config.alias ||= {};
|
|
244
273
|
config.alias["#graphql/schema"] = schemaPath;
|
|
@@ -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 = {
|
|
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({
|
|
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
|
|
287
|
-
|
|
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) => {
|
|
@@ -304,6 +356,7 @@ const module$1 = defineNuxtModule({
|
|
|
304
356
|
});
|
|
305
357
|
}
|
|
306
358
|
addImportsDir(resolve("./runtime/composables"));
|
|
359
|
+
addServerImportsDir(resolve("./runtime/server/utils"));
|
|
307
360
|
addPlugin(resolve("./runtime/plugins/graphql"));
|
|
308
361
|
addTypeTemplate({
|
|
309
362
|
filename: "types/graphql-client.d.ts",
|
|
@@ -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,7 +1,7 @@
|
|
|
1
1
|
import { type MutationName, type MutationResult, type MutationVariables } from "#graphql/registry";
|
|
2
2
|
import type { IsEmptyObject } from "../utils/helpers.js";
|
|
3
3
|
export declare function useGraphQLMutation<N extends MutationName>(operationName: N): {
|
|
4
|
-
mutate: (...args: IsEmptyObject<MutationVariables<N>> extends true ? [variables?: MutationVariables<N
|
|
4
|
+
mutate: (...args: IsEmptyObject<MutationVariables<N>> extends true ? [variables?: MutationVariables<N>, headers?: HeadersInit] : [variables: MutationVariables<N>, headers?: HeadersInit]) => Promise<{
|
|
5
5
|
data: MutationResult<N> | null;
|
|
6
6
|
error: Error | null;
|
|
7
7
|
}>;
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { ref } from "vue";
|
|
2
|
-
import {
|
|
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
|
-
const {
|
|
7
|
+
const { $graphql } = useNuxtApp();
|
|
7
8
|
const pending = ref(false);
|
|
8
9
|
async function mutate(...args) {
|
|
9
10
|
pending.value = true;
|
|
10
11
|
try {
|
|
11
|
-
const
|
|
12
|
+
const [variables, headers] = args;
|
|
13
|
+
const result = await $graphql().request(document, variables, headers);
|
|
12
14
|
return { data: result, error: null };
|
|
13
|
-
} catch (
|
|
14
|
-
|
|
15
|
-
return { data: null, error };
|
|
15
|
+
} catch (error) {
|
|
16
|
+
return { data: null, error: wrapError(error) };
|
|
16
17
|
} finally {
|
|
17
18
|
pending.value = false;
|
|
18
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
|
|
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,11 +1,37 @@
|
|
|
1
|
-
import { useAsyncData } from "#imports";
|
|
2
|
-
import { hash } from "ohash";
|
|
3
|
-
import { useGraphQL } from "./useGraphQL.js";
|
|
1
|
+
import { useAsyncData, useNuxtApp, useRuntimeConfig, onScopeDispose } from "#imports";
|
|
4
2
|
import { queries } from "#graphql/registry";
|
|
3
|
+
import { cacheGet, cacheSet, dedupeGet, dedupeSet, registerRefresh, initCache, getCacheKey } from "../utils/graphql-cache.js";
|
|
5
4
|
export function useGraphQLQuery(operationName, ...args) {
|
|
5
|
+
const { $graphql } = useNuxtApp();
|
|
6
|
+
const { public: { graphql: { cache: globalCache } } } = useRuntimeConfig();
|
|
6
7
|
const document = queries[operationName];
|
|
7
|
-
const [variables,
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
|
|
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;
|
|
11
37
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type MaybeRefOrGetter, type Ref } from "vue";
|
|
2
|
+
import { type SubscriptionName, type SubscriptionResult, type SubscriptionVariables } from "#graphql/registry";
|
|
3
|
+
import { type GraphQLClientError } from "../utils/graphql-error.js";
|
|
4
|
+
import type { IsEmptyObject } from "../utils/helpers.js";
|
|
5
|
+
export type UseGraphQLSubscriptionReturn<N extends SubscriptionName> = {
|
|
6
|
+
data: Ref<SubscriptionResult<N> | null>;
|
|
7
|
+
error: Ref<GraphQLClientError | null>;
|
|
8
|
+
start: () => void;
|
|
9
|
+
stop: () => void;
|
|
10
|
+
};
|
|
11
|
+
export declare function useGraphQLSubscription<N extends SubscriptionName>(operationName: N, ...args: IsEmptyObject<SubscriptionVariables<N>> extends true ? [variables?: MaybeRefOrGetter<SubscriptionVariables<N>>] : [variables: MaybeRefOrGetter<SubscriptionVariables<N>>]): UseGraphQLSubscriptionReturn<N>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { ref, onScopeDispose, toValue } from "vue";
|
|
2
|
+
import { print } from "graphql";
|
|
3
|
+
import { useNuxtApp } from "#imports";
|
|
4
|
+
import { subscriptions } from "#graphql/registry";
|
|
5
|
+
import { wrapError } from "../utils/graphql-error.js";
|
|
6
|
+
export function useGraphQLSubscription(operationName, ...args) {
|
|
7
|
+
const { $graphqlSSE } = useNuxtApp();
|
|
8
|
+
const [variables] = args;
|
|
9
|
+
const data = ref(null);
|
|
10
|
+
const error = ref(null);
|
|
11
|
+
let unsubscribe = null;
|
|
12
|
+
function start() {
|
|
13
|
+
stop();
|
|
14
|
+
error.value = null;
|
|
15
|
+
unsubscribe = $graphqlSSE().subscribe(
|
|
16
|
+
{
|
|
17
|
+
query: print(subscriptions[operationName]),
|
|
18
|
+
variables: toValue(variables)
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
next: (result) => {
|
|
22
|
+
if (result.errors?.length) {
|
|
23
|
+
error.value = wrapError({ errors: result.errors });
|
|
24
|
+
} else if (result.data) {
|
|
25
|
+
data.value = result.data;
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
error: (e) => {
|
|
29
|
+
error.value = wrapError(e);
|
|
30
|
+
},
|
|
31
|
+
complete: () => {
|
|
32
|
+
unsubscribe = null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
function stop() {
|
|
38
|
+
unsubscribe?.();
|
|
39
|
+
unsubscribe = null;
|
|
40
|
+
}
|
|
41
|
+
if (import.meta.client) {
|
|
42
|
+
start();
|
|
43
|
+
}
|
|
44
|
+
onScopeDispose(stop);
|
|
45
|
+
return { data, error, start, stop };
|
|
46
|
+
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { GraphQLClient } from "graphql-request";
|
|
2
|
+
import { type Client as SSEClient } from "graphql-sse";
|
|
2
3
|
declare const _default: import("nuxt/app").Plugin<{
|
|
3
|
-
graphql: GraphQLClient;
|
|
4
|
+
graphql: () => GraphQLClient;
|
|
5
|
+
graphqlSSE: () => SSEClient;
|
|
4
6
|
}> & import("nuxt/app").ObjectPlugin<{
|
|
5
|
-
graphql: GraphQLClient;
|
|
7
|
+
graphql: () => GraphQLClient;
|
|
8
|
+
graphqlSSE: () => SSEClient;
|
|
6
9
|
}>;
|
|
7
10
|
export default _default;
|
|
@@ -1,12 +1,41 @@
|
|
|
1
1
|
import { GraphQLClient } from "graphql-request";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import { createClient } from "graphql-sse";
|
|
3
|
+
import { defineNuxtPlugin, useRequestHeaders, useRequestURL, useRuntimeConfig } from "#imports";
|
|
4
|
+
import { wrapError } from "../utils/graphql-error.js";
|
|
5
|
+
export default defineNuxtPlugin((nuxtApp) => {
|
|
6
|
+
const { public: { graphql: { endpoint, headers: staticHeaders } } } = useRuntimeConfig();
|
|
5
7
|
const { origin } = useRequestURL();
|
|
6
|
-
const
|
|
8
|
+
const url = `${origin}${endpoint}`;
|
|
9
|
+
const getClient = () => {
|
|
10
|
+
const headers = { ...staticHeaders };
|
|
11
|
+
if (import.meta.server) {
|
|
12
|
+
const ssrHeaders = useRequestHeaders(["cookie", "authorization"]);
|
|
13
|
+
Object.assign(headers, ssrHeaders);
|
|
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
|
+
});
|
|
23
|
+
return client;
|
|
24
|
+
};
|
|
25
|
+
let sseClient = null;
|
|
26
|
+
const getSSEClient = () => {
|
|
27
|
+
if (import.meta.server) {
|
|
28
|
+
throw new Error("SSE subscriptions are not available on the server");
|
|
29
|
+
}
|
|
30
|
+
if (!sseClient) {
|
|
31
|
+
sseClient = createClient({ url, headers: staticHeaders });
|
|
32
|
+
}
|
|
33
|
+
return sseClient;
|
|
34
|
+
};
|
|
7
35
|
return {
|
|
8
36
|
provide: {
|
|
9
|
-
graphql:
|
|
37
|
+
graphql: getClient,
|
|
38
|
+
graphqlSSE: getSSEClient
|
|
10
39
|
}
|
|
11
40
|
};
|
|
12
41
|
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { H3Event } from "h3";
|
|
2
|
+
import { GraphQLClient } from "graphql-request";
|
|
3
|
+
/**
|
|
4
|
+
* Create server-side GraphQL client instance for the given H3 event.
|
|
5
|
+
*
|
|
6
|
+
* @param event H3 event
|
|
7
|
+
*
|
|
8
|
+
* @returns GraphQL client instance
|
|
9
|
+
*/
|
|
10
|
+
export declare function getGraphQLClient(event: H3Event): GraphQLClient;
|
|
11
|
+
declare module "h3" {
|
|
12
|
+
interface H3EventContext {
|
|
13
|
+
__nuxtGraphQLClient?: GraphQLClient;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { getRequestHeaders, getRequestURL } from "h3";
|
|
2
|
+
import { GraphQLClient } from "graphql-request";
|
|
3
|
+
import { useRuntimeConfig } from "#imports";
|
|
4
|
+
export function getGraphQLClient(event) {
|
|
5
|
+
if (event.context.__nuxtGraphQLClient) {
|
|
6
|
+
return event.context.__nuxtGraphQLClient;
|
|
7
|
+
}
|
|
8
|
+
const { public: { graphql: { endpoint } } } = useRuntimeConfig();
|
|
9
|
+
const { origin } = getRequestURL(event);
|
|
10
|
+
const url = `${origin}${endpoint}`;
|
|
11
|
+
const headers = getRequestHeaders(event);
|
|
12
|
+
event.context.__nuxtGraphQLClient = new GraphQLClient(url, { headers });
|
|
13
|
+
return event.context.__nuxtGraphQLClient;
|
|
14
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { H3Event } from "h3";
|
|
2
|
+
import { type MutationName, type MutationResult, type MutationVariables } from "#graphql/registry";
|
|
3
|
+
import type { IsEmptyObject } from "../../utils/helpers.js";
|
|
4
|
+
export declare function useGraphQLMutation<N extends MutationName>(event: H3Event, operationName: N): Promise<{
|
|
5
|
+
mutate: (...args: IsEmptyObject<MutationVariables<N>> extends true ? [variables?: MutationVariables<N>, headers?: HeadersInit] : [variables: MutationVariables<N>, headers?: HeadersInit]) => Promise<{
|
|
6
|
+
data: MutationResult<N> | null;
|
|
7
|
+
error: Error | null;
|
|
8
|
+
}>;
|
|
9
|
+
}>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { getGraphQLClient } from "./graphql-client.js";
|
|
2
|
+
import { mutations } from "#graphql/registry";
|
|
3
|
+
export async function useGraphQLMutation(event, operationName) {
|
|
4
|
+
const client = getGraphQLClient(event);
|
|
5
|
+
async function mutate(...args) {
|
|
6
|
+
try {
|
|
7
|
+
const [variables, headers] = args;
|
|
8
|
+
const result = await client.request(mutations[operationName], variables, headers);
|
|
9
|
+
return { data: result, error: null };
|
|
10
|
+
} catch (e) {
|
|
11
|
+
const error = e instanceof Error ? e : new Error(String(e));
|
|
12
|
+
return { data: null, error };
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return { mutate };
|
|
16
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { H3Event } from "h3";
|
|
2
|
+
import { type QueryName, type QueryResult, type QueryVariables } from "#graphql/registry";
|
|
3
|
+
import type { IsEmptyObject } from "../../utils/helpers.js";
|
|
4
|
+
export declare function useGraphQLQuery<N extends QueryName>(event: H3Event, operationName: N, ...args: IsEmptyObject<QueryVariables<N>> extends true ? [variables?: QueryVariables<N>, headers?: HeadersInit] : [variables: QueryVariables<N>, headers?: HeadersInit]): Promise<QueryResult<N>>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { getGraphQLClient } from "./graphql-client.js";
|
|
2
|
+
import { queries } from "#graphql/registry";
|
|
3
|
+
export async function useGraphQLQuery(event, operationName, ...args) {
|
|
4
|
+
const client = getGraphQLClient(event);
|
|
5
|
+
const [variables, headers] = args;
|
|
6
|
+
return client.request(queries[operationName], variables, headers);
|
|
7
|
+
}
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import type { GraphQLClient } from "graphql-request";
|
|
2
|
+
import type { Client as SSEClient } from "graphql-sse";
|
|
3
|
+
import type { GraphQLClientError } from "../utils/graphql-error";
|
|
2
4
|
|
|
3
5
|
declare module "#app" {
|
|
4
6
|
interface NuxtApp {
|
|
5
|
-
$graphql: GraphQLClient;
|
|
7
|
+
$graphql: () => GraphQLClient;
|
|
8
|
+
$graphqlSSE: () => SSEClient;
|
|
9
|
+
}
|
|
10
|
+
interface RuntimeNuxtHooks {
|
|
11
|
+
"graphql:headers": (headers: Record<string, string>) => void | Promise<void>;
|
|
12
|
+
"graphql:error": (error: GraphQLClientError) => void;
|
|
6
13
|
}
|
|
7
14
|
}
|
|
8
15
|
|
|
@@ -10,6 +17,8 @@ declare module "nuxt/schema" {
|
|
|
10
17
|
interface PublicRuntimeConfig {
|
|
11
18
|
graphql: {
|
|
12
19
|
endpoint: string;
|
|
20
|
+
headers: Record<string, string>;
|
|
21
|
+
cache: GraphQLCacheConfig;
|
|
13
22
|
};
|
|
14
23
|
}
|
|
15
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.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"description": "Opinionated Nuxt module for using GraphQL",
|
|
5
5
|
"repository": "lewebsimple/nuxt-graphql",
|
|
6
6
|
"license": "MIT",
|
|
@@ -22,6 +22,17 @@
|
|
|
22
22
|
"files": [
|
|
23
23
|
"dist"
|
|
24
24
|
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"prepack": "nuxt-module-build build",
|
|
27
|
+
"dev": "npm run dev:prepare && nuxi dev playground",
|
|
28
|
+
"dev:build": "nuxi build playground",
|
|
29
|
+
"dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground",
|
|
30
|
+
"release": "npm run lint && npm run test && npm run prepack && changelogen --release --push && npm publish",
|
|
31
|
+
"lint": "eslint .",
|
|
32
|
+
"test": "vitest run",
|
|
33
|
+
"test:watch": "vitest watch",
|
|
34
|
+
"test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit"
|
|
35
|
+
},
|
|
25
36
|
"dependencies": {
|
|
26
37
|
"@graphql-codegen/cli": "^5.0.7",
|
|
27
38
|
"@graphql-codegen/typed-document-node": "^5.1.2",
|
|
@@ -30,11 +41,14 @@
|
|
|
30
41
|
"@graphql-typed-document-node/core": "^3.2.0",
|
|
31
42
|
"@nuxt/kit": "^4.2.2",
|
|
32
43
|
"graphql": "^16.12.0",
|
|
44
|
+
"graphql-codegen-typescript-validation-schema": "^0.18.1",
|
|
33
45
|
"graphql-request": "^7.4.0",
|
|
46
|
+
"graphql-sse": "^2.6.0",
|
|
34
47
|
"graphql-yoga": "^5.18.0",
|
|
35
48
|
"jiti": "^2.6.1",
|
|
36
49
|
"ohash": "^2.0.11",
|
|
37
|
-
"tinyglobby": "^0.2.15"
|
|
50
|
+
"tinyglobby": "^0.2.15",
|
|
51
|
+
"unstorage": "^1.17.3"
|
|
38
52
|
},
|
|
39
53
|
"devDependencies": {
|
|
40
54
|
"@nuxt/devtools": "^3.1.1",
|
|
@@ -58,15 +72,5 @@
|
|
|
58
72
|
"types": {
|
|
59
73
|
"chore": false
|
|
60
74
|
}
|
|
61
|
-
},
|
|
62
|
-
"scripts": {
|
|
63
|
-
"dev": "npm run dev:prepare && nuxi dev playground",
|
|
64
|
-
"dev:build": "nuxi build playground",
|
|
65
|
-
"dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground",
|
|
66
|
-
"release": "npm run lint && npm run test && npm run prepack && changelogen --release --push && npm publish",
|
|
67
|
-
"lint": "eslint .",
|
|
68
|
-
"test": "vitest run",
|
|
69
|
-
"test:watch": "vitest watch",
|
|
70
|
-
"test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit"
|
|
71
75
|
}
|
|
72
76
|
}
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { TypedDocumentNode } from "@graphql-typed-document-node/core";
|
|
2
|
-
export declare function useGraphQL(): {
|
|
3
|
-
request: <TResult, TVariables extends Record<string, unknown> | undefined>(document: TypedDocumentNode<TResult, TVariables>, variables?: TVariables) => Promise<TResult>;
|
|
4
|
-
};
|
|
File without changes
|
|
File without changes
|