@invect/nextjs 0.0.1

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/dist/index.mjs ADDED
@@ -0,0 +1,650 @@
1
+ import { BatchProvider, GraphNodeType, Invect } from "@invect/core";
2
+ import { ZodError } from "zod";
3
+ //#region src/index.ts
4
+ const parseParamsFromSearch = (value) => {
5
+ if (!value) return {};
6
+ try {
7
+ return JSON.parse(value);
8
+ } catch {
9
+ return {};
10
+ }
11
+ };
12
+ function createPluginDatabaseApi(core) {
13
+ const connection = core.getDatabaseConnection();
14
+ const normalizeSql = (statement) => {
15
+ if (connection.type !== "postgresql") return statement;
16
+ let index = 0;
17
+ return statement.replace(/\?/g, () => `$${++index}`);
18
+ };
19
+ const query = async (statement, params = []) => {
20
+ switch (connection.type) {
21
+ case "postgresql": return await connection.db.$client.unsafe(normalizeSql(statement), params);
22
+ case "sqlite": return connection.db.$client.prepare(statement).all(...params);
23
+ case "mysql": {
24
+ const [rows] = await connection.db.$client.execute(statement, params);
25
+ return Array.isArray(rows) ? rows : [];
26
+ }
27
+ }
28
+ throw new Error(`Unsupported database type: ${String(connection.type)}`);
29
+ };
30
+ return {
31
+ type: connection.type,
32
+ query,
33
+ async execute(statement, params = []) {
34
+ await query(statement, params);
35
+ }
36
+ };
37
+ }
38
+ function createInvectHandler(config) {
39
+ let core = null;
40
+ let initializationPromise = null;
41
+ const ensureInitialized = async () => {
42
+ if (core && core.isInitialized()) return core;
43
+ if (!initializationPromise) initializationPromise = (async () => {
44
+ try {
45
+ if (process.env.NODE_ENV === "production" && process.env.NEXT_PHASE === "phase-production-build") throw new Error("Skipping database initialization during build");
46
+ core = new Invect(config);
47
+ await core.initialize();
48
+ await core.startBatchPolling();
49
+ console.log("✅ Invect initialized and batch polling started");
50
+ } catch (error) {
51
+ console.error("Failed to initialize Invect Core:", error);
52
+ core = null;
53
+ initializationPromise = null;
54
+ throw error;
55
+ }
56
+ })();
57
+ await initializationPromise;
58
+ if (!core) throw new Error("Invect Core failed to initialize");
59
+ return core;
60
+ };
61
+ const getInitializedCore = async () => {
62
+ try {
63
+ return await ensureInitialized();
64
+ } catch (error) {
65
+ const errMsg = error instanceof Error ? error.message : String(error);
66
+ if (!(errMsg.includes("missing") && errMsg.includes("table") || errMsg.includes("DATABASE NOT READY") || errMsg.includes("DATABASE CONNECTION FAILED") || errMsg.includes("connectivity check failed"))) console.error("Invect initialization failed:", error);
67
+ return Response.json({
68
+ error: "Service Unavailable",
69
+ message: errMsg
70
+ }, { status: 503 });
71
+ }
72
+ };
73
+ const handleError = (error) => {
74
+ if (error instanceof ZodError) return Response.json({
75
+ error: "Validation Error",
76
+ message: "Invalid request data",
77
+ details: error.errors.map((err) => ({
78
+ path: err.path.join("."),
79
+ message: err.message,
80
+ code: err.code
81
+ }))
82
+ }, { status: 400 });
83
+ if (error && typeof error === "object" && "name" in error && error.name === "DatabaseError") return Response.json({
84
+ error: "Database Error",
85
+ message: error instanceof Error ? error.message : "A database error occurred"
86
+ }, { status: 500 });
87
+ if (error && typeof error === "object" && "statusCode" in error && typeof error.statusCode === "number") {
88
+ const statusCode = error.statusCode;
89
+ const message = error instanceof Error ? error.message : "An error occurred";
90
+ const code = "code" in error && typeof error.code === "string" ? error.code : void 0;
91
+ return Response.json({
92
+ error: statusCode < 500 ? "Bad Request" : "Internal Server Error",
93
+ message,
94
+ ...code ? { code } : {}
95
+ }, { status: statusCode });
96
+ }
97
+ console.error("Invect Handler Error:", error);
98
+ return Response.json({
99
+ error: "Internal Server Error",
100
+ message: "An unexpected error occurred"
101
+ }, { status: 500 });
102
+ };
103
+ const parseRequestBody = async (request) => {
104
+ try {
105
+ if (request.headers.get("content-type")?.includes("application/json")) return await request.json();
106
+ return {};
107
+ } catch {
108
+ return {};
109
+ }
110
+ };
111
+ const handleRequest = async (request, context) => {
112
+ try {
113
+ const coreOrResponse = await getInitializedCore();
114
+ if (coreOrResponse instanceof Response) return coreOrResponse;
115
+ const initializedCore = coreOrResponse;
116
+ const method = request.method;
117
+ const path = (await context.params).invect.join("/");
118
+ const requestClone = request.clone();
119
+ const body = await parseRequestBody(request);
120
+ const searchParams = new URL(request.url).searchParams;
121
+ if (method === "GET" && path === "flows/list") {
122
+ const flows = await initializedCore.listFlows();
123
+ return Response.json(flows);
124
+ }
125
+ if (method === "POST" && path === "flows/list") {
126
+ const flows = await initializedCore.listFlows(body);
127
+ return Response.json(flows);
128
+ }
129
+ if (method === "POST" && path === "flows") {
130
+ const flow = await initializedCore.createFlow(body);
131
+ return Response.json(flow, { status: 201 });
132
+ }
133
+ if (method === "POST" && path === "validate-flow") {
134
+ const { flowId, flowDefinition } = body;
135
+ const result = await initializedCore.validateFlowDefinition(flowId, flowDefinition);
136
+ return Response.json(result);
137
+ }
138
+ if (method === "GET" && path.match(/^flows\/[^/]+\/react-flow$/)) {
139
+ const flowId = path.split("/")[1];
140
+ const options = {};
141
+ const version = searchParams.get("version");
142
+ const flowRunId = searchParams.get("flowRunId");
143
+ if (version) options.version = version;
144
+ if (flowRunId) options.flowRunId = flowRunId;
145
+ const result = await initializedCore.renderToReactFlow(flowId, options);
146
+ return Response.json(result);
147
+ }
148
+ if (method === "POST" && path.match(/^flows\/[^/]+\/versions\/list$/)) {
149
+ const flowId = path.split("/")[1];
150
+ const versions = await initializedCore.listFlowVersions(flowId, body);
151
+ return Response.json(versions);
152
+ }
153
+ if (method === "POST" && path.match(/^flows\/[^/]+\/versions$/) && !path.endsWith("/list")) {
154
+ const flowId = path.split("/")[1];
155
+ const version = await initializedCore.createFlowVersion(flowId, body);
156
+ return Response.json(version, { status: 201 });
157
+ }
158
+ if (method === "GET" && path.match(/^flows\/[^/]+\/versions\/[^/]+$/)) {
159
+ const parts = path.split("/");
160
+ const flowId = parts[1];
161
+ const version = parts[3];
162
+ const flowVersion = await initializedCore.getFlowVersion(flowId, version);
163
+ if (!flowVersion) return Response.json({
164
+ error: "Not Found",
165
+ message: `Version ${version} not found for flow ${flowId}`
166
+ }, { status: 404 });
167
+ return Response.json(flowVersion);
168
+ }
169
+ if (method === "POST" && path.match(/^flows\/[^/]+\/run-to-node\/[^/]+$/)) {
170
+ const parts = path.split("/");
171
+ const flowId = parts[1];
172
+ const nodeId = parts[3];
173
+ const { inputs = {}, options } = body;
174
+ const result = await initializedCore.executeFlowToNode(flowId, nodeId, inputs, options);
175
+ return Response.json(result, { status: 201 });
176
+ }
177
+ if (method === "POST" && path.match(/^flows\/[^/]+\/run$/)) {
178
+ const flowId = path.split("/")[1];
179
+ const { inputs = {}, options } = body;
180
+ const result = await initializedCore.startFlowRunAsync(flowId, inputs, options);
181
+ return Response.json(result, { status: 201 });
182
+ }
183
+ if (method === "POST" && path === "flow-runs/list") {
184
+ const flowRuns = await initializedCore.listFlowRuns(body);
185
+ return Response.json(flowRuns);
186
+ }
187
+ if (method === "POST" && path.match(/^flow-runs\/[^/]+\/resume$/)) {
188
+ const flowRunId = path.split("/")[1];
189
+ const result = await initializedCore.resumeExecution(flowRunId);
190
+ return Response.json(result);
191
+ }
192
+ if (method === "POST" && path.match(/^flow-runs\/[^/]+\/cancel$/)) {
193
+ const flowRunId = path.split("/")[1];
194
+ const result = await initializedCore.cancelFlowRun(flowRunId);
195
+ return Response.json(result);
196
+ }
197
+ if (method === "POST" && path.match(/^flow-runs\/[^/]+\/pause$/)) {
198
+ const flowRunId = path.split("/")[1];
199
+ const { reason } = body;
200
+ const result = await initializedCore.pauseFlowRun(flowRunId, reason);
201
+ return Response.json(result);
202
+ }
203
+ if (method === "GET" && path.match(/^flow-runs\/[^/]+\/node-executions$/)) {
204
+ const flowRunId = path.split("/")[1];
205
+ const nodeExecutions = await initializedCore.getNodeExecutionsByRunId(flowRunId);
206
+ return Response.json(nodeExecutions);
207
+ }
208
+ if (method === "GET" && path.match(/^flow-runs\/[^/]+\/stream$/)) {
209
+ const flowRunId = path.split("/")[1];
210
+ const stream = initializedCore.createFlowRunEventStream(flowRunId);
211
+ const encoder = new TextEncoder();
212
+ const readable = new ReadableStream({ async start(controller) {
213
+ try {
214
+ for await (const event of stream) {
215
+ const data = JSON.stringify(event);
216
+ controller.enqueue(encoder.encode(`event: ${event.type}\ndata: ${data}\n\n`));
217
+ }
218
+ } catch (error) {
219
+ const message = error instanceof Error ? error.message : "Stream failed";
220
+ controller.enqueue(encoder.encode(`event: error\ndata: ${JSON.stringify({
221
+ type: "error",
222
+ message
223
+ })}\n\n`));
224
+ } finally {
225
+ controller.close();
226
+ }
227
+ } });
228
+ return new Response(readable, { headers: {
229
+ "Content-Type": "text/event-stream",
230
+ "Cache-Control": "no-cache",
231
+ Connection: "keep-alive",
232
+ "X-Accel-Buffering": "no"
233
+ } });
234
+ }
235
+ if (method === "GET" && path.match(/^flows\/[^/]+\/flow-runs$/)) {
236
+ const flowId = path.split("/")[1];
237
+ const flowRuns = await initializedCore.listFlowRunsByFlowId(flowId);
238
+ return Response.json(flowRuns);
239
+ }
240
+ if (method === "GET" && path.match(/^flow-runs\/[^/]+$/)) {
241
+ const flowRunId = path.split("/")[1];
242
+ const flowRun = await initializedCore.getFlowRunById(flowRunId);
243
+ return Response.json(flowRun);
244
+ }
245
+ if (method === "POST" && path === "node-executions/list") {
246
+ const nodeExecutions = await initializedCore.listNodeExecutions(body);
247
+ return Response.json(nodeExecutions);
248
+ }
249
+ if (method === "POST" && path === "node-data/sql-query") {
250
+ const result = await initializedCore.executeSqlQuery(body);
251
+ return Response.json(result);
252
+ }
253
+ if (method === "POST" && path === "node-data/test-expression") {
254
+ const result = await initializedCore.testJsExpression(body);
255
+ return Response.json(result);
256
+ }
257
+ if (method === "POST" && path === "node-data/test-mapper") {
258
+ const result = await initializedCore.testMapper(body);
259
+ return Response.json(result);
260
+ }
261
+ if (method === "POST" && path === "node-data/model-query") {
262
+ const result = await initializedCore.testModelPrompt(body);
263
+ return Response.json(result);
264
+ }
265
+ if (method === "GET" && path === "node-data/models") {
266
+ const credentialId = searchParams.get("credentialId");
267
+ const providerParam = searchParams.get("provider");
268
+ if (credentialId) return Response.json(await initializedCore.getModelsForCredential(credentialId));
269
+ if (providerParam) {
270
+ const normalized = providerParam.trim().toUpperCase();
271
+ if (!Object.values(BatchProvider).includes(normalized)) return Response.json({
272
+ error: "INVALID_PROVIDER",
273
+ message: `Unsupported provider '${providerParam}'`
274
+ }, { status: 400 });
275
+ return Response.json(await initializedCore.getModelsForProvider(normalized));
276
+ }
277
+ return Response.json(await initializedCore.getAvailableModels());
278
+ }
279
+ if (method === "GET" && path === "node-data/databases") return Response.json(initializedCore.getAvailableDatabases());
280
+ if (method === "POST" && path === "node-config/update") {
281
+ const response = await initializedCore.handleNodeConfigUpdate(body);
282
+ return Response.json(response);
283
+ }
284
+ if (method === "GET" && path.startsWith("node-definition/")) {
285
+ const rawNodeType = path.split("/")[1] ?? "";
286
+ const nodeTypeParam = rawNodeType.includes(".") ? rawNodeType : rawNodeType.toUpperCase();
287
+ const isLegacyEnum = !rawNodeType.includes(".") && nodeTypeParam in GraphNodeType;
288
+ const isActionId = rawNodeType.includes(".");
289
+ if (!isLegacyEnum && !isActionId) return Response.json({
290
+ error: "INVALID_NODE_TYPE",
291
+ message: `Unknown node type '${rawNodeType}'`
292
+ }, { status: 400 });
293
+ const params = parseParamsFromSearch(searchParams.get("params"));
294
+ const changeField = searchParams.get("changeField") ?? void 0;
295
+ const changeValue = searchParams.get("changeValue") ?? void 0;
296
+ const nodeId = searchParams.get("nodeId") ?? `definition-${nodeTypeParam.toLowerCase()}`;
297
+ const flowId = searchParams.get("flowId") ?? void 0;
298
+ const response = await initializedCore.handleNodeConfigUpdate({
299
+ nodeType: nodeTypeParam,
300
+ nodeId,
301
+ flowId,
302
+ params,
303
+ change: changeField ? {
304
+ field: changeField,
305
+ value: changeValue
306
+ } : void 0
307
+ });
308
+ return Response.json(response);
309
+ }
310
+ if (method === "GET" && path === "nodes") return Response.json(initializedCore.getAvailableNodes());
311
+ if (method === "GET" && path.match(/^actions\/[^/]+\/fields\/[^/]+\/options$/)) {
312
+ const parts = path.split("/");
313
+ const actionId = parts[1];
314
+ const fieldName = parts[3];
315
+ let dependencyValues = {};
316
+ const depsParam = searchParams.get("deps");
317
+ if (depsParam) try {
318
+ dependencyValues = JSON.parse(depsParam);
319
+ } catch {
320
+ return Response.json({ error: "Invalid deps JSON" }, { status: 400 });
321
+ }
322
+ return Response.json(await initializedCore.resolveFieldOptions(actionId, fieldName, dependencyValues));
323
+ }
324
+ if (method === "POST" && path === "nodes/test") {
325
+ const { nodeType, params: nodeParams, inputData } = body;
326
+ if (!nodeType || typeof nodeType !== "string") return Response.json({ error: "nodeType is required and must be a string" }, { status: 400 });
327
+ if (!nodeParams || typeof nodeParams !== "object") return Response.json({ error: "params is required and must be an object" }, { status: 400 });
328
+ return Response.json(await initializedCore.testNode(nodeType, nodeParams, inputData || {}));
329
+ }
330
+ if (method === "GET" && path === "credentials/oauth2/providers") return Response.json(initializedCore.getOAuth2Providers());
331
+ if (method === "GET" && path.match(/^credentials\/oauth2\/providers\/[^/]+$/)) {
332
+ const providerId = path.split("/")[3];
333
+ const provider = initializedCore.getOAuth2Provider(providerId);
334
+ if (!provider) return Response.json({ error: "OAuth2 provider not found" }, { status: 404 });
335
+ return Response.json(provider);
336
+ }
337
+ if (method === "POST" && path === "credentials/oauth2/start") {
338
+ const { providerId, clientId, clientSecret, redirectUri, scopes, returnUrl, credentialName } = body;
339
+ if (!providerId || !clientId || !clientSecret || !redirectUri) return Response.json({ error: "Missing required fields: providerId, clientId, clientSecret, redirectUri" }, { status: 400 });
340
+ const result = initializedCore.startOAuth2Flow(providerId, {
341
+ clientId,
342
+ clientSecret,
343
+ redirectUri
344
+ }, {
345
+ scopes,
346
+ returnUrl,
347
+ credentialName
348
+ });
349
+ return Response.json(result);
350
+ }
351
+ if (method === "POST" && path === "credentials/oauth2/callback") {
352
+ const { code, state, clientId, clientSecret, redirectUri } = body;
353
+ if (!code || !state || !clientId || !clientSecret || !redirectUri) return Response.json({ error: "Missing required fields: code, state, clientId, clientSecret, redirectUri" }, { status: 400 });
354
+ const credential = await initializedCore.handleOAuth2Callback(code, state, {
355
+ clientId,
356
+ clientSecret,
357
+ redirectUri
358
+ });
359
+ return Response.json(credential);
360
+ }
361
+ if (method === "GET" && path === "credentials/expiring") {
362
+ const daysParam = searchParams.get("daysUntilExpiry");
363
+ const days = daysParam ? parseInt(daysParam) : 7;
364
+ return Response.json(await initializedCore.getExpiringCredentials(days));
365
+ }
366
+ if (method === "POST" && path === "credentials/test-request") {
367
+ const { url: targetUrl, method: reqMethod = "GET", headers: reqHeaders = {}, body: reqBody } = body;
368
+ if (!targetUrl) return Response.json({ error: "URL is required" }, { status: 400 });
369
+ const fetchOptions = {
370
+ method: reqMethod,
371
+ headers: reqHeaders
372
+ };
373
+ if (reqBody && [
374
+ "POST",
375
+ "PUT",
376
+ "PATCH"
377
+ ].includes(reqMethod)) fetchOptions.body = typeof reqBody === "string" ? reqBody : JSON.stringify(reqBody);
378
+ const resp = await fetch(targetUrl, fetchOptions);
379
+ const respText = await resp.text();
380
+ let respBody;
381
+ try {
382
+ respBody = JSON.parse(respText);
383
+ } catch {
384
+ respBody = respText;
385
+ }
386
+ return Response.json({
387
+ status: resp.status,
388
+ statusText: resp.statusText,
389
+ ok: resp.ok,
390
+ body: respBody
391
+ });
392
+ }
393
+ if (method === "POST" && path === "credentials") {
394
+ const credential = await initializedCore.createCredential(body);
395
+ return Response.json(credential, { status: 201 });
396
+ }
397
+ if (method === "GET" && path === "credentials") {
398
+ const filters = {};
399
+ const type = searchParams.get("type");
400
+ const authType = searchParams.get("authType");
401
+ const isActive = searchParams.get("isActive");
402
+ if (type) filters.type = type;
403
+ if (authType) filters.authType = authType;
404
+ if (isActive !== null) filters.isActive = isActive === "true";
405
+ return Response.json(await initializedCore.listCredentials(filters));
406
+ }
407
+ if (method === "POST" && path.match(/^credentials\/[^/]+\/refresh$/)) {
408
+ const credId = path.split("/")[1];
409
+ return Response.json(await initializedCore.refreshOAuth2Credential(credId));
410
+ }
411
+ if (method === "POST" && path.match(/^credentials\/[^/]+\/track-usage$/)) {
412
+ const credId = path.split("/")[1];
413
+ await initializedCore.updateCredentialLastUsed(credId);
414
+ return new Response(null, { status: 204 });
415
+ }
416
+ if (method === "POST" && path.match(/^credentials\/[^/]+\/test$/)) {
417
+ const credentialId = path.split("/")[1];
418
+ const result = await initializedCore.testCredential(credentialId);
419
+ return Response.json(result);
420
+ }
421
+ if (method === "GET" && path.match(/^credentials\/[^/]+$/)) {
422
+ const credentialId = path.split("/")[1];
423
+ return Response.json(await initializedCore.getCredential(credentialId));
424
+ }
425
+ if (method === "DELETE" && path.match(/^credentials\/[^/]+$/)) {
426
+ const credentialId = path.split("/")[1];
427
+ await initializedCore.deleteCredential(credentialId);
428
+ return new Response(null, { status: 204 });
429
+ }
430
+ if (method === "GET" && path.match(/^triggers\/[^/]+$/)) {
431
+ const triggerId = path.split("/")[1];
432
+ const trigger = await initializedCore.getTrigger(triggerId);
433
+ if (!trigger) return Response.json({
434
+ error: "Not Found",
435
+ message: `Trigger ${triggerId} not found`
436
+ }, { status: 404 });
437
+ return Response.json(trigger);
438
+ }
439
+ if (method === "PUT" && path.match(/^triggers\/[^/]+$/)) {
440
+ const triggerId = path.split("/")[1];
441
+ const trigger = await initializedCore.updateTrigger(triggerId, body);
442
+ if (!trigger) return Response.json({ error: "Not Found" }, { status: 404 });
443
+ return Response.json(trigger);
444
+ }
445
+ if (method === "DELETE" && path.match(/^triggers\/[^/]+$/)) {
446
+ const triggerId = path.split("/")[1];
447
+ await initializedCore.deleteTrigger(triggerId);
448
+ return new Response(null, { status: 204 });
449
+ }
450
+ if (method === "GET" && path === "agent/tools") return Response.json(initializedCore.getAgentTools());
451
+ if (method === "GET" && path === "dashboard/stats") return Response.json(await initializedCore.getDashboardStats());
452
+ if (method === "GET" && path === "chat/status") return Response.json({ enabled: initializedCore.isChatEnabled() });
453
+ if (method === "POST" && path === "chat") {
454
+ const { messages, context: chatContext } = body;
455
+ if (!messages || !Array.isArray(messages)) return Response.json({
456
+ error: "Validation Error",
457
+ message: "\"messages\" must be an array of chat messages"
458
+ }, { status: 400 });
459
+ const stream = await initializedCore.createChatStream({
460
+ messages,
461
+ context: chatContext || {}
462
+ });
463
+ const encoder = new TextEncoder();
464
+ const readable = new ReadableStream({ async start(controller) {
465
+ try {
466
+ for await (const event of stream) {
467
+ const data = JSON.stringify(event);
468
+ controller.enqueue(encoder.encode(`event: ${event.type}\ndata: ${data}\n\n`));
469
+ }
470
+ } catch (error) {
471
+ const message = error instanceof Error ? error.message : "Chat stream failed";
472
+ controller.enqueue(encoder.encode(`event: error\ndata: ${JSON.stringify({
473
+ type: "error",
474
+ message,
475
+ recoverable: false
476
+ })}\n\n`));
477
+ } finally {
478
+ controller.close();
479
+ }
480
+ } });
481
+ return new Response(readable, { headers: {
482
+ "Content-Type": "text/event-stream",
483
+ "Cache-Control": "no-cache",
484
+ Connection: "keep-alive",
485
+ "X-Accel-Buffering": "no"
486
+ } });
487
+ }
488
+ if (method === "GET" && path.match(/^chat\/messages\/[^/]+$/)) {
489
+ const flowId = path.split("/")[2];
490
+ return Response.json(await initializedCore.getChatMessages(flowId));
491
+ }
492
+ if (method === "PUT" && path.match(/^chat\/messages\/[^/]+$/)) {
493
+ const flowId = path.split("/")[2];
494
+ const { messages } = body;
495
+ if (!messages || !Array.isArray(messages)) return Response.json({ error: "\"messages\" must be an array" }, { status: 400 });
496
+ return Response.json(await initializedCore.saveChatMessages(flowId, messages));
497
+ }
498
+ if (method === "DELETE" && path.match(/^chat\/messages\/[^/]+$/)) {
499
+ const flowId = path.split("/")[2];
500
+ await initializedCore.deleteChatMessages(flowId);
501
+ return Response.json({ success: true });
502
+ }
503
+ if (method === "GET" && path.match(/^flows\/[^/]+$/)) {
504
+ const flowId = path.split("/")[1];
505
+ return Response.json(await initializedCore.getFlow(flowId));
506
+ }
507
+ if (method === "PUT" && path.match(/^flows\/[^/]+$/)) {
508
+ const flowId = path.split("/")[1];
509
+ return Response.json(await initializedCore.updateFlow(flowId, body));
510
+ }
511
+ if (method === "DELETE" && path.match(/^flows\/[^/]+$/)) {
512
+ const flowId = path.split("/")[1];
513
+ await initializedCore.deleteFlow(flowId);
514
+ return new Response(null, { status: 204 });
515
+ }
516
+ if (path.startsWith("plugins/")) {
517
+ const pluginPath = "/" + path.replace(/^plugins\/?/, "");
518
+ const matchedEndpoint = initializedCore.getPluginEndpoints().find((ep) => {
519
+ if (ep.method !== method) return false;
520
+ const pattern = ep.path.replace(/\*/g, "(.*)").replace(/:([^/]+)/g, "([^/]+)");
521
+ return new RegExp(`^${pattern}$`).test(pluginPath);
522
+ });
523
+ if (!matchedEndpoint) return Response.json({
524
+ error: "Not Found",
525
+ message: `Plugin route ${method} ${pluginPath} not found`
526
+ }, { status: 404 });
527
+ const paramNames = [];
528
+ const paramPattern = matchedEndpoint.path.replace(/\*/g, "(.*)").replace(/:([^/]+)/g, (_m, name) => {
529
+ paramNames.push(name);
530
+ return "([^/]+)";
531
+ });
532
+ const paramMatch = new RegExp(`^${paramPattern}$`).exec(pluginPath);
533
+ const pluginParams = {};
534
+ if (paramMatch) paramNames.forEach((name, i) => {
535
+ pluginParams[name] = paramMatch[i + 1] || "";
536
+ });
537
+ const pluginRequestContext = {
538
+ path: "/" + path.replace(/^plugins\/?/, ""),
539
+ method,
540
+ identity: null
541
+ };
542
+ const hookResult = await initializedCore.getPluginHookRunner().runOnRequest(requestClone.clone(), pluginRequestContext);
543
+ if (hookResult.intercepted && hookResult.response) return hookResult.response;
544
+ const pluginResult = await matchedEndpoint.handler({
545
+ body,
546
+ params: pluginParams,
547
+ query: Object.fromEntries(new URL(request.url).searchParams.entries()),
548
+ headers: (() => {
549
+ const h = {};
550
+ request.headers.forEach((v, k) => {
551
+ h[k] = v;
552
+ });
553
+ return h;
554
+ })(),
555
+ identity: pluginRequestContext.identity,
556
+ database: createPluginDatabaseApi(initializedCore),
557
+ request: requestClone,
558
+ core: {
559
+ getPermissions: (identity) => initializedCore.getPermissions(identity),
560
+ getAvailableRoles: () => initializedCore.getAvailableRoles(),
561
+ getResolvedRole: (identity) => initializedCore.getAuthService().getResolvedRole(identity),
562
+ isFlowAccessTableEnabled: () => initializedCore.isFlowAccessTableEnabled(),
563
+ listFlowAccess: (flowId) => initializedCore.listFlowAccess(flowId),
564
+ grantFlowAccess: (input) => initializedCore.grantFlowAccess(input),
565
+ revokeFlowAccess: (accessId) => initializedCore.revokeFlowAccess(accessId),
566
+ getAccessibleFlowIds: (userId, teamIds) => initializedCore.getAccessibleFlowIds(userId, teamIds),
567
+ getFlowPermission: (flowId, userId, teamIds) => initializedCore.getFlowPermission(flowId, userId, teamIds),
568
+ authorize: (context) => initializedCore.authorize(context)
569
+ }
570
+ });
571
+ if (pluginResult instanceof Response) return pluginResult;
572
+ if ("stream" in pluginResult && pluginResult.stream) return new Response(pluginResult.stream, {
573
+ status: pluginResult.status || 200,
574
+ headers: { "Content-Type": "text/event-stream" }
575
+ });
576
+ const jsonBody = "body" in pluginResult ? pluginResult.body : null;
577
+ return Response.json(jsonBody, { status: pluginResult.status || 200 });
578
+ }
579
+ return Response.json({
580
+ error: "Not Found",
581
+ message: `Route ${method} /${path} not found`
582
+ }, { status: 404 });
583
+ } catch (error) {
584
+ return handleError(error);
585
+ }
586
+ };
587
+ return {
588
+ GET: handleRequest,
589
+ POST: handleRequest,
590
+ PATCH: handleRequest,
591
+ PUT: handleRequest,
592
+ DELETE: handleRequest
593
+ };
594
+ }
595
+ /**
596
+ * Convenience function for creating a simple handler when you don't need catch-all routing
597
+ * This creates individual route handlers for specific endpoints
598
+ */
599
+ function createInvectEndpoint(config) {
600
+ let core = null;
601
+ let initializationPromise = null;
602
+ const ensureInitialized = async () => {
603
+ if (core && core.isInitialized()) return core;
604
+ if (!initializationPromise) initializationPromise = (async () => {
605
+ try {
606
+ core = new Invect(config);
607
+ await core.initialize();
608
+ await core.startBatchPolling();
609
+ console.log("✅ Invect batch polling started");
610
+ } catch (error) {
611
+ console.error("Failed to initialize Invect Core:", error);
612
+ core = null;
613
+ initializationPromise = null;
614
+ throw error;
615
+ }
616
+ })();
617
+ await initializationPromise;
618
+ if (!core) throw new Error("Invect Core failed to initialize");
619
+ return core;
620
+ };
621
+ return {
622
+ core: () => core,
623
+ createEndpoint: (handler) => {
624
+ return async (request) => {
625
+ try {
626
+ return await handler(await ensureInitialized(), request);
627
+ } catch (error) {
628
+ if (error instanceof ZodError) return Response.json({
629
+ error: "Validation Error",
630
+ message: "Invalid request data",
631
+ details: error.errors.map((err) => ({
632
+ path: err.path.join("."),
633
+ message: err.message,
634
+ code: err.code
635
+ }))
636
+ }, { status: 400 });
637
+ console.error("Invect Endpoint Error:", error);
638
+ return Response.json({
639
+ error: "Internal Server Error",
640
+ message: "An unexpected error occurred"
641
+ }, { status: 500 });
642
+ }
643
+ };
644
+ }
645
+ };
646
+ }
647
+ //#endregion
648
+ export { createInvectEndpoint, createInvectHandler };
649
+
650
+ //# sourceMappingURL=index.mjs.map