@forinda/kickjs-graphql 0.6.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/LICENSE +21 -0
- package/dist/index.d.ts +80 -0
- package/dist/index.js +258 -0
- package/dist/index.js.map +1 -0
- package/package.json +65 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Felix Orinda
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { AppAdapter, Container } from '@forinda/kickjs-core';
|
|
2
|
+
|
|
3
|
+
interface GraphQLAdapterOptions {
|
|
4
|
+
/** URL path for the GraphQL endpoint (default: '/graphql') */
|
|
5
|
+
path?: string;
|
|
6
|
+
/** Enable GraphQL Playground/GraphiQL UI (default: true in dev) */
|
|
7
|
+
playground?: boolean;
|
|
8
|
+
/** Resolver classes decorated with @Resolver */
|
|
9
|
+
resolvers: any[];
|
|
10
|
+
/** Custom GraphQL schema string (merged with auto-generated schema) */
|
|
11
|
+
typeDefs?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* GraphQL adapter for KickJS.
|
|
15
|
+
*
|
|
16
|
+
* Scans `@Resolver` classes for `@Query` and `@Mutation` methods,
|
|
17
|
+
* builds a GraphQL schema, and mounts a `/graphql` endpoint.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { GraphQLAdapter } from '@forinda/kickjs-graphql'
|
|
22
|
+
*
|
|
23
|
+
* bootstrap({
|
|
24
|
+
* modules,
|
|
25
|
+
* adapters: [
|
|
26
|
+
* new GraphQLAdapter({
|
|
27
|
+
* resolvers: [UserResolver, PostResolver],
|
|
28
|
+
* }),
|
|
29
|
+
* ],
|
|
30
|
+
* })
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
declare class GraphQLAdapter implements AppAdapter {
|
|
34
|
+
name: string;
|
|
35
|
+
private options;
|
|
36
|
+
private container;
|
|
37
|
+
constructor(options: GraphQLAdapterOptions);
|
|
38
|
+
beforeMount(app: any, container: Container): void;
|
|
39
|
+
private buildSchema;
|
|
40
|
+
private buildArgString;
|
|
41
|
+
private renderPlayground;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface ResolverMeta {
|
|
45
|
+
typeName?: string;
|
|
46
|
+
}
|
|
47
|
+
interface FieldMeta {
|
|
48
|
+
name: string;
|
|
49
|
+
handlerName: string;
|
|
50
|
+
returnType?: string;
|
|
51
|
+
description?: string;
|
|
52
|
+
}
|
|
53
|
+
interface ArgMeta {
|
|
54
|
+
paramIndex: number;
|
|
55
|
+
name: string;
|
|
56
|
+
type?: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Mark a class as a GraphQL resolver.
|
|
60
|
+
* Optionally bind to a specific type (e.g., `@Resolver('User')`).
|
|
61
|
+
*/
|
|
62
|
+
declare function Resolver(typeName?: string): ClassDecorator;
|
|
63
|
+
/** Define a Query field */
|
|
64
|
+
declare function Query(name?: string, options?: {
|
|
65
|
+
description?: string;
|
|
66
|
+
returnType?: string;
|
|
67
|
+
}): MethodDecorator;
|
|
68
|
+
/** Define a Mutation field */
|
|
69
|
+
declare function Mutation(name?: string, options?: {
|
|
70
|
+
description?: string;
|
|
71
|
+
returnType?: string;
|
|
72
|
+
}): MethodDecorator;
|
|
73
|
+
/** Define a Subscription field */
|
|
74
|
+
declare function Subscription(name?: string, options?: {
|
|
75
|
+
description?: string;
|
|
76
|
+
}): MethodDecorator;
|
|
77
|
+
/** Mark a method parameter as a GraphQL argument */
|
|
78
|
+
declare function Arg(name: string, type?: string): ParameterDecorator;
|
|
79
|
+
|
|
80
|
+
export { Arg, type ArgMeta, type FieldMeta, GraphQLAdapter, type GraphQLAdapterOptions, Mutation, Query, Resolver, type ResolverMeta, Subscription };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
4
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
5
|
+
}) : x)(function(x) {
|
|
6
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
7
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
// src/graphql.adapter.ts
|
|
11
|
+
import { Logger } from "@forinda/kickjs-core";
|
|
12
|
+
|
|
13
|
+
// src/decorators.ts
|
|
14
|
+
import "reflect-metadata";
|
|
15
|
+
var RESOLVER_META = /* @__PURE__ */ Symbol("gql:resolver");
|
|
16
|
+
var QUERY_META = /* @__PURE__ */ Symbol("gql:query");
|
|
17
|
+
var MUTATION_META = /* @__PURE__ */ Symbol("gql:mutation");
|
|
18
|
+
var SUBSCRIPTION_META = /* @__PURE__ */ Symbol("gql:subscription");
|
|
19
|
+
var ARG_META = /* @__PURE__ */ Symbol("gql:arg");
|
|
20
|
+
function Resolver(typeName) {
|
|
21
|
+
return (target) => {
|
|
22
|
+
Reflect.defineMetadata(RESOLVER_META, {
|
|
23
|
+
typeName: typeName ?? target.name.replace("Resolver", "")
|
|
24
|
+
}, target);
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
__name(Resolver, "Resolver");
|
|
28
|
+
function Query(name, options) {
|
|
29
|
+
return (target, propertyKey) => {
|
|
30
|
+
const existing = Reflect.getMetadata(QUERY_META, target.constructor) ?? [];
|
|
31
|
+
existing.push({
|
|
32
|
+
name: name ?? propertyKey,
|
|
33
|
+
handlerName: propertyKey,
|
|
34
|
+
returnType: options?.returnType,
|
|
35
|
+
description: options?.description
|
|
36
|
+
});
|
|
37
|
+
Reflect.defineMetadata(QUERY_META, existing, target.constructor);
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
__name(Query, "Query");
|
|
41
|
+
function Mutation(name, options) {
|
|
42
|
+
return (target, propertyKey) => {
|
|
43
|
+
const existing = Reflect.getMetadata(MUTATION_META, target.constructor) ?? [];
|
|
44
|
+
existing.push({
|
|
45
|
+
name: name ?? propertyKey,
|
|
46
|
+
handlerName: propertyKey,
|
|
47
|
+
returnType: options?.returnType,
|
|
48
|
+
description: options?.description
|
|
49
|
+
});
|
|
50
|
+
Reflect.defineMetadata(MUTATION_META, existing, target.constructor);
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
__name(Mutation, "Mutation");
|
|
54
|
+
function Subscription(name, options) {
|
|
55
|
+
return (target, propertyKey) => {
|
|
56
|
+
const existing = Reflect.getMetadata(SUBSCRIPTION_META, target.constructor) ?? [];
|
|
57
|
+
existing.push({
|
|
58
|
+
name: name ?? propertyKey,
|
|
59
|
+
handlerName: propertyKey,
|
|
60
|
+
description: options?.description
|
|
61
|
+
});
|
|
62
|
+
Reflect.defineMetadata(SUBSCRIPTION_META, existing, target.constructor);
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
__name(Subscription, "Subscription");
|
|
66
|
+
function Arg(name, type) {
|
|
67
|
+
return (target, propertyKey, paramIndex) => {
|
|
68
|
+
const key = propertyKey;
|
|
69
|
+
const existing = Reflect.getMetadata(ARG_META, target.constructor, key) ?? [];
|
|
70
|
+
existing.push({
|
|
71
|
+
paramIndex,
|
|
72
|
+
name,
|
|
73
|
+
type
|
|
74
|
+
});
|
|
75
|
+
Reflect.defineMetadata(ARG_META, existing, target.constructor, key);
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
__name(Arg, "Arg");
|
|
79
|
+
|
|
80
|
+
// src/graphql.adapter.ts
|
|
81
|
+
var log = Logger.for("GraphQLAdapter");
|
|
82
|
+
var GraphQLAdapter = class {
|
|
83
|
+
static {
|
|
84
|
+
__name(this, "GraphQLAdapter");
|
|
85
|
+
}
|
|
86
|
+
name = "GraphQLAdapter";
|
|
87
|
+
options;
|
|
88
|
+
container = null;
|
|
89
|
+
constructor(options) {
|
|
90
|
+
this.options = {
|
|
91
|
+
path: options.path ?? "/graphql",
|
|
92
|
+
playground: options.playground ?? process.env.NODE_ENV !== "production",
|
|
93
|
+
...options
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
beforeMount(app, container) {
|
|
97
|
+
this.container = container;
|
|
98
|
+
for (const ResolverClass of this.options.resolvers) {
|
|
99
|
+
container.register(ResolverClass, ResolverClass);
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
const graphql = __require("graphql");
|
|
103
|
+
const { schema, rootValue } = this.buildSchema(graphql, container);
|
|
104
|
+
app.post(this.options.path, async (req, res) => {
|
|
105
|
+
const { query, variables, operationName } = req.body ?? {};
|
|
106
|
+
if (!query) {
|
|
107
|
+
res.status(400).json({
|
|
108
|
+
errors: [
|
|
109
|
+
{
|
|
110
|
+
message: "Query is required"
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
});
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
const result = await graphql.graphql({
|
|
118
|
+
schema,
|
|
119
|
+
source: query,
|
|
120
|
+
rootValue,
|
|
121
|
+
variableValues: variables,
|
|
122
|
+
operationName,
|
|
123
|
+
contextValue: {
|
|
124
|
+
req,
|
|
125
|
+
res,
|
|
126
|
+
container
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
res.json(result);
|
|
130
|
+
} catch (err) {
|
|
131
|
+
res.status(500).json({
|
|
132
|
+
errors: [
|
|
133
|
+
{
|
|
134
|
+
message: err.message
|
|
135
|
+
}
|
|
136
|
+
]
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
app.get(this.options.path, (req, res) => {
|
|
141
|
+
if (this.options.playground) {
|
|
142
|
+
res.type("html").send(this.renderPlayground());
|
|
143
|
+
} else {
|
|
144
|
+
res.status(404).json({
|
|
145
|
+
message: "GraphQL Playground disabled"
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
log.info(`GraphQL endpoint: ${this.options.path}`);
|
|
150
|
+
if (this.options.playground) {
|
|
151
|
+
log.info(`GraphQL Playground: ${this.options.path} (GET)`);
|
|
152
|
+
}
|
|
153
|
+
} catch {
|
|
154
|
+
log.warn("graphql package not found. Install it: pnpm add graphql");
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
buildSchema(graphql, container) {
|
|
158
|
+
const queryFields = [];
|
|
159
|
+
const mutationFields = [];
|
|
160
|
+
const rootValue = {};
|
|
161
|
+
for (const ResolverClass of this.options.resolvers) {
|
|
162
|
+
const meta = Reflect.getMetadata(RESOLVER_META, ResolverClass);
|
|
163
|
+
if (!meta) continue;
|
|
164
|
+
const queries = Reflect.getMetadata(QUERY_META, ResolverClass) ?? [];
|
|
165
|
+
const mutations = Reflect.getMetadata(MUTATION_META, ResolverClass) ?? [];
|
|
166
|
+
for (const q of queries) {
|
|
167
|
+
const args = this.buildArgString(ResolverClass, q.handlerName);
|
|
168
|
+
const returnType = q.returnType ?? "String";
|
|
169
|
+
queryFields.push(` ${q.name}${args}: ${returnType}`);
|
|
170
|
+
rootValue[q.name] = async (argsObj, context) => {
|
|
171
|
+
const instance = container.resolve(ResolverClass);
|
|
172
|
+
const argMeta = Reflect.getMetadata(ARG_META, ResolverClass, q.handlerName) ?? [];
|
|
173
|
+
const params = argMeta.sort((a, b) => a.paramIndex - b.paramIndex).map((a) => argsObj[a.name]);
|
|
174
|
+
if (params.length === 0) {
|
|
175
|
+
return instance[q.handlerName](argsObj, context);
|
|
176
|
+
}
|
|
177
|
+
return instance[q.handlerName](...params, context);
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
for (const m of mutations) {
|
|
181
|
+
const args = this.buildArgString(ResolverClass, m.handlerName);
|
|
182
|
+
const returnType = m.returnType ?? "String";
|
|
183
|
+
mutationFields.push(` ${m.name}${args}: ${returnType}`);
|
|
184
|
+
rootValue[m.name] = async (argsObj, context) => {
|
|
185
|
+
const instance = container.resolve(ResolverClass);
|
|
186
|
+
const argMeta = Reflect.getMetadata(ARG_META, ResolverClass, m.handlerName) ?? [];
|
|
187
|
+
const params = argMeta.sort((a, b) => a.paramIndex - b.paramIndex).map((a) => argsObj[a.name]);
|
|
188
|
+
if (params.length === 0) {
|
|
189
|
+
return instance[m.handlerName](argsObj, context);
|
|
190
|
+
}
|
|
191
|
+
return instance[m.handlerName](...params, context);
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
let typeDefs = "";
|
|
196
|
+
if (queryFields.length > 0) {
|
|
197
|
+
typeDefs += `type Query {
|
|
198
|
+
${queryFields.join("\n")}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
`;
|
|
202
|
+
}
|
|
203
|
+
if (mutationFields.length > 0) {
|
|
204
|
+
typeDefs += `type Mutation {
|
|
205
|
+
${mutationFields.join("\n")}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
`;
|
|
209
|
+
}
|
|
210
|
+
if (this.options.typeDefs) {
|
|
211
|
+
typeDefs = this.options.typeDefs + "\n\n" + typeDefs;
|
|
212
|
+
}
|
|
213
|
+
if (!typeDefs.trim()) {
|
|
214
|
+
typeDefs = "type Query { _empty: String }";
|
|
215
|
+
}
|
|
216
|
+
const schema = graphql.buildSchema(typeDefs);
|
|
217
|
+
return {
|
|
218
|
+
schema,
|
|
219
|
+
rootValue
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
buildArgString(ResolverClass, handlerName) {
|
|
223
|
+
const argMeta = Reflect.getMetadata(ARG_META, ResolverClass, handlerName) ?? [];
|
|
224
|
+
if (argMeta.length === 0) return "";
|
|
225
|
+
const args = argMeta.sort((a, b) => a.paramIndex - b.paramIndex).map((a) => `${a.name}: ${a.type ?? "String"}`).join(", ");
|
|
226
|
+
return `(${args})`;
|
|
227
|
+
}
|
|
228
|
+
renderPlayground() {
|
|
229
|
+
return `<!DOCTYPE html>
|
|
230
|
+
<html>
|
|
231
|
+
<head>
|
|
232
|
+
<title>GraphQL Playground</title>
|
|
233
|
+
<link rel="stylesheet" href="https://unpkg.com/graphiql@3/graphiql.min.css" />
|
|
234
|
+
</head>
|
|
235
|
+
<body style="margin:0;height:100vh;">
|
|
236
|
+
<div id="graphiql" style="height:100vh;"></div>
|
|
237
|
+
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
|
|
238
|
+
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
|
|
239
|
+
<script crossorigin src="https://unpkg.com/graphiql@3/graphiql.min.js"></script>
|
|
240
|
+
<script>
|
|
241
|
+
const fetcher = GraphiQL.createFetcher({ url: window.location.href });
|
|
242
|
+
ReactDOM.createRoot(document.getElementById('graphiql')).render(
|
|
243
|
+
React.createElement(GraphiQL, { fetcher })
|
|
244
|
+
);
|
|
245
|
+
</script>
|
|
246
|
+
</body>
|
|
247
|
+
</html>`;
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
export {
|
|
251
|
+
Arg,
|
|
252
|
+
GraphQLAdapter,
|
|
253
|
+
Mutation,
|
|
254
|
+
Query,
|
|
255
|
+
Resolver,
|
|
256
|
+
Subscription
|
|
257
|
+
};
|
|
258
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/graphql.adapter.ts","../src/decorators.ts"],"sourcesContent":["import { Logger, type AppAdapter, type Container } from '@forinda/kickjs-core'\nimport type { Request, Response } from 'express'\nimport {\n RESOLVER_META,\n QUERY_META,\n MUTATION_META,\n ARG_META,\n type ResolverMeta,\n type FieldMeta,\n type ArgMeta,\n} from './decorators'\n\nconst log = Logger.for('GraphQLAdapter')\n\nexport interface GraphQLAdapterOptions {\n /** URL path for the GraphQL endpoint (default: '/graphql') */\n path?: string\n /** Enable GraphQL Playground/GraphiQL UI (default: true in dev) */\n playground?: boolean\n /** Resolver classes decorated with @Resolver */\n resolvers: any[]\n /** Custom GraphQL schema string (merged with auto-generated schema) */\n typeDefs?: string\n}\n\n/**\n * GraphQL adapter for KickJS.\n *\n * Scans `@Resolver` classes for `@Query` and `@Mutation` methods,\n * builds a GraphQL schema, and mounts a `/graphql` endpoint.\n *\n * @example\n * ```ts\n * import { GraphQLAdapter } from '@forinda/kickjs-graphql'\n *\n * bootstrap({\n * modules,\n * adapters: [\n * new GraphQLAdapter({\n * resolvers: [UserResolver, PostResolver],\n * }),\n * ],\n * })\n * ```\n */\nexport class GraphQLAdapter implements AppAdapter {\n name = 'GraphQLAdapter'\n private options: GraphQLAdapterOptions\n private container: Container | null = null\n\n constructor(options: GraphQLAdapterOptions) {\n this.options = {\n path: options.path ?? '/graphql',\n playground: options.playground ?? process.env.NODE_ENV !== 'production',\n ...options,\n }\n }\n\n beforeMount(app: any, container: Container): void {\n this.container = container\n\n // Register resolver classes in DI\n for (const ResolverClass of this.options.resolvers) {\n container.register(ResolverClass, ResolverClass)\n }\n\n try {\n const graphql = require('graphql')\n\n // Build schema from decorators\n const { schema, rootValue } = this.buildSchema(graphql, container)\n\n // Mount GraphQL endpoint\n app.post(this.options.path, async (req: Request, res: Response) => {\n const { query, variables, operationName } = req.body ?? {}\n if (!query) {\n res.status(400).json({ errors: [{ message: 'Query is required' }] })\n return\n }\n\n try {\n const result = await graphql.graphql({\n schema,\n source: query,\n rootValue,\n variableValues: variables,\n operationName,\n contextValue: { req, res, container },\n })\n res.json(result)\n } catch (err: any) {\n res.status(500).json({ errors: [{ message: err.message }] })\n }\n })\n\n // GET for playground/introspection\n app.get(this.options.path, (req: Request, res: Response) => {\n if (this.options.playground) {\n res.type('html').send(this.renderPlayground())\n } else {\n res.status(404).json({ message: 'GraphQL Playground disabled' })\n }\n })\n\n log.info(`GraphQL endpoint: ${this.options.path}`)\n if (this.options.playground) {\n log.info(`GraphQL Playground: ${this.options.path} (GET)`)\n }\n } catch {\n log.warn('graphql package not found. Install it: pnpm add graphql')\n }\n }\n\n private buildSchema(graphql: any, container: Container) {\n const queryFields: string[] = []\n const mutationFields: string[] = []\n const rootValue: Record<string, any> = {}\n\n for (const ResolverClass of this.options.resolvers) {\n const meta: ResolverMeta = Reflect.getMetadata(RESOLVER_META, ResolverClass)\n if (!meta) continue\n\n const queries: FieldMeta[] = Reflect.getMetadata(QUERY_META, ResolverClass) ?? []\n const mutations: FieldMeta[] = Reflect.getMetadata(MUTATION_META, ResolverClass) ?? []\n\n for (const q of queries) {\n const args = this.buildArgString(ResolverClass, q.handlerName)\n const returnType = q.returnType ?? 'String'\n queryFields.push(` ${q.name}${args}: ${returnType}`)\n\n rootValue[q.name] = async (argsObj: any, context: any) => {\n const instance = container.resolve(ResolverClass)\n const argMeta: ArgMeta[] =\n Reflect.getMetadata(ARG_META, ResolverClass, q.handlerName) ?? []\n const params = argMeta\n .sort((a, b) => a.paramIndex - b.paramIndex)\n .map((a) => argsObj[a.name])\n\n if (params.length === 0) {\n return instance[q.handlerName](argsObj, context)\n }\n return instance[q.handlerName](...params, context)\n }\n }\n\n for (const m of mutations) {\n const args = this.buildArgString(ResolverClass, m.handlerName)\n const returnType = m.returnType ?? 'String'\n mutationFields.push(` ${m.name}${args}: ${returnType}`)\n\n rootValue[m.name] = async (argsObj: any, context: any) => {\n const instance = container.resolve(ResolverClass)\n const argMeta: ArgMeta[] =\n Reflect.getMetadata(ARG_META, ResolverClass, m.handlerName) ?? []\n const params = argMeta\n .sort((a, b) => a.paramIndex - b.paramIndex)\n .map((a) => argsObj[a.name])\n\n if (params.length === 0) {\n return instance[m.handlerName](argsObj, context)\n }\n return instance[m.handlerName](...params, context)\n }\n }\n }\n\n let typeDefs = ''\n if (queryFields.length > 0) {\n typeDefs += `type Query {\\n${queryFields.join('\\n')}\\n}\\n\\n`\n }\n if (mutationFields.length > 0) {\n typeDefs += `type Mutation {\\n${mutationFields.join('\\n')}\\n}\\n\\n`\n }\n\n // Merge custom typeDefs\n if (this.options.typeDefs) {\n typeDefs = this.options.typeDefs + '\\n\\n' + typeDefs\n }\n\n if (!typeDefs.trim()) {\n typeDefs = 'type Query { _empty: String }'\n }\n\n const schema = graphql.buildSchema(typeDefs)\n return { schema, rootValue }\n }\n\n private buildArgString(ResolverClass: any, handlerName: string): string {\n const argMeta: ArgMeta[] = Reflect.getMetadata(ARG_META, ResolverClass, handlerName) ?? []\n if (argMeta.length === 0) return ''\n\n const args = argMeta\n .sort((a, b) => a.paramIndex - b.paramIndex)\n .map((a) => `${a.name}: ${a.type ?? 'String'}`)\n .join(', ')\n\n return `(${args})`\n }\n\n private renderPlayground(): string {\n return `<!DOCTYPE html>\n<html>\n<head>\n <title>GraphQL Playground</title>\n <link rel=\"stylesheet\" href=\"https://unpkg.com/graphiql@3/graphiql.min.css\" />\n</head>\n<body style=\"margin:0;height:100vh;\">\n <div id=\"graphiql\" style=\"height:100vh;\"></div>\n <script crossorigin src=\"https://unpkg.com/react@18/umd/react.production.min.js\"></script>\n <script crossorigin src=\"https://unpkg.com/react-dom@18/umd/react-dom.production.min.js\"></script>\n <script crossorigin src=\"https://unpkg.com/graphiql@3/graphiql.min.js\"></script>\n <script>\n const fetcher = GraphiQL.createFetcher({ url: window.location.href });\n ReactDOM.createRoot(document.getElementById('graphiql')).render(\n React.createElement(GraphiQL, { fetcher })\n );\n </script>\n</body>\n</html>`\n }\n}\n","import 'reflect-metadata'\n\nconst RESOLVER_META = Symbol('gql:resolver')\nconst QUERY_META = Symbol('gql:query')\nconst MUTATION_META = Symbol('gql:mutation')\nconst SUBSCRIPTION_META = Symbol('gql:subscription')\nconst FIELD_META = Symbol('gql:field')\nconst ARG_META = Symbol('gql:arg')\n\nexport interface ResolverMeta {\n typeName?: string\n}\n\nexport interface FieldMeta {\n name: string\n handlerName: string\n returnType?: string\n description?: string\n}\n\nexport interface ArgMeta {\n paramIndex: number\n name: string\n type?: string\n}\n\n/**\n * Mark a class as a GraphQL resolver.\n * Optionally bind to a specific type (e.g., `@Resolver('User')`).\n */\nexport function Resolver(typeName?: string): ClassDecorator {\n return (target: any) => {\n Reflect.defineMetadata(\n RESOLVER_META,\n { typeName: typeName ?? target.name.replace('Resolver', '') },\n target,\n )\n }\n}\n\n/** Define a Query field */\nexport function Query(\n name?: string,\n options?: { description?: string; returnType?: string },\n): MethodDecorator {\n return (target, propertyKey) => {\n const existing: FieldMeta[] = Reflect.getMetadata(QUERY_META, target.constructor) ?? []\n existing.push({\n name: name ?? (propertyKey as string),\n handlerName: propertyKey as string,\n returnType: options?.returnType,\n description: options?.description,\n })\n Reflect.defineMetadata(QUERY_META, existing, target.constructor)\n }\n}\n\n/** Define a Mutation field */\nexport function Mutation(\n name?: string,\n options?: { description?: string; returnType?: string },\n): MethodDecorator {\n return (target, propertyKey) => {\n const existing: FieldMeta[] = Reflect.getMetadata(MUTATION_META, target.constructor) ?? []\n existing.push({\n name: name ?? (propertyKey as string),\n handlerName: propertyKey as string,\n returnType: options?.returnType,\n description: options?.description,\n })\n Reflect.defineMetadata(MUTATION_META, existing, target.constructor)\n }\n}\n\n/** Define a Subscription field */\nexport function Subscription(name?: string, options?: { description?: string }): MethodDecorator {\n return (target, propertyKey) => {\n const existing: FieldMeta[] = Reflect.getMetadata(SUBSCRIPTION_META, target.constructor) ?? []\n existing.push({\n name: name ?? (propertyKey as string),\n handlerName: propertyKey as string,\n description: options?.description,\n })\n Reflect.defineMetadata(SUBSCRIPTION_META, existing, target.constructor)\n }\n}\n\n/** Mark a method parameter as a GraphQL argument */\nexport function Arg(name: string, type?: string): ParameterDecorator {\n return (target, propertyKey, paramIndex) => {\n const key = propertyKey as string\n const existing: ArgMeta[] = Reflect.getMetadata(ARG_META, target.constructor, key) ?? []\n existing.push({ paramIndex, name, type })\n Reflect.defineMetadata(ARG_META, existing, target.constructor, key)\n }\n}\n\n// Re-export metadata keys for the adapter\nexport { RESOLVER_META, QUERY_META, MUTATION_META, SUBSCRIPTION_META, FIELD_META, ARG_META }\n"],"mappings":"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","names":["Logger","RESOLVER_META","Symbol","QUERY_META","MUTATION_META","SUBSCRIPTION_META","ARG_META","Symbol","Resolver","typeName","target","Reflect","defineMetadata","RESOLVER_META","name","replace","Query","options","propertyKey","existing","getMetadata","QUERY_META","push","handlerName","returnType","description","Mutation","MUTATION_META","Subscription","SUBSCRIPTION_META","Arg","type","paramIndex","key","log","Logger","for","GraphQLAdapter","name","options","container","path","playground","process","env","NODE_ENV","beforeMount","app","ResolverClass","resolvers","register","graphql","require","schema","rootValue","buildSchema","post","req","res","query","variables","operationName","body","status","json","errors","message","result","source","variableValues","contextValue","err","get","type","send","renderPlayground","info","warn","queryFields","mutationFields","meta","Reflect","getMetadata","RESOLVER_META","queries","QUERY_META","mutations","MUTATION_META","q","args","buildArgString","handlerName","returnType","push","argsObj","context","instance","resolve","argMeta","ARG_META","params","sort","a","b","paramIndex","map","length","m","typeDefs","join","trim"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@forinda/kickjs-graphql",
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"description": "GraphQL module for KickJS with decorator-based resolvers, schema generation, and playground",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"kickjs",
|
|
7
|
+
"graphql",
|
|
8
|
+
"decorators",
|
|
9
|
+
"resolver",
|
|
10
|
+
"schema",
|
|
11
|
+
"api",
|
|
12
|
+
"typescript",
|
|
13
|
+
"@forinda/kickjs-core",
|
|
14
|
+
"@forinda/kickjs-http"
|
|
15
|
+
],
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "dist/index.js",
|
|
18
|
+
"types": "dist/index.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"import": "./dist/index.js",
|
|
22
|
+
"types": "./dist/index.d.ts"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist"
|
|
27
|
+
],
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"reflect-metadata": "^0.2.2",
|
|
30
|
+
"@forinda/kickjs-core": "0.7.0",
|
|
31
|
+
"@forinda/kickjs-http": "0.7.0"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"graphql": ">=16.0.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"graphql": "^16.11.0",
|
|
38
|
+
"@types/node": "^24.5.2",
|
|
39
|
+
"tsup": "^8.5.0",
|
|
40
|
+
"typescript": "^5.9.2"
|
|
41
|
+
},
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
44
|
+
},
|
|
45
|
+
"license": "MIT",
|
|
46
|
+
"author": "Felix Orinda",
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=20.0"
|
|
49
|
+
},
|
|
50
|
+
"homepage": "https://forinda.github.io/kick-js/",
|
|
51
|
+
"repository": {
|
|
52
|
+
"type": "git",
|
|
53
|
+
"url": "https://github.com/forinda/kick-js.git",
|
|
54
|
+
"directory": "packages/graphql"
|
|
55
|
+
},
|
|
56
|
+
"bugs": {
|
|
57
|
+
"url": "https://github.com/forinda/kick-js/issues"
|
|
58
|
+
},
|
|
59
|
+
"scripts": {
|
|
60
|
+
"build": "tsup",
|
|
61
|
+
"dev": "tsup --watch",
|
|
62
|
+
"typecheck": "tsc --noEmit",
|
|
63
|
+
"clean": "rm -rf dist .turbo"
|
|
64
|
+
}
|
|
65
|
+
}
|