@lewebsimple/nuxt-graphql 0.2.1 → 0.3.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.
- package/README.md +366 -30
- package/dist/module.d.mts +34 -21
- package/dist/module.json +1 -1
- package/dist/module.mjs +336 -355
- package/dist/runtime/app/composables/useGraphQLCache.client.d.ts +10 -0
- package/dist/runtime/app/composables/useGraphQLCache.client.js +41 -0
- package/dist/runtime/app/composables/useGraphQLMutation.d.ts +17 -8
- package/dist/runtime/app/composables/useGraphQLMutation.js +10 -9
- package/dist/runtime/app/composables/useGraphQLQuery.d.ts +5 -12
- package/dist/runtime/app/composables/useGraphQLQuery.js +74 -28
- package/dist/runtime/app/composables/useGraphQLSubscription.d.ts +5 -6
- package/dist/runtime/app/composables/useGraphQLSubscription.js +13 -12
- package/dist/runtime/app/lib/graphql-cache.d.ts +7 -0
- package/dist/runtime/app/lib/graphql-cache.js +24 -0
- package/dist/runtime/app/lib/persisted.d.ts +4 -0
- package/dist/runtime/app/lib/persisted.js +70 -0
- package/dist/runtime/app/plugins/graphql-request.d.ts +7 -0
- package/dist/runtime/app/plugins/graphql-request.js +21 -0
- package/dist/runtime/app/plugins/graphql-sse.client.d.ts +7 -0
- package/dist/runtime/app/plugins/graphql-sse.client.js +15 -0
- package/dist/runtime/app/types/nuxt-graphql.d.ts +37 -0
- package/dist/runtime/server/api/yoga-handler.js +42 -0
- package/dist/runtime/server/lib/define-graphql-context.d.ts +5 -0
- package/dist/runtime/server/lib/define-graphql-context.js +4 -0
- package/dist/runtime/server/lib/define-remote-exec-middleware.d.ts +30 -0
- package/dist/runtime/server/lib/define-remote-exec-middleware.js +3 -0
- package/dist/runtime/server/lib/define-yoga-middleware.d.ts +21 -0
- package/dist/runtime/server/lib/define-yoga-middleware.js +3 -0
- package/dist/runtime/server/lib/execute-server-graphql.d.ts +7 -0
- package/dist/runtime/server/lib/execute-server-graphql.js +34 -0
- package/dist/runtime/server/lib/remote-executor.d.ts +35 -0
- package/dist/runtime/server/lib/remote-executor.js +64 -0
- package/dist/runtime/server/tsconfig.json +1 -1
- package/dist/runtime/server/utils/useServerGraphQLMutation.d.ts +8 -14
- package/dist/runtime/server/utils/useServerGraphQLMutation.js +8 -11
- package/dist/runtime/server/utils/useServerGraphQLQuery.d.ts +2 -10
- package/dist/runtime/server/utils/useServerGraphQLQuery.js +3 -4
- package/dist/runtime/shared/lib/graphql-error.d.ts +17 -0
- package/dist/runtime/shared/lib/graphql-error.js +28 -0
- package/dist/runtime/shared/lib/headers.d.ts +3 -0
- package/dist/runtime/shared/lib/headers.js +39 -0
- package/dist/types.d.mts +13 -1
- package/package.json +10 -14
- package/dist/runtime/app/composables/useGraphQLCache.d.ts +0 -10
- package/dist/runtime/app/composables/useGraphQLCache.js +0 -15
- package/dist/runtime/app/plugins/graphql.d.ts +0 -31
- package/dist/runtime/app/plugins/graphql.js +0 -42
- package/dist/runtime/app/utils/graphql-cache.d.ts +0 -36
- package/dist/runtime/app/utils/graphql-cache.js +0 -65
- package/dist/runtime/app/utils/graphql-error.d.ts +0 -12
- package/dist/runtime/app/utils/graphql-error.js +0 -24
- package/dist/runtime/server/api/graphql-handler.js +0 -14
- package/dist/runtime/server/lib/constants.d.ts +0 -1
- package/dist/runtime/server/lib/constants.js +0 -1
- package/dist/runtime/server/lib/create-yoga.d.ts +0 -1
- package/dist/runtime/server/lib/create-yoga.js +0 -17
- package/dist/runtime/server/lib/default-context.d.ts +0 -7
- package/dist/runtime/server/lib/default-context.js +0 -1
- package/dist/runtime/server/utils/graphql-client.d.ts +0 -14
- package/dist/runtime/server/utils/graphql-client.js +0 -14
- /package/dist/runtime/server/api/{graphql-handler.d.ts → yoga-handler.d.ts} +0 -0
package/dist/module.mjs
CHANGED
|
@@ -1,19 +1,34 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { join, relative, resolve, dirname } from 'node:path';
|
|
2
|
+
import { useLogger, defineNuxtModule, createResolver, getLayerDirectories, addServerHandler, addServerImportsDir, addPlugin, addImportsDir } from '@nuxt/kit';
|
|
3
3
|
import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { glob } from 'tinyglobby';
|
|
5
5
|
import { generate } from '@graphql-codegen/cli';
|
|
6
|
-
import { parse
|
|
7
|
-
|
|
6
|
+
import { parse, Kind } from 'graphql';
|
|
7
|
+
export { defineGraphQLContext } from '../dist/runtime/server/lib/define-graphql-context.js';
|
|
8
|
+
export { defineRemoteExecMiddleware } from '../dist/runtime/server/lib/define-remote-exec-middleware.js';
|
|
9
|
+
export { defineYogaMiddleware } from '../dist/runtime/server/lib/define-yoga-middleware.js';
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
function removeFileExtension(filePath) {
|
|
12
|
+
return filePath.replace(/\.[^/.]+$/, "");
|
|
13
|
+
}
|
|
14
|
+
function getGenericServerProxy(modulePath) {
|
|
15
|
+
return `export * from ${JSON.stringify(removeFileExtension(modulePath))};`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getGraphQLContextProxy(contextPath) {
|
|
19
|
+
if (!contextPath) {
|
|
20
|
+
return [
|
|
21
|
+
`export const getGraphQLContext = async () => ({});`,
|
|
22
|
+
`export type GraphQLContext = Awaited<ReturnType<typeof getGraphQLContext>>;`
|
|
23
|
+
].join("\n");
|
|
24
|
+
}
|
|
25
|
+
return [
|
|
26
|
+
`import context from ${JSON.stringify(removeFileExtension(contextPath))};`,
|
|
27
|
+
``,
|
|
28
|
+
`export const getGraphQLContext = context.getGraphQLContext;`,
|
|
29
|
+
`export type GraphQLContext = Awaited<ReturnType<typeof getGraphQLContext>>;`
|
|
30
|
+
].join("\n");
|
|
31
|
+
}
|
|
17
32
|
|
|
18
33
|
async function findSingleFile(dirs, pattern, isRequired = false) {
|
|
19
34
|
for (const dir of dirs) {
|
|
@@ -24,7 +39,7 @@ async function findSingleFile(dirs, pattern, isRequired = false) {
|
|
|
24
39
|
}
|
|
25
40
|
}
|
|
26
41
|
if (isRequired) {
|
|
27
|
-
throw new Error(`File not found: ${
|
|
42
|
+
throw new Error(`File not found: ${pattern} in directories:
|
|
28
43
|
${dirs.join("\n")}`);
|
|
29
44
|
}
|
|
30
45
|
}
|
|
@@ -44,288 +59,253 @@ function writeFileIfChanged(path, content) {
|
|
|
44
59
|
writeFileSync(path, content, "utf-8");
|
|
45
60
|
return true;
|
|
46
61
|
}
|
|
47
|
-
function
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (!
|
|
51
|
-
|
|
62
|
+
function toRelativePath(from, to) {
|
|
63
|
+
let relativePath = relative(resolve(from), resolve(to));
|
|
64
|
+
relativePath = relativePath.replace(/\\/g, "/");
|
|
65
|
+
if (!relativePath.startsWith("./") && !relativePath.startsWith("../")) {
|
|
66
|
+
relativePath = `./${relativePath}`;
|
|
52
67
|
}
|
|
53
|
-
return
|
|
68
|
+
return relativePath;
|
|
54
69
|
}
|
|
55
70
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return printSchema(lexicographicSortSchema(module.schema));
|
|
71
|
+
const defaultCacheConfig = {
|
|
72
|
+
cachePolicy: "cache-first",
|
|
73
|
+
cacheVersion: "1",
|
|
74
|
+
keyPrefix: "gql",
|
|
75
|
+
ttl: 60
|
|
76
|
+
};
|
|
77
|
+
function resolveCacheConfig(...overrides) {
|
|
78
|
+
return Object.assign({}, defaultCacheConfig, ...overrides);
|
|
65
79
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
zodScalars[name] = "z.coerce.number()";
|
|
95
|
-
break;
|
|
96
|
-
case "boolean":
|
|
97
|
-
zodScalars[name] = "z.coerce.boolean()";
|
|
98
|
-
break;
|
|
99
|
-
default:
|
|
100
|
-
zodScalars[name] = "z.string()";
|
|
80
|
+
|
|
81
|
+
async function runGraphQLCodegen({ schema, documents, typedDocumentsPath, zodPath }) {
|
|
82
|
+
const config = {
|
|
83
|
+
schema,
|
|
84
|
+
documents,
|
|
85
|
+
generates: {
|
|
86
|
+
// Typed document nodes
|
|
87
|
+
[typedDocumentsPath]: {
|
|
88
|
+
plugins: ["typescript", "typescript-operations", "typed-document-node"],
|
|
89
|
+
config: {
|
|
90
|
+
defaultScalarType: "never",
|
|
91
|
+
documentMode: "documentNode",
|
|
92
|
+
documentVariableSuffix: "Document",
|
|
93
|
+
enumsAsTypes: true,
|
|
94
|
+
inlineFragmentTypes: "combine",
|
|
95
|
+
preResolveTypes: false,
|
|
96
|
+
skipTypename: true,
|
|
97
|
+
strictScalars: true,
|
|
98
|
+
useTypeImports: true
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
// Zod schemas
|
|
102
|
+
[zodPath]: {
|
|
103
|
+
plugins: ["typescript-validation-schema"],
|
|
104
|
+
config: {
|
|
105
|
+
schema: "zodv4",
|
|
106
|
+
importFrom: "#graphql/operations",
|
|
107
|
+
useTypeImports: true
|
|
101
108
|
}
|
|
102
109
|
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
schema: "zodv4",
|
|
110
|
-
importFrom: "#graphql/operations",
|
|
111
|
-
useTypeImports: true,
|
|
112
|
-
directives: {
|
|
113
|
-
constraint: {
|
|
114
|
-
minLength: "min",
|
|
115
|
-
maxLength: "max",
|
|
116
|
-
pattern: "regex"
|
|
117
|
-
}
|
|
118
|
-
},
|
|
119
|
-
scalarSchemas: zodScalars
|
|
120
|
-
}
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
await generate({ generates, silent: true, errorsOnly: true }, true);
|
|
110
|
+
},
|
|
111
|
+
ignoreNoDocuments: true,
|
|
112
|
+
silent: true
|
|
113
|
+
};
|
|
114
|
+
const result = await generate(config, true);
|
|
115
|
+
return result.map(({ filename }) => filename);
|
|
124
116
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
117
|
+
|
|
118
|
+
const logger = useLogger("graphql");
|
|
119
|
+
const cyan = "\x1B[36m";
|
|
120
|
+
const reset = "\x1B[0m";
|
|
121
|
+
|
|
122
|
+
async function getRegistryContent({ layerRootDirs, rootDir, documents }) {
|
|
123
|
+
const docs = (await findMultipleFiles(layerRootDirs, documents)).map((path) => ({
|
|
124
|
+
path: toRelativePath(rootDir, path),
|
|
125
|
+
content: readFileSync(path, "utf-8")
|
|
126
|
+
}));
|
|
127
|
+
const parsed = {
|
|
128
|
+
fragments: /* @__PURE__ */ new Map(),
|
|
129
|
+
operations: {
|
|
130
|
+
query: /* @__PURE__ */ new Map(),
|
|
131
|
+
mutation: /* @__PURE__ */ new Map(),
|
|
132
|
+
subscription: /* @__PURE__ */ new Map()
|
|
133
|
+
}
|
|
132
134
|
};
|
|
133
|
-
const operationNameToFile = /* @__PURE__ */ new Map();
|
|
134
|
-
const fragmentNameToFile = /* @__PURE__ */ new Map();
|
|
135
135
|
for (const doc of docs) {
|
|
136
|
-
const ast = parse
|
|
137
|
-
const defs = [];
|
|
136
|
+
const ast = parse(doc.content);
|
|
138
137
|
for (const def of ast.definitions) {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
138
|
+
switch (def.kind) {
|
|
139
|
+
// Process fragment definitions
|
|
140
|
+
case Kind.FRAGMENT_DEFINITION: {
|
|
141
|
+
const name = def.name.value;
|
|
142
|
+
const existing = parsed.fragments.get(name);
|
|
143
|
+
if (existing) {
|
|
144
|
+
throw new Error(`Duplicate fragment name ${name} in document ${doc.path} (previously defined in ${existing.path})`);
|
|
145
|
+
}
|
|
146
|
+
parsed.fragments.set(name, { kind: "fragment", name, path: doc.path });
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
// Process operation definitions
|
|
150
|
+
case Kind.OPERATION_DEFINITION: {
|
|
151
|
+
const type = def.operation;
|
|
152
|
+
if (!["query", "mutation", "subscription"].includes(type)) continue;
|
|
153
|
+
const name = def.name?.value;
|
|
154
|
+
if (!name) throw new Error(`Unnamed ${type} operation in document ${doc.path}`);
|
|
155
|
+
const existing = parsed.operations[type].get(name);
|
|
156
|
+
if (existing) {
|
|
157
|
+
throw new Error(`Duplicate operation name ${name} in document ${doc.path} (previously defined in ${existing.path})`);
|
|
158
|
+
}
|
|
159
|
+
parsed.operations[type].set(name, { kind: "operation", type, name, path: doc.path });
|
|
160
|
+
break;
|
|
146
161
|
}
|
|
147
|
-
fragmentNameToFile.set(name2, doc.path);
|
|
148
|
-
defs.push({ kind: "fragment", name: name2 });
|
|
149
|
-
continue;
|
|
150
|
-
}
|
|
151
|
-
if (def.kind !== Kind.OPERATION_DEFINITION) continue;
|
|
152
|
-
const type = def.operation;
|
|
153
|
-
if (!["query", "mutation", "subscription"].includes(type)) continue;
|
|
154
|
-
const name = def.name?.value;
|
|
155
|
-
if (!name) {
|
|
156
|
-
throw new Error(`Unnamed ${type} operation in ${doc.path}`);
|
|
157
|
-
}
|
|
158
|
-
const prev = operationNameToFile.get(name);
|
|
159
|
-
if (prev) {
|
|
160
|
-
throw new Error(`Duplicate ${type} operation name '${name}' in:
|
|
161
|
-
- ${prev}
|
|
162
|
-
- ${doc.path}`);
|
|
163
162
|
}
|
|
164
|
-
operationNameToFile.set(name, doc.path);
|
|
165
|
-
const op = { kind: "operation", type, name };
|
|
166
|
-
defs.push(op);
|
|
167
|
-
operationsByType[type].push(op);
|
|
168
163
|
}
|
|
169
|
-
byFile.set(doc.path, defs);
|
|
170
164
|
}
|
|
171
|
-
return { byFile, operationsByType };
|
|
172
|
-
}
|
|
173
|
-
function formatDefinitions(defs) {
|
|
174
|
-
if (defs.length === 0) return "";
|
|
175
|
-
const colorOf = (def) => {
|
|
176
|
-
if (def.kind === "fragment") return green;
|
|
177
|
-
switch (def.type) {
|
|
178
|
-
case "query":
|
|
179
|
-
return blue;
|
|
180
|
-
case "mutation":
|
|
181
|
-
return magenta;
|
|
182
|
-
case "subscription":
|
|
183
|
-
return yellow;
|
|
184
|
-
}
|
|
185
|
-
};
|
|
186
|
-
return defs.map((def) => `${colorOf(def)}${def.name}${reset}`).join(`${dim} / ${reset}`);
|
|
187
|
-
}
|
|
188
|
-
function writeRegistryModule(registryPath, { operationsByType }) {
|
|
189
|
-
const queries = operationsByType.query.map((o) => o.name);
|
|
190
|
-
const mutations = operationsByType.mutation.map((o) => o.name);
|
|
191
|
-
const subscriptions = operationsByType.subscription.map((o) => o.name);
|
|
192
165
|
const content = [
|
|
193
|
-
`import
|
|
194
|
-
`import * as ops from "#graphql/operations";`,
|
|
166
|
+
`import * as ops from "#graphql/typed-documents";`,
|
|
195
167
|
``,
|
|
196
168
|
`type ResultOf<T> = T extends { __apiType?: (variables: infer _) => infer R } ? R : never;`,
|
|
197
|
-
`type VariablesOf<T> = T extends { __apiType?: (variables: infer V) => infer _ } ? V : never
|
|
169
|
+
`type VariablesOf<T> = T extends { __apiType?: (variables: infer V) => infer _ } ? V : never;`,
|
|
170
|
+
``
|
|
198
171
|
];
|
|
199
|
-
|
|
200
|
-
content.push(
|
|
201
|
-
``,
|
|
202
|
-
`// Queries`,
|
|
203
|
-
`export const queries = {`,
|
|
204
|
-
...queries.map((name) => ` ${name}: ops.${name}Document,`),
|
|
205
|
-
`} as const;`
|
|
206
|
-
);
|
|
207
|
-
} else {
|
|
208
|
-
content.push(``, `export const queries = {} as const;`);
|
|
209
|
-
}
|
|
172
|
+
const queries = Array.from(parsed.operations.query.values());
|
|
210
173
|
content.push(
|
|
211
|
-
|
|
174
|
+
`// Queries`,
|
|
175
|
+
`export const queries = {`,
|
|
176
|
+
...queries.map(({ name }) => ` ${name}: ops.${name}Document,`),
|
|
177
|
+
`};`,
|
|
212
178
|
`export type QueryName = keyof typeof queries;`,
|
|
213
179
|
`export type QueryResult<N extends QueryName> = ResultOf<(typeof queries)[N]>;`,
|
|
214
|
-
`export type QueryVariables<N extends QueryName> = VariablesOf<(typeof queries)[N]
|
|
180
|
+
`export type QueryVariables<N extends QueryName> = VariablesOf<(typeof queries)[N]>;`,
|
|
181
|
+
``
|
|
215
182
|
);
|
|
216
|
-
|
|
217
|
-
content.push(
|
|
218
|
-
``,
|
|
219
|
-
`// Mutations`,
|
|
220
|
-
`export const mutations = {`,
|
|
221
|
-
...mutations.map((name) => ` ${name}: ops.${name}Document,`),
|
|
222
|
-
`} as const;`
|
|
223
|
-
);
|
|
224
|
-
} else {
|
|
225
|
-
content.push(``, `export const mutations = {} as const;`);
|
|
226
|
-
}
|
|
183
|
+
const mutations = Array.from(parsed.operations.mutation.values());
|
|
227
184
|
content.push(
|
|
228
|
-
|
|
185
|
+
`// Mutations`,
|
|
186
|
+
`export const mutations = {`,
|
|
187
|
+
...mutations.map(({ name }) => ` ${name}: ops.${name}Document,`),
|
|
188
|
+
`};`,
|
|
229
189
|
`export type MutationName = keyof typeof mutations;`,
|
|
230
190
|
`export type MutationResult<N extends MutationName> = ResultOf<(typeof mutations)[N]>;`,
|
|
231
|
-
`export type MutationVariables<N extends MutationName> = VariablesOf<(typeof mutations)[N]
|
|
191
|
+
`export type MutationVariables<N extends MutationName> = VariablesOf<(typeof mutations)[N]>;`,
|
|
192
|
+
``
|
|
232
193
|
);
|
|
233
|
-
|
|
234
|
-
content.push(
|
|
235
|
-
``,
|
|
236
|
-
`// Subscriptions`,
|
|
237
|
-
`export const subscriptions = {`,
|
|
238
|
-
...subscriptions.map((name) => ` ${name}: ops.${name}Document,`),
|
|
239
|
-
`} as const;`
|
|
240
|
-
);
|
|
241
|
-
} else {
|
|
242
|
-
content.push(``, `export const subscriptions = {} as const;`);
|
|
243
|
-
}
|
|
194
|
+
const subscriptions = Array.from(parsed.operations.subscription.values());
|
|
244
195
|
content.push(
|
|
245
|
-
|
|
196
|
+
`// Subscriptions`,
|
|
197
|
+
`export const subscriptions = {`,
|
|
198
|
+
...subscriptions.map(({ name }) => ` ${name}: ops.${name}Document,`),
|
|
199
|
+
`};`,
|
|
246
200
|
`export type SubscriptionName = keyof typeof subscriptions;`,
|
|
247
201
|
`export type SubscriptionResult<N extends SubscriptionName> = ResultOf<(typeof subscriptions)[N]>;`,
|
|
248
|
-
`export type SubscriptionVariables<N extends SubscriptionName> = VariablesOf<(typeof subscriptions)[N]
|
|
202
|
+
`export type SubscriptionVariables<N extends SubscriptionName> = VariablesOf<(typeof subscriptions)[N]>;`,
|
|
203
|
+
``
|
|
249
204
|
);
|
|
250
|
-
return
|
|
205
|
+
return content.join("\n");
|
|
251
206
|
}
|
|
252
207
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
throw new Error(`Local schema file not found at path: ${localPath}`);
|
|
208
|
+
function getRemoteExecMiddlewareProxy(middlewarePath) {
|
|
209
|
+
if (!middlewarePath) {
|
|
210
|
+
return [
|
|
211
|
+
`export const remoteExecMiddlewareHandler = undefined;`
|
|
212
|
+
].join("\n");
|
|
259
213
|
}
|
|
260
|
-
|
|
261
|
-
`
|
|
214
|
+
return [
|
|
215
|
+
`import middleware from ${JSON.stringify(removeFileExtension(middlewarePath))};`,
|
|
216
|
+
``,
|
|
217
|
+
`export const remoteExecMiddlewareHandler = middleware.remoteExecMiddlewareHandler;`
|
|
262
218
|
].join("\n");
|
|
263
|
-
return writeFileIfChanged(modulePath, content);
|
|
264
219
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
headers: { "Content-Type": "application/json", ...headers },
|
|
269
|
-
body: JSON.stringify({ query: getIntrospectionQuery() })
|
|
270
|
-
});
|
|
271
|
-
const json = await response.json();
|
|
272
|
-
if (json.errors) {
|
|
273
|
-
throw new Error(`Failed to fetch remote schema from ${url}: ${JSON.stringify(json.errors)}`);
|
|
274
|
-
}
|
|
275
|
-
const schema = buildClientSchema(json.data);
|
|
276
|
-
const sdl = printSchema(schema);
|
|
277
|
-
const content = [`export const sdl = ${serializeSDL(sdl)};`, ""].join("\n");
|
|
278
|
-
return writeFileIfChanged(sdlPath, content);
|
|
279
|
-
}
|
|
280
|
-
function writeRemoteSchemaModule({ url, headers }, remoteSdlPath, modulePath) {
|
|
281
|
-
const headerSource = headers && Object.keys(headers).length > 0 ? JSON.stringify(headers, null, 2) : "{}";
|
|
220
|
+
|
|
221
|
+
async function getLocalSchemaProxy({ layerRootDirs, schemaDef }) {
|
|
222
|
+
const schemaPath = await findSingleFile(layerRootDirs, schemaDef.path, true);
|
|
282
223
|
const content = [
|
|
283
|
-
`
|
|
284
|
-
|
|
224
|
+
`export { schema } from ${JSON.stringify(removeFileExtension(schemaPath))};`
|
|
225
|
+
].join("\n");
|
|
226
|
+
return content;
|
|
227
|
+
}
|
|
228
|
+
async function getRemoteSchemaProxy({ rootDir, schemaName, schemaDef }) {
|
|
229
|
+
const middlewareContent = getRemoteExecMiddlewareProxy(schemaDef.middleware ? join(rootDir, schemaDef.middleware) : void 0);
|
|
230
|
+
const schema = await fetchGraphQLSchema(schemaDef.url, schemaDef.headers);
|
|
231
|
+
const sdl = await getSDLFromGraphQLSchema(schema);
|
|
232
|
+
const sdlContent = `export const sdl = /* GraphQL */ \`${sdl.replace(/`/g, "\\`")}\`;`;
|
|
233
|
+
const schemaContent = [
|
|
234
|
+
`import { buildSchema } from "graphql";`,
|
|
285
235
|
`import type { SubschemaConfig } from "@graphql-tools/delegate";`,
|
|
286
|
-
`import {
|
|
236
|
+
`import { remoteExecMiddlewareHandler } from "./${schemaName}-middleware";`,
|
|
237
|
+
`import { sdl } from "./${schemaName}-sdl";`,
|
|
238
|
+
`import { createRemoteExecutor } from "../remote-executor";`,
|
|
287
239
|
``,
|
|
288
|
-
`const
|
|
289
|
-
`const headers = ${headerSource} as Record<string, string>;`,
|
|
240
|
+
`const headers = ${JSON.stringify(schemaDef.headers ?? {}, null, 2)} as HeadersInit;`,
|
|
290
241
|
``,
|
|
291
|
-
`const executor
|
|
292
|
-
`
|
|
293
|
-
`
|
|
294
|
-
`
|
|
295
|
-
`
|
|
296
|
-
`
|
|
297
|
-
` });`,
|
|
298
|
-
``,
|
|
299
|
-
` return response.json();`,
|
|
300
|
-
`};`,
|
|
242
|
+
`const executor = createRemoteExecutor({`,
|
|
243
|
+
` url: ${JSON.stringify(schemaDef.url)},`,
|
|
244
|
+
` remoteName: ${JSON.stringify(schemaName)},`,
|
|
245
|
+
` headers,`,
|
|
246
|
+
` middleware: remoteExecMiddlewareHandler,`,
|
|
247
|
+
`});`,
|
|
301
248
|
``,
|
|
302
249
|
`export const schema: SubschemaConfig = {`,
|
|
303
250
|
` schema: buildSchema(sdl),`,
|
|
304
251
|
` executor,`,
|
|
305
|
-
`}
|
|
306
|
-
``
|
|
252
|
+
`};`
|
|
307
253
|
].join("\n");
|
|
308
|
-
return
|
|
254
|
+
return { middlewareContent, sdlContent, schemaContent };
|
|
309
255
|
}
|
|
310
|
-
function
|
|
311
|
-
const schemas = schemaNames.map((name) => ({
|
|
312
|
-
path: `./schemas/${name}`,
|
|
313
|
-
ref: `${name}Schema`
|
|
314
|
-
}));
|
|
256
|
+
async function getStitchedSchemaProxy({ schemaNames }) {
|
|
315
257
|
const content = [
|
|
316
|
-
`import { stitchSchemas } from "@graphql-tools/stitch"
|
|
317
|
-
`import type { GraphQLSchema } from "graphql"
|
|
318
|
-
`import type { SubschemaConfig } from "@graphql-tools/delegate"
|
|
319
|
-
...
|
|
258
|
+
`import { stitchSchemas } from "@graphql-tools/stitch"; `,
|
|
259
|
+
`import type { GraphQLSchema } from "graphql"; `,
|
|
260
|
+
`import type { SubschemaConfig } from "@graphql-tools/delegate"; `,
|
|
261
|
+
...schemaNames.map((name) => `import { schema as ${name}Schema } from ${JSON.stringify(`./schemas/${name}`)}; `),
|
|
320
262
|
``,
|
|
321
263
|
`const subschemas: Array<GraphQLSchema | SubschemaConfig> = [`,
|
|
322
|
-
...
|
|
323
|
-
`]
|
|
264
|
+
...schemaNames.map((name) => ` ${name}Schema, `),
|
|
265
|
+
`]; `,
|
|
324
266
|
``,
|
|
325
|
-
`export const schema = stitchSchemas({ subschemas })
|
|
326
|
-
|
|
267
|
+
`export const schema = stitchSchemas({ subschemas }); `
|
|
268
|
+
].join("\n");
|
|
269
|
+
return content;
|
|
270
|
+
}
|
|
271
|
+
async function loadGraphQLSchema(schemaPath) {
|
|
272
|
+
const { createJiti } = await import('jiti');
|
|
273
|
+
const jiti = createJiti(import.meta.url, { interopDefault: true });
|
|
274
|
+
const module = await jiti.import(schemaPath);
|
|
275
|
+
if (!module.schema || !(module.schema instanceof Object) || typeof module.schema.getQueryType !== "function") {
|
|
276
|
+
throw new Error(`${schemaPath} must export a valid 'schema' of type GraphQLSchema.`);
|
|
277
|
+
}
|
|
278
|
+
return module.schema;
|
|
279
|
+
}
|
|
280
|
+
async function fetchGraphQLSchema(schemaUrl, headers) {
|
|
281
|
+
const { getIntrospectionQuery, buildClientSchema } = await import('graphql');
|
|
282
|
+
const response = await fetch(schemaUrl, {
|
|
283
|
+
method: "POST",
|
|
284
|
+
headers: { "Content-Type": "application/json", ...headers },
|
|
285
|
+
body: JSON.stringify({ query: getIntrospectionQuery() })
|
|
286
|
+
});
|
|
287
|
+
const json = await response.json();
|
|
288
|
+
if (json.errors) {
|
|
289
|
+
throw new Error(`Failed to fetch GraphQL schema from ${schemaUrl}: ${JSON.stringify(json.errors)} `);
|
|
290
|
+
}
|
|
291
|
+
return buildClientSchema(json.data);
|
|
292
|
+
}
|
|
293
|
+
async function getSDLFromGraphQLSchema(schema) {
|
|
294
|
+
const { printSchema, lexicographicSortSchema } = await import('graphql');
|
|
295
|
+
return printSchema(lexicographicSortSchema(schema));
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function getYogaMiddlewareProxy(middlewarePath) {
|
|
299
|
+
if (!middlewarePath) {
|
|
300
|
+
return [
|
|
301
|
+
`export const yogaMiddlewareHandler = undefined;`
|
|
302
|
+
].join("\n");
|
|
303
|
+
}
|
|
304
|
+
return [
|
|
305
|
+
`import middleware from ${JSON.stringify(removeFileExtension(middlewarePath))};`,
|
|
306
|
+
``,
|
|
307
|
+
`export const yogaMiddlewareHandler = middleware.yogaMiddlewareHandler;`
|
|
327
308
|
].join("\n");
|
|
328
|
-
return writeFileIfChanged(modulePath, content);
|
|
329
309
|
}
|
|
330
310
|
|
|
331
311
|
const module$1 = defineNuxtModule({
|
|
@@ -333,130 +313,131 @@ const module$1 = defineNuxtModule({
|
|
|
333
313
|
name: "@lewebsimple/nuxt-graphql",
|
|
334
314
|
configKey: "graphql"
|
|
335
315
|
},
|
|
336
|
-
defaults: {
|
|
337
|
-
schemas: {},
|
|
338
|
-
codegen: {
|
|
339
|
-
documents: "**/*.gql",
|
|
340
|
-
saveSchema: "server/graphql/schema.graphql"
|
|
341
|
-
},
|
|
342
|
-
client: {
|
|
343
|
-
headers: {},
|
|
344
|
-
cache: {
|
|
345
|
-
enabled: false,
|
|
346
|
-
ttl: 6e4,
|
|
347
|
-
storage: "memory"
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
},
|
|
316
|
+
defaults: {},
|
|
351
317
|
async setup(options, nuxt) {
|
|
352
318
|
const { resolve } = createResolver(import.meta.url);
|
|
319
|
+
const { buildDir, rootDir } = nuxt.options;
|
|
353
320
|
const layerRootDirs = getLayerDirectories(nuxt).map(({ root }) => root);
|
|
354
|
-
const stitchedPath = join(nuxt.options.buildDir, "graphql/schema.ts");
|
|
355
|
-
const sdlPath = join(nuxt.options.rootDir, options.codegen?.saveSchema || ".nuxt/graphql/schema.graphql");
|
|
356
321
|
nuxt.options.alias ||= {};
|
|
357
|
-
|
|
358
|
-
|
|
322
|
+
nuxt.options.runtimeConfig.public.graphql = {
|
|
323
|
+
cacheConfig: resolveCacheConfig(options.cache)
|
|
324
|
+
};
|
|
325
|
+
const serverProxies = {};
|
|
326
|
+
const contextPath = options.context ? await findSingleFile(layerRootDirs, options.context, true) : void 0;
|
|
327
|
+
if (contextPath) {
|
|
328
|
+
logger.info(`GraphQL context registered: ${cyan}${toRelativePath(rootDir, contextPath)}${reset}`);
|
|
359
329
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
logger.info(`Using default GraphQL context`);
|
|
368
|
-
}
|
|
369
|
-
const schemasPath = {};
|
|
370
|
-
for (const [name, schemaDef] of Object.entries(options.schemas)) {
|
|
371
|
-
schemasPath[name] = join(nuxt.options.buildDir, `graphql/schemas/${name}.ts`);
|
|
372
|
-
if (schemaDef.type === "local") {
|
|
373
|
-
const localPath = await findSingleFile(layerRootDirs, schemaDef.path, true);
|
|
374
|
-
writeLocalSchemaModule(localPath, schemasPath[name]);
|
|
375
|
-
logger.info(`Local GraphQL schema "${blue}${name}${reset}" loaded from ${cyan}${relative(nuxt.options.rootDir, localPath)}${reset}`);
|
|
376
|
-
} else if (schemaDef.type === "remote") {
|
|
377
|
-
const remoteSdlPath = join(nuxt.options.buildDir, `graphql/schemas/${name}-sdl.ts`);
|
|
378
|
-
await writeRemoteSchemaSdl(schemaDef, remoteSdlPath);
|
|
379
|
-
writeRemoteSchemaModule(schemaDef, remoteSdlPath, schemasPath[name]);
|
|
380
|
-
logger.info(`Remote GraphQL schema "${magenta}${name}${reset}" loaded from ${cyan}${schemaDef.url}${reset}`);
|
|
381
|
-
} else {
|
|
382
|
-
throw new Error(`Unknown schema type for schema '${name}'`);
|
|
330
|
+
serverProxies["context"] = getGraphQLContextProxy(contextPath);
|
|
331
|
+
serverProxies["remote-executor"] = getGenericServerProxy(resolve("./runtime/server/lib/remote-executor.ts"));
|
|
332
|
+
for (const [schemaName, schemaDef] of Object.entries(options.schemas)) {
|
|
333
|
+
switch (schemaDef.type) {
|
|
334
|
+
case "local": {
|
|
335
|
+
serverProxies[`schemas/${schemaName}`] = await getLocalSchemaProxy({ layerRootDirs, schemaDef });
|
|
336
|
+
break;
|
|
383
337
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
config.alias[`#graphql/schemas/${name}`] = schemasPath[name];
|
|
338
|
+
case "remote": {
|
|
339
|
+
const { middlewareContent, sdlContent, schemaContent } = await getRemoteSchemaProxy({ rootDir, schemaName, schemaDef });
|
|
340
|
+
serverProxies[`schemas/${schemaName}-middleware`] = middlewareContent;
|
|
341
|
+
serverProxies[`schemas/${schemaName}-sdl`] = sdlContent;
|
|
342
|
+
serverProxies[`schemas/${schemaName}`] = schemaContent;
|
|
343
|
+
break;
|
|
391
344
|
}
|
|
392
|
-
|
|
393
|
-
|
|
345
|
+
default:
|
|
346
|
+
throw new Error(`Unsupported GraphQL schema type: ${schemaDef.type}`);
|
|
347
|
+
}
|
|
394
348
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
const
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
const relativePath = relative(nuxt.options.rootDir, path);
|
|
410
|
-
logger.info(`${cyan}${relativePath}${reset} [${formatDefinitions(defs)}]`);
|
|
411
|
-
});
|
|
412
|
-
writeRegistryModule(registryPath, analysis);
|
|
413
|
-
const config = {
|
|
414
|
-
schema: relative(nuxt.options.rootDir, sdlPath),
|
|
415
|
-
documents: documentsPattern
|
|
416
|
-
};
|
|
417
|
-
writeFileIfChanged(configPath, JSON.stringify(config, null, 2));
|
|
418
|
-
} catch (error) {
|
|
419
|
-
logger.warn(`GraphQL codegen failed: ${error.message}`);
|
|
420
|
-
}
|
|
349
|
+
serverProxies["schema"] = await getStitchedSchemaProxy({ schemaNames: Object.keys(options.schemas) });
|
|
350
|
+
if (options.middleware) {
|
|
351
|
+
const yogaMiddlewarePath = await findSingleFile(layerRootDirs, options.middleware, true);
|
|
352
|
+
logger.info(`GraphQL Yoga middleware registered: ${cyan}${toRelativePath(nuxt.options.rootDir, yogaMiddlewarePath)}${reset}`);
|
|
353
|
+
serverProxies["yoga-middleware"] = getYogaMiddlewareProxy(yogaMiddlewarePath);
|
|
354
|
+
} else {
|
|
355
|
+
serverProxies["yoga-middleware"] = getYogaMiddlewareProxy();
|
|
356
|
+
}
|
|
357
|
+
const sdlPath = options.saveSdl ? join(rootDir, options.saveSdl) : join(buildDir, "graphql/schema.graphql");
|
|
358
|
+
async function generateGraphQLSDL() {
|
|
359
|
+
const schema = await loadGraphQLSchema(join(buildDir, "graphql/schema.ts"));
|
|
360
|
+
const sdlContent = await getSDLFromGraphQLSchema(schema);
|
|
361
|
+
if (writeFileIfChanged(sdlPath, sdlContent)) {
|
|
362
|
+
logger.info(`GraphQL SDL generated: ${cyan}${toRelativePath(rootDir, sdlPath)}${reset}`);
|
|
421
363
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
nuxt.hook("builder:watch", async (event, path) => {
|
|
430
|
-
if (path.endsWith(".gql")) {
|
|
431
|
-
await generate();
|
|
432
|
-
}
|
|
433
|
-
});
|
|
364
|
+
}
|
|
365
|
+
const configPath = join(rootDir, options.saveConfig || "graphql.config.json");
|
|
366
|
+
const documents = options.documents || "**/*.gql";
|
|
367
|
+
async function generateGraphQLConfig() {
|
|
368
|
+
const configContent = JSON.stringify({ schema: toRelativePath(rootDir, sdlPath), documents }, null, 2);
|
|
369
|
+
if (writeFileIfChanged(configPath, configContent)) {
|
|
370
|
+
logger.info(`GraphQL Config generated: ${cyan}${toRelativePath(rootDir, configPath)}${reset}`);
|
|
434
371
|
}
|
|
435
372
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
373
|
+
const typedDocumentsPath = join(buildDir, "graphql/typed-documents.ts");
|
|
374
|
+
nuxt.options.alias["#graphql/typed-documents"] = typedDocumentsPath;
|
|
375
|
+
const zodPath = join(buildDir, "graphql/zod.ts");
|
|
376
|
+
nuxt.options.alias["#graphql/zod"] = zodPath;
|
|
377
|
+
async function generateGraphQLCodegen() {
|
|
378
|
+
try {
|
|
379
|
+
const files = await runGraphQLCodegen({ schema: sdlPath, documents, typedDocumentsPath, zodPath });
|
|
380
|
+
for (const file of files) {
|
|
381
|
+
logger.info(`GraphQL Codegen generated: ${cyan}${toRelativePath(rootDir, file)}${reset}`);
|
|
382
|
+
}
|
|
383
|
+
} catch (error) {
|
|
384
|
+
if (!nuxt.options.dev) {
|
|
385
|
+
throw error;
|
|
386
|
+
}
|
|
387
|
+
const message = error instanceof AggregateError ? error.errors[0].message : String(error);
|
|
388
|
+
logger.warn(message);
|
|
389
|
+
}
|
|
441
390
|
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
391
|
+
const registryPath = join(buildDir, "graphql/registry.ts");
|
|
392
|
+
nuxt.options.alias["#graphql/registry"] = registryPath;
|
|
393
|
+
async function generateGraphQLRegistry() {
|
|
394
|
+
const registryContent = await getRegistryContent({ layerRootDirs, rootDir, documents });
|
|
395
|
+
writeFileIfChanged(registryPath, registryContent);
|
|
396
|
+
}
|
|
397
|
+
let artifactsPromise;
|
|
398
|
+
async function generateGraphQLArtifacts() {
|
|
399
|
+
if (!artifactsPromise) {
|
|
400
|
+
artifactsPromise = (async () => {
|
|
401
|
+
await generateGraphQLSDL();
|
|
402
|
+
await generateGraphQLConfig();
|
|
403
|
+
await generateGraphQLCodegen();
|
|
404
|
+
await generateGraphQLRegistry();
|
|
405
|
+
})();
|
|
406
|
+
}
|
|
407
|
+
return artifactsPromise;
|
|
408
|
+
}
|
|
409
|
+
addServerHandler({ route: "/api/graphql", handler: resolve("./runtime/server/api/yoga-handler") });
|
|
410
|
+
nuxt.hook("listen", (_, { url }) => {
|
|
411
|
+
logger.success(`GraphQL Yoga ready: ${cyan}${url.replace(/\/$/, "")}/api/graphql${reset}`);
|
|
412
|
+
});
|
|
413
|
+
addServerImportsDir(resolve("./runtime/server/utils"));
|
|
414
|
+
addPlugin(resolve("./runtime/app/plugins/graphql-request"));
|
|
415
|
+
addPlugin(resolve("./runtime/app/plugins/graphql-sse.client"));
|
|
416
|
+
addImportsDir(resolve("./runtime/app/composables"));
|
|
417
|
+
nuxt.hook("nitro:config", async (nitroConfig) => {
|
|
418
|
+
nitroConfig.alias ||= {};
|
|
419
|
+
for (const [proxyName, proxyContent] of Object.entries(serverProxies)) {
|
|
420
|
+
const proxyPath = join(buildDir, "graphql", `${proxyName}.ts`);
|
|
421
|
+
nitroConfig.alias[`#graphql/${proxyName}`] = proxyPath;
|
|
422
|
+
writeFileIfChanged(proxyPath, proxyContent);
|
|
423
|
+
}
|
|
424
|
+
await generateGraphQLArtifacts();
|
|
425
|
+
});
|
|
426
|
+
nuxt.hook("prepare:types", async ({ references }) => {
|
|
427
|
+
await generateGraphQLArtifacts();
|
|
428
|
+
references.push({ path: registryPath });
|
|
429
|
+
references.push({ path: typedDocumentsPath });
|
|
430
|
+
references.push({ path: zodPath });
|
|
431
|
+
});
|
|
432
|
+
if (nuxt.options.dev) {
|
|
433
|
+
nuxt.hook("builder:watch", async (_event, path) => {
|
|
434
|
+
if (path.endsWith(".gql")) {
|
|
435
|
+
logger.info(`GraphQL document change detected: ${cyan}${toRelativePath(nuxt.options.rootDir, path)}${reset}`);
|
|
436
|
+
await generateGraphQLCodegen();
|
|
437
|
+
await generateGraphQLRegistry();
|
|
450
438
|
}
|
|
451
|
-
};
|
|
452
|
-
addPlugin(resolve("./runtime/app/plugins/graphql"));
|
|
453
|
-
addImportsDir(resolve("./runtime/app/composables"));
|
|
454
|
-
addServerImportsDir(resolve("./runtime/server/utils"));
|
|
439
|
+
});
|
|
455
440
|
}
|
|
456
|
-
await setupContextSchemas();
|
|
457
|
-
await setupCodegen();
|
|
458
|
-
setupYogaHandler();
|
|
459
|
-
setupClient();
|
|
460
441
|
}
|
|
461
442
|
});
|
|
462
443
|
|