beddel 0.1.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 +297 -0
- package/dist/agents/agentRegistry.d.ts +68 -0
- package/dist/agents/agentRegistry.d.ts.map +1 -0
- package/dist/agents/agentRegistry.js +222 -0
- package/dist/agents/agentRegistry.js.map +1 -0
- package/dist/agents/formatter-agent.d.ts +10 -0
- package/dist/agents/formatter-agent.d.ts.map +1 -0
- package/dist/agents/formatter-agent.js +49 -0
- package/dist/agents/formatter-agent.js.map +1 -0
- package/dist/agents/genkit-agent.d.ts +12 -0
- package/dist/agents/genkit-agent.d.ts.map +1 -0
- package/dist/agents/genkit-agent.js +119 -0
- package/dist/agents/genkit-agent.js.map +1 -0
- package/dist/agents/i18n-messages.d.ts +17 -0
- package/dist/agents/i18n-messages.d.ts.map +1 -0
- package/dist/agents/i18n-messages.js +92 -0
- package/dist/agents/i18n-messages.js.map +1 -0
- package/dist/agents/index.d.ts +10 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/index.js +26 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/pipeline.d.ts +15 -0
- package/dist/agents/pipeline.d.ts.map +1 -0
- package/dist/agents/pipeline.js +45 -0
- package/dist/agents/pipeline.js.map +1 -0
- package/dist/agents/schema-factory.d.ts +40 -0
- package/dist/agents/schema-factory.d.ts.map +1 -0
- package/dist/agents/schema-factory.js +121 -0
- package/dist/agents/schema-factory.js.map +1 -0
- package/dist/agents/translation-validators.d.ts +26 -0
- package/dist/agents/translation-validators.d.ts.map +1 -0
- package/dist/agents/translation-validators.js +77 -0
- package/dist/agents/translation-validators.js.map +1 -0
- package/dist/agents/translator-agents.d.ts +184 -0
- package/dist/agents/translator-agents.d.ts.map +1 -0
- package/dist/agents/translator-agents.js +613 -0
- package/dist/agents/translator-agents.js.map +1 -0
- package/dist/agents/types/translation.types.d.ts +100 -0
- package/dist/agents/types/translation.types.d.ts.map +1 -0
- package/dist/agents/types/translation.types.js +3 -0
- package/dist/agents/types/translation.types.js.map +1 -0
- package/dist/agents/validator-agent.d.ts +42 -0
- package/dist/agents/validator-agent.d.ts.map +1 -0
- package/dist/agents/validator-agent.js +122 -0
- package/dist/agents/validator-agent.js.map +1 -0
- package/dist/audit/auditTrail.d.ts +55 -0
- package/dist/audit/auditTrail.d.ts.map +1 -0
- package/dist/audit/auditTrail.js +93 -0
- package/dist/audit/auditTrail.js.map +1 -0
- package/dist/compliance/gdprEngine.d.ts +44 -0
- package/dist/compliance/gdprEngine.d.ts.map +1 -0
- package/dist/compliance/gdprEngine.js +178 -0
- package/dist/compliance/gdprEngine.js.map +1 -0
- package/dist/compliance/lgpdEngine.d.ts +51 -0
- package/dist/compliance/lgpdEngine.d.ts.map +1 -0
- package/dist/compliance/lgpdEngine.js +221 -0
- package/dist/compliance/lgpdEngine.js.map +1 -0
- package/dist/config.d.ts +78 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +77 -0
- package/dist/config.js.map +1 -0
- package/dist/errors.d.ts +17 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +40 -0
- package/dist/errors.js.map +1 -0
- package/dist/firebase/tenantManager.d.ts +84 -0
- package/dist/firebase/tenantManager.d.ts.map +1 -0
- package/dist/firebase/tenantManager.js +378 -0
- package/dist/firebase/tenantManager.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +118 -0
- package/dist/index.js.map +1 -0
- package/dist/integration/secure-yaml-runtime.d.ts +68 -0
- package/dist/integration/secure-yaml-runtime.d.ts.map +1 -0
- package/dist/integration/secure-yaml-runtime.js +245 -0
- package/dist/integration/secure-yaml-runtime.js.map +1 -0
- package/dist/parser/secure-yaml-parser.d.ts +62 -0
- package/dist/parser/secure-yaml-parser.d.ts.map +1 -0
- package/dist/parser/secure-yaml-parser.js +234 -0
- package/dist/parser/secure-yaml-parser.js.map +1 -0
- package/dist/performance/autoscaling.d.ts +100 -0
- package/dist/performance/autoscaling.d.ts.map +1 -0
- package/dist/performance/autoscaling.js +339 -0
- package/dist/performance/autoscaling.js.map +1 -0
- package/dist/performance/benchmark.d.ts +104 -0
- package/dist/performance/benchmark.d.ts.map +1 -0
- package/dist/performance/benchmark.js +514 -0
- package/dist/performance/benchmark.js.map +1 -0
- package/dist/performance/index.d.ts +14 -0
- package/dist/performance/index.d.ts.map +1 -0
- package/dist/performance/index.js +35 -0
- package/dist/performance/index.js.map +1 -0
- package/dist/performance/monitor.d.ts +126 -0
- package/dist/performance/monitor.d.ts.map +1 -0
- package/dist/performance/monitor.js +324 -0
- package/dist/performance/monitor.js.map +1 -0
- package/dist/performance/streaming.d.ts +82 -0
- package/dist/performance/streaming.d.ts.map +1 -0
- package/dist/performance/streaming.js +287 -0
- package/dist/performance/streaming.js.map +1 -0
- package/dist/runtime/audit.d.ts +240 -0
- package/dist/runtime/audit.d.ts.map +1 -0
- package/dist/runtime/audit.js +641 -0
- package/dist/runtime/audit.js.map +1 -0
- package/dist/runtime/declarativeAgentRuntime.d.ts +123 -0
- package/dist/runtime/declarativeAgentRuntime.d.ts.map +1 -0
- package/dist/runtime/declarativeAgentRuntime.js +576 -0
- package/dist/runtime/declarativeAgentRuntime.js.map +1 -0
- package/dist/runtime/isolatedRuntime.d.ts +119 -0
- package/dist/runtime/isolatedRuntime.d.ts.map +1 -0
- package/dist/runtime/isolatedRuntime.js +425 -0
- package/dist/runtime/isolatedRuntime.js.map +1 -0
- package/dist/runtime/schemaCompiler.d.ts +35 -0
- package/dist/runtime/schemaCompiler.d.ts.map +1 -0
- package/dist/runtime/schemaCompiler.js +151 -0
- package/dist/runtime/schemaCompiler.js.map +1 -0
- package/dist/runtime/simpleRuntime.d.ts +57 -0
- package/dist/runtime/simpleRuntime.d.ts.map +1 -0
- package/dist/runtime/simpleRuntime.js +187 -0
- package/dist/runtime/simpleRuntime.js.map +1 -0
- package/dist/security/dashboard.d.ts +89 -0
- package/dist/security/dashboard.d.ts.map +1 -0
- package/dist/security/dashboard.js +300 -0
- package/dist/security/dashboard.js.map +1 -0
- package/dist/security/hardening.d.ts +130 -0
- package/dist/security/hardening.d.ts.map +1 -0
- package/dist/security/hardening.js +414 -0
- package/dist/security/hardening.js.map +1 -0
- package/dist/security/index.d.ts +128 -0
- package/dist/security/index.d.ts.map +1 -0
- package/dist/security/index.js +353 -0
- package/dist/security/index.js.map +1 -0
- package/dist/security/monitor.d.ts +88 -0
- package/dist/security/monitor.d.ts.map +1 -0
- package/dist/security/monitor.js +356 -0
- package/dist/security/monitor.js.map +1 -0
- package/dist/security/scanner.d.ts +104 -0
- package/dist/security/scanner.d.ts.map +1 -0
- package/dist/security/scanner.js +298 -0
- package/dist/security/scanner.js.map +1 -0
- package/dist/security/score.d.ts +150 -0
- package/dist/security/score.d.ts.map +1 -0
- package/dist/security/score.js +983 -0
- package/dist/security/score.js.map +1 -0
- package/dist/security/test-security.d.ts +22 -0
- package/dist/security/test-security.d.ts.map +1 -0
- package/dist/security/test-security.js +154 -0
- package/dist/security/test-security.js.map +1 -0
- package/dist/security/threatDetector.d.ts +39 -0
- package/dist/security/threatDetector.d.ts.map +1 -0
- package/dist/security/threatDetector.js +354 -0
- package/dist/security/threatDetector.js.map +1 -0
- package/dist/security/validation.d.ts +69 -0
- package/dist/security/validation.d.ts.map +1 -0
- package/dist/security/validation.js +286 -0
- package/dist/security/validation.js.map +1 -0
- package/dist/server/api/clientsRoute.d.ts +9 -0
- package/dist/server/api/clientsRoute.d.ts.map +1 -0
- package/dist/server/api/clientsRoute.js +71 -0
- package/dist/server/api/clientsRoute.js.map +1 -0
- package/dist/server/api/endpointsRoute.d.ts +8 -0
- package/dist/server/api/endpointsRoute.d.ts.map +1 -0
- package/dist/server/api/endpointsRoute.js +76 -0
- package/dist/server/api/endpointsRoute.js.map +1 -0
- package/dist/server/api/graphql.d.ts +9 -0
- package/dist/server/api/graphql.d.ts.map +1 -0
- package/dist/server/api/graphql.js +180 -0
- package/dist/server/api/graphql.js.map +1 -0
- package/dist/server/errors.d.ts +19 -0
- package/dist/server/errors.d.ts.map +1 -0
- package/dist/server/errors.js +42 -0
- package/dist/server/errors.js.map +1 -0
- package/dist/server/index.d.ts +7 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +24 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/kvStore.d.ts +27 -0
- package/dist/server/kvStore.d.ts.map +1 -0
- package/dist/server/kvStore.js +128 -0
- package/dist/server/kvStore.js.map +1 -0
- package/dist/server/runtimeSecurity.d.ts +28 -0
- package/dist/server/runtimeSecurity.d.ts.map +1 -0
- package/dist/server/runtimeSecurity.js +85 -0
- package/dist/server/runtimeSecurity.js.map +1 -0
- package/dist/server/types.d.ts +53 -0
- package/dist/server/types.d.ts.map +1 -0
- package/dist/server/types.js +8 -0
- package/dist/server/types.js.map +1 -0
- package/dist/types/executionContext.d.ts +16 -0
- package/dist/types/executionContext.d.ts.map +1 -0
- package/dist/types/executionContext.js +3 -0
- package/dist/types/executionContext.js.map +1 -0
- package/package.json +77 -0
- package/src/agents/agentRegistry.ts +272 -0
- package/src/agents/image-agent.yaml +86 -0
- package/src/agents/joker-agent.yaml +47 -0
- package/src/agents/translator-agent.yaml +80 -0
- package/src/audit/auditTrail.ts +134 -0
- package/src/compliance/gdprEngine.ts +209 -0
- package/src/compliance/lgpdEngine.ts +268 -0
- package/src/config.ts +179 -0
- package/src/errors.ts +35 -0
- package/src/firebase/tenantManager.ts +443 -0
- package/src/index.ts +125 -0
- package/src/integration/secure-yaml-runtime.ts +341 -0
- package/src/parser/secure-yaml-parser.ts +273 -0
- package/src/performance/autoscaling.ts +495 -0
- package/src/performance/benchmark.ts +644 -0
- package/src/performance/index.ts +34 -0
- package/src/performance/monitor.ts +469 -0
- package/src/performance/streaming.ts +317 -0
- package/src/runtime/audit.ts +907 -0
- package/src/runtime/declarativeAgentRuntime.ts +836 -0
- package/src/runtime/isolatedRuntime.ts +572 -0
- package/src/runtime/schemaCompiler.ts +228 -0
- package/src/runtime/simpleRuntime.ts +201 -0
- package/src/security/dashboard.ts +462 -0
- package/src/security/hardening.ts +560 -0
- package/src/security/index.ts +439 -0
- package/src/security/monitor.ts +490 -0
- package/src/security/scanner.ts +368 -0
- package/src/security/score.ts +1138 -0
- package/src/security/threatDetector.ts +481 -0
- package/src/security/validation.ts +365 -0
- package/src/server/api/clientsRoute.ts +92 -0
- package/src/server/api/endpointsRoute.ts +97 -0
- package/src/server/api/graphql.ts +249 -0
- package/src/server/errors.ts +38 -0
- package/src/server/index.ts +6 -0
- package/src/server/kvStore.ts +152 -0
- package/src/server/runtimeSecurity.ts +102 -0
- package/src/server/types.ts +60 -0
- package/src/types/executionContext.ts +16 -0
- package/tools/seed.ts +365 -0
- package/tools/test-endpoints.ts +174 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GraphQL helpers used by the /api/graphql route.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { agentRegistry } from "../../agents/agentRegistry";
|
|
6
|
+
import {
|
|
7
|
+
getClientByApiKey,
|
|
8
|
+
getEndpointByName,
|
|
9
|
+
logExecution,
|
|
10
|
+
checkRateLimit,
|
|
11
|
+
} from "../kvStore";
|
|
12
|
+
import {
|
|
13
|
+
sanitizeInput,
|
|
14
|
+
isValidMethodName,
|
|
15
|
+
isValidApiKey,
|
|
16
|
+
executeInSandbox,
|
|
17
|
+
validateRequiredProps,
|
|
18
|
+
} from "../runtimeSecurity";
|
|
19
|
+
import {
|
|
20
|
+
AuthenticationError,
|
|
21
|
+
RateLimitError,
|
|
22
|
+
ValidationError,
|
|
23
|
+
NotFoundError,
|
|
24
|
+
} from "../errors";
|
|
25
|
+
import type {
|
|
26
|
+
ExecuteMethodInput,
|
|
27
|
+
ExecuteMethodResult,
|
|
28
|
+
ExecutionContext,
|
|
29
|
+
} from "../types";
|
|
30
|
+
|
|
31
|
+
const schema = `
|
|
32
|
+
type Query { ping: String! }
|
|
33
|
+
type Mutation { executeMethod(methodName: String!, params: JSON!, props: JSON!): ExecutionResult! }
|
|
34
|
+
type ExecutionResult { success: Boolean!, data: JSON, error: String, executionTime: Int! }
|
|
35
|
+
scalar JSON
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
export function getGraphQLSchema(): string {
|
|
39
|
+
return schema;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function executeRegisteredMethod(
|
|
43
|
+
input: ExecuteMethodInput,
|
|
44
|
+
clientId: string
|
|
45
|
+
): Promise<ExecuteMethodResult> {
|
|
46
|
+
const startTime = Date.now();
|
|
47
|
+
const context: ExecutionContext = {
|
|
48
|
+
logs: [],
|
|
49
|
+
status: "running",
|
|
50
|
+
output: undefined,
|
|
51
|
+
error: undefined,
|
|
52
|
+
log: (message: string) =>
|
|
53
|
+
context.logs.push(`[${new Date().toISOString()}] ${message}`),
|
|
54
|
+
setOutput: (output: unknown) => {
|
|
55
|
+
context.output = output;
|
|
56
|
+
context.status = "success";
|
|
57
|
+
},
|
|
58
|
+
setError: (error: string) => {
|
|
59
|
+
context.error = error;
|
|
60
|
+
context.status = "error";
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
context.log("Method execution initiated.");
|
|
66
|
+
if (!isValidMethodName(input.methodName)) {
|
|
67
|
+
throw new ValidationError("Invalid method name format");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const declarativeAgent = agentRegistry.getAgent(input.methodName);
|
|
71
|
+
if (declarativeAgent) {
|
|
72
|
+
context.log(`Found declarative agent: ${input.methodName}`);
|
|
73
|
+
|
|
74
|
+
const result = await agentRegistry.executeAgent(
|
|
75
|
+
input.methodName,
|
|
76
|
+
sanitizeInput(input.params) as Record<string, any>,
|
|
77
|
+
sanitizeInput(input.props) as Record<string, string>,
|
|
78
|
+
context
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const executionTime = Date.now() - startTime;
|
|
82
|
+
await logExecution({
|
|
83
|
+
id: `log_${Date.now()}`,
|
|
84
|
+
clientId,
|
|
85
|
+
endpointName: input.methodName,
|
|
86
|
+
timestamp: new Date().toISOString(),
|
|
87
|
+
duration: executionTime,
|
|
88
|
+
success: true,
|
|
89
|
+
input: input.params,
|
|
90
|
+
output: result,
|
|
91
|
+
logs: context.logs,
|
|
92
|
+
});
|
|
93
|
+
return { success: true, data: result, executionTime };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const endpoint = await getEndpointByName(input.methodName);
|
|
97
|
+
if (!endpoint) {
|
|
98
|
+
throw new NotFoundError(`Method '${input.methodName}' not found`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
context.log(`Found endpoint: ${endpoint.name}`);
|
|
102
|
+
const sanitizedParams = sanitizeInput(input.params) as Record<
|
|
103
|
+
string,
|
|
104
|
+
unknown
|
|
105
|
+
>;
|
|
106
|
+
const sanitizedProps = sanitizeInput(input.props) as Record<string, string>;
|
|
107
|
+
const { valid, missing } = validateRequiredProps(
|
|
108
|
+
endpoint.requiredProps,
|
|
109
|
+
sanitizedProps
|
|
110
|
+
);
|
|
111
|
+
if (!valid) {
|
|
112
|
+
throw new ValidationError(
|
|
113
|
+
`Missing required props: ${missing.join(", ")}`
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
context.log("Props validated. Executing sandbox.");
|
|
118
|
+
await executeInSandbox(
|
|
119
|
+
endpoint.code,
|
|
120
|
+
sanitizedParams,
|
|
121
|
+
sanitizedProps,
|
|
122
|
+
context
|
|
123
|
+
);
|
|
124
|
+
context.log("Sandbox execution finished.");
|
|
125
|
+
|
|
126
|
+
if (context.status === "error") {
|
|
127
|
+
throw new Error(context.error || "Sandbox execution failed.");
|
|
128
|
+
}
|
|
129
|
+
if (context.status !== "success") {
|
|
130
|
+
throw new Error("Sandbox finished in an indeterminate state.");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const executionTime = Date.now() - startTime;
|
|
134
|
+
await logExecution({
|
|
135
|
+
id: `log_${Date.now()}`,
|
|
136
|
+
clientId,
|
|
137
|
+
endpointName: input.methodName,
|
|
138
|
+
timestamp: new Date().toISOString(),
|
|
139
|
+
duration: executionTime,
|
|
140
|
+
success: true,
|
|
141
|
+
input: sanitizedParams,
|
|
142
|
+
output: context.output,
|
|
143
|
+
logs: context.logs,
|
|
144
|
+
});
|
|
145
|
+
return { success: true, data: context.output, executionTime };
|
|
146
|
+
} catch (error) {
|
|
147
|
+
const executionTime = Date.now() - startTime;
|
|
148
|
+
const errorMessage =
|
|
149
|
+
error instanceof Error ? error.message : "Unknown error";
|
|
150
|
+
if (!context.error) context.setError(errorMessage);
|
|
151
|
+
|
|
152
|
+
await logExecution({
|
|
153
|
+
id: `log_${Date.now()}`,
|
|
154
|
+
clientId,
|
|
155
|
+
endpointName: input.methodName,
|
|
156
|
+
timestamp: new Date().toISOString(),
|
|
157
|
+
duration: executionTime,
|
|
158
|
+
success: false,
|
|
159
|
+
error: errorMessage,
|
|
160
|
+
input: input.params,
|
|
161
|
+
logs: context.logs,
|
|
162
|
+
});
|
|
163
|
+
return { success: false, error: errorMessage, executionTime };
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export async function handleGraphQLPost(request: Request) {
|
|
168
|
+
try {
|
|
169
|
+
let clientId: string;
|
|
170
|
+
const authHeader = request.headers.get("authorization");
|
|
171
|
+
|
|
172
|
+
if (authHeader && authHeader.startsWith("Bearer ")) {
|
|
173
|
+
const apiKey = authHeader.substring(7);
|
|
174
|
+
if (!isValidApiKey(apiKey)) {
|
|
175
|
+
throw new AuthenticationError("Invalid API key format");
|
|
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
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const body = await request.json();
|
|
191
|
+
if (!body.query) {
|
|
192
|
+
throw new ValidationError("Missing query in request body");
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (body.query && body.query.includes("executeMethod")) {
|
|
196
|
+
if (!body.variables || !body.variables.methodName) {
|
|
197
|
+
throw new ValidationError(
|
|
198
|
+
"Missing 'variables' or 'methodName' in request body"
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const result = await executeRegisteredMethod(
|
|
203
|
+
{
|
|
204
|
+
methodName: body.variables.methodName,
|
|
205
|
+
params: body.variables.params || {},
|
|
206
|
+
props: body.variables.props || {},
|
|
207
|
+
},
|
|
208
|
+
clientId
|
|
209
|
+
);
|
|
210
|
+
return Response.json({ data: { executeMethod: result } });
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return Response.json(
|
|
214
|
+
{ errors: [{ message: "Unsupported operation" }] },
|
|
215
|
+
{ status: 400 }
|
|
216
|
+
);
|
|
217
|
+
} catch (error) {
|
|
218
|
+
const status =
|
|
219
|
+
error instanceof AuthenticationError
|
|
220
|
+
? 401
|
|
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
|
+
);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function handleGraphQLGet() {
|
|
243
|
+
return new Response(
|
|
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
|
+
);
|
|
249
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom error classes shared across server runtimes.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export class AuthenticationError extends Error {
|
|
6
|
+
constructor(message = "Authentication failed") {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "AuthenticationError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class RateLimitError extends Error {
|
|
13
|
+
constructor(message = "Rate limit exceeded") {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = "RateLimitError";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class ValidationError extends Error {
|
|
20
|
+
constructor(message: string) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = "ValidationError";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class ExecutionError extends Error {
|
|
27
|
+
constructor(message: string) {
|
|
28
|
+
super(message);
|
|
29
|
+
this.name = "ExecutionError";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class NotFoundError extends Error {
|
|
34
|
+
constructor(message: string) {
|
|
35
|
+
super(message);
|
|
36
|
+
this.name = "NotFoundError";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upstash KV helpers shared between the Next.js runtime and npm package.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Redis } from "@upstash/redis";
|
|
6
|
+
import type { Client, Endpoint, ExecutionLog } from "./types";
|
|
7
|
+
|
|
8
|
+
if (!process.env.KV_REST_API_URL || !process.env.KV_REST_API_TOKEN) {
|
|
9
|
+
throw new Error("Missing Upstash Redis credentials in environment variables");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const redis = new Redis({
|
|
13
|
+
url: process.env.KV_REST_API_URL,
|
|
14
|
+
token: process.env.KV_REST_API_TOKEN,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export const KV_PREFIXES = {
|
|
18
|
+
CLIENT: "client:",
|
|
19
|
+
API_KEY: "apikey:",
|
|
20
|
+
ENDPOINT: "endpoint:",
|
|
21
|
+
ENDPOINT_NAME: "endpoint:name:",
|
|
22
|
+
EXECUTION_LOG: "log:",
|
|
23
|
+
RATE_LIMIT: "ratelimit:",
|
|
24
|
+
CLIENTS_LIST: "clients:list",
|
|
25
|
+
ENDPOINTS_LIST: "endpoints:list",
|
|
26
|
+
} as const;
|
|
27
|
+
|
|
28
|
+
export async function getClient(clientId: string): Promise<Client | null> {
|
|
29
|
+
return await redis.get<Client>(`${KV_PREFIXES.CLIENT}${clientId}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function getClientByApiKey(apiKey: string): Promise<Client | null> {
|
|
33
|
+
const clientId = await redis.get<string>(`${KV_PREFIXES.API_KEY}${apiKey}`);
|
|
34
|
+
if (!clientId) return null;
|
|
35
|
+
return await getClient(clientId);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function getAllClients(): Promise<Client[]> {
|
|
39
|
+
const clientIds =
|
|
40
|
+
(await redis.get<string[]>(KV_PREFIXES.CLIENTS_LIST)) || [];
|
|
41
|
+
if (clientIds.length === 0) return [];
|
|
42
|
+
const clients = await Promise.all(clientIds.map((id) => getClient(id)));
|
|
43
|
+
return clients.filter((c): c is Client => c !== null);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function saveClient(client: Client): Promise<void> {
|
|
47
|
+
await redis.set(`${KV_PREFIXES.CLIENT}${client.id}`, JSON.stringify(client));
|
|
48
|
+
|
|
49
|
+
for (const apiKey of client.apiKeys) {
|
|
50
|
+
await redis.set(`${KV_PREFIXES.API_KEY}${apiKey}`, client.id);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const clientIds =
|
|
54
|
+
(await redis.get<string[]>(KV_PREFIXES.CLIENTS_LIST)) || [];
|
|
55
|
+
if (!clientIds.includes(client.id)) {
|
|
56
|
+
clientIds.push(client.id);
|
|
57
|
+
await redis.set(KV_PREFIXES.CLIENTS_LIST, clientIds);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function deleteClient(clientId: string): Promise<void> {
|
|
62
|
+
const client = await getClient(clientId);
|
|
63
|
+
if (!client) return;
|
|
64
|
+
|
|
65
|
+
for (const apiKey of client.apiKeys) {
|
|
66
|
+
await redis.del(`${KV_PREFIXES.API_KEY}${apiKey}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let clientIds =
|
|
70
|
+
(await redis.get<string[]>(KV_PREFIXES.CLIENTS_LIST)) || [];
|
|
71
|
+
clientIds = clientIds.filter((id) => id !== clientId);
|
|
72
|
+
await redis.set(KV_PREFIXES.CLIENTS_LIST, clientIds);
|
|
73
|
+
|
|
74
|
+
await redis.del(`${KV_PREFIXES.CLIENT}${clientId}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function getEndpoint(endpointId: string): Promise<Endpoint | null> {
|
|
78
|
+
return await redis.get<Endpoint>(`${KV_PREFIXES.ENDPOINT}${endpointId}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function getEndpointByName(
|
|
82
|
+
name: string
|
|
83
|
+
): Promise<Endpoint | null> {
|
|
84
|
+
const endpointId = await redis.get<string>(
|
|
85
|
+
`${KV_PREFIXES.ENDPOINT_NAME}${name}`
|
|
86
|
+
);
|
|
87
|
+
if (!endpointId) return null;
|
|
88
|
+
return await getEndpoint(endpointId);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function getAllEndpoints(): Promise<Endpoint[]> {
|
|
92
|
+
const endpointIds =
|
|
93
|
+
(await redis.get<string[]>(KV_PREFIXES.ENDPOINTS_LIST)) || [];
|
|
94
|
+
if (endpointIds.length === 0) return [];
|
|
95
|
+
const endpoints = await Promise.all(endpointIds.map((id) => getEndpoint(id)));
|
|
96
|
+
return endpoints.filter((e): e is Endpoint => e !== null);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function saveEndpoint(endpoint: Endpoint): Promise<void> {
|
|
100
|
+
await redis.set(
|
|
101
|
+
`${KV_PREFIXES.ENDPOINT}${endpoint.id}`,
|
|
102
|
+
JSON.stringify(endpoint)
|
|
103
|
+
);
|
|
104
|
+
await redis.set(`${KV_PREFIXES.ENDPOINT_NAME}${endpoint.name}`, endpoint.id);
|
|
105
|
+
|
|
106
|
+
const endpointIds =
|
|
107
|
+
(await redis.get<string[]>(KV_PREFIXES.ENDPOINTS_LIST)) || [];
|
|
108
|
+
if (!endpointIds.includes(endpoint.id)) {
|
|
109
|
+
endpointIds.push(endpoint.id);
|
|
110
|
+
await redis.set(KV_PREFIXES.ENDPOINTS_LIST, endpointIds);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export async function deleteEndpoint(endpointId: string): Promise<void> {
|
|
115
|
+
const endpoint = await getEndpoint(endpointId);
|
|
116
|
+
if (!endpoint) return;
|
|
117
|
+
|
|
118
|
+
await redis.del(`${KV_PREFIXES.ENDPOINT_NAME}${endpoint.name}`);
|
|
119
|
+
|
|
120
|
+
let endpointIds =
|
|
121
|
+
(await redis.get<string[]>(KV_PREFIXES.ENDPOINTS_LIST)) || [];
|
|
122
|
+
endpointIds = endpointIds.filter((id) => id !== endpointId);
|
|
123
|
+
await redis.set(KV_PREFIXES.ENDPOINTS_LIST, endpointIds);
|
|
124
|
+
|
|
125
|
+
await redis.del(`${KV_PREFIXES.ENDPOINT}${endpointId}`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export async function logExecution(log: ExecutionLog): Promise<void> {
|
|
129
|
+
const key = `${KV_PREFIXES.EXECUTION_LOG}${log.id}`;
|
|
130
|
+
await redis.set(key, JSON.stringify(log), {
|
|
131
|
+
ex: 60 * 60 * 24 * 30,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export async function checkRateLimit(
|
|
136
|
+
clientId: string,
|
|
137
|
+
limit: number
|
|
138
|
+
): Promise<boolean> {
|
|
139
|
+
const key = `${KV_PREFIXES.RATE_LIMIT}${clientId}`;
|
|
140
|
+
|
|
141
|
+
const pipeline = redis.pipeline();
|
|
142
|
+
pipeline.incr(key);
|
|
143
|
+
pipeline.ttl(key);
|
|
144
|
+
|
|
145
|
+
const [current, ttl] = await pipeline.exec<[number, number]>();
|
|
146
|
+
|
|
147
|
+
if (ttl === -1) {
|
|
148
|
+
await redis.expire(key, 60);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return current <= limit;
|
|
152
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security helpers used by the Next.js runtime and exported as part of the package.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ExecutionContext } from "./types";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Sanitize user input to prevent code injection
|
|
9
|
+
*/
|
|
10
|
+
export function sanitizeInput(input: unknown): unknown {
|
|
11
|
+
if (typeof input === "string") {
|
|
12
|
+
return input
|
|
13
|
+
.replace(/[<>]/g, "")
|
|
14
|
+
.replace(/javascript:/gi, "")
|
|
15
|
+
.replace(/on\w+=/gi, "")
|
|
16
|
+
.trim();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (Array.isArray(input)) {
|
|
20
|
+
return input.map(sanitizeInput);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (typeof input === "object" && input !== null) {
|
|
24
|
+
const sanitized: Record<string, unknown> = {};
|
|
25
|
+
for (const [key, value] of Object.entries(input)) {
|
|
26
|
+
sanitized[key] = sanitizeInput(value);
|
|
27
|
+
}
|
|
28
|
+
return sanitized;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return input;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Validate method name (alphanumeric, underscores, and hyphens)
|
|
36
|
+
*/
|
|
37
|
+
export function isValidMethodName(name: string): boolean {
|
|
38
|
+
return /^[a-zA-Z_-][a-zA-Z0-9_.-]*$/.test(name);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Validate API key format
|
|
43
|
+
*/
|
|
44
|
+
export function isValidApiKey(apiKey: string): boolean {
|
|
45
|
+
return /^opal_[a-z0-9_-]+_key_[a-zA-Z0-9]{12,}$/.test(apiKey);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Execute stored code in a sandbox scope with a time limit.
|
|
50
|
+
*/
|
|
51
|
+
export async function executeInSandbox(
|
|
52
|
+
code: string,
|
|
53
|
+
params: Record<string, unknown>,
|
|
54
|
+
props: Record<string, string>,
|
|
55
|
+
context: ExecutionContext
|
|
56
|
+
): Promise<void> {
|
|
57
|
+
const executionPromise = (async () => {
|
|
58
|
+
try {
|
|
59
|
+
const executeFunction = new Function(`return ${code}`)();
|
|
60
|
+
await executeFunction(params, props, context);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error("[Sandbox Execution Error]:", error);
|
|
63
|
+
const errorMessage =
|
|
64
|
+
error instanceof Error ? error.message : "Internal sandbox error";
|
|
65
|
+
if (context.status !== "error") {
|
|
66
|
+
context.setError(errorMessage);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
})();
|
|
70
|
+
|
|
71
|
+
const timeoutPromise = new Promise<void>((_, reject) =>
|
|
72
|
+
setTimeout(
|
|
73
|
+
() => reject(new Error("Execution timed out after 3000ms")),
|
|
74
|
+
3000
|
|
75
|
+
)
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
await Promise.race([executionPromise, timeoutPromise]);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
const errorMessage =
|
|
82
|
+
error instanceof Error ? error.message : "Unknown sandbox error";
|
|
83
|
+
if (context.status !== "error") {
|
|
84
|
+
context.setError(errorMessage);
|
|
85
|
+
}
|
|
86
|
+
context.log(`Sandbox execution failed: ${errorMessage}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Validate required props are provided
|
|
92
|
+
*/
|
|
93
|
+
export function validateRequiredProps(
|
|
94
|
+
requiredProps: string[],
|
|
95
|
+
providedProps: Record<string, string>
|
|
96
|
+
): { valid: boolean; missing: string[] } {
|
|
97
|
+
const missing = requiredProps.filter((prop) => !providedProps[prop]);
|
|
98
|
+
return {
|
|
99
|
+
valid: missing.length === 0,
|
|
100
|
+
missing,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared server-side types for the Opal Support App runtime.
|
|
3
|
+
* These live inside the Beddel package so both the Next.js app and
|
|
4
|
+
* the npm distribution reference exactly the same contracts.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ExecutionContext as DeclarativeExecutionContext } from "../types/executionContext";
|
|
8
|
+
|
|
9
|
+
export interface Client {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
email: string;
|
|
13
|
+
apiKeys: string[];
|
|
14
|
+
createdAt: string;
|
|
15
|
+
rateLimit: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface Endpoint {
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
description: string;
|
|
22
|
+
code: string;
|
|
23
|
+
visibility: "public" | "private";
|
|
24
|
+
requiredProps: string[];
|
|
25
|
+
createdAt: string;
|
|
26
|
+
updatedAt: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ExecutionLog {
|
|
30
|
+
id: string;
|
|
31
|
+
clientId: string;
|
|
32
|
+
endpointName: string;
|
|
33
|
+
timestamp: string;
|
|
34
|
+
duration: number;
|
|
35
|
+
success: boolean;
|
|
36
|
+
error?: string;
|
|
37
|
+
input?: unknown;
|
|
38
|
+
output?: unknown;
|
|
39
|
+
logs?: string[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type ExecutionContext = DeclarativeExecutionContext;
|
|
43
|
+
|
|
44
|
+
export interface ExecuteMethodInput {
|
|
45
|
+
methodName: string;
|
|
46
|
+
params: Record<string, unknown>;
|
|
47
|
+
props: Record<string, string>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface ExecuteMethodResult {
|
|
51
|
+
success: boolean;
|
|
52
|
+
data?: unknown;
|
|
53
|
+
error?: string;
|
|
54
|
+
executionTime: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface GraphQLContext {
|
|
58
|
+
clientId: string;
|
|
59
|
+
client: Client;
|
|
60
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project-agnostic execution context contract for declarative runtimes.
|
|
3
|
+
* Mirrors the shape used inside the Opal Support App but stays self-contained
|
|
4
|
+
* so the npm package does not depend on repository-local files.
|
|
5
|
+
*/
|
|
6
|
+
export interface ExecutionContext {
|
|
7
|
+
logs: string[];
|
|
8
|
+
status: "running" | "success" | "error";
|
|
9
|
+
output: unknown;
|
|
10
|
+
error?: string;
|
|
11
|
+
log: (message: string) => void;
|
|
12
|
+
setOutput: (output: unknown) => void;
|
|
13
|
+
setError: (error: string) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default ExecutionContext;
|