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.
Files changed (236) hide show
  1. package/README.md +297 -0
  2. package/dist/agents/agentRegistry.d.ts +68 -0
  3. package/dist/agents/agentRegistry.d.ts.map +1 -0
  4. package/dist/agents/agentRegistry.js +222 -0
  5. package/dist/agents/agentRegistry.js.map +1 -0
  6. package/dist/agents/formatter-agent.d.ts +10 -0
  7. package/dist/agents/formatter-agent.d.ts.map +1 -0
  8. package/dist/agents/formatter-agent.js +49 -0
  9. package/dist/agents/formatter-agent.js.map +1 -0
  10. package/dist/agents/genkit-agent.d.ts +12 -0
  11. package/dist/agents/genkit-agent.d.ts.map +1 -0
  12. package/dist/agents/genkit-agent.js +119 -0
  13. package/dist/agents/genkit-agent.js.map +1 -0
  14. package/dist/agents/i18n-messages.d.ts +17 -0
  15. package/dist/agents/i18n-messages.d.ts.map +1 -0
  16. package/dist/agents/i18n-messages.js +92 -0
  17. package/dist/agents/i18n-messages.js.map +1 -0
  18. package/dist/agents/index.d.ts +10 -0
  19. package/dist/agents/index.d.ts.map +1 -0
  20. package/dist/agents/index.js +26 -0
  21. package/dist/agents/index.js.map +1 -0
  22. package/dist/agents/pipeline.d.ts +15 -0
  23. package/dist/agents/pipeline.d.ts.map +1 -0
  24. package/dist/agents/pipeline.js +45 -0
  25. package/dist/agents/pipeline.js.map +1 -0
  26. package/dist/agents/schema-factory.d.ts +40 -0
  27. package/dist/agents/schema-factory.d.ts.map +1 -0
  28. package/dist/agents/schema-factory.js +121 -0
  29. package/dist/agents/schema-factory.js.map +1 -0
  30. package/dist/agents/translation-validators.d.ts +26 -0
  31. package/dist/agents/translation-validators.d.ts.map +1 -0
  32. package/dist/agents/translation-validators.js +77 -0
  33. package/dist/agents/translation-validators.js.map +1 -0
  34. package/dist/agents/translator-agents.d.ts +184 -0
  35. package/dist/agents/translator-agents.d.ts.map +1 -0
  36. package/dist/agents/translator-agents.js +613 -0
  37. package/dist/agents/translator-agents.js.map +1 -0
  38. package/dist/agents/types/translation.types.d.ts +100 -0
  39. package/dist/agents/types/translation.types.d.ts.map +1 -0
  40. package/dist/agents/types/translation.types.js +3 -0
  41. package/dist/agents/types/translation.types.js.map +1 -0
  42. package/dist/agents/validator-agent.d.ts +42 -0
  43. package/dist/agents/validator-agent.d.ts.map +1 -0
  44. package/dist/agents/validator-agent.js +122 -0
  45. package/dist/agents/validator-agent.js.map +1 -0
  46. package/dist/audit/auditTrail.d.ts +55 -0
  47. package/dist/audit/auditTrail.d.ts.map +1 -0
  48. package/dist/audit/auditTrail.js +93 -0
  49. package/dist/audit/auditTrail.js.map +1 -0
  50. package/dist/compliance/gdprEngine.d.ts +44 -0
  51. package/dist/compliance/gdprEngine.d.ts.map +1 -0
  52. package/dist/compliance/gdprEngine.js +178 -0
  53. package/dist/compliance/gdprEngine.js.map +1 -0
  54. package/dist/compliance/lgpdEngine.d.ts +51 -0
  55. package/dist/compliance/lgpdEngine.d.ts.map +1 -0
  56. package/dist/compliance/lgpdEngine.js +221 -0
  57. package/dist/compliance/lgpdEngine.js.map +1 -0
  58. package/dist/config.d.ts +78 -0
  59. package/dist/config.d.ts.map +1 -0
  60. package/dist/config.js +77 -0
  61. package/dist/config.js.map +1 -0
  62. package/dist/errors.d.ts +17 -0
  63. package/dist/errors.d.ts.map +1 -0
  64. package/dist/errors.js +40 -0
  65. package/dist/errors.js.map +1 -0
  66. package/dist/firebase/tenantManager.d.ts +84 -0
  67. package/dist/firebase/tenantManager.d.ts.map +1 -0
  68. package/dist/firebase/tenantManager.js +378 -0
  69. package/dist/firebase/tenantManager.js.map +1 -0
  70. package/dist/index.d.ts +36 -0
  71. package/dist/index.d.ts.map +1 -0
  72. package/dist/index.js +118 -0
  73. package/dist/index.js.map +1 -0
  74. package/dist/integration/secure-yaml-runtime.d.ts +68 -0
  75. package/dist/integration/secure-yaml-runtime.d.ts.map +1 -0
  76. package/dist/integration/secure-yaml-runtime.js +245 -0
  77. package/dist/integration/secure-yaml-runtime.js.map +1 -0
  78. package/dist/parser/secure-yaml-parser.d.ts +62 -0
  79. package/dist/parser/secure-yaml-parser.d.ts.map +1 -0
  80. package/dist/parser/secure-yaml-parser.js +234 -0
  81. package/dist/parser/secure-yaml-parser.js.map +1 -0
  82. package/dist/performance/autoscaling.d.ts +100 -0
  83. package/dist/performance/autoscaling.d.ts.map +1 -0
  84. package/dist/performance/autoscaling.js +339 -0
  85. package/dist/performance/autoscaling.js.map +1 -0
  86. package/dist/performance/benchmark.d.ts +104 -0
  87. package/dist/performance/benchmark.d.ts.map +1 -0
  88. package/dist/performance/benchmark.js +514 -0
  89. package/dist/performance/benchmark.js.map +1 -0
  90. package/dist/performance/index.d.ts +14 -0
  91. package/dist/performance/index.d.ts.map +1 -0
  92. package/dist/performance/index.js +35 -0
  93. package/dist/performance/index.js.map +1 -0
  94. package/dist/performance/monitor.d.ts +126 -0
  95. package/dist/performance/monitor.d.ts.map +1 -0
  96. package/dist/performance/monitor.js +324 -0
  97. package/dist/performance/monitor.js.map +1 -0
  98. package/dist/performance/streaming.d.ts +82 -0
  99. package/dist/performance/streaming.d.ts.map +1 -0
  100. package/dist/performance/streaming.js +287 -0
  101. package/dist/performance/streaming.js.map +1 -0
  102. package/dist/runtime/audit.d.ts +240 -0
  103. package/dist/runtime/audit.d.ts.map +1 -0
  104. package/dist/runtime/audit.js +641 -0
  105. package/dist/runtime/audit.js.map +1 -0
  106. package/dist/runtime/declarativeAgentRuntime.d.ts +123 -0
  107. package/dist/runtime/declarativeAgentRuntime.d.ts.map +1 -0
  108. package/dist/runtime/declarativeAgentRuntime.js +576 -0
  109. package/dist/runtime/declarativeAgentRuntime.js.map +1 -0
  110. package/dist/runtime/isolatedRuntime.d.ts +119 -0
  111. package/dist/runtime/isolatedRuntime.d.ts.map +1 -0
  112. package/dist/runtime/isolatedRuntime.js +425 -0
  113. package/dist/runtime/isolatedRuntime.js.map +1 -0
  114. package/dist/runtime/schemaCompiler.d.ts +35 -0
  115. package/dist/runtime/schemaCompiler.d.ts.map +1 -0
  116. package/dist/runtime/schemaCompiler.js +151 -0
  117. package/dist/runtime/schemaCompiler.js.map +1 -0
  118. package/dist/runtime/simpleRuntime.d.ts +57 -0
  119. package/dist/runtime/simpleRuntime.d.ts.map +1 -0
  120. package/dist/runtime/simpleRuntime.js +187 -0
  121. package/dist/runtime/simpleRuntime.js.map +1 -0
  122. package/dist/security/dashboard.d.ts +89 -0
  123. package/dist/security/dashboard.d.ts.map +1 -0
  124. package/dist/security/dashboard.js +300 -0
  125. package/dist/security/dashboard.js.map +1 -0
  126. package/dist/security/hardening.d.ts +130 -0
  127. package/dist/security/hardening.d.ts.map +1 -0
  128. package/dist/security/hardening.js +414 -0
  129. package/dist/security/hardening.js.map +1 -0
  130. package/dist/security/index.d.ts +128 -0
  131. package/dist/security/index.d.ts.map +1 -0
  132. package/dist/security/index.js +353 -0
  133. package/dist/security/index.js.map +1 -0
  134. package/dist/security/monitor.d.ts +88 -0
  135. package/dist/security/monitor.d.ts.map +1 -0
  136. package/dist/security/monitor.js +356 -0
  137. package/dist/security/monitor.js.map +1 -0
  138. package/dist/security/scanner.d.ts +104 -0
  139. package/dist/security/scanner.d.ts.map +1 -0
  140. package/dist/security/scanner.js +298 -0
  141. package/dist/security/scanner.js.map +1 -0
  142. package/dist/security/score.d.ts +150 -0
  143. package/dist/security/score.d.ts.map +1 -0
  144. package/dist/security/score.js +983 -0
  145. package/dist/security/score.js.map +1 -0
  146. package/dist/security/test-security.d.ts +22 -0
  147. package/dist/security/test-security.d.ts.map +1 -0
  148. package/dist/security/test-security.js +154 -0
  149. package/dist/security/test-security.js.map +1 -0
  150. package/dist/security/threatDetector.d.ts +39 -0
  151. package/dist/security/threatDetector.d.ts.map +1 -0
  152. package/dist/security/threatDetector.js +354 -0
  153. package/dist/security/threatDetector.js.map +1 -0
  154. package/dist/security/validation.d.ts +69 -0
  155. package/dist/security/validation.d.ts.map +1 -0
  156. package/dist/security/validation.js +286 -0
  157. package/dist/security/validation.js.map +1 -0
  158. package/dist/server/api/clientsRoute.d.ts +9 -0
  159. package/dist/server/api/clientsRoute.d.ts.map +1 -0
  160. package/dist/server/api/clientsRoute.js +71 -0
  161. package/dist/server/api/clientsRoute.js.map +1 -0
  162. package/dist/server/api/endpointsRoute.d.ts +8 -0
  163. package/dist/server/api/endpointsRoute.d.ts.map +1 -0
  164. package/dist/server/api/endpointsRoute.js +76 -0
  165. package/dist/server/api/endpointsRoute.js.map +1 -0
  166. package/dist/server/api/graphql.d.ts +9 -0
  167. package/dist/server/api/graphql.d.ts.map +1 -0
  168. package/dist/server/api/graphql.js +180 -0
  169. package/dist/server/api/graphql.js.map +1 -0
  170. package/dist/server/errors.d.ts +19 -0
  171. package/dist/server/errors.d.ts.map +1 -0
  172. package/dist/server/errors.js +42 -0
  173. package/dist/server/errors.js.map +1 -0
  174. package/dist/server/index.d.ts +7 -0
  175. package/dist/server/index.d.ts.map +1 -0
  176. package/dist/server/index.js +24 -0
  177. package/dist/server/index.js.map +1 -0
  178. package/dist/server/kvStore.d.ts +27 -0
  179. package/dist/server/kvStore.d.ts.map +1 -0
  180. package/dist/server/kvStore.js +128 -0
  181. package/dist/server/kvStore.js.map +1 -0
  182. package/dist/server/runtimeSecurity.d.ts +28 -0
  183. package/dist/server/runtimeSecurity.d.ts.map +1 -0
  184. package/dist/server/runtimeSecurity.js +85 -0
  185. package/dist/server/runtimeSecurity.js.map +1 -0
  186. package/dist/server/types.d.ts +53 -0
  187. package/dist/server/types.d.ts.map +1 -0
  188. package/dist/server/types.js +8 -0
  189. package/dist/server/types.js.map +1 -0
  190. package/dist/types/executionContext.d.ts +16 -0
  191. package/dist/types/executionContext.d.ts.map +1 -0
  192. package/dist/types/executionContext.js +3 -0
  193. package/dist/types/executionContext.js.map +1 -0
  194. package/package.json +77 -0
  195. package/src/agents/agentRegistry.ts +272 -0
  196. package/src/agents/image-agent.yaml +86 -0
  197. package/src/agents/joker-agent.yaml +47 -0
  198. package/src/agents/translator-agent.yaml +80 -0
  199. package/src/audit/auditTrail.ts +134 -0
  200. package/src/compliance/gdprEngine.ts +209 -0
  201. package/src/compliance/lgpdEngine.ts +268 -0
  202. package/src/config.ts +179 -0
  203. package/src/errors.ts +35 -0
  204. package/src/firebase/tenantManager.ts +443 -0
  205. package/src/index.ts +125 -0
  206. package/src/integration/secure-yaml-runtime.ts +341 -0
  207. package/src/parser/secure-yaml-parser.ts +273 -0
  208. package/src/performance/autoscaling.ts +495 -0
  209. package/src/performance/benchmark.ts +644 -0
  210. package/src/performance/index.ts +34 -0
  211. package/src/performance/monitor.ts +469 -0
  212. package/src/performance/streaming.ts +317 -0
  213. package/src/runtime/audit.ts +907 -0
  214. package/src/runtime/declarativeAgentRuntime.ts +836 -0
  215. package/src/runtime/isolatedRuntime.ts +572 -0
  216. package/src/runtime/schemaCompiler.ts +228 -0
  217. package/src/runtime/simpleRuntime.ts +201 -0
  218. package/src/security/dashboard.ts +462 -0
  219. package/src/security/hardening.ts +560 -0
  220. package/src/security/index.ts +439 -0
  221. package/src/security/monitor.ts +490 -0
  222. package/src/security/scanner.ts +368 -0
  223. package/src/security/score.ts +1138 -0
  224. package/src/security/threatDetector.ts +481 -0
  225. package/src/security/validation.ts +365 -0
  226. package/src/server/api/clientsRoute.ts +92 -0
  227. package/src/server/api/endpointsRoute.ts +97 -0
  228. package/src/server/api/graphql.ts +249 -0
  229. package/src/server/errors.ts +38 -0
  230. package/src/server/index.ts +6 -0
  231. package/src/server/kvStore.ts +152 -0
  232. package/src/server/runtimeSecurity.ts +102 -0
  233. package/src/server/types.ts +60 -0
  234. package/src/types/executionContext.ts +16 -0
  235. package/tools/seed.ts +365 -0
  236. 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,6 @@
1
+ export * from "./types";
2
+ export * from "./errors";
3
+ export * from "./kvStore";
4
+ export * from "./runtimeSecurity";
5
+ export { agentRegistry } from "../agents/agentRegistry";
6
+ export type { AgentRegistration } from "../agents/agentRegistry";
@@ -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;