beddel 0.2.0 → 0.2.2
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/CHANGELOG.md +5 -0
- package/README.md +15 -7
- package/dist/server/api/graphql.d.ts +1 -1
- package/dist/server/api/graphql.d.ts.map +1 -1
- package/dist/server/api/graphql.js +101 -59
- package/dist/server/api/graphql.js.map +1 -1
- package/package.json +12 -3
- package/src/server/api/graphql.ts +108 -73
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -215,21 +215,29 @@ output:
|
|
|
215
215
|
|
|
216
216
|
2. **The agent is automatically registered** when your application starts.
|
|
217
217
|
|
|
218
|
-
3. **Execute via GraphQL or directly
|
|
218
|
+
3. **Execute via GraphQL or directly** (GraphQL Yoga, introspection always enabled). GraphiQL is available at `GET /api/graphql`:
|
|
219
219
|
|
|
220
220
|
```graphql
|
|
221
|
-
mutation {
|
|
222
|
-
executeMethod(
|
|
223
|
-
methodName: "greeting.execute"
|
|
224
|
-
params: { name: "Alice" }
|
|
225
|
-
props: { gemini_api_key: "your-api-key" }
|
|
226
|
-
) {
|
|
221
|
+
mutation Execute($methodName: String!, $params: JSON!, $props: JSON!) {
|
|
222
|
+
executeMethod(methodName: $methodName, params: $params, props: $props) {
|
|
227
223
|
success
|
|
228
224
|
data
|
|
225
|
+
error
|
|
226
|
+
executionTime
|
|
229
227
|
}
|
|
230
228
|
}
|
|
231
229
|
```
|
|
232
230
|
|
|
231
|
+
Example variables:
|
|
232
|
+
|
|
233
|
+
```json
|
|
234
|
+
{
|
|
235
|
+
"methodName": "greeting.execute",
|
|
236
|
+
"params": { "name": "Alice" },
|
|
237
|
+
"props": { "gemini_api_key": "your-api-key" }
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
233
241
|
### Custom Agents with TypeScript Code-Behind
|
|
234
242
|
|
|
235
243
|
For more complex logic, create custom agents with TypeScript implementations:
|
|
@@ -5,5 +5,5 @@ import type { ExecuteMethodInput, ExecuteMethodResult } from "../types";
|
|
|
5
5
|
export declare function getGraphQLSchema(): string;
|
|
6
6
|
export declare function executeRegisteredMethod(input: ExecuteMethodInput, clientId: string): Promise<ExecuteMethodResult>;
|
|
7
7
|
export declare function handleGraphQLPost(request: Request): Promise<Response>;
|
|
8
|
-
export declare function handleGraphQLGet(): Response
|
|
8
|
+
export declare function handleGraphQLGet(request: Request): Promise<Response>;
|
|
9
9
|
//# sourceMappingURL=graphql.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graphql.d.ts","sourceRoot":"","sources":["../../../src/server/api/graphql.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"graphql.d.ts","sourceRoot":"","sources":["../../../src/server/api/graphql.ts"],"names":[],"mappings":"AAAA;;GAEG;AAwBH,OAAO,KAAK,EACV,kBAAkB,EAClB,mBAAmB,EAEpB,MAAM,UAAU,CAAC;AASlB,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAmCD,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,kBAAkB,EACzB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,mBAAmB,CAAC,CAwH9B;AA8ED,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,OAAO,qBAEvD;AAED,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,OAAO,qBAEtD"}
|
|
@@ -7,6 +7,8 @@ exports.getGraphQLSchema = getGraphQLSchema;
|
|
|
7
7
|
exports.executeRegisteredMethod = executeRegisteredMethod;
|
|
8
8
|
exports.handleGraphQLPost = handleGraphQLPost;
|
|
9
9
|
exports.handleGraphQLGet = handleGraphQLGet;
|
|
10
|
+
const graphql_1 = require("graphql");
|
|
11
|
+
const graphql_yoga_1 = require("graphql-yoga");
|
|
10
12
|
const agentRegistry_1 = require("../../agents/agentRegistry");
|
|
11
13
|
const kvStore_1 = require("../kvStore");
|
|
12
14
|
const runtimeSecurity_1 = require("../runtimeSecurity");
|
|
@@ -20,6 +22,37 @@ const schema = `
|
|
|
20
22
|
function getGraphQLSchema() {
|
|
21
23
|
return schema;
|
|
22
24
|
}
|
|
25
|
+
const parseJsonLiteral = (ast) => {
|
|
26
|
+
switch (ast.kind) {
|
|
27
|
+
case graphql_1.Kind.STRING:
|
|
28
|
+
case graphql_1.Kind.BOOLEAN:
|
|
29
|
+
return ast.value;
|
|
30
|
+
case graphql_1.Kind.INT:
|
|
31
|
+
return parseInt(ast.value, 10);
|
|
32
|
+
case graphql_1.Kind.FLOAT:
|
|
33
|
+
return parseFloat(ast.value);
|
|
34
|
+
case graphql_1.Kind.OBJECT: {
|
|
35
|
+
const value = {};
|
|
36
|
+
for (const field of ast.fields) {
|
|
37
|
+
value[field.name.value] = parseJsonLiteral(field.value);
|
|
38
|
+
}
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
case graphql_1.Kind.LIST:
|
|
42
|
+
return ast.values.map(parseJsonLiteral);
|
|
43
|
+
case graphql_1.Kind.NULL:
|
|
44
|
+
return null;
|
|
45
|
+
default:
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const jsonScalar = new graphql_1.GraphQLScalarType({
|
|
50
|
+
name: "JSON",
|
|
51
|
+
description: "Arbitrary JSON value",
|
|
52
|
+
serialize: (value) => value,
|
|
53
|
+
parseValue: (value) => value,
|
|
54
|
+
parseLiteral: (ast) => parseJsonLiteral(ast),
|
|
55
|
+
});
|
|
23
56
|
async function executeRegisteredMethod(input, clientId) {
|
|
24
57
|
const startTime = Date.now();
|
|
25
58
|
const context = {
|
|
@@ -113,68 +146,77 @@ async function executeRegisteredMethod(input, clientId) {
|
|
|
113
146
|
return { success: false, error: errorMessage, executionTime };
|
|
114
147
|
}
|
|
115
148
|
}
|
|
116
|
-
async function
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
if (
|
|
121
|
-
|
|
122
|
-
if (!(0, runtimeSecurity_1.isValidApiKey)(apiKey)) {
|
|
123
|
-
throw new errors_1.AuthenticationError("Invalid API key format");
|
|
124
|
-
}
|
|
125
|
-
const client = await (0, kvStore_1.getClientByApiKey)(apiKey);
|
|
126
|
-
if (!client)
|
|
127
|
-
throw new errors_1.AuthenticationError("Invalid API key");
|
|
128
|
-
const rateLimitOk = await (0, kvStore_1.checkRateLimit)(client.id, client.rateLimit);
|
|
129
|
-
if (!rateLimitOk)
|
|
130
|
-
throw new errors_1.RateLimitError("Rate limit exceeded.");
|
|
131
|
-
clientId = client.id;
|
|
132
|
-
}
|
|
133
|
-
else if (request.headers.get("x-admin-tenant") === "true") {
|
|
134
|
-
clientId = "admin_tenant";
|
|
135
|
-
}
|
|
136
|
-
else {
|
|
137
|
-
throw new errors_1.AuthenticationError("Missing or invalid authorization header");
|
|
138
|
-
}
|
|
139
|
-
const body = await request.json();
|
|
140
|
-
if (!body.query) {
|
|
141
|
-
throw new errors_1.ValidationError("Missing query in request body");
|
|
142
|
-
}
|
|
143
|
-
if (body.query && body.query.includes("executeMethod")) {
|
|
144
|
-
if (!body.variables || !body.variables.methodName) {
|
|
145
|
-
throw new errors_1.ValidationError("Missing 'variables' or 'methodName' in request body");
|
|
146
|
-
}
|
|
147
|
-
const result = await executeRegisteredMethod({
|
|
148
|
-
methodName: body.variables.methodName,
|
|
149
|
-
params: body.variables.params || {},
|
|
150
|
-
props: body.variables.props || {},
|
|
151
|
-
}, clientId);
|
|
152
|
-
return Response.json({ data: { executeMethod: result } });
|
|
149
|
+
async function resolveClientId(request) {
|
|
150
|
+
const authHeader = request.headers.get("authorization");
|
|
151
|
+
if (authHeader && authHeader.startsWith("Bearer ")) {
|
|
152
|
+
const apiKey = authHeader.substring(7);
|
|
153
|
+
if (!(0, runtimeSecurity_1.isValidApiKey)(apiKey)) {
|
|
154
|
+
throw new errors_1.AuthenticationError("Invalid API key format");
|
|
153
155
|
}
|
|
154
|
-
|
|
156
|
+
const client = await (0, kvStore_1.getClientByApiKey)(apiKey);
|
|
157
|
+
if (!client)
|
|
158
|
+
throw new errors_1.AuthenticationError("Invalid API key");
|
|
159
|
+
const rateLimitOk = await (0, kvStore_1.checkRateLimit)(client.id, client.rateLimit);
|
|
160
|
+
if (!rateLimitOk)
|
|
161
|
+
throw new errors_1.RateLimitError("Rate limit exceeded.");
|
|
162
|
+
return client.id;
|
|
155
163
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}, { status });
|
|
164
|
+
if (request.headers.get("x-admin-tenant") === "true") {
|
|
165
|
+
return "admin_tenant";
|
|
166
|
+
}
|
|
167
|
+
throw new errors_1.AuthenticationError("Missing or invalid authorization header");
|
|
168
|
+
}
|
|
169
|
+
function toGraphQLError(error) {
|
|
170
|
+
const message = error instanceof Error ? error.message : "Internal server error";
|
|
171
|
+
let code = "INTERNAL_SERVER_ERROR";
|
|
172
|
+
if (error instanceof errors_1.AuthenticationError) {
|
|
173
|
+
code = "UNAUTHENTICATED";
|
|
174
|
+
}
|
|
175
|
+
else if (error instanceof errors_1.RateLimitError) {
|
|
176
|
+
code = "RATE_LIMITED";
|
|
177
|
+
}
|
|
178
|
+
else if (error instanceof errors_1.ValidationError) {
|
|
179
|
+
code = "BAD_USER_INPUT";
|
|
173
180
|
}
|
|
181
|
+
else if (error instanceof errors_1.NotFoundError) {
|
|
182
|
+
code = "NOT_FOUND";
|
|
183
|
+
}
|
|
184
|
+
return new graphql_1.GraphQLError(message, { extensions: { code } });
|
|
185
|
+
}
|
|
186
|
+
const yoga = (0, graphql_yoga_1.createYoga)({
|
|
187
|
+
schema: (0, graphql_yoga_1.createSchema)({
|
|
188
|
+
typeDefs: schema,
|
|
189
|
+
resolvers: {
|
|
190
|
+
JSON: jsonScalar,
|
|
191
|
+
Query: {
|
|
192
|
+
ping: () => "pong",
|
|
193
|
+
},
|
|
194
|
+
Mutation: {
|
|
195
|
+
executeMethod: async (_parent, args, context) => {
|
|
196
|
+
try {
|
|
197
|
+
const clientId = await resolveClientId(context.request);
|
|
198
|
+
return await executeRegisteredMethod({
|
|
199
|
+
methodName: args.methodName,
|
|
200
|
+
params: args.params || {},
|
|
201
|
+
props: args.props || {},
|
|
202
|
+
}, clientId);
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
throw toGraphQLError(error);
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
}),
|
|
211
|
+
context: ({ request }) => ({ request }),
|
|
212
|
+
graphqlEndpoint: "/api/graphql",
|
|
213
|
+
graphiql: true,
|
|
214
|
+
fetchAPI: { Request, Response },
|
|
215
|
+
});
|
|
216
|
+
async function handleGraphQLPost(request) {
|
|
217
|
+
return yoga.handleRequest(request, { request });
|
|
174
218
|
}
|
|
175
|
-
function handleGraphQLGet() {
|
|
176
|
-
return
|
|
177
|
-
<html><head><title>Opal Support API - GraphQL</title><style>body{font-family:sans-serif;max-width:800px;margin:50px auto;padding:20px}code,pre{background:#f4f4f4;padding:4px 8px;border-radius:4px}</style></head>
|
|
178
|
-
<body><h1>Opal Support API</h1><p>GraphQL endpoint for executing registered methods.</p><h2>Endpoint</h2><code>POST /api/graphql</code><h2>Authentication</h2><p>Use Bearer token in Authorization header.</p><h2>Schema</h2><pre>${schema}</pre></body></html>`, { headers: { "Content-Type": "text/html" } });
|
|
219
|
+
async function handleGraphQLGet(request) {
|
|
220
|
+
return yoga.handleRequest(request, { request });
|
|
179
221
|
}
|
|
180
222
|
//# sourceMappingURL=graphql.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graphql.js","sourceRoot":"","sources":["../../../src/server/api/graphql.ts"],"names":[],"mappings":";AAAA;;GAEG;;
|
|
1
|
+
{"version":3,"file":"graphql.js","sourceRoot":"","sources":["../../../src/server/api/graphql.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "beddel",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Beddel - A secure YAML parser and OpenAPI endpoint manager for Node.js applications",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -23,6 +23,10 @@
|
|
|
23
23
|
"./server/*": {
|
|
24
24
|
"types": "./dist/server/*.d.ts",
|
|
25
25
|
"default": "./dist/server/*.js"
|
|
26
|
+
},
|
|
27
|
+
"./server/api/graphql": {
|
|
28
|
+
"types": "./dist/server/api/graphql.d.ts",
|
|
29
|
+
"default": "./dist/server/api/graphql.js"
|
|
26
30
|
}
|
|
27
31
|
},
|
|
28
32
|
"scripts": {
|
|
@@ -33,7 +37,10 @@
|
|
|
33
37
|
"test:watch": "jest --watch",
|
|
34
38
|
"test:coverage": "jest --coverage",
|
|
35
39
|
"lint": "eslint src --ext .ts",
|
|
36
|
-
"lint:fix": "eslint src --ext .ts --fix"
|
|
40
|
+
"lint:fix": "eslint src --ext .ts --fix",
|
|
41
|
+
"version:patch": "npm version patch",
|
|
42
|
+
"version:minor": "npm version minor",
|
|
43
|
+
"version:major": "npm version major"
|
|
37
44
|
},
|
|
38
45
|
"keywords": [
|
|
39
46
|
"yaml",
|
|
@@ -59,6 +66,8 @@
|
|
|
59
66
|
"@upstash/redis": "^1.34.3",
|
|
60
67
|
"ai": "^5.0.70",
|
|
61
68
|
"firebase-admin": "12.7.0",
|
|
69
|
+
"graphql": "^16.10.0",
|
|
70
|
+
"graphql-yoga": "^5.13.5",
|
|
62
71
|
"isolated-vm": "5.0.1",
|
|
63
72
|
"js-yaml": "^4.1.0",
|
|
64
73
|
"zod": "^4.1.12"
|
|
@@ -77,4 +86,4 @@
|
|
|
77
86
|
"engines": {
|
|
78
87
|
"node": ">=18.0.0"
|
|
79
88
|
}
|
|
80
|
-
}
|
|
89
|
+
}
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* GraphQL helpers used by the /api/graphql route.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { GraphQLError, GraphQLScalarType, Kind, type ValueNode } from "graphql";
|
|
6
|
+
import { createSchema, createYoga } from "graphql-yoga";
|
|
5
7
|
import { agentRegistry } from "../../agents/agentRegistry";
|
|
6
8
|
import {
|
|
7
9
|
getClientByApiKey,
|
|
@@ -39,6 +41,39 @@ export function getGraphQLSchema(): string {
|
|
|
39
41
|
return schema;
|
|
40
42
|
}
|
|
41
43
|
|
|
44
|
+
const parseJsonLiteral = (ast: ValueNode): unknown => {
|
|
45
|
+
switch (ast.kind) {
|
|
46
|
+
case Kind.STRING:
|
|
47
|
+
case Kind.BOOLEAN:
|
|
48
|
+
return ast.value;
|
|
49
|
+
case Kind.INT:
|
|
50
|
+
return parseInt(ast.value, 10);
|
|
51
|
+
case Kind.FLOAT:
|
|
52
|
+
return parseFloat(ast.value);
|
|
53
|
+
case Kind.OBJECT: {
|
|
54
|
+
const value: Record<string, any> = {};
|
|
55
|
+
for (const field of ast.fields) {
|
|
56
|
+
value[field.name.value] = parseJsonLiteral(field.value);
|
|
57
|
+
}
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
case Kind.LIST:
|
|
61
|
+
return ast.values.map(parseJsonLiteral);
|
|
62
|
+
case Kind.NULL:
|
|
63
|
+
return null;
|
|
64
|
+
default:
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const jsonScalar = new GraphQLScalarType({
|
|
70
|
+
name: "JSON",
|
|
71
|
+
description: "Arbitrary JSON value",
|
|
72
|
+
serialize: (value: unknown) => value,
|
|
73
|
+
parseValue: (value: unknown) => value,
|
|
74
|
+
parseLiteral: (ast: ValueNode) => parseJsonLiteral(ast),
|
|
75
|
+
});
|
|
76
|
+
|
|
42
77
|
export async function executeRegisteredMethod(
|
|
43
78
|
input: ExecuteMethodInput,
|
|
44
79
|
clientId: string
|
|
@@ -164,86 +199,86 @@ export async function executeRegisteredMethod(
|
|
|
164
199
|
}
|
|
165
200
|
}
|
|
166
201
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
let clientId: string;
|
|
170
|
-
const authHeader = request.headers.get("authorization");
|
|
202
|
+
async function resolveClientId(request: Request): Promise<string> {
|
|
203
|
+
const authHeader = request.headers.get("authorization");
|
|
171
204
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
177
|
-
const client = await getClientByApiKey(apiKey);
|
|
178
|
-
if (!client) throw new AuthenticationError("Invalid API key");
|
|
179
|
-
const rateLimitOk = await checkRateLimit(client.id, client.rateLimit);
|
|
180
|
-
if (!rateLimitOk) throw new RateLimitError("Rate limit exceeded.");
|
|
181
|
-
clientId = client.id;
|
|
182
|
-
} else if (request.headers.get("x-admin-tenant") === "true") {
|
|
183
|
-
clientId = "admin_tenant";
|
|
184
|
-
} else {
|
|
185
|
-
throw new AuthenticationError(
|
|
186
|
-
"Missing or invalid authorization header"
|
|
187
|
-
);
|
|
205
|
+
if (authHeader && authHeader.startsWith("Bearer ")) {
|
|
206
|
+
const apiKey = authHeader.substring(7);
|
|
207
|
+
if (!isValidApiKey(apiKey)) {
|
|
208
|
+
throw new AuthenticationError("Invalid API key format");
|
|
188
209
|
}
|
|
210
|
+
const client = await getClientByApiKey(apiKey);
|
|
211
|
+
if (!client) throw new AuthenticationError("Invalid API key");
|
|
212
|
+
const rateLimitOk = await checkRateLimit(client.id, client.rateLimit);
|
|
213
|
+
if (!rateLimitOk) throw new RateLimitError("Rate limit exceeded.");
|
|
214
|
+
return client.id;
|
|
215
|
+
}
|
|
189
216
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
217
|
+
if (request.headers.get("x-admin-tenant") === "true") {
|
|
218
|
+
return "admin_tenant";
|
|
219
|
+
}
|
|
194
220
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
throw new ValidationError(
|
|
198
|
-
"Missing 'variables' or 'methodName' in request body"
|
|
199
|
-
);
|
|
200
|
-
}
|
|
221
|
+
throw new AuthenticationError("Missing or invalid authorization header");
|
|
222
|
+
}
|
|
201
223
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
params: body.variables.params || {},
|
|
206
|
-
props: body.variables.props || {},
|
|
207
|
-
},
|
|
208
|
-
clientId
|
|
209
|
-
);
|
|
210
|
-
return Response.json({ data: { executeMethod: result } });
|
|
211
|
-
}
|
|
224
|
+
function toGraphQLError(error: unknown): GraphQLError {
|
|
225
|
+
const message = error instanceof Error ? error.message : "Internal server error";
|
|
226
|
+
let code = "INTERNAL_SERVER_ERROR";
|
|
212
227
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
: error instanceof RateLimitError
|
|
222
|
-
? 429
|
|
223
|
-
: error instanceof ValidationError
|
|
224
|
-
? 400
|
|
225
|
-
: error instanceof NotFoundError
|
|
226
|
-
? 404
|
|
227
|
-
: 500;
|
|
228
|
-
return Response.json(
|
|
229
|
-
{
|
|
230
|
-
errors: [
|
|
231
|
-
{
|
|
232
|
-
message:
|
|
233
|
-
error instanceof Error ? error.message : "Internal server error",
|
|
234
|
-
},
|
|
235
|
-
],
|
|
236
|
-
},
|
|
237
|
-
{ status }
|
|
238
|
-
);
|
|
228
|
+
if (error instanceof AuthenticationError) {
|
|
229
|
+
code = "UNAUTHENTICATED";
|
|
230
|
+
} else if (error instanceof RateLimitError) {
|
|
231
|
+
code = "RATE_LIMITED";
|
|
232
|
+
} else if (error instanceof ValidationError) {
|
|
233
|
+
code = "BAD_USER_INPUT";
|
|
234
|
+
} else if (error instanceof NotFoundError) {
|
|
235
|
+
code = "NOT_FOUND";
|
|
239
236
|
}
|
|
237
|
+
|
|
238
|
+
return new GraphQLError(message, { extensions: { code } });
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const yoga = createYoga({
|
|
242
|
+
schema: createSchema({
|
|
243
|
+
typeDefs: schema,
|
|
244
|
+
resolvers: {
|
|
245
|
+
JSON: jsonScalar,
|
|
246
|
+
Query: {
|
|
247
|
+
ping: () => "pong",
|
|
248
|
+
},
|
|
249
|
+
Mutation: {
|
|
250
|
+
executeMethod: async (
|
|
251
|
+
_parent: unknown,
|
|
252
|
+
args: { methodName: string; params?: Record<string, unknown>; props?: Record<string, string> },
|
|
253
|
+
context: { request: Request }
|
|
254
|
+
) => {
|
|
255
|
+
try {
|
|
256
|
+
const clientId = await resolveClientId(context.request);
|
|
257
|
+
return await executeRegisteredMethod(
|
|
258
|
+
{
|
|
259
|
+
methodName: args.methodName,
|
|
260
|
+
params: args.params || {},
|
|
261
|
+
props: args.props || {},
|
|
262
|
+
},
|
|
263
|
+
clientId
|
|
264
|
+
);
|
|
265
|
+
} catch (error) {
|
|
266
|
+
throw toGraphQLError(error);
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
}),
|
|
272
|
+
context: ({ request }: { request: Request }) => ({ request }),
|
|
273
|
+
graphqlEndpoint: "/api/graphql",
|
|
274
|
+
graphiql: true,
|
|
275
|
+
fetchAPI: { Request, Response },
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
export async function handleGraphQLPost(request: Request) {
|
|
279
|
+
return yoga.handleRequest(request, { request });
|
|
240
280
|
}
|
|
241
281
|
|
|
242
|
-
export function handleGraphQLGet() {
|
|
243
|
-
return
|
|
244
|
-
`<!DOCTYPE html>
|
|
245
|
-
<html><head><title>Opal Support API - GraphQL</title><style>body{font-family:sans-serif;max-width:800px;margin:50px auto;padding:20px}code,pre{background:#f4f4f4;padding:4px 8px;border-radius:4px}</style></head>
|
|
246
|
-
<body><h1>Opal Support API</h1><p>GraphQL endpoint for executing registered methods.</p><h2>Endpoint</h2><code>POST /api/graphql</code><h2>Authentication</h2><p>Use Bearer token in Authorization header.</p><h2>Schema</h2><pre>${schema}</pre></body></html>`,
|
|
247
|
-
{ headers: { "Content-Type": "text/html" } }
|
|
248
|
-
);
|
|
282
|
+
export async function handleGraphQLGet(request: Request) {
|
|
283
|
+
return yoga.handleRequest(request, { request });
|
|
249
284
|
}
|