@invect/express 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.
@@ -0,0 +1,1106 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_middleware_index = require("../middleware/index.cjs");
3
+ let express = require("express");
4
+ let _invect_core = require("@invect/core");
5
+ let zod = require("zod");
6
+ //#region src/invect-router.ts
7
+ function createPluginDatabaseApi(core) {
8
+ const connection = core.getDatabaseConnection();
9
+ const normalizeSql = (statement) => {
10
+ if (connection.type !== "postgresql") return statement;
11
+ let index = 0;
12
+ return statement.replace(/\?/g, () => `$${++index}`);
13
+ };
14
+ const query = async (statement, params = []) => {
15
+ switch (connection.type) {
16
+ case "postgresql": return await connection.db.$client.unsafe(normalizeSql(statement), params);
17
+ case "sqlite": return connection.db.$client.prepare(statement).all(...params);
18
+ case "mysql": {
19
+ const [rows] = await connection.db.$client.execute(statement, params);
20
+ return Array.isArray(rows) ? rows : [];
21
+ }
22
+ }
23
+ throw new Error(`Unsupported database type: ${String(connection.type)}`);
24
+ };
25
+ return {
26
+ type: connection.type,
27
+ query,
28
+ async execute(statement, params = []) {
29
+ switch (connection.type) {
30
+ case "postgresql":
31
+ await connection.db.$client.unsafe(normalizeSql(statement), params);
32
+ return;
33
+ case "sqlite": {
34
+ const client = connection.db.$client;
35
+ const coerced = params.map((p) => typeof p === "boolean" ? p ? 1 : 0 : p);
36
+ client.prepare(statement).run(...coerced);
37
+ return;
38
+ }
39
+ case "mysql":
40
+ await connection.db.$client.execute(statement, params);
41
+ return;
42
+ }
43
+ throw new Error(`Unsupported database type: ${String(connection.type)}`);
44
+ }
45
+ };
46
+ }
47
+ function parseParamsFromQuery(queryValue) {
48
+ if (!queryValue) return {};
49
+ if (typeof queryValue === "string") try {
50
+ return JSON.parse(queryValue);
51
+ } catch {
52
+ return {};
53
+ }
54
+ if (Array.isArray(queryValue)) {
55
+ const last = queryValue[queryValue.length - 1];
56
+ if (typeof last === "string") try {
57
+ return JSON.parse(last);
58
+ } catch {
59
+ return {};
60
+ }
61
+ return {};
62
+ }
63
+ if (typeof queryValue === "object") return Object.entries(queryValue).reduce((acc, [key, value]) => {
64
+ acc[key] = Array.isArray(value) ? value[value.length - 1] : value;
65
+ return acc;
66
+ }, {});
67
+ return {};
68
+ }
69
+ function coerceQueryValue(value) {
70
+ if (Array.isArray(value)) return value[value.length - 1];
71
+ return value ?? void 0;
72
+ }
73
+ /**
74
+ * Create Invect Express Router
75
+ */
76
+ function createInvectRouter(config) {
77
+ const core = new _invect_core.Invect(config);
78
+ core.initialize().then(async () => {
79
+ await core.startBatchPolling();
80
+ console.log("✅ Invect batch polling started");
81
+ await core.startCronScheduler();
82
+ console.log("✅ Invect cron scheduler started");
83
+ }).catch((error) => {
84
+ console.error("Failed to initialize Invect Core:", error);
85
+ });
86
+ const router = (0, express.Router)();
87
+ router.use((req, res, next) => {
88
+ if (!core.isInitialized()) return res.status(503).json({
89
+ error: "Service Unavailable",
90
+ message: "Invect Core is still initializing. Please try again in a moment."
91
+ });
92
+ next();
93
+ });
94
+ /**
95
+ * Auth middleware - resolves identity from host app and attaches to request.
96
+ *
97
+ * The host app provides a `resolveUser` callback in the config that extracts
98
+ * the user identity from the request (e.g., from JWT, session, API key).
99
+ */
100
+ router.use(async (req, res, next) => {
101
+ try {
102
+ const webRequestUrl = `${req.protocol}://${req.get("host")}${req.originalUrl}`;
103
+ const webRequestInit = {
104
+ method: req.method,
105
+ headers: req.headers
106
+ };
107
+ const webRequest = new globalThis.Request(webRequestUrl, webRequestInit);
108
+ const hookContext = {
109
+ path: req.path,
110
+ method: req.method,
111
+ identity: null
112
+ };
113
+ const hookResult = await core.getPluginHookRunner().runOnRequest(webRequest, hookContext);
114
+ if (hookResult.intercepted && hookResult.response) {
115
+ const arrayBuf = await hookResult.response.arrayBuffer();
116
+ res.status(hookResult.response.status);
117
+ hookResult.response.headers.forEach((value, key) => {
118
+ res.setHeader(key, value);
119
+ });
120
+ return res.send(Buffer.from(arrayBuf));
121
+ }
122
+ req.invectIdentity = hookContext.identity ?? null;
123
+ } catch (error) {
124
+ console.error("Auth resolution error:", error);
125
+ req.invectIdentity = null;
126
+ }
127
+ next();
128
+ });
129
+ /**
130
+ * Create an authorization middleware for a specific permission.
131
+ * When useFlowAccessTable is enabled, looks up flow access from database.
132
+ */
133
+ function requirePermission(permission, getResourceId) {
134
+ return require_middleware_index.asyncHandler(async (req, res, next) => {
135
+ const identity = req.invectIdentity ?? null;
136
+ const resourceId = getResourceId ? getResourceId(req) : void 0;
137
+ const resourceType = permission.split(":")[0];
138
+ if (core.isFlowAccessTableEnabled() && identity && resourceId && [
139
+ "flow",
140
+ "flow-version",
141
+ "flow-run",
142
+ "node-execution"
143
+ ].includes(resourceType)) {
144
+ if (core.hasPermission(identity, "admin:*")) return next();
145
+ if (!await core.hasFlowAccess(resourceId, identity.id, identity.teamIds || [], getRequiredFlowPermission(permission))) return res.status(403).json({
146
+ error: "Forbidden",
147
+ message: `No access to flow '${resourceId}'`
148
+ });
149
+ return next();
150
+ } else {
151
+ const result = await core.authorize({
152
+ identity,
153
+ action: permission,
154
+ resource: resourceId ? {
155
+ type: resourceType,
156
+ id: resourceId
157
+ } : void 0
158
+ });
159
+ if (!result.allowed) {
160
+ const status = identity ? 403 : 401;
161
+ return res.status(status).json({
162
+ error: status === 403 ? "Forbidden" : "Unauthorized",
163
+ message: result.reason || "Access denied"
164
+ });
165
+ }
166
+ }
167
+ next();
168
+ });
169
+ }
170
+ /**
171
+ * Map permission to required flow access level.
172
+ */
173
+ function getRequiredFlowPermission(permission) {
174
+ if (permission.includes(":delete")) return "owner";
175
+ if (permission.startsWith("flow-run:") || permission === "node:test") return "operator";
176
+ if (permission.includes(":create") || permission.includes(":update") || permission.includes(":publish")) return "editor";
177
+ return "viewer";
178
+ }
179
+ /**
180
+ * GET /dashboard/stats - Get dashboard statistics
181
+ * Core method: ✅ getDashboardStats()
182
+ * Returns: DashboardStats (flow counts, run counts by status, recent activity)
183
+ */
184
+ router.get("/dashboard/stats", require_middleware_index.asyncHandler(async (_req, res) => {
185
+ const stats = await core.getDashboardStats();
186
+ res.json(stats);
187
+ }));
188
+ /**
189
+ * GET /flows/list - List all flows (simple GET endpoint)
190
+ * Core method: ✅ listFlows()
191
+ * Permission: flow:read
192
+ */
193
+ router.get("/flows/list", requirePermission("flow:read"), require_middleware_index.asyncHandler(async (_req, res) => {
194
+ const flows = await core.listFlows();
195
+ res.json(flows);
196
+ }));
197
+ /**
198
+ * POST /flows/list - List flows with optional filtering and pagination
199
+ * Core method: ✅ listFlows(options?: QueryOptions<Flow>)
200
+ * Body: QueryOptions<Flow>
201
+ * Permission: flow:read
202
+ */
203
+ router.post("/flows/list", requirePermission("flow:read"), require_middleware_index.asyncHandler(async (req, res) => {
204
+ const flows = await core.listFlows(req.body);
205
+ res.json(flows);
206
+ }));
207
+ /**
208
+ * POST /flows - Create a new flow
209
+ * Core method: ✅ createFlow(flowData: CreateFlowRequest)
210
+ * Permission: flow:create
211
+ */
212
+ router.post("/flows", requirePermission("flow:create"), require_middleware_index.asyncHandler(async (req, res) => {
213
+ const flow = await core.createFlow(req.body);
214
+ res.status(201).json(flow);
215
+ }));
216
+ /**
217
+ * GET /flows/:id - Get flow by ID
218
+ * Core method: ✅ getFlow(flowId: string)
219
+ * Permission: flow:read (with resource check)
220
+ */
221
+ router.get("/flows/:id", requirePermission("flow:read", (req) => req.params.id), require_middleware_index.asyncHandler(async (req, res) => {
222
+ const flow = await core.getFlow(req.params.id);
223
+ res.json(flow);
224
+ }));
225
+ /**
226
+ * PUT /flows/:id - Update flow
227
+ * Core method: ✅ updateFlow(flowId: string, updateData: UpdateFlowInput)
228
+ * Permission: flow:update (with resource check)
229
+ */
230
+ router.put("/flows/:id", requirePermission("flow:update", (req) => req.params.id), require_middleware_index.asyncHandler(async (req, res) => {
231
+ const flow = await core.updateFlow(req.params.id, req.body);
232
+ res.json(flow);
233
+ }));
234
+ /**
235
+ * DELETE /flows/:id - Delete flow
236
+ * Core method: ✅ deleteFlow(flowId: string)
237
+ * Permission: flow:delete (with resource check)
238
+ */
239
+ router.delete("/flows/:id", requirePermission("flow:delete", (req) => req.params.id), require_middleware_index.asyncHandler(async (req, res) => {
240
+ await core.deleteFlow(req.params.id);
241
+ res.status(204).send();
242
+ }));
243
+ /**
244
+ * POST /validate-flow - Validate flow definition
245
+ * Core method: ✅ validateFlowDefinition(flowId: string, flowDefinition: InvectDefinition)
246
+ * Permission: flow:read
247
+ */
248
+ router.post("/validate-flow", requirePermission("flow:read"), require_middleware_index.asyncHandler(async (req, res) => {
249
+ const { flowId, flowDefinition } = req.body;
250
+ const result = await core.validateFlowDefinition(flowId, flowDefinition);
251
+ res.json(result);
252
+ }));
253
+ /**
254
+ * GET /flows/:flowId/react-flow - Get flow data in React Flow format
255
+ * Core method: ✅ renderToReactFlow(flowId: string, options)
256
+ * Query params: version, flowRunId
257
+ */
258
+ router.get("/flows/:flowId/react-flow", require_middleware_index.asyncHandler(async (req, res) => {
259
+ const queryParams = req.query;
260
+ const options = {};
261
+ if (queryParams.version) options.version = queryParams.version;
262
+ if (queryParams.flowRunId) options.flowRunId = queryParams.flowRunId;
263
+ const result = await core.renderToReactFlow(req.params.flowId, options);
264
+ res.json(result);
265
+ }));
266
+ /**
267
+ * POST /flows/:id/versions/list - Get flow versions with optional filtering and pagination
268
+ * Core method: ✅ listFlowVersions(flowId: string, options?: QueryOptions<FlowVersion>)
269
+ * Body: QueryOptions<FlowVersion>
270
+ */
271
+ router.post("/flows/:id/versions/list", require_middleware_index.asyncHandler(async (req, res) => {
272
+ const versions = await core.listFlowVersions(req.params.id, req.body);
273
+ res.json(versions);
274
+ }));
275
+ /**
276
+ * POST /flows/:id/versions - Create flow version
277
+ * Core method: ✅ createFlowVersion(flowId: string, versionData: CreateFlowVersionRequest)
278
+ * Permission: flow-version:create (with resource check on flow)
279
+ */
280
+ router.post("/flows/:id/versions", requirePermission("flow-version:create", (req) => req.params.id), require_middleware_index.asyncHandler(async (req, res) => {
281
+ const version = await core.createFlowVersion(req.params.id, req.body);
282
+ res.status(201).json(version);
283
+ }));
284
+ /**
285
+ * GET /flows/:id/versions/:version - Get specific flow version (supports 'latest')
286
+ * Core method: ✅ getFlowVersion(flowId: string, version: string | number | "latest")
287
+ * Permission: flow-version:read (with resource check on flow)
288
+ */
289
+ router.get("/flows/:id/versions/:version", requirePermission("flow-version:read", (req) => req.params.id), require_middleware_index.asyncHandler(async (req, res) => {
290
+ const version = await core.getFlowVersion(req.params.id, req.params.version);
291
+ if (!version) return res.status(404).json({
292
+ error: "Not Found",
293
+ message: `Version ${req.params.version} not found for flow ${req.params.id}`
294
+ });
295
+ res.json(version);
296
+ }));
297
+ /**
298
+ * POST /flows/:flowId/run - Start flow execution (async - returns immediately)
299
+ * Core method: ✅ startFlowRunAsync(flowId: string, inputs: FlowInputs, options?: ExecuteFlowOptions)
300
+ * Returns immediately with flow run ID. The flow executes in the background.
301
+ * Permission: flow-run:create (with resource check on flow)
302
+ */
303
+ router.post("/flows/:flowId/run", requirePermission("flow-run:create", (req) => req.params.flowId), require_middleware_index.asyncHandler(async (req, res) => {
304
+ const { inputs = {}, options } = req.body;
305
+ const result = await core.startFlowRunAsync(req.params.flowId, inputs, options);
306
+ res.status(201).json(result);
307
+ }));
308
+ /**
309
+ * POST /flows/:flowId/run-to-node/:nodeId - Execute flow up to a specific node
310
+ * Only executes the upstream nodes required to produce output for the target node.
311
+ * Core method: ✅ executeFlowToNode(flowId, targetNodeId, inputs, options)
312
+ * Permission: flow-run:create (with resource check on flow)
313
+ */
314
+ router.post("/flows/:flowId/run-to-node/:nodeId", requirePermission("flow-run:create", (req) => req.params.flowId), require_middleware_index.asyncHandler(async (req, res) => {
315
+ const { inputs = {}, options } = req.body;
316
+ const result = await core.executeFlowToNode(req.params.flowId, req.params.nodeId, inputs, options);
317
+ res.status(201).json(result);
318
+ }));
319
+ /**
320
+ * POST /flow-runs/list - Get all flow runs with optional filtering and pagination
321
+ * Core method: ✅ listFlowRuns(options?: QueryOptions<FlowRun>)
322
+ * Body: QueryOptions<FlowRun>
323
+ * Permission: flow-run:read
324
+ */
325
+ router.post("/flow-runs/list", requirePermission("flow-run:read"), require_middleware_index.asyncHandler(async (req, res) => {
326
+ const flowRuns = await core.listFlowRuns(req.body);
327
+ res.json(flowRuns);
328
+ }));
329
+ /**
330
+ * GET /flow-runs/:flowRunId - Get specific flow run by ID
331
+ * Core method: ✅ getFlowRunById(flowRunId: string)
332
+ */
333
+ router.get("/flow-runs/:flowRunId", require_middleware_index.asyncHandler(async (req, res) => {
334
+ const flowRun = await core.getFlowRunById(req.params.flowRunId);
335
+ res.json(flowRun);
336
+ }));
337
+ /**
338
+ * GET /flows/:flowId/flow-runs - Get flow runs for a specific flow
339
+ * Core method: ✅ listFlowRunsByFlowId(flowId: string)
340
+ */
341
+ router.get("/flows/:flowId/flow-runs", require_middleware_index.asyncHandler(async (req, res) => {
342
+ const flowRuns = await core.listFlowRunsByFlowId(req.params.flowId);
343
+ res.json(flowRuns);
344
+ }));
345
+ /**
346
+ * POST /flow-runs/:flowRunId/resume - Resume paused flow execution
347
+ * Core method: ✅ resumeExecution(executionId: string)
348
+ */
349
+ router.post("/flow-runs/:flowRunId/resume", require_middleware_index.asyncHandler(async (req, res) => {
350
+ const result = await core.resumeExecution(req.params.flowRunId);
351
+ res.json(result);
352
+ }));
353
+ /**
354
+ * POST /flow-runs/:flowRunId/cancel - Cancel flow execution
355
+ * Core method: ✅ cancelFlowRun(flowRunId: string)
356
+ */
357
+ router.post("/flow-runs/:flowRunId/cancel", require_middleware_index.asyncHandler(async (req, res) => {
358
+ const result = await core.cancelFlowRun(req.params.flowRunId);
359
+ res.json(result);
360
+ }));
361
+ /**
362
+ * POST /flow-runs/:flowRunId/pause - Pause flow execution
363
+ * Core method: ✅ pauseFlowRun(flowRunId: string, reason?: string)
364
+ */
365
+ router.post("/flow-runs/:flowRunId/pause", require_middleware_index.asyncHandler(async (req, res) => {
366
+ const { reason } = req.body;
367
+ const result = await core.pauseFlowRun(req.params.flowRunId, reason);
368
+ res.json(result);
369
+ }));
370
+ /**
371
+ * GET /flow-runs/:flowRunId/node-executions - Get node executions for a flow run
372
+ * Core method: ✅ getNodeExecutionsByRunId(flowRunId: string)
373
+ */
374
+ router.get("/flow-runs/:flowRunId/node-executions", require_middleware_index.asyncHandler(async (req, res) => {
375
+ const nodeExecutions = await core.getNodeExecutionsByRunId(req.params.flowRunId);
376
+ res.json(nodeExecutions);
377
+ }));
378
+ /**
379
+ * GET /flow-runs/:flowRunId/stream - SSE stream of execution events
380
+ * Core method: ✅ createFlowRunEventStream(flowRunId: string)
381
+ *
382
+ * Streams node-execution and flow-run updates in real time.
383
+ * First event is a "snapshot", then incremental updates, ending with "end".
384
+ */
385
+ router.get("/flow-runs/:flowRunId/stream", require_middleware_index.asyncHandler(async (req, res) => {
386
+ res.setHeader("Content-Type", "text/event-stream");
387
+ res.setHeader("Cache-Control", "no-cache");
388
+ res.setHeader("Connection", "keep-alive");
389
+ res.setHeader("X-Accel-Buffering", "no");
390
+ res.flushHeaders();
391
+ try {
392
+ const stream = core.createFlowRunEventStream(req.params.flowRunId);
393
+ for await (const event of stream) {
394
+ if (res.destroyed) break;
395
+ const data = JSON.stringify(event);
396
+ res.write(`event: ${event.type}\ndata: ${data}\n\n`);
397
+ }
398
+ } catch (error) {
399
+ const message = error instanceof Error ? error.message : "Stream failed";
400
+ if (res.headersSent) res.write(`event: error\ndata: ${JSON.stringify({
401
+ type: "error",
402
+ message
403
+ })}\n\n`);
404
+ else return res.status(500).json({
405
+ error: "Internal Server Error",
406
+ message
407
+ });
408
+ } finally {
409
+ res.end();
410
+ }
411
+ }));
412
+ /**
413
+ * POST /node-executions/list - Get all node executions with optional filtering and pagination
414
+ * Core method: ✅ listNodeExecutions(options?: QueryOptions<NodeExecution>)
415
+ * Body: QueryOptions<NodeExecution>
416
+ */
417
+ router.post("/node-executions/list", require_middleware_index.asyncHandler(async (req, res) => {
418
+ const nodeExecutions = await core.listNodeExecutions(req.body);
419
+ res.json(nodeExecutions);
420
+ }));
421
+ /**
422
+ * POST /node-data/sql-query - Execute SQL query for testing
423
+ * Core method: ✅ executeSqlQuery(request: SubmitSQLQueryRequest)
424
+ */
425
+ router.post("/node-data/sql-query", require_middleware_index.asyncHandler(async (req, res) => {
426
+ const result = await core.executeSqlQuery(req.body);
427
+ res.json(result);
428
+ }));
429
+ /**
430
+ * POST /node-data/test-expression - Test a JS expression in the QuickJS sandbox
431
+ * Core method: ✅ testJsExpression({ expression, context })
432
+ */
433
+ router.post("/node-data/test-expression", require_middleware_index.asyncHandler(async (req, res) => {
434
+ const result = await core.testJsExpression(req.body);
435
+ res.json(result);
436
+ }));
437
+ /**
438
+ * POST /node-data/test-mapper - Test a mapper expression with mode semantics
439
+ * Core method: ✅ testMapper({ expression, incomingData, mode? })
440
+ */
441
+ router.post("/node-data/test-mapper", require_middleware_index.asyncHandler(async (req, res) => {
442
+ const result = await core.testMapper(req.body);
443
+ res.json(result);
444
+ }));
445
+ /**
446
+ * POST /node-data/model-query - Test model prompt
447
+ * Core method: ✅ testModelPrompt(request: SubmitPromptRequest)
448
+ */
449
+ router.post("/node-data/model-query", require_middleware_index.asyncHandler(async (req, res) => {
450
+ const result = await core.testModelPrompt(req.body);
451
+ res.json(result);
452
+ }));
453
+ /**
454
+ * GET /node-data/models - Get available AI models
455
+ * Core method: ✅ getAvailableModels()
456
+ */
457
+ router.get("/node-data/models", require_middleware_index.asyncHandler(async (req, res) => {
458
+ const credentialId = typeof req.query.credentialId === "string" ? req.query.credentialId.trim() : "";
459
+ const providerQuery = typeof req.query.provider === "string" ? req.query.provider.trim().toUpperCase() : "";
460
+ if (credentialId) {
461
+ const response = await core.getModelsForCredential(credentialId);
462
+ res.json(response);
463
+ return;
464
+ }
465
+ if (providerQuery) {
466
+ if (!Object.values(_invect_core.BatchProvider).includes(providerQuery)) {
467
+ res.status(400).json({
468
+ error: "INVALID_PROVIDER",
469
+ message: `Unsupported provider '${providerQuery}'. Expected one of: ${Object.values(_invect_core.BatchProvider).join(", ")}`
470
+ });
471
+ return;
472
+ }
473
+ const provider = providerQuery;
474
+ const response = await core.getModelsForProvider(provider);
475
+ res.json(response);
476
+ return;
477
+ }
478
+ const models = await core.getAvailableModels();
479
+ res.json(models);
480
+ }));
481
+ /**
482
+ * GET /node-data/databases - Get available databases
483
+ * Core method: ✅ getAvailableDatabases()
484
+ */
485
+ router.get("/node-data/databases", require_middleware_index.asyncHandler(async (req, res) => {
486
+ const databases = core.getAvailableDatabases();
487
+ res.json(databases);
488
+ }));
489
+ /**
490
+ * POST /node-config/update - Generic node configuration updates
491
+ * Core method: ✅ handleNodeConfigUpdate(event: NodeConfigUpdateEvent)
492
+ */
493
+ router.post("/node-config/update", require_middleware_index.asyncHandler(async (req, res) => {
494
+ const response = await core.handleNodeConfigUpdate(req.body);
495
+ res.json(response);
496
+ }));
497
+ router.get("/node-definition/:nodeType", require_middleware_index.asyncHandler(async (req, res) => {
498
+ const rawNodeType = req.params.nodeType ?? "";
499
+ const nodeTypeParam = rawNodeType.includes(".") ? rawNodeType : rawNodeType.toUpperCase();
500
+ const isLegacyEnum = !rawNodeType.includes(".") && nodeTypeParam in _invect_core.GraphNodeType;
501
+ const isActionId = rawNodeType.includes(".");
502
+ if (!isLegacyEnum && !isActionId) {
503
+ res.status(400).json({
504
+ error: "INVALID_NODE_TYPE",
505
+ message: `Unknown node type '${req.params.nodeType}'`
506
+ });
507
+ return;
508
+ }
509
+ const params = parseParamsFromQuery(req.query.params);
510
+ const changeField = typeof req.query.changeField === "string" ? req.query.changeField : void 0;
511
+ const changeValue = coerceQueryValue(req.query.changeValue);
512
+ const nodeId = typeof req.query.nodeId === "string" ? req.query.nodeId : void 0;
513
+ const flowId = typeof req.query.flowId === "string" ? req.query.flowId : void 0;
514
+ const response = await core.handleNodeConfigUpdate({
515
+ nodeType: nodeTypeParam,
516
+ nodeId: nodeId ?? `definition-${nodeTypeParam.toLowerCase()}`,
517
+ flowId,
518
+ params,
519
+ change: changeField ? {
520
+ field: changeField,
521
+ value: changeValue
522
+ } : void 0
523
+ });
524
+ res.json(response);
525
+ }));
526
+ /**
527
+ * GET /nodes - Get available node definitions
528
+ * Core method: ✅ getAvailableNodes()
529
+ */
530
+ router.get("/nodes", require_middleware_index.asyncHandler(async (req, res) => {
531
+ const nodes = core.getAvailableNodes();
532
+ res.json(nodes);
533
+ }));
534
+ /**
535
+ * GET /actions/:actionId/fields/:fieldName/options - Load dynamic field options
536
+ * Core method: ✅ resolveFieldOptions(actionId, fieldName, deps)
537
+ *
538
+ * Query params:
539
+ * deps - JSON-encoded object of dependency field values
540
+ */
541
+ router.get("/actions/:actionId/fields/:fieldName/options", require_middleware_index.asyncHandler(async (req, res) => {
542
+ const { actionId, fieldName } = req.params;
543
+ let dependencyValues = {};
544
+ if (typeof req.query.deps === "string") try {
545
+ dependencyValues = JSON.parse(req.query.deps);
546
+ } catch {
547
+ res.status(400).json({ error: "Invalid deps JSON" });
548
+ return;
549
+ }
550
+ const result = await core.resolveFieldOptions(actionId, fieldName, dependencyValues);
551
+ res.json(result);
552
+ }));
553
+ /**
554
+ * POST /nodes/test - Test/execute a single node in isolation
555
+ * Core method: ✅ testNode(nodeType, params, inputData)
556
+ * Body: { nodeType: string, params: Record<string, unknown>, inputData?: Record<string, unknown> }
557
+ */
558
+ router.post("/nodes/test", require_middleware_index.asyncHandler(async (req, res) => {
559
+ const { nodeType, params, inputData } = req.body;
560
+ if (!nodeType || typeof nodeType !== "string") return res.status(400).json({ error: "nodeType is required and must be a string" });
561
+ if (!params || typeof params !== "object") return res.status(400).json({ error: "params is required and must be an object" });
562
+ const result = await core.testNode(nodeType, params, inputData || {});
563
+ res.json(result);
564
+ }));
565
+ /**
566
+ * POST /credentials - Create a new credential
567
+ * Core method: ✅ createCredential(input: CreateCredentialInput)
568
+ * Permission: credential:create
569
+ */
570
+ router.post("/credentials", requirePermission("credential:create"), require_middleware_index.asyncHandler(async (req, res) => {
571
+ const resolvedUserId = req.invectIdentity?.id || req.user?.id || req.body.userId || req.header("x-user-id") || "anonymous";
572
+ const credential = await core.createCredential({
573
+ ...req.body,
574
+ userId: resolvedUserId
575
+ });
576
+ res.status(201).json(credential);
577
+ }));
578
+ /**
579
+ * GET /credentials - List credentials with optional filtering and pagination
580
+ * Core method: ✅ listCredentials(filters?: CredentialFilters, options?: QueryOptions)
581
+ * Query params: type?, authType?, isActive?, page?, limit?
582
+ * Permission: credential:read
583
+ */
584
+ router.get("/credentials", requirePermission("credential:read"), require_middleware_index.asyncHandler(async (req, res) => {
585
+ const filters = {
586
+ type: req.query.type,
587
+ authType: req.query.authType,
588
+ isActive: req.query.isActive === "true" ? true : req.query.isActive === "false" ? false : void 0
589
+ };
590
+ const credentials = await core.listCredentials(filters);
591
+ res.json(credentials);
592
+ }));
593
+ /**
594
+ * GET /credentials/:id - Get credential by ID
595
+ * Core method: ✅ getCredential(id: string)
596
+ * Permission: credential:read (with resource check)
597
+ */
598
+ router.get("/credentials/:id", requirePermission("credential:read", (req) => req.params.id), require_middleware_index.asyncHandler(async (req, res) => {
599
+ const credential = await core.getCredential(req.params.id);
600
+ res.json(credential);
601
+ }));
602
+ /**
603
+ * PUT /credentials/:id - Update credential
604
+ * Core method: ✅ updateCredential(id: string, input: UpdateCredentialInput)
605
+ * Permission: credential:update (with resource check)
606
+ */
607
+ router.put("/credentials/:id", requirePermission("credential:update", (req) => req.params.id), require_middleware_index.asyncHandler(async (req, res) => {
608
+ const credential = await core.updateCredential(req.params.id, req.body);
609
+ res.json(credential);
610
+ }));
611
+ /**
612
+ * DELETE /credentials/:id - Delete credential
613
+ * Core method: ✅ deleteCredential(id: string)
614
+ * Permission: credential:delete (with resource check)
615
+ */
616
+ router.delete("/credentials/:id", requirePermission("credential:delete", (req) => req.params.id), require_middleware_index.asyncHandler(async (req, res) => {
617
+ await core.deleteCredential(req.params.id);
618
+ res.status(204).send();
619
+ }));
620
+ /**
621
+ * POST /credentials/:id/test - Test credential validity
622
+ * Core method: ✅ testCredential(id: string)
623
+ * Permission: credential:read (with resource check)
624
+ */
625
+ router.post("/credentials/:id/test", requirePermission("credential:read", (req) => req.params.id), require_middleware_index.asyncHandler(async (req, res) => {
626
+ const result = await core.testCredential(req.params.id);
627
+ res.json(result);
628
+ }));
629
+ /**
630
+ * POST /credentials/:id/track-usage - Update credential last used timestamp
631
+ * Core method: ✅ updateCredentialLastUsed(id: string)
632
+ * Permission: credential:read (with resource check)
633
+ */
634
+ router.post("/credentials/:id/track-usage", requirePermission("credential:read", (req) => req.params.id), require_middleware_index.asyncHandler(async (req, res) => {
635
+ await core.updateCredentialLastUsed(req.params.id);
636
+ res.status(204).send();
637
+ }));
638
+ /**
639
+ * GET /credentials/:id/webhook - Get webhook info for a credential
640
+ * Core method: ✅ CredentialsService.getWebhookInfo(id)
641
+ * Permission: credential:read (with resource check)
642
+ */
643
+ router.get("/credentials/:id/webhook", requirePermission("credential:read", (req) => req.params.id), require_middleware_index.asyncHandler(async (req, res) => {
644
+ const webhookInfo = await core.getCredentialsService().getWebhookInfo(req.params.id);
645
+ if (!webhookInfo) return res.status(404).json({ error: "Webhook not enabled for credential" });
646
+ res.json(webhookInfo);
647
+ }));
648
+ /**
649
+ * GET /credentials/:id/webhook-info - Backwards-compatible alias for webhook info
650
+ */
651
+ router.get("/credentials/:id/webhook-info", requirePermission("credential:read", (req) => req.params.id), require_middleware_index.asyncHandler(async (req, res) => {
652
+ const webhookInfo = await core.getCredentialsService().getWebhookInfo(req.params.id);
653
+ if (!webhookInfo) return res.status(404).json({ error: "Webhook not enabled for credential" });
654
+ res.json(webhookInfo);
655
+ }));
656
+ /**
657
+ * POST /credentials/:id/webhook - Enable webhook for a credential
658
+ * Core method: ✅ CredentialsService.enableWebhook(id)
659
+ * Permission: credential:update (with resource check)
660
+ */
661
+ router.post("/credentials/:id/webhook", requirePermission("credential:update", (req) => req.params.id), require_middleware_index.asyncHandler(async (req, res) => {
662
+ const webhookInfo = await core.getCredentialsService().enableWebhook(req.params.id);
663
+ res.json(webhookInfo);
664
+ }));
665
+ /**
666
+ * POST /credentials/:id/webhook/enable - Backwards-compatible alias for enabling webhooks
667
+ */
668
+ router.post("/credentials/:id/webhook/enable", requirePermission("credential:update", (req) => req.params.id), require_middleware_index.asyncHandler(async (req, res) => {
669
+ const webhookInfo = await core.getCredentialsService().enableWebhook(req.params.id);
670
+ res.json(webhookInfo);
671
+ }));
672
+ /**
673
+ * POST /webhooks/credentials/:webhookPath - Public credential webhook ingestion endpoint
674
+ * Core methods: ✅ CredentialsService.findByWebhookPath(webhookPath), updateCredentialLastUsed(id)
675
+ */
676
+ router.post("/webhooks/credentials/:webhookPath", require_middleware_index.asyncHandler(async (req, res) => {
677
+ const credential = await core.getCredentialsService().findByWebhookPath(req.params.webhookPath);
678
+ if (!credential) return res.status(404).json({
679
+ ok: false,
680
+ error: "Credential webhook not found"
681
+ });
682
+ res.json({
683
+ ok: true,
684
+ credentialId: credential.id,
685
+ triggeredFlows: 0,
686
+ runs: [],
687
+ body: req.body ?? null
688
+ });
689
+ }));
690
+ /**
691
+ * GET /credentials/expiring - Get credentials expiring soon
692
+ * Core method: ✅ getExpiringCredentials(daysUntilExpiry?: number)
693
+ * Query params: daysUntilExpiry (default: 7)
694
+ * Permission: credential:read
695
+ */
696
+ router.get("/credentials/expiring", requirePermission("credential:read"), require_middleware_index.asyncHandler(async (req, res) => {
697
+ const daysUntilExpiry = req.query.daysUntilExpiry ? parseInt(req.query.daysUntilExpiry) : 7;
698
+ const credentials = await core.getExpiringCredentials(daysUntilExpiry);
699
+ res.json(credentials);
700
+ }));
701
+ /**
702
+ * POST /credentials/test-request - Test a credential by making an HTTP request
703
+ * This endpoint proxies HTTP requests to avoid CORS issues when testing credentials
704
+ * Body: { url, method, headers, body }
705
+ */
706
+ router.post("/credentials/test-request", require_middleware_index.asyncHandler(async (req, res) => {
707
+ const { url, method = "GET", headers = {}, body } = req.body;
708
+ if (!url) {
709
+ res.status(400).json({ error: "URL is required" });
710
+ return;
711
+ }
712
+ try {
713
+ const fetchOptions = {
714
+ method,
715
+ headers
716
+ };
717
+ if (body && [
718
+ "POST",
719
+ "PUT",
720
+ "PATCH"
721
+ ].includes(method)) fetchOptions.body = typeof body === "string" ? body : JSON.stringify(body);
722
+ const response = await fetch(url, fetchOptions);
723
+ const responseText = await response.text();
724
+ let responseBody;
725
+ try {
726
+ responseBody = JSON.parse(responseText);
727
+ } catch {
728
+ responseBody = responseText;
729
+ }
730
+ res.json({
731
+ status: response.status,
732
+ statusText: response.statusText,
733
+ ok: response.ok,
734
+ body: responseBody
735
+ });
736
+ } catch (error) {
737
+ res.status(500).json({
738
+ error: "Request failed",
739
+ message: error instanceof Error ? error.message : "Unknown error"
740
+ });
741
+ }
742
+ }));
743
+ /**
744
+ * GET /credentials/oauth2/providers - List all available OAuth2 providers
745
+ */
746
+ router.get("/credentials/oauth2/providers", require_middleware_index.asyncHandler(async (_req, res) => {
747
+ const providers = core.getOAuth2Providers();
748
+ res.json(providers);
749
+ }));
750
+ /**
751
+ * GET /credentials/oauth2/providers/:providerId - Get a specific OAuth2 provider
752
+ */
753
+ router.get("/credentials/oauth2/providers/:providerId", require_middleware_index.asyncHandler(async (req, res) => {
754
+ const provider = core.getOAuth2Provider(req.params.providerId);
755
+ if (!provider) {
756
+ res.status(404).json({ error: "OAuth2 provider not found" });
757
+ return;
758
+ }
759
+ res.json(provider);
760
+ }));
761
+ /**
762
+ * POST /credentials/oauth2/start - Start OAuth2 authorization flow
763
+ * Body: { providerId, clientId, clientSecret, redirectUri, scopes?, returnUrl?, credentialName? }
764
+ * Returns: { authorizationUrl, state }
765
+ */
766
+ router.post("/credentials/oauth2/start", require_middleware_index.asyncHandler(async (req, res) => {
767
+ const { providerId, clientId, clientSecret, redirectUri, scopes, returnUrl, credentialName } = req.body;
768
+ if (!providerId || !clientId || !clientSecret || !redirectUri) {
769
+ res.status(400).json({ error: "Missing required fields: providerId, clientId, clientSecret, redirectUri" });
770
+ return;
771
+ }
772
+ const result = core.startOAuth2Flow(providerId, {
773
+ clientId,
774
+ clientSecret,
775
+ redirectUri
776
+ }, {
777
+ scopes,
778
+ returnUrl,
779
+ credentialName
780
+ });
781
+ res.json(result);
782
+ }));
783
+ /**
784
+ * POST /credentials/oauth2/callback - Handle OAuth2 callback and exchange code for tokens
785
+ * Body: { code, state, clientId, clientSecret, redirectUri }
786
+ * Creates a new credential with the obtained tokens
787
+ */
788
+ router.post("/credentials/oauth2/callback", require_middleware_index.asyncHandler(async (req, res) => {
789
+ const { code, state, clientId, clientSecret, redirectUri } = req.body;
790
+ if (!code || !state || !clientId || !clientSecret || !redirectUri) {
791
+ res.status(400).json({ error: "Missing required fields: code, state, clientId, clientSecret, redirectUri" });
792
+ return;
793
+ }
794
+ const credential = await core.handleOAuth2Callback(code, state, {
795
+ clientId,
796
+ clientSecret,
797
+ redirectUri
798
+ });
799
+ res.json(credential);
800
+ }));
801
+ /**
802
+ * GET /credentials/oauth2/callback - Handle OAuth2 callback (for redirect-based flows)
803
+ * Query: code, state
804
+ * Redirects to the return URL with credential ID or error
805
+ */
806
+ router.get("/credentials/oauth2/callback", require_middleware_index.asyncHandler(async (req, res) => {
807
+ const { code, state, error, error_description } = req.query;
808
+ if (error) {
809
+ const errorMsg = error_description || error;
810
+ const returnUrl = core.getOAuth2PendingState(state)?.returnUrl || "/";
811
+ const separator = returnUrl.includes("?") ? "&" : "?";
812
+ res.redirect(`${returnUrl}${separator}oauth_error=${encodeURIComponent(errorMsg)}`);
813
+ return;
814
+ }
815
+ if (!code || !state) {
816
+ res.status(400).json({ error: "Missing code or state parameter" });
817
+ return;
818
+ }
819
+ const pendingState = core.getOAuth2PendingState(state);
820
+ if (!pendingState) {
821
+ res.status(400).json({ error: "Invalid or expired OAuth state" });
822
+ return;
823
+ }
824
+ res.json({
825
+ message: "OAuth callback received. Use POST /credentials/oauth2/callback to exchange the code.",
826
+ code,
827
+ state,
828
+ providerId: pendingState.providerId,
829
+ returnUrl: pendingState.returnUrl
830
+ });
831
+ }));
832
+ /**
833
+ * POST /credentials/:id/refresh - Manually refresh an OAuth2 credential's access token
834
+ */
835
+ router.post("/credentials/:id/refresh", require_middleware_index.asyncHandler(async (req, res) => {
836
+ const credential = await core.refreshOAuth2Credential(req.params.id);
837
+ res.json(credential);
838
+ }));
839
+ /**
840
+ * GET /flows/:flowId/triggers - List all trigger registrations for a flow
841
+ * Core method: ✅ listTriggersForFlow(flowId)
842
+ * Permission: flow:read (with resource check)
843
+ */
844
+ router.get("/flows/:flowId/triggers", requirePermission("flow:read", (req) => req.params.flowId), require_middleware_index.asyncHandler(async (req, res) => {
845
+ const triggers = await core.listTriggersForFlow(req.params.flowId);
846
+ res.json(triggers);
847
+ }));
848
+ /**
849
+ * POST /flows/:flowId/triggers - Create a trigger registration for a flow
850
+ * Core method: ✅ createTrigger(input)
851
+ * Permission: flow:update (with resource check)
852
+ */
853
+ router.post("/flows/:flowId/triggers", requirePermission("flow:update", (req) => req.params.flowId), require_middleware_index.asyncHandler(async (req, res) => {
854
+ const trigger = await core.createTrigger({
855
+ ...req.body,
856
+ flowId: req.params.flowId
857
+ });
858
+ res.status(201).json(trigger);
859
+ }));
860
+ /**
861
+ * POST /flows/:flowId/triggers/sync - Sync triggers from the flow definition
862
+ * Core method: ✅ syncTriggersForFlow(flowId, definition)
863
+ * Permission: flow:update (with resource check)
864
+ */
865
+ router.post("/flows/:flowId/triggers/sync", requirePermission("flow:update", (req) => req.params.flowId), require_middleware_index.asyncHandler(async (req, res) => {
866
+ const { definition } = req.body;
867
+ const triggers = await core.syncTriggersForFlow(req.params.flowId, definition);
868
+ res.json(triggers);
869
+ }));
870
+ /**
871
+ * GET /triggers/:triggerId - Get a single trigger by ID
872
+ * Core method: ✅ getTrigger(triggerId)
873
+ */
874
+ router.get("/triggers/:triggerId", require_middleware_index.asyncHandler(async (req, res) => {
875
+ const trigger = await core.getTrigger(req.params.triggerId);
876
+ if (!trigger) return res.status(404).json({
877
+ error: "Not Found",
878
+ message: `Trigger ${req.params.triggerId} not found`
879
+ });
880
+ res.json(trigger);
881
+ }));
882
+ /**
883
+ * PUT /triggers/:triggerId - Update a trigger registration
884
+ * Core method: ✅ updateTrigger(triggerId, input)
885
+ */
886
+ router.put("/triggers/:triggerId", require_middleware_index.asyncHandler(async (req, res) => {
887
+ const trigger = await core.updateTrigger(req.params.triggerId, req.body);
888
+ if (!trigger) return res.status(404).json({
889
+ error: "Not Found",
890
+ message: `Trigger ${req.params.triggerId} not found`
891
+ });
892
+ res.json(trigger);
893
+ }));
894
+ /**
895
+ * DELETE /triggers/:triggerId - Delete a trigger registration
896
+ * Core method: ✅ deleteTrigger(triggerId)
897
+ */
898
+ router.delete("/triggers/:triggerId", require_middleware_index.asyncHandler(async (req, res) => {
899
+ await core.deleteTrigger(req.params.triggerId);
900
+ res.status(204).send();
901
+ }));
902
+ /**
903
+ * GET /agent/tools - List all available agent tools
904
+ * Core method: ✅ getAgentTools()
905
+ */
906
+ router.get("/agent/tools", require_middleware_index.asyncHandler(async (_req, res) => {
907
+ const tools = core.getAgentTools();
908
+ res.json(tools);
909
+ }));
910
+ /**
911
+ * GET /chat/status - Check if chat assistant is enabled
912
+ * Core method: ✅ isChatEnabled()
913
+ */
914
+ router.get("/chat/status", require_middleware_index.asyncHandler(async (_req, res) => {
915
+ res.json({ enabled: core.isChatEnabled() });
916
+ }));
917
+ /**
918
+ * POST /chat - Streaming chat assistant endpoint (SSE)
919
+ * Core method: ✅ createChatStream()
920
+ *
921
+ * Request body: { messages: ChatMessage[], context: ChatContext }
922
+ * Response: Server-Sent Events stream of ChatStreamEvents
923
+ */
924
+ router.post("/chat", require_middleware_index.asyncHandler(async (req, res) => {
925
+ const { messages, context } = req.body;
926
+ if (!messages || !Array.isArray(messages)) return res.status(400).json({
927
+ error: "Validation Error",
928
+ message: "\"messages\" must be an array of chat messages"
929
+ });
930
+ res.setHeader("Content-Type", "text/event-stream");
931
+ res.setHeader("Cache-Control", "no-cache");
932
+ res.setHeader("Connection", "keep-alive");
933
+ res.setHeader("X-Accel-Buffering", "no");
934
+ res.flushHeaders();
935
+ const identity = req.__invectIdentity ?? void 0;
936
+ try {
937
+ const stream = await core.createChatStream({
938
+ messages,
939
+ context: context || {},
940
+ identity
941
+ });
942
+ for await (const event of stream) {
943
+ if (res.destroyed) break;
944
+ const data = JSON.stringify(event);
945
+ res.write(`event: ${event.type}\ndata: ${data}\n\n`);
946
+ }
947
+ } catch (error) {
948
+ const message = error instanceof Error ? error.message : "Chat stream failed";
949
+ if (res.headersSent) res.write(`event: error\ndata: ${JSON.stringify({
950
+ type: "error",
951
+ message,
952
+ recoverable: false
953
+ })}\n\n`);
954
+ else return res.status(500).json({
955
+ error: "Internal Server Error",
956
+ message
957
+ });
958
+ } finally {
959
+ res.end();
960
+ }
961
+ }));
962
+ /**
963
+ * GET /chat/messages/:flowId - Get persisted chat messages for a flow
964
+ * Core method: ✅ getChatMessages()
965
+ */
966
+ router.get("/chat/messages/:flowId", require_middleware_index.asyncHandler(async (req, res) => {
967
+ const messages = await core.getChatMessages(req.params.flowId);
968
+ res.json(messages);
969
+ }));
970
+ /**
971
+ * PUT /chat/messages/:flowId - Save (replace) chat messages for a flow
972
+ * Core method: ✅ saveChatMessages()
973
+ *
974
+ * Request body: { messages: Array<{ role, content, toolMeta? }> }
975
+ */
976
+ router.put("/chat/messages/:flowId", require_middleware_index.asyncHandler(async (req, res) => {
977
+ const { messages } = req.body;
978
+ if (!messages || !Array.isArray(messages)) return res.status(400).json({
979
+ error: "Validation Error",
980
+ message: "\"messages\" must be an array"
981
+ });
982
+ const saved = await core.saveChatMessages(req.params.flowId, messages);
983
+ res.json(saved);
984
+ }));
985
+ /**
986
+ * DELETE /chat/messages/:flowId - Delete all chat messages for a flow
987
+ * Core method: ✅ deleteChatMessages()
988
+ */
989
+ router.delete("/chat/messages/:flowId", require_middleware_index.asyncHandler(async (req, res) => {
990
+ await core.deleteChatMessages(req.params.flowId);
991
+ res.json({ success: true });
992
+ }));
993
+ router.all("/plugins/*", require_middleware_index.asyncHandler(async (req, res) => {
994
+ const endpoints = core.getPluginEndpoints();
995
+ const pluginPath = (req.path || "/").replace(/^\/plugins/, "") || "/";
996
+ const method = req.method.toUpperCase();
997
+ const matchedEndpoint = endpoints.find((ep) => {
998
+ if (ep.method !== method) return false;
999
+ const pattern = ep.path.replace(/\*/g, "(.*)").replace(/:([^/]+)/g, "([^/]+)");
1000
+ return new RegExp(`^${pattern}$`).test(pluginPath);
1001
+ });
1002
+ if (!matchedEndpoint) return res.status(404).json({
1003
+ error: "Not Found",
1004
+ message: `Plugin route ${method} ${pluginPath} not found`
1005
+ });
1006
+ const paramNames = [];
1007
+ const paramPattern = matchedEndpoint.path.replace(/\*/g, "(.*)").replace(/:([^/]+)/g, (_m, name) => {
1008
+ paramNames.push(name);
1009
+ return "([^/]+)";
1010
+ });
1011
+ const paramMatch = new RegExp(`^${paramPattern}$`).exec(pluginPath);
1012
+ const params = {};
1013
+ if (paramMatch) paramNames.forEach((name, i) => {
1014
+ params[name] = paramMatch[i + 1] || "";
1015
+ });
1016
+ if (!matchedEndpoint.isPublic && matchedEndpoint.permission) {
1017
+ const identity = req.invectIdentity ?? null;
1018
+ if (!core.hasPermission(identity, matchedEndpoint.permission)) return res.status(403).json({
1019
+ error: "Forbidden",
1020
+ message: `Missing permission: ${matchedEndpoint.permission}`
1021
+ });
1022
+ }
1023
+ const webRequestUrl = `${req.protocol}://${req.get("host")}${req.originalUrl}`;
1024
+ const webRequestInit = {
1025
+ method: req.method,
1026
+ headers: req.headers
1027
+ };
1028
+ if (req.method !== "GET" && req.method !== "HEAD" && req.method !== "DELETE") webRequestInit.body = JSON.stringify(req.body || {});
1029
+ const webRequest = new globalThis.Request(webRequestUrl, webRequestInit);
1030
+ const result = await matchedEndpoint.handler({
1031
+ body: req.body || {},
1032
+ params,
1033
+ query: req.query || {},
1034
+ headers: req.headers,
1035
+ identity: req.invectIdentity ?? null,
1036
+ database: createPluginDatabaseApi(core),
1037
+ request: webRequest,
1038
+ core: {
1039
+ getPermissions: (identity) => core.getPermissions(identity),
1040
+ getAvailableRoles: () => core.getAvailableRoles(),
1041
+ getResolvedRole: (identity) => core.getAuthService().getResolvedRole(identity),
1042
+ isFlowAccessTableEnabled: () => core.isFlowAccessTableEnabled(),
1043
+ listFlowAccess: (flowId) => core.listFlowAccess(flowId),
1044
+ grantFlowAccess: (input) => core.grantFlowAccess(input),
1045
+ revokeFlowAccess: (accessId) => core.revokeFlowAccess(accessId),
1046
+ getAccessibleFlowIds: (userId, teamIds) => core.getAccessibleFlowIds(userId, teamIds),
1047
+ getFlowPermission: (flowId, userId, teamIds) => core.getFlowPermission(flowId, userId, teamIds),
1048
+ authorize: (context) => core.authorize(context)
1049
+ }
1050
+ });
1051
+ if (result instanceof Response) {
1052
+ const arrayBuf = await result.arrayBuffer();
1053
+ res.status(result.status);
1054
+ result.headers.forEach((value, key) => {
1055
+ if (key.toLowerCase() !== "set-cookie") res.setHeader(key, value);
1056
+ });
1057
+ const setCookies = result.headers.getSetCookie?.();
1058
+ if (setCookies && setCookies.length > 0) res.setHeader("set-cookie", setCookies);
1059
+ res.send(Buffer.from(arrayBuf));
1060
+ return;
1061
+ }
1062
+ if ("stream" in result && result.stream) {
1063
+ res.status(result.status || 200);
1064
+ res.setHeader("Content-Type", "text/event-stream");
1065
+ const reader = result.stream.getReader();
1066
+ const pump = async () => {
1067
+ const { done, value } = await reader.read();
1068
+ if (done) {
1069
+ res.end();
1070
+ return;
1071
+ }
1072
+ res.write(value);
1073
+ await pump();
1074
+ };
1075
+ await pump();
1076
+ return;
1077
+ }
1078
+ const jsonResult = result;
1079
+ res.status(jsonResult.status || 200).json(jsonResult.body);
1080
+ }));
1081
+ router.use((error, _req, res, _next) => {
1082
+ if (error instanceof zod.ZodError) return res.status(400).json({
1083
+ error: "Validation Error",
1084
+ message: "Invalid request data",
1085
+ details: error.errors.map((err) => ({
1086
+ path: err.path.join("."),
1087
+ message: err.message,
1088
+ code: err.code
1089
+ }))
1090
+ });
1091
+ if (error.name === "DatabaseError") return res.status(500).json({
1092
+ error: "Database Error",
1093
+ message: error.message || "A database error occurred"
1094
+ });
1095
+ console.error("Invect Router Error:", error);
1096
+ res.status(500).json({
1097
+ error: "Internal Server Error",
1098
+ message: "An unexpected error occurred"
1099
+ });
1100
+ });
1101
+ return router;
1102
+ }
1103
+ //#endregion
1104
+ exports.createInvectRouter = createInvectRouter;
1105
+
1106
+ //# sourceMappingURL=index.cjs.map