khotan-data 0.0.1 → 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 (51) hide show
  1. package/AGENTS.md +54 -0
  2. package/README.md +62 -0
  3. package/dist/cli.js +2585 -0
  4. package/dist/factory.cjs +2319 -0
  5. package/dist/factory.cjs.map +1 -0
  6. package/dist/factory.d.cts +475 -0
  7. package/dist/factory.d.ts +475 -0
  8. package/dist/factory.js +2311 -0
  9. package/dist/factory.js.map +1 -0
  10. package/dist/plug-client.cjs +99 -0
  11. package/dist/plug-client.cjs.map +1 -0
  12. package/dist/plug-client.d.cts +71 -0
  13. package/dist/plug-client.d.ts +71 -0
  14. package/dist/plug-client.js +96 -0
  15. package/dist/plug-client.js.map +1 -0
  16. package/dist/templates/agent-skill.md +73 -0
  17. package/dist/templates/agents.md +41 -0
  18. package/dist/templates/catch.example.ts +36 -0
  19. package/dist/templates/catch.ts +107 -0
  20. package/dist/templates/config-page.tsx +20 -0
  21. package/dist/templates/debug-index-page.tsx +101 -0
  22. package/dist/templates/debug-page.tsx +48 -0
  23. package/dist/templates/graph-page.tsx +11 -0
  24. package/dist/templates/hub.tsx +450 -0
  25. package/dist/templates/inflow.example.ts +61 -0
  26. package/dist/templates/inflow.ts +99 -0
  27. package/dist/templates/khotan-config.ts +40 -0
  28. package/dist/templates/khotan-route.ts +13 -0
  29. package/dist/templates/logs-page.tsx +9 -0
  30. package/dist/templates/logs.tsx +20 -0
  31. package/dist/templates/outflow.example.ts +52 -0
  32. package/dist/templates/outflow.ts +90 -0
  33. package/dist/templates/pass.example.ts +51 -0
  34. package/dist/templates/pass.ts +124 -0
  35. package/dist/templates/plug-debugger.tsx +1185 -0
  36. package/dist/templates/plug.example.ts +93 -0
  37. package/dist/templates/plug.ts +806 -0
  38. package/dist/templates/relay.example.ts +61 -0
  39. package/dist/templates/relay.ts +95 -0
  40. package/dist/templates/runs-table.tsx +592 -0
  41. package/dist/templates/schema.ts +424 -0
  42. package/dist/templates/skill-dashboard.md +144 -0
  43. package/dist/templates/skill-plug.md +193 -0
  44. package/dist/templates/skill-setup.md +119 -0
  45. package/dist/templates/skill-webhook.md +196 -0
  46. package/dist/templates/topology-canvas.tsx +1406 -0
  47. package/dist/templates/var-panel.tsx +276 -0
  48. package/dist/templates/webhook-events-table.tsx +241 -0
  49. package/dist/templates/wire-panel.tsx +216 -0
  50. package/dist/templates/wire.ts +155 -0
  51. package/package.json +46 -5
@@ -0,0 +1,2311 @@
1
+ import { eq, desc, sql, inArray, count } from 'drizzle-orm';
2
+ import { pgTable, timestamp, text, boolean, unique, index, jsonb, integer } from 'drizzle-orm/pg-core';
3
+
4
+ // src/factory.ts
5
+ var _khotanDebug = process?.env?.["KHOTAN_DEBUG"];
6
+ function kd(scope, ...args) {
7
+ if (_khotanDebug) console.log(`[khotan:${scope}]`, ...args);
8
+ }
9
+ var khotanPlugs = pgTable("khotan_plugs", {
10
+ id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
11
+ name: text("name").notNull().unique(),
12
+ baseUrl: text("base_url").notNull(),
13
+ authType: text("auth_type", {
14
+ enum: ["bearer", "basic", "apiKey", "custom"]
15
+ }).notNull(),
16
+ enabled: boolean("enabled").default(true).notNull(),
17
+ status: text("status", {
18
+ enum: ["connected", "error", "idle"]
19
+ }).default("idle").notNull(),
20
+ statusMessage: text("status_message"),
21
+ encryptedVars: text("encrypted_vars"),
22
+ createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
23
+ updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull()
24
+ });
25
+ var khotanResources = pgTable("khotan_resources", {
26
+ id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
27
+ name: text("name").notNull().unique(),
28
+ connectField: text("connect_field").notNull(),
29
+ description: text("description"),
30
+ createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
31
+ updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull()
32
+ });
33
+ var khotanFlows = pgTable(
34
+ "khotan_flows",
35
+ {
36
+ id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
37
+ plugId: text("plug_id").notNull(),
38
+ name: text("name").notNull(),
39
+ type: text("type", {
40
+ enum: ["inflow", "outflow", "relay", "webhook"]
41
+ }).notNull(),
42
+ enabled: boolean("enabled").default(true).notNull(),
43
+ schedule: text("schedule"),
44
+ resourceId: text("resource_id"),
45
+ lastRunAt: timestamp("last_run_at", { withTimezone: true }),
46
+ lastRunStatus: text("last_run_status", {
47
+ enum: ["completed", "partial", "failed", "cancelled"]
48
+ }),
49
+ createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
50
+ updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull()
51
+ },
52
+ (table) => [
53
+ unique("khotan_flows_plug_id_name_unique").on(table.plugId, table.name),
54
+ index("khotan_flows_plug_id_idx").on(table.plugId),
55
+ index("khotan_flows_resource_id_idx").on(table.resourceId)
56
+ ]
57
+ );
58
+ var khotanWires = pgTable(
59
+ "khotan_wires",
60
+ {
61
+ id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
62
+ plugId: text("plug_id").notNull(),
63
+ remoteId: text("remote_id").notNull(),
64
+ callbackUrl: text("callback_url").notNull(),
65
+ eventTypes: jsonb("event_types").notNull().$type(),
66
+ status: text("status", {
67
+ enum: ["active", "disabled", "pending"]
68
+ }).default("pending").notNull(),
69
+ metadata: jsonb("metadata"),
70
+ createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
71
+ updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull()
72
+ },
73
+ (table) => [
74
+ index("khotan_wires_plug_id_idx").on(table.plugId),
75
+ index("khotan_wires_status_idx").on(table.status)
76
+ ]
77
+ );
78
+ var khotanWebhookHandlers = pgTable(
79
+ "khotan_webhook_handlers",
80
+ {
81
+ id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
82
+ wireId: text("wire_id").notNull(),
83
+ name: text("name").notNull(),
84
+ type: text("type", {
85
+ enum: ["catch", "pass"]
86
+ }).notNull(),
87
+ destinationPlugId: text("destination_plug_id"),
88
+ enabled: boolean("enabled").default(true).notNull(),
89
+ createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
90
+ updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull()
91
+ },
92
+ (table) => [
93
+ unique("khotan_webhook_handlers_wire_id_name_unique").on(
94
+ table.wireId,
95
+ table.name
96
+ ),
97
+ index("khotan_webhook_handlers_wire_id_idx").on(table.wireId)
98
+ ]
99
+ );
100
+ var khotanWebhookEvents = pgTable(
101
+ "khotan_webhook_events",
102
+ {
103
+ id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
104
+ wireId: text("wire_id").notNull(),
105
+ webhookHandlerId: text("webhook_handler_id").notNull(),
106
+ khotanRunId: text("khotan_run_id").notNull(),
107
+ eventType: text("event_type").notNull(),
108
+ payload: jsonb("payload").notNull().$type(),
109
+ headers: jsonb("headers").notNull().$type(),
110
+ receivedAt: timestamp("received_at", { withTimezone: true }).defaultNow().notNull()
111
+ },
112
+ (table) => [
113
+ index("khotan_webhook_events_wire_id_idx").on(table.wireId),
114
+ index("khotan_webhook_events_webhook_handler_id_idx").on(
115
+ table.webhookHandlerId
116
+ ),
117
+ index("khotan_webhook_events_khotan_run_id_idx").on(table.khotanRunId),
118
+ index("khotan_webhook_events_received_at_idx").on(table.receivedAt.desc())
119
+ ]
120
+ );
121
+ var khotanRuns = pgTable(
122
+ "khotan_runs",
123
+ {
124
+ id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
125
+ flowId: text("flow_id"),
126
+ wireId: text("wire_id"),
127
+ webhookHandlerId: text("webhook_handler_id"),
128
+ workflowRunId: text("workflow_run_id"),
129
+ runType: text("run_type", {
130
+ enum: ["full", "delta", "backfill", "reconcile", "dry-run", "webhook"]
131
+ }).notNull(),
132
+ status: text("status", {
133
+ enum: [
134
+ "pending",
135
+ "running",
136
+ "completed",
137
+ "partial",
138
+ "failed",
139
+ "cancelled"
140
+ ]
141
+ }).default("pending").notNull(),
142
+ startedAt: timestamp("started_at", { withTimezone: true }).defaultNow().notNull(),
143
+ completedAt: timestamp("completed_at", { withTimezone: true }),
144
+ durationMs: integer("duration_ms"),
145
+ extracted: integer("extracted").default(0).notNull(),
146
+ transformed: integer("transformed").default(0).notNull(),
147
+ created: integer("created").default(0).notNull(),
148
+ updated: integer("updated").default(0).notNull(),
149
+ deleted: integer("deleted").default(0).notNull(),
150
+ failed: integer("failed").default(0).notNull(),
151
+ error: text("error"),
152
+ metadata: jsonb("metadata")
153
+ },
154
+ (table) => [
155
+ index("khotan_runs_flow_id_idx").on(table.flowId),
156
+ index("khotan_runs_wire_id_idx").on(table.wireId),
157
+ index("khotan_runs_webhook_handler_id_idx").on(table.webhookHandlerId),
158
+ index("khotan_runs_status_idx").on(table.status),
159
+ index("khotan_runs_flow_id_started_at_idx").on(
160
+ table.flowId,
161
+ table.startedAt.desc()
162
+ )
163
+ ]
164
+ );
165
+ var khotanMappings = pgTable(
166
+ "khotan_mappings",
167
+ {
168
+ id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
169
+ resourceId: text("resource_id").notNull(),
170
+ connectValue: text("connect_value").notNull(),
171
+ refs: jsonb("refs").notNull().$type().default({}),
172
+ metadata: jsonb("metadata").$type(),
173
+ createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
174
+ updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull()
175
+ },
176
+ (table) => [
177
+ unique("khotan_mappings_resource_id_connect_value_unique").on(
178
+ table.resourceId,
179
+ table.connectValue
180
+ ),
181
+ index("khotan_mappings_resource_id_idx").on(table.resourceId),
182
+ index("khotan_mappings_refs_gin_idx").using("gin", table.refs)
183
+ ]
184
+ );
185
+ async function deriveKey(secret) {
186
+ const encoded = new TextEncoder().encode(secret);
187
+ const hash = await crypto.subtle.digest("SHA-256", encoded);
188
+ return crypto.subtle.importKey("raw", hash, { name: "AES-GCM" }, false, [
189
+ "encrypt",
190
+ "decrypt"
191
+ ]);
192
+ }
193
+ async function encryptVars(plaintext, secret) {
194
+ const iv = crypto.getRandomValues(new Uint8Array(12));
195
+ const key = await deriveKey(secret);
196
+ const encoded = new TextEncoder().encode(plaintext);
197
+ const ciphertext = await crypto.subtle.encrypt(
198
+ { name: "AES-GCM", iv },
199
+ key,
200
+ encoded
201
+ );
202
+ const combined = new Uint8Array(iv.length + ciphertext.byteLength);
203
+ combined.set(iv, 0);
204
+ combined.set(new Uint8Array(ciphertext), iv.length);
205
+ return bytesToHex(combined);
206
+ }
207
+ async function decryptVars(encrypted, secret) {
208
+ const combined = hexToBytes(encrypted);
209
+ const iv = combined.slice(0, 12);
210
+ const ciphertext = combined.slice(12);
211
+ const key = await deriveKey(secret);
212
+ const decrypted = await crypto.subtle.decrypt(
213
+ { name: "AES-GCM", iv },
214
+ key,
215
+ ciphertext
216
+ );
217
+ return new TextDecoder().decode(decrypted);
218
+ }
219
+ function hexToBytes(hex) {
220
+ const bytes = new Uint8Array(hex.length / 2);
221
+ for (let i = 0; i < bytes.length; i++) {
222
+ bytes[i] = parseInt(hex.substring(i * 2, i * 2 + 2), 16);
223
+ }
224
+ return bytes;
225
+ }
226
+ function bytesToHex(bytes) {
227
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
228
+ }
229
+ function drizzleAdapter(db) {
230
+ return {
231
+ async upsertPlug(plug) {
232
+ const rows = await db.insert(khotanPlugs).values({
233
+ name: plug.name,
234
+ baseUrl: plug.baseUrl,
235
+ authType: plug.authType
236
+ }).onConflictDoUpdate({
237
+ target: khotanPlugs.name,
238
+ set: {
239
+ baseUrl: plug.baseUrl,
240
+ authType: plug.authType,
241
+ updatedAt: /* @__PURE__ */ new Date()
242
+ }
243
+ }).returning({ id: khotanPlugs.id });
244
+ return { id: rows[0].id };
245
+ },
246
+ async upsertFlow(flow) {
247
+ const rows = await db.insert(khotanFlows).values({
248
+ plugId: flow.plugId,
249
+ name: flow.name,
250
+ type: flow.type,
251
+ schedule: flow.schedule ?? null
252
+ }).onConflictDoUpdate({
253
+ target: [khotanFlows.plugId, khotanFlows.name],
254
+ set: {
255
+ type: flow.type,
256
+ schedule: flow.schedule ?? null,
257
+ updatedAt: /* @__PURE__ */ new Date()
258
+ }
259
+ }).returning({ id: khotanFlows.id });
260
+ return { id: rows[0].id };
261
+ },
262
+ async listPlugs() {
263
+ const flowCounts = db.select({
264
+ plugId: khotanFlows.plugId,
265
+ flowCount: count(khotanFlows.id).as("flow_count")
266
+ }).from(khotanFlows).groupBy(khotanFlows.plugId).as("flow_counts");
267
+ const rows = await db.select({
268
+ id: khotanPlugs.id,
269
+ name: khotanPlugs.name,
270
+ baseUrl: khotanPlugs.baseUrl,
271
+ authType: khotanPlugs.authType,
272
+ enabled: khotanPlugs.enabled,
273
+ status: khotanPlugs.status,
274
+ statusMessage: khotanPlugs.statusMessage,
275
+ createdAt: khotanPlugs.createdAt,
276
+ updatedAt: khotanPlugs.updatedAt,
277
+ flowCount: sql`coalesce(${flowCounts.flowCount}, 0)`
278
+ }).from(khotanPlugs).leftJoin(flowCounts, eq(khotanPlugs.id, flowCounts.plugId));
279
+ return rows;
280
+ },
281
+ async getPlug(id) {
282
+ const rows = await db.select().from(khotanPlugs).where(eq(khotanPlugs.id, id)).limit(1);
283
+ return rows[0] ?? null;
284
+ },
285
+ async getPlugFlows(plugId) {
286
+ return db.select().from(khotanFlows).where(eq(khotanFlows.plugId, plugId));
287
+ },
288
+ async getFlow(flowId) {
289
+ const rows = await db.select({
290
+ id: khotanFlows.id,
291
+ plugId: khotanFlows.plugId,
292
+ name: khotanFlows.name,
293
+ type: khotanFlows.type,
294
+ enabled: khotanFlows.enabled,
295
+ schedule: khotanFlows.schedule,
296
+ resourceId: khotanFlows.resourceId,
297
+ lastRunAt: khotanFlows.lastRunAt,
298
+ lastRunStatus: khotanFlows.lastRunStatus,
299
+ createdAt: khotanFlows.createdAt,
300
+ updatedAt: khotanFlows.updatedAt,
301
+ plugName: khotanPlugs.name
302
+ }).from(khotanFlows).leftJoin(khotanPlugs, eq(khotanFlows.plugId, khotanPlugs.id)).where(eq(khotanFlows.id, flowId)).limit(1);
303
+ return rows[0] ?? null;
304
+ },
305
+ async listFlows() {
306
+ const rows = await db.select({
307
+ id: khotanFlows.id,
308
+ plugId: khotanFlows.plugId,
309
+ name: khotanFlows.name,
310
+ type: khotanFlows.type,
311
+ enabled: khotanFlows.enabled,
312
+ schedule: khotanFlows.schedule,
313
+ resourceId: khotanFlows.resourceId,
314
+ lastRunAt: khotanFlows.lastRunAt,
315
+ lastRunStatus: khotanFlows.lastRunStatus,
316
+ createdAt: khotanFlows.createdAt,
317
+ updatedAt: khotanFlows.updatedAt,
318
+ plugName: khotanPlugs.name
319
+ }).from(khotanFlows).leftJoin(khotanPlugs, eq(khotanFlows.plugId, khotanPlugs.id));
320
+ return rows;
321
+ },
322
+ async listRuns(flowId) {
323
+ return db.select().from(khotanRuns).where(eq(khotanRuns.flowId, flowId)).orderBy(desc(khotanRuns.startedAt));
324
+ },
325
+ async getRun(runId) {
326
+ const rows = await db.select().from(khotanRuns).where(eq(khotanRuns.id, runId)).limit(1);
327
+ return rows[0] ?? null;
328
+ },
329
+ async listRunsPage({ limit, offset }) {
330
+ const rows = await db.select().from(khotanRuns).orderBy(desc(khotanRuns.startedAt)).limit(limit + 1).offset(offset);
331
+ const hasMore = rows.length > limit;
332
+ const pageRows = rows.slice(0, limit);
333
+ const flowIds = [
334
+ ...new Set(
335
+ pageRows.map((row) => row.flowId).filter((value) => typeof value === "string")
336
+ )
337
+ ];
338
+ const handlerIds = [
339
+ ...new Set(
340
+ pageRows.map((row) => row.webhookHandlerId).filter((value) => typeof value === "string")
341
+ )
342
+ ];
343
+ const flowRows = flowIds.length > 0 ? await db.select({
344
+ id: khotanFlows.id,
345
+ name: khotanFlows.name,
346
+ plugName: khotanPlugs.name
347
+ }).from(khotanFlows).leftJoin(khotanPlugs, eq(khotanFlows.plugId, khotanPlugs.id)).where(inArray(khotanFlows.id, flowIds)) : [];
348
+ const handlerRows = handlerIds.length > 0 ? await db.select({
349
+ id: khotanWebhookHandlers.id,
350
+ name: khotanWebhookHandlers.name,
351
+ type: khotanWebhookHandlers.type,
352
+ plugName: khotanPlugs.name
353
+ }).from(khotanWebhookHandlers).leftJoin(
354
+ khotanWires,
355
+ eq(khotanWebhookHandlers.wireId, khotanWires.id)
356
+ ).leftJoin(khotanPlugs, eq(khotanWires.plugId, khotanPlugs.id)).where(inArray(khotanWebhookHandlers.id, handlerIds)) : [];
357
+ const flowMap = new Map(flowRows.map((row) => [row.id, row]));
358
+ const handlerMap = new Map(handlerRows.map((row) => [row.id, row]));
359
+ return {
360
+ items: pageRows.map((row) => {
361
+ const flow = row.flowId ? flowMap.get(row.flowId) : null;
362
+ const handler = row.webhookHandlerId ? handlerMap.get(row.webhookHandlerId) : null;
363
+ return {
364
+ ...row,
365
+ sourceType: flow ? "flow" : handler ? "webhook" : "unknown",
366
+ sourceName: flow?.name ?? handler?.name ?? null,
367
+ sourceKind: handler?.type ?? null,
368
+ plugName: flow?.plugName ?? handler?.plugName ?? null
369
+ };
370
+ }),
371
+ hasMore
372
+ };
373
+ },
374
+ async upsertResource(resource) {
375
+ const rows = await db.insert(khotanResources).values({
376
+ name: resource.name,
377
+ connectField: resource.connectField,
378
+ description: resource.description ?? null
379
+ }).onConflictDoUpdate({
380
+ target: khotanResources.name,
381
+ set: {
382
+ connectField: resource.connectField,
383
+ description: resource.description ?? null,
384
+ updatedAt: /* @__PURE__ */ new Date()
385
+ }
386
+ }).returning({ id: khotanResources.id });
387
+ return { id: rows[0].id };
388
+ },
389
+ async listResources() {
390
+ const flowCounts = db.select({
391
+ resourceId: khotanFlows.resourceId,
392
+ flowCount: count(khotanFlows.id).as("flow_count")
393
+ }).from(khotanFlows).where(sql`${khotanFlows.resourceId} is not null`).groupBy(khotanFlows.resourceId).as("flow_counts");
394
+ const mappingCounts = db.select({
395
+ resourceId: khotanMappings.resourceId,
396
+ mappingCount: count(khotanMappings.id).as("mapping_count")
397
+ }).from(khotanMappings).groupBy(khotanMappings.resourceId).as("mapping_counts");
398
+ const rows = await db.select({
399
+ id: khotanResources.id,
400
+ name: khotanResources.name,
401
+ connectField: khotanResources.connectField,
402
+ description: khotanResources.description,
403
+ createdAt: khotanResources.createdAt,
404
+ updatedAt: khotanResources.updatedAt,
405
+ flowCount: sql`coalesce(${flowCounts.flowCount}, 0)`,
406
+ mappingCount: sql`coalesce(${mappingCounts.mappingCount}, 0)`
407
+ }).from(khotanResources).leftJoin(flowCounts, eq(khotanResources.id, flowCounts.resourceId)).leftJoin(
408
+ mappingCounts,
409
+ eq(khotanResources.id, mappingCounts.resourceId)
410
+ );
411
+ return rows;
412
+ },
413
+ async getResource(id) {
414
+ const rows = await db.select().from(khotanResources).where(eq(khotanResources.id, id)).limit(1);
415
+ return rows[0] ?? null;
416
+ },
417
+ async getResourceFlows(resourceId) {
418
+ return db.select().from(khotanFlows).where(eq(khotanFlows.resourceId, resourceId));
419
+ },
420
+ async upsertMapping(mapping) {
421
+ if (mapping.id) {
422
+ const rows2 = await db.update(khotanMappings).set({
423
+ resourceId: mapping.resourceId,
424
+ connectValue: mapping.connectValue,
425
+ refs: mapping.refs,
426
+ metadata: mapping.metadata ?? null,
427
+ updatedAt: /* @__PURE__ */ new Date()
428
+ }).where(eq(khotanMappings.id, mapping.id)).returning({ id: khotanMappings.id });
429
+ return { id: rows2[0].id, created: false };
430
+ }
431
+ const existing = await db.select({ id: khotanMappings.id }).from(khotanMappings).where(
432
+ sql`${khotanMappings.resourceId} = ${mapping.resourceId} and ${khotanMappings.connectValue} = ${mapping.connectValue}`
433
+ ).limit(1);
434
+ const rows = await db.insert(khotanMappings).values({
435
+ resourceId: mapping.resourceId,
436
+ connectValue: mapping.connectValue,
437
+ refs: mapping.refs,
438
+ metadata: mapping.metadata ?? null
439
+ }).onConflictDoUpdate({
440
+ target: [khotanMappings.resourceId, khotanMappings.connectValue],
441
+ set: {
442
+ refs: sql`${khotanMappings.refs} || ${JSON.stringify(mapping.refs)}::jsonb`,
443
+ metadata: mapping.metadata ?? null,
444
+ updatedAt: /* @__PURE__ */ new Date()
445
+ }
446
+ }).returning({ id: khotanMappings.id });
447
+ return { id: rows[0].id, created: existing.length === 0 };
448
+ },
449
+ async getMapping(id) {
450
+ const rows = await db.select().from(khotanMappings).where(eq(khotanMappings.id, id)).limit(1);
451
+ return rows[0] ?? null;
452
+ },
453
+ async listMappings(resourceId) {
454
+ return db.select().from(khotanMappings).where(eq(khotanMappings.resourceId, resourceId));
455
+ },
456
+ async deleteMapping(id) {
457
+ await db.delete(khotanMappings).where(eq(khotanMappings.id, id));
458
+ },
459
+ async lookupMapping({ resourceId, plugName, ref }) {
460
+ const rows = await db.select().from(khotanMappings).where(
461
+ sql`${khotanMappings.resourceId} = ${resourceId} and ${khotanMappings.refs}->>${plugName} = ${ref}`
462
+ ).limit(1);
463
+ return rows[0] ?? null;
464
+ },
465
+ async updateFlowResourceId(flowId, resourceId) {
466
+ await db.update(khotanFlows).set({ resourceId, updatedAt: /* @__PURE__ */ new Date() }).where(eq(khotanFlows.id, flowId));
467
+ },
468
+ async togglePlugEnabled(plugId, enabled) {
469
+ await db.update(khotanPlugs).set({ enabled, updatedAt: /* @__PURE__ */ new Date() }).where(eq(khotanPlugs.id, plugId));
470
+ },
471
+ async toggleFlowEnabled(flowId, enabled) {
472
+ await db.update(khotanFlows).set({ enabled, updatedAt: /* @__PURE__ */ new Date() }).where(eq(khotanFlows.id, flowId));
473
+ },
474
+ async toggleWebhookHandlerEnabled(handlerId, enabled) {
475
+ await db.update(khotanWebhookHandlers).set({ enabled, updatedAt: /* @__PURE__ */ new Date() }).where(eq(khotanWebhookHandlers.id, handlerId));
476
+ },
477
+ async insertWire(wire) {
478
+ const rows = await db.insert(khotanWires).values({
479
+ plugId: wire.plugId,
480
+ remoteId: wire.remoteId,
481
+ callbackUrl: wire.callbackUrl,
482
+ eventTypes: wire.eventTypes,
483
+ status: "active"
484
+ }).returning();
485
+ return { id: rows[0].id };
486
+ },
487
+ async upsertWire(wire) {
488
+ const existing = await db.select({ id: khotanWires.id }).from(khotanWires).where(eq(khotanWires.plugId, wire.plugId)).limit(1);
489
+ if (existing.length > 0) {
490
+ return { id: existing[0].id };
491
+ }
492
+ const rows = await db.insert(khotanWires).values({
493
+ plugId: wire.plugId,
494
+ remoteId: "",
495
+ callbackUrl: "",
496
+ eventTypes: [],
497
+ status: "pending"
498
+ }).returning();
499
+ return { id: rows[0].id };
500
+ },
501
+ async getActiveWire(plugId) {
502
+ const rows = await db.select().from(khotanWires).where(
503
+ sql`${khotanWires.plugId} = ${plugId} and ${khotanWires.status} = 'active'`
504
+ ).limit(1);
505
+ return rows[0] ?? null;
506
+ },
507
+ async getPlugWire(plugId) {
508
+ const rows = await db.select().from(khotanWires).where(eq(khotanWires.plugId, plugId)).limit(1);
509
+ return rows[0] ?? null;
510
+ },
511
+ async getWire(wireId) {
512
+ const rows = await db.select().from(khotanWires).where(eq(khotanWires.id, wireId)).limit(1);
513
+ return rows[0] ?? null;
514
+ },
515
+ async updateWireStatus(wireId, status) {
516
+ await db.update(khotanWires).set({ status, updatedAt: /* @__PURE__ */ new Date() }).where(eq(khotanWires.id, wireId));
517
+ },
518
+ async updateWireDetails(wireId, details) {
519
+ await db.update(khotanWires).set({
520
+ remoteId: details.remoteId,
521
+ callbackUrl: details.callbackUrl,
522
+ eventTypes: details.eventTypes,
523
+ status: details.status,
524
+ updatedAt: /* @__PURE__ */ new Date()
525
+ }).where(eq(khotanWires.id, wireId));
526
+ },
527
+ async getWireMetadata(wireId) {
528
+ const rows = await db.select({ metadata: khotanWires.metadata }).from(khotanWires).where(eq(khotanWires.id, wireId)).limit(1);
529
+ const meta = rows[0]?.metadata;
530
+ if (!meta) return null;
531
+ return typeof meta === "string" ? meta : JSON.stringify(meta);
532
+ },
533
+ async updateWireMetadata(wireId, metadata) {
534
+ await db.update(khotanWires).set({ metadata, updatedAt: /* @__PURE__ */ new Date() }).where(eq(khotanWires.id, wireId));
535
+ },
536
+ async getEncryptedVariables(plugId) {
537
+ const rows = await db.select({ encryptedVars: khotanPlugs.encryptedVars }).from(khotanPlugs).where(eq(khotanPlugs.id, plugId)).limit(1);
538
+ return rows[0]?.encryptedVars ?? null;
539
+ },
540
+ async setEncryptedVariables(plugId, encrypted) {
541
+ await db.update(khotanPlugs).set({ encryptedVars: encrypted, updatedAt: /* @__PURE__ */ new Date() }).where(eq(khotanPlugs.id, plugId));
542
+ },
543
+ async clearEncryptedVariables(plugId) {
544
+ await db.update(khotanPlugs).set({ encryptedVars: null, updatedAt: /* @__PURE__ */ new Date() }).where(eq(khotanPlugs.id, plugId));
545
+ },
546
+ async upsertWebhookHandler(handler) {
547
+ const rows = await db.insert(khotanWebhookHandlers).values({
548
+ wireId: handler.wireId,
549
+ name: handler.name,
550
+ type: handler.type,
551
+ destinationPlugId: handler.destinationPlugId ?? null
552
+ }).onConflictDoUpdate({
553
+ target: [khotanWebhookHandlers.wireId, khotanWebhookHandlers.name],
554
+ set: {
555
+ type: handler.type,
556
+ destinationPlugId: handler.destinationPlugId ?? null,
557
+ updatedAt: /* @__PURE__ */ new Date()
558
+ }
559
+ }).returning({ id: khotanWebhookHandlers.id });
560
+ return { id: rows[0].id };
561
+ },
562
+ async listWebhookHandlers(wireId) {
563
+ return db.select().from(khotanWebhookHandlers).where(eq(khotanWebhookHandlers.wireId, wireId));
564
+ },
565
+ async getLatestWebhookHandlerRun(handlerId) {
566
+ const rows = await db.select({
567
+ id: khotanRuns.id,
568
+ status: khotanRuns.status,
569
+ startedAt: khotanRuns.startedAt
570
+ }).from(khotanRuns).where(eq(khotanRuns.webhookHandlerId, handlerId)).orderBy(desc(khotanRuns.startedAt)).limit(1);
571
+ return rows[0] ?? null;
572
+ },
573
+ async insertRun(run) {
574
+ const rows = await db.insert(khotanRuns).values({
575
+ flowId: run.flowId ?? null,
576
+ wireId: run.wireId ?? null,
577
+ webhookHandlerId: run.webhookHandlerId ?? null,
578
+ workflowRunId: run.workflowRunId ?? null,
579
+ runType: run.runType,
580
+ status: run.status
581
+ }).returning({ id: khotanRuns.id });
582
+ return { id: rows[0].id };
583
+ },
584
+ async updateRun(runId, updates) {
585
+ await db.update(khotanRuns).set({
586
+ status: updates.status,
587
+ workflowRunId: updates.workflowRunId,
588
+ completedAt: updates.completedAt,
589
+ durationMs: updates.durationMs,
590
+ extracted: updates.extracted,
591
+ transformed: updates.transformed,
592
+ created: updates.created,
593
+ updated: updates.updated,
594
+ deleted: updates.deleted,
595
+ failed: updates.failed,
596
+ error: updates.error,
597
+ metadata: updates.metadata
598
+ }).where(eq(khotanRuns.id, runId));
599
+ },
600
+ async insertWebhookEvent(event) {
601
+ const rows = await db.insert(khotanWebhookEvents).values({
602
+ wireId: event.wireId,
603
+ webhookHandlerId: event.webhookHandlerId,
604
+ khotanRunId: event.khotanRunId,
605
+ eventType: event.eventType,
606
+ payload: event.payload,
607
+ headers: event.headers
608
+ }).returning({ id: khotanWebhookEvents.id });
609
+ return { id: rows[0].id };
610
+ },
611
+ async listWebhookEventsPage({ limit, offset }) {
612
+ let rows;
613
+ try {
614
+ rows = await db.select().from(khotanWebhookEvents).orderBy(desc(khotanWebhookEvents.receivedAt)).limit(limit + 1).offset(offset);
615
+ } catch (error) {
616
+ const message = error instanceof Error ? error.message : String(error);
617
+ const isLegacyShapeError = message.includes(`column "wire_id" does not exist`) || message.includes(`column "webhook_handler_id" does not exist`) || message.includes(`column "headers" does not exist`);
618
+ if (!isLegacyShapeError) {
619
+ throw error;
620
+ }
621
+ const legacyResult = await db.execute(sql`
622
+ select
623
+ "id",
624
+ null::text as "wire_id",
625
+ null::text as "webhook_handler_id",
626
+ "khotan_run_id",
627
+ "event_type",
628
+ "payload",
629
+ '{}'::jsonb as "headers",
630
+ "received_at"
631
+ from "khotan_webhook_events"
632
+ order by "received_at" desc
633
+ limit ${limit + 1}
634
+ offset ${offset}
635
+ `);
636
+ const rawRows = Array.isArray(legacyResult) ? legacyResult : "rows" in legacyResult && Array.isArray(legacyResult.rows) ? legacyResult.rows : [];
637
+ rows = rawRows.map((row) => ({
638
+ id: String(row["id"]),
639
+ wireId: typeof row["wire_id"] === "string" ? row["wire_id"] : null,
640
+ webhookHandlerId: typeof row["webhook_handler_id"] === "string" ? row["webhook_handler_id"] : null,
641
+ khotanRunId: String(row["khotan_run_id"]),
642
+ eventType: String(row["event_type"]),
643
+ payload: row["payload"] && typeof row["payload"] === "object" ? row["payload"] : {},
644
+ headers: row["headers"] && typeof row["headers"] === "object" ? row["headers"] : {},
645
+ receivedAt: row["received_at"] instanceof Date ? row["received_at"] : new Date(String(row["received_at"]))
646
+ }));
647
+ }
648
+ const hasMore = rows.length > limit;
649
+ const pageRows = rows.slice(0, limit);
650
+ const handlerIds = [
651
+ ...new Set(
652
+ pageRows.map((row) => row.webhookHandlerId).filter((value) => typeof value === "string")
653
+ )
654
+ ];
655
+ const runIds = [
656
+ ...new Set(
657
+ pageRows.map((row) => row.khotanRunId).filter((value) => typeof value === "string")
658
+ )
659
+ ];
660
+ const handlerRows = handlerIds.length > 0 ? await db.select({
661
+ id: khotanWebhookHandlers.id,
662
+ name: khotanWebhookHandlers.name,
663
+ type: khotanWebhookHandlers.type,
664
+ plugName: khotanPlugs.name
665
+ }).from(khotanWebhookHandlers).leftJoin(
666
+ khotanWires,
667
+ eq(khotanWebhookHandlers.wireId, khotanWires.id)
668
+ ).leftJoin(khotanPlugs, eq(khotanWires.plugId, khotanPlugs.id)).where(inArray(khotanWebhookHandlers.id, handlerIds)) : [];
669
+ const runRows = runIds.length > 0 ? await db.select({
670
+ id: khotanRuns.id,
671
+ workflowRunId: khotanRuns.workflowRunId,
672
+ status: khotanRuns.status,
673
+ startedAt: khotanRuns.startedAt
674
+ }).from(khotanRuns).where(inArray(khotanRuns.id, runIds)) : [];
675
+ const handlerMap = new Map(handlerRows.map((row) => [row.id, row]));
676
+ const runMap = new Map(runRows.map((row) => [row.id, row]));
677
+ return {
678
+ items: pageRows.map((row) => {
679
+ const handler = row.webhookHandlerId ? handlerMap.get(row.webhookHandlerId) : void 0;
680
+ const run = runMap.get(row.khotanRunId);
681
+ return {
682
+ ...row,
683
+ handlerName: handler?.name ?? null,
684
+ handlerType: handler?.type ?? null,
685
+ plugName: handler?.plugName ?? null,
686
+ workflowRunId: run?.workflowRunId ?? null,
687
+ runStatus: run?.status ?? null,
688
+ runStartedAt: run?.startedAt ?? null
689
+ };
690
+ }),
691
+ hasMore
692
+ };
693
+ },
694
+ async updateFlowLastRun(flowId, updates) {
695
+ await db.update(khotanFlows).set({
696
+ lastRunAt: updates.lastRunAt,
697
+ lastRunStatus: updates.lastRunStatus,
698
+ updatedAt: /* @__PURE__ */ new Date()
699
+ }).where(eq(khotanFlows.id, flowId));
700
+ }
701
+ };
702
+ }
703
+ var _workflowStart = null;
704
+ var _workflowGetRun = null;
705
+ var _workflowGetWritable = null;
706
+ function __setWorkflowStartForTests(start) {
707
+ _workflowStart = start;
708
+ }
709
+ function __setWorkflowGetRunForTests(getRun) {
710
+ _workflowGetRun = getRun;
711
+ }
712
+ function __setWorkflowGetWritableForTests(getWritable) {
713
+ _workflowGetWritable = getWritable;
714
+ }
715
+ async function importWorkflowStart() {
716
+ if (_workflowStart) return _workflowStart;
717
+ try {
718
+ const mod = await import(
719
+ /* webpackIgnore: true */
720
+ 'workflow/api'
721
+ );
722
+ _workflowStart = mod.start;
723
+ return _workflowStart;
724
+ } catch {
725
+ throw new Error(
726
+ "Failed to import workflow/api. Install Vercel Workflow: npm install workflow"
727
+ );
728
+ }
729
+ }
730
+ async function importWorkflowGetRun() {
731
+ if (_workflowGetRun) return _workflowGetRun;
732
+ try {
733
+ const mod = await import(
734
+ /* webpackIgnore: true */
735
+ 'workflow/api'
736
+ );
737
+ _workflowGetRun = mod.getRun;
738
+ return _workflowGetRun;
739
+ } catch {
740
+ throw new Error(
741
+ "Failed to import workflow/api. Install Vercel Workflow: npm install workflow"
742
+ );
743
+ }
744
+ }
745
+ async function importWorkflowGetWritable() {
746
+ if (_workflowGetWritable) return _workflowGetWritable;
747
+ try {
748
+ const mod = await import(
749
+ /* webpackIgnore: true */
750
+ 'workflow'
751
+ );
752
+ _workflowGetWritable = mod.getWritable;
753
+ return _workflowGetWritable;
754
+ } catch {
755
+ throw new Error(
756
+ "Failed to import workflow. Install Vercel Workflow: npm install workflow"
757
+ );
758
+ }
759
+ }
760
+ async function sendUpdate(update, options = {}) {
761
+ const getWritable = await importWorkflowGetWritable();
762
+ const writable = getWritable(options);
763
+ const writer = writable.getWriter();
764
+ const payload = typeof update === "string" ? { type: "log", message: update } : { type: "progress", ...update };
765
+ try {
766
+ await writer.write(
767
+ `${JSON.stringify({ ...payload, timestamp: (/* @__PURE__ */ new Date()).toISOString() })}
768
+ `
769
+ );
770
+ } finally {
771
+ writer.releaseLock();
772
+ }
773
+ }
774
+ function getWorkflowRunId(result) {
775
+ if (!result || typeof result !== "object") return null;
776
+ if ("runId" in result) return String(result.runId);
777
+ if ("id" in result) return String(result.id);
778
+ return null;
779
+ }
780
+ function getWorkflowReturnValue(result) {
781
+ if (!result || typeof result !== "object" || !("returnValue" in result)) {
782
+ return null;
783
+ }
784
+ const returnValue = result.returnValue;
785
+ return returnValue && typeof returnValue.then === "function" ? returnValue : null;
786
+ }
787
+ function getErrorMessage(error) {
788
+ if (error && typeof error === "object" && "cause" in error && error.cause instanceof Error) {
789
+ return error.cause.message;
790
+ }
791
+ if (error instanceof Error) return error.message;
792
+ return "Unknown error";
793
+ }
794
+ function isWorkflowCancelledError(error) {
795
+ if (!error || typeof error !== "object") return false;
796
+ const record = error;
797
+ const name = typeof record["name"] === "string" ? record["name"] : "";
798
+ const status = typeof record["status"] === "string" ? record["status"] : "";
799
+ const message = typeof record["message"] === "string" ? record["message"] : "";
800
+ return name === "WorkflowRunCancelledError" || status === "cancelled" || message.toLowerCase().includes("cancelled");
801
+ }
802
+ function toFlowRunResult(value) {
803
+ return value && typeof value === "object" ? value : void 0;
804
+ }
805
+ function getFlowRunCounters(result) {
806
+ return {
807
+ extracted: result?.extracted ?? 0,
808
+ transformed: result?.transformed ?? 0,
809
+ created: result?.created ?? 0,
810
+ updated: result?.updated ?? 0,
811
+ deleted: result?.deleted ?? 0,
812
+ failed: result?.failed ?? 0
813
+ };
814
+ }
815
+ function resolveTerminalRunStatus(result, counters) {
816
+ if (result?.status === "completed" || result?.status === "partial" || result?.status === "failed" || result?.status === "cancelled") {
817
+ return result.status;
818
+ }
819
+ return counters.failed > 0 ? "partial" : "completed";
820
+ }
821
+ function serializeZodSchema(schema) {
822
+ if (!schema) return null;
823
+ try {
824
+ const def = schema?._def ?? schema?.def ?? null;
825
+ const rawTypeName = typeof def?.typeName === "string" ? def.typeName : typeof def?.type === "string" ? `Zod${def.type.charAt(0).toUpperCase()}${def.type.slice(1)}` : "";
826
+ const inner = def?.innerType ?? def?.element ?? (typeof def?.type === "object" ? def.type : null) ?? null;
827
+ if ((rawTypeName === "ZodOptional" || rawTypeName === "ZodNullable") && inner) {
828
+ return serializeZodSchema(inner);
829
+ }
830
+ const shape = typeof schema.shape === "function" ? schema.shape() : schema.shape ?? (typeof def?.shape === "function" ? def.shape() : def?.shape);
831
+ if (shape && typeof shape === "object") {
832
+ const result = {};
833
+ for (const [key, val] of Object.entries(shape)) {
834
+ result[key] = serializeZodField(val);
835
+ }
836
+ return result;
837
+ }
838
+ if (rawTypeName === "ZodArray" && inner) {
839
+ return {
840
+ _type: "array",
841
+ items: serializeZodSchema(inner)
842
+ };
843
+ }
844
+ if (rawTypeName) {
845
+ return { _type: rawTypeName.replace("Zod", "").toLowerCase() };
846
+ }
847
+ } catch {
848
+ }
849
+ return null;
850
+ }
851
+ function serializeZodField(field) {
852
+ const def = field?._def ?? field?.def ?? null;
853
+ if (!def) return "unknown";
854
+ const typeName = typeof def.typeName === "string" ? def.typeName : typeof def.type === "string" ? `Zod${def.type.charAt(0).toUpperCase()}${def.type.slice(1)}` : "";
855
+ const inner = def.innerType ?? def.element ?? (typeof def.type === "object" ? def.type : null) ?? null;
856
+ if (typeName === "ZodOptional" && inner) {
857
+ const serialized = serializeZodField(inner);
858
+ if (typeof serialized === "string") {
859
+ return serialized.endsWith("?") ? serialized : `${serialized}?`;
860
+ }
861
+ return serialized;
862
+ }
863
+ if (typeName === "ZodNullable" && inner) {
864
+ const serialized = serializeZodField(inner);
865
+ if (typeof serialized === "string") {
866
+ return serialized.includes(" | null") ? serialized : `${serialized} | null`;
867
+ }
868
+ return serialized;
869
+ }
870
+ if (typeName === "ZodObject" || typeName === "ZodArray" || typeName === "ZodRecord" || typeName === "ZodUnion" || typeName === "ZodDiscriminatedUnion") {
871
+ return serializeZodSchema(field) ?? "unknown";
872
+ }
873
+ if (typeName === "ZodEnum") {
874
+ const values = Array.isArray(def.values) ? def.values : def.entries && typeof def.entries === "object" ? Object.values(def.entries) : [];
875
+ if (values.length > 0) {
876
+ return values.map((v) => `"${v}"`).join(" | ");
877
+ }
878
+ }
879
+ return typeName.replace("Zod", "").toLowerCase() || "unknown";
880
+ }
881
+ function serializeEndpoints(endpoints) {
882
+ if (!endpoints) return null;
883
+ const result = {};
884
+ for (const [name, ep] of Object.entries(endpoints)) {
885
+ const entry = {
886
+ method: ep.method,
887
+ path: ep.path
888
+ };
889
+ if (ep.description) entry["description"] = ep.description;
890
+ const bodySchema = serializeZodSchema(ep.body);
891
+ if (bodySchema) entry["body"] = bodySchema;
892
+ const querySchema = serializeZodSchema(ep.query);
893
+ if (querySchema) entry["query"] = querySchema;
894
+ if (ep.responses) {
895
+ entry["responses"] = Object.fromEntries(
896
+ Object.entries(ep.responses).map(([code, schema]) => [
897
+ code,
898
+ serializeZodSchema(schema)
899
+ ])
900
+ );
901
+ }
902
+ result[name] = entry;
903
+ }
904
+ return result;
905
+ }
906
+ function khotan(config) {
907
+ const { adapter, plugs, resources = [] } = config;
908
+ const resourceNames = /* @__PURE__ */ new Set();
909
+ for (const resource of resources) {
910
+ if (resourceNames.has(resource.name)) {
911
+ throw new Error(`Duplicate resource name: "${resource.name}"`);
912
+ }
913
+ resourceNames.add(resource.name);
914
+ }
915
+ const plugNames = /* @__PURE__ */ new Set();
916
+ for (const plug of plugs) {
917
+ if (plugNames.has(plug.name)) {
918
+ throw new Error(`Duplicate plug name: "${plug.name}"`);
919
+ }
920
+ plugNames.add(plug.name);
921
+ if (plug.flows) {
922
+ for (const flow2 of plug.flows) {
923
+ if (flow2.resource && !resourceNames.has(flow2.resource)) {
924
+ throw new Error(
925
+ `Flow "${flow2.name}" references unknown resource: "${flow2.resource}"`
926
+ );
927
+ }
928
+ }
929
+ }
930
+ }
931
+ const registeredFlowKeys = new Set(
932
+ plugs.flatMap(
933
+ (plug) => (plug.flows ?? []).map(
934
+ (flow2) => `${plug.name}\0${flow2.name}\0${flow2.type}`
935
+ )
936
+ )
937
+ );
938
+ function isRegisteredFlowRecord(flow2) {
939
+ return typeof flow2["plugName"] === "string" && typeof flow2["name"] === "string" && typeof flow2["type"] === "string" && registeredFlowKeys.has(
940
+ `${flow2["plugName"]}\0${flow2["name"]}\0${flow2["type"]}`
941
+ );
942
+ }
943
+ function getWebhookHandlersForPlug(plug) {
944
+ const handlers = [];
945
+ if (plug.webhooks) handlers.push(...plug.webhooks);
946
+ if (plug.catches) handlers.push(...plug.catches);
947
+ if (plug.passes) handlers.push(...plug.passes);
948
+ return handlers;
949
+ }
950
+ for (const plug of plugs) {
951
+ const webhookHandlers = getWebhookHandlersForPlug(plug);
952
+ if (webhookHandlers.length > 0) {
953
+ const wireConfig = plug.wires?.[0];
954
+ if (!wireConfig?.onVerify) {
955
+ throw new Error(
956
+ `Plug "${plug.name}" has webhook handlers but its wire does not define onVerify. onVerify is required for webhook processing.`
957
+ );
958
+ }
959
+ }
960
+ for (const handler2 of webhookHandlers) {
961
+ if (handler2.type === "pass") {
962
+ if (!plugNames.has(handler2.to)) {
963
+ throw new Error(
964
+ `Pass on plug "${plug.name}" references unknown destination plug: "${handler2.to}"`
965
+ );
966
+ }
967
+ }
968
+ }
969
+ }
970
+ let initialized = false;
971
+ let initPromise = null;
972
+ async function doInit() {
973
+ if (initialized) return;
974
+ const resourceIdMap = /* @__PURE__ */ new Map();
975
+ for (const resource of resources) {
976
+ const { id } = await adapter.upsertResource({
977
+ name: resource.name,
978
+ connectField: resource.connectField,
979
+ description: resource.description ?? null
980
+ });
981
+ resourceIdMap.set(resource.name, id);
982
+ }
983
+ for (const plug of plugs) {
984
+ const { id: plugId } = await adapter.upsertPlug({
985
+ name: plug.name,
986
+ baseUrl: plug.plug.baseUrl,
987
+ authType: plug.plug.authType
988
+ });
989
+ await seedDefaultVarsForPlug(plugId, plug.name);
990
+ if (plug.flows) {
991
+ for (const flow2 of plug.flows) {
992
+ const { id: flowId } = await adapter.upsertFlow({
993
+ plugId,
994
+ name: flow2.name,
995
+ type: flow2.type,
996
+ schedule: flow2.schedule ?? null
997
+ });
998
+ if (flow2.resource) {
999
+ const resourceId = resourceIdMap.get(flow2.resource);
1000
+ await adapter.updateFlowResourceId(flowId, resourceId);
1001
+ }
1002
+ }
1003
+ }
1004
+ if (plug.wires) {
1005
+ for (const _wire of plug.wires) {
1006
+ const { id: wireId } = await adapter.upsertWire({ plugId });
1007
+ const webhookHandlers = getWebhookHandlersForPlug(plug);
1008
+ for (const handler2 of webhookHandlers) {
1009
+ if (handler2.type === "catch") {
1010
+ await adapter.upsertWebhookHandler({
1011
+ wireId,
1012
+ name: handler2.name,
1013
+ type: "catch"
1014
+ });
1015
+ continue;
1016
+ }
1017
+ const destPlugRow = await adapter.listPlugs().then((all) => all.find((row) => row["name"] === handler2.to));
1018
+ await adapter.upsertWebhookHandler({
1019
+ wireId,
1020
+ name: handler2.name,
1021
+ type: "pass",
1022
+ destinationPlugId: destPlugRow ? destPlugRow["id"] : null
1023
+ });
1024
+ }
1025
+ }
1026
+ }
1027
+ }
1028
+ initialized = true;
1029
+ }
1030
+ async function init() {
1031
+ initPromise ??= doInit();
1032
+ return initPromise;
1033
+ }
1034
+ function wire(plugName) {
1035
+ const plugReg = plugs.find((p) => p.name === plugName);
1036
+ if (!plugReg) {
1037
+ throw new Error(`Plug "${plugName}" not registered`);
1038
+ }
1039
+ if (!plugReg.wires || plugReg.wires.length === 0) {
1040
+ throw new Error(`Plug "${plugName}" has no wire configuration`);
1041
+ }
1042
+ const wireConfig = plugReg.wires[0];
1043
+ function createBoundPlug(vars, _setVars) {
1044
+ const plug = plugReg.plug;
1045
+ const opts = (extra) => ({
1046
+ ...extra,
1047
+ vars,
1048
+ ..._setVars && { _setVars }
1049
+ });
1050
+ return {
1051
+ get(path, extra) {
1052
+ return plug.get(path, opts(extra));
1053
+ },
1054
+ post(path, extra) {
1055
+ return plug.post(path, opts(extra));
1056
+ },
1057
+ put(path, extra) {
1058
+ return plug.put(path, opts(extra));
1059
+ },
1060
+ patch(path, extra) {
1061
+ return plug.patch(path, opts(extra));
1062
+ },
1063
+ delete(path, extra) {
1064
+ return plug.delete(path, opts(extra));
1065
+ }
1066
+ };
1067
+ }
1068
+ async function getWireVars(wireId) {
1069
+ const raw = await adapter.getWireMetadata(wireId);
1070
+ if (!raw) return {};
1071
+ if (!secret) {
1072
+ try {
1073
+ return JSON.parse(raw);
1074
+ } catch {
1075
+ return {};
1076
+ }
1077
+ }
1078
+ try {
1079
+ const decrypted = await decryptVars(raw, secret);
1080
+ return JSON.parse(decrypted);
1081
+ } catch {
1082
+ try {
1083
+ return JSON.parse(raw);
1084
+ } catch {
1085
+ return {};
1086
+ }
1087
+ }
1088
+ }
1089
+ async function setWireVars(wireId, vars) {
1090
+ const serialized = JSON.stringify(vars);
1091
+ const toStore = secret ? await encryptVars(serialized, secret) : serialized;
1092
+ await adapter.updateWireMetadata(wireId, toStore);
1093
+ }
1094
+ return {
1095
+ async create(callbackUrl) {
1096
+ await init();
1097
+ kd(
1098
+ "wire",
1099
+ `${plugName}: creating subscription, callbackUrl=${callbackUrl}`
1100
+ );
1101
+ const allPlugs = await adapter.listPlugs();
1102
+ const dbPlug = allPlugs.find((p) => p["name"] === plugName);
1103
+ if (!dbPlug) {
1104
+ throw new Error(`Plug "${plugName}" not found in database`);
1105
+ }
1106
+ const plugId = dbPlug["id"];
1107
+ const existingWire = await adapter.getPlugWire(plugId);
1108
+ const wireId = existingWire ? existingWire["id"] : (await adapter.insertWire({
1109
+ plugId,
1110
+ remoteId: "",
1111
+ callbackUrl,
1112
+ eventTypes: wireConfig.events
1113
+ })).id;
1114
+ const vars = secret ? await getVars(plugName).catch(() => ({})) : {};
1115
+ const _setVars = secret ? (updates) => setVars(plugName, { ...vars, ...updates }) : void 0;
1116
+ const boundPlug = createBoundPlug(vars, _setVars);
1117
+ const wireVars = await getWireVars(wireId);
1118
+ const result = await wireConfig.onSubscribe({
1119
+ plug: boundPlug,
1120
+ callbackUrl,
1121
+ events: wireConfig.events,
1122
+ wireVars,
1123
+ setWireVars: (updates) => setWireVars(wireId, { ...wireVars, ...updates })
1124
+ });
1125
+ kd(
1126
+ "wire",
1127
+ `${plugName}: subscription created, remoteId=${result.remoteId}`
1128
+ );
1129
+ await adapter.updateWireDetails(wireId, {
1130
+ remoteId: result.remoteId,
1131
+ callbackUrl,
1132
+ eventTypes: wireConfig.events,
1133
+ status: "active"
1134
+ });
1135
+ const record = await adapter.getWire(wireId);
1136
+ return record;
1137
+ },
1138
+ async delete(wireId) {
1139
+ await init();
1140
+ kd("wire", `${plugName}: deleting wire ${wireId}`);
1141
+ const wireRecord = await adapter.getWire(wireId);
1142
+ if (!wireRecord) {
1143
+ throw new Error(`Wire "${wireId}" not found`);
1144
+ }
1145
+ const remoteId = wireRecord["remoteId"] ?? wireRecord["remote_id"];
1146
+ kd("wire", `${plugName}: remoteId=${remoteId}`);
1147
+ if (!remoteId) {
1148
+ await adapter.updateWireStatus(wireId, "disabled");
1149
+ return;
1150
+ }
1151
+ const vars = secret ? await getVars(plugName).catch(() => ({})) : {};
1152
+ const _setVars = secret ? (updates) => setVars(plugName, { ...vars, ...updates }) : void 0;
1153
+ const boundPlug = createBoundPlug(vars, _setVars);
1154
+ const wireVars = await getWireVars(wireId);
1155
+ await wireConfig.onUnsubscribe({
1156
+ plug: boundPlug,
1157
+ remoteId,
1158
+ wireVars,
1159
+ setWireVars: (updates) => setWireVars(wireId, { ...wireVars, ...updates })
1160
+ });
1161
+ kd("wire", `${plugName}: unsubscribed successfully`);
1162
+ await adapter.updateWireStatus(wireId, "disabled");
1163
+ },
1164
+ async get() {
1165
+ await init();
1166
+ const allPlugs = await adapter.listPlugs();
1167
+ const dbPlug = allPlugs.find((p) => p["name"] === plugName);
1168
+ if (!dbPlug) return null;
1169
+ return adapter.getPlugWire(dbPlug["id"]);
1170
+ }
1171
+ };
1172
+ }
1173
+ async function triggerFlowRun(flowId, body) {
1174
+ const flow2 = await adapter.getFlow(flowId);
1175
+ if (!flow2 || typeof flow2["plugName"] !== "string" || !plugNames.has(flow2["plugName"])) {
1176
+ return Response.json({ error: "Flow not found" }, { status: 404 });
1177
+ }
1178
+ if (flow2["enabled"] === false) {
1179
+ return Response.json({ error: "Flow is disabled" }, { status: 409 });
1180
+ }
1181
+ const plugName = flow2["plugName"];
1182
+ const plugReg = plugs.find((p) => p.name === plugName);
1183
+ const flowName = flow2["name"];
1184
+ const flowType = flow2["type"];
1185
+ const flowReg = plugReg?.flows?.find(
1186
+ (candidate) => candidate.name === flowName && candidate.type === flowType
1187
+ );
1188
+ if (!plugReg || !flowReg) {
1189
+ return Response.json({ error: "Flow not registered" }, { status: 404 });
1190
+ }
1191
+ if (flowReg.type === "webhook") {
1192
+ return Response.json(
1193
+ { error: "Webhook flows are triggered through webhook routes" },
1194
+ { status: 400 }
1195
+ );
1196
+ }
1197
+ const requestBody = body && typeof body === "object" ? body : {};
1198
+ const runType = typeof requestBody["runType"] === "string" ? requestBody["runType"] : "full";
1199
+ const { id: runId } = await adapter.insertRun({
1200
+ flowId,
1201
+ runType,
1202
+ status: "running"
1203
+ });
1204
+ const startedAt = Date.now();
1205
+ async function completeRunOk(result) {
1206
+ const completedAt = /* @__PURE__ */ new Date();
1207
+ const counters = getFlowRunCounters(result);
1208
+ const status = resolveTerminalRunStatus(result, counters);
1209
+ await adapter.updateRun(runId, {
1210
+ status,
1211
+ completedAt,
1212
+ durationMs: Date.now() - startedAt,
1213
+ ...counters,
1214
+ error: result?.error ?? null,
1215
+ metadata: result?.metadata ?? null
1216
+ });
1217
+ await adapter.updateFlowLastRun(flowId, {
1218
+ lastRunAt: completedAt,
1219
+ lastRunStatus: status
1220
+ });
1221
+ return { completedAt, counters, status };
1222
+ }
1223
+ async function completeRunFailed(error) {
1224
+ const completedAt = /* @__PURE__ */ new Date();
1225
+ const message = getErrorMessage(error);
1226
+ const status = isWorkflowCancelledError(error) ? "cancelled" : "failed";
1227
+ await adapter.updateRun(runId, {
1228
+ status,
1229
+ completedAt,
1230
+ durationMs: Date.now() - startedAt,
1231
+ failed: status === "failed" ? 1 : 0,
1232
+ error: message
1233
+ });
1234
+ await adapter.updateFlowLastRun(flowId, {
1235
+ lastRunAt: completedAt,
1236
+ lastRunStatus: status
1237
+ });
1238
+ return message;
1239
+ }
1240
+ function observeWorkflowCompletion(workflowResult) {
1241
+ const returnValue = getWorkflowReturnValue(workflowResult);
1242
+ if (!returnValue) return;
1243
+ void returnValue.then(async (value) => {
1244
+ await completeRunOk(toFlowRunResult(value));
1245
+ }).catch(async (error) => {
1246
+ await completeRunFailed(error);
1247
+ }).catch((error) => {
1248
+ kd("flow", `Failed to reconcile workflow run ${runId}`, error);
1249
+ });
1250
+ }
1251
+ try {
1252
+ const vars = secret ? await getVars(plugName).catch(() => ({})) : {};
1253
+ const setFlowVars = async (updates) => {
1254
+ await setVars(plugName, { ...vars, ...updates });
1255
+ };
1256
+ const opts = (extra) => ({
1257
+ ...extra,
1258
+ vars,
1259
+ ...secret ? { _setVars: setFlowVars } : {}
1260
+ });
1261
+ const boundPlug = {
1262
+ get(path, extra) {
1263
+ return plugReg.plug.get(path, opts(extra));
1264
+ },
1265
+ post(path, extra) {
1266
+ return plugReg.plug.post(path, opts(extra));
1267
+ },
1268
+ put(path, extra) {
1269
+ return plugReg.plug.put(path, opts(extra));
1270
+ },
1271
+ patch(path, extra) {
1272
+ return plugReg.plug.patch(path, opts(extra));
1273
+ },
1274
+ delete(path, extra) {
1275
+ return plugReg.plug.delete(path, opts(extra));
1276
+ }
1277
+ };
1278
+ const flowContext = {
1279
+ id: flowId,
1280
+ name: flowReg.name,
1281
+ type: flowReg.type,
1282
+ resource: flowReg.resource ?? null,
1283
+ to: flowReg.to ?? null
1284
+ };
1285
+ if (flowReg.workflow) {
1286
+ const startWorkflow = await importWorkflowStart();
1287
+ const result2 = await startWorkflow(flowReg.workflow, [
1288
+ {
1289
+ flow: flowContext,
1290
+ runType,
1291
+ body: requestBody["body"],
1292
+ vars,
1293
+ khotanRunId: runId
1294
+ }
1295
+ ]);
1296
+ const workflowRunId = getWorkflowRunId(result2);
1297
+ if (workflowRunId) {
1298
+ await adapter.updateRun(runId, {
1299
+ status: "running",
1300
+ workflowRunId
1301
+ });
1302
+ }
1303
+ observeWorkflowCompletion(result2);
1304
+ return Response.json({
1305
+ id: runId,
1306
+ flowId,
1307
+ workflowRunId,
1308
+ status: "running",
1309
+ runType
1310
+ });
1311
+ }
1312
+ const result = await flowReg.run?.({
1313
+ plug: boundPlug,
1314
+ flow: flowContext,
1315
+ runType,
1316
+ body: requestBody["body"],
1317
+ vars,
1318
+ setVars: setFlowVars
1319
+ });
1320
+ const runResult = toFlowRunResult(result);
1321
+ const { counters, status } = await completeRunOk(runResult);
1322
+ return Response.json({
1323
+ id: runId,
1324
+ flowId,
1325
+ status,
1326
+ runType,
1327
+ ...counters,
1328
+ error: runResult?.error ?? null,
1329
+ metadata: runResult?.metadata ?? null
1330
+ });
1331
+ } catch (error) {
1332
+ const message = await completeRunFailed(error);
1333
+ return Response.json(
1334
+ { id: runId, flowId, status: "failed", error: message },
1335
+ { status: 500 }
1336
+ );
1337
+ }
1338
+ }
1339
+ async function resolveFlowId(flowNameOrId, options = {}) {
1340
+ await init();
1341
+ const byId = await adapter.getFlow(flowNameOrId);
1342
+ if (byId && typeof byId["plugName"] === "string" && plugNames.has(byId["plugName"])) {
1343
+ return flowNameOrId;
1344
+ }
1345
+ const matches = (await adapter.listFlows()).filter((flow2) => {
1346
+ if (flow2["name"] !== flowNameOrId) return false;
1347
+ if (!isRegisteredFlowRecord(flow2)) return false;
1348
+ return !options.plugName || flow2["plugName"] === options.plugName;
1349
+ });
1350
+ if (matches.length === 0) {
1351
+ const suffix = options.plugName ? ` on plug "${options.plugName}"` : "";
1352
+ throw new Error(`Flow "${flowNameOrId}"${suffix} not found`);
1353
+ }
1354
+ if (matches.length > 1) {
1355
+ const plugs2 = matches.map((flow2) => String(flow2["plugName"])).filter(Boolean).join(", ");
1356
+ throw new Error(
1357
+ `Flow "${flowNameOrId}" is registered on multiple plugs (${plugs2}). Pass { plugName } to select one.`
1358
+ );
1359
+ }
1360
+ const id = matches[0]?.["id"];
1361
+ if (typeof id !== "string") {
1362
+ throw new Error(`Flow "${flowNameOrId}" has no database ID`);
1363
+ }
1364
+ return id;
1365
+ }
1366
+ function flow(flowNameOrId, selectorOptions = {}) {
1367
+ return {
1368
+ async start(startOptions = {}) {
1369
+ const flowId = await resolveFlowId(flowNameOrId, selectorOptions);
1370
+ const response = await triggerFlowRun(flowId, startOptions);
1371
+ const payload = await response.json().catch(() => ({}));
1372
+ if (!response.ok) {
1373
+ const message = payload && typeof payload === "object" && "error" in payload ? String(payload.error) : `Failed to start flow "${flowNameOrId}"`;
1374
+ throw new Error(message);
1375
+ }
1376
+ return payload;
1377
+ }
1378
+ };
1379
+ }
1380
+ async function getRunWithWorkflowStatus(runId) {
1381
+ const run = await adapter.getRun(runId);
1382
+ if (!run) return null;
1383
+ const workflowRunId = typeof run["workflowRunId"] === "string" ? run["workflowRunId"] : typeof run["workflow_run_id"] === "string" ? run["workflow_run_id"] : null;
1384
+ if (!workflowRunId) {
1385
+ return { ...run, workflowStatus: null };
1386
+ }
1387
+ try {
1388
+ const getRun = await importWorkflowGetRun();
1389
+ const workflowRun = getRun(workflowRunId);
1390
+ const workflowStatus = workflowRun.status ? await workflowRun.status : null;
1391
+ return { ...run, workflowStatus };
1392
+ } catch (error) {
1393
+ return {
1394
+ ...run,
1395
+ workflowStatus: null,
1396
+ workflowError: getErrorMessage(error)
1397
+ };
1398
+ }
1399
+ }
1400
+ function getRunWorkflowId(run) {
1401
+ return typeof run["workflowRunId"] === "string" ? run["workflowRunId"] : typeof run["workflow_run_id"] === "string" ? run["workflow_run_id"] : null;
1402
+ }
1403
+ async function handler(request) {
1404
+ await init();
1405
+ const url = new URL(request.url);
1406
+ const segments = url.pathname.replace(/^\/+|\/+$/g, "").split("/").filter(Boolean);
1407
+ const plugsIdx = segments.indexOf("plugs");
1408
+ const flowsIdx = segments.indexOf("flows");
1409
+ const resourcesIdx = segments.indexOf("resources");
1410
+ const mappingsIdx = segments.indexOf("mappings");
1411
+ const runsIdx = segments.indexOf("runs");
1412
+ const wiresIdx = segments.indexOf("wires");
1413
+ const webhookHandlersIdx = segments.indexOf("webhook-handlers");
1414
+ const webhookEventsIdx = segments.indexOf("webhook-events");
1415
+ const variablesIdx = segments.indexOf("variables");
1416
+ const debugIdx = segments.indexOf("debug");
1417
+ const limit = Math.min(
1418
+ Math.max(
1419
+ Number.parseInt(url.searchParams.get("limit") ?? "20", 10) || 20,
1420
+ 1
1421
+ ),
1422
+ 100
1423
+ );
1424
+ const offset = Math.max(
1425
+ Number.parseInt(url.searchParams.get("offset") ?? "0", 10) || 0,
1426
+ 0
1427
+ );
1428
+ if (request.method === "GET") {
1429
+ if (debugIdx !== -1 && debugIdx === segments.length - 1) {
1430
+ const debugActive = process?.env?.["KHOTAN_DEBUG"];
1431
+ if (!debugActive) {
1432
+ return Response.json({ error: "Not found" }, { status: 404 });
1433
+ }
1434
+ return Response.json({ enabled: true });
1435
+ }
1436
+ if (debugIdx !== -1 && debugIdx === segments.length - 2) {
1437
+ const debugActive = process?.env?.["KHOTAN_DEBUG"];
1438
+ if (!debugActive) {
1439
+ return Response.json({ error: "Not found" }, { status: 404 });
1440
+ }
1441
+ const plugName = segments[debugIdx + 1];
1442
+ const plugReg = plugs.find((p) => p.name === plugName);
1443
+ if (!plugReg) {
1444
+ return Response.json({ error: "Plug not found" }, { status: 404 });
1445
+ }
1446
+ const fields = plugReg.vars ?? plugReg.plug.varFields ?? [];
1447
+ const hasConfigured = await hasVars(plugName).catch(() => false);
1448
+ const rawEndpoints = plugReg.plug.endpoints ?? plugReg.endpoints ?? null;
1449
+ let varValues = {};
1450
+ if (hasConfigured || Object.keys(getDefaultVars(plugName)).length > 0) {
1451
+ try {
1452
+ const raw = await getVars(plugName);
1453
+ varValues = Object.fromEntries(
1454
+ Object.entries(maskVars(plugName, raw)).filter(([key]) => {
1455
+ const field = fields.find((f) => f.key === key);
1456
+ return field && !field.hidden;
1457
+ })
1458
+ );
1459
+ } catch {
1460
+ }
1461
+ }
1462
+ return Response.json({
1463
+ name: plugReg.name,
1464
+ baseUrl: plugReg.plug.baseUrl,
1465
+ authType: plugReg.plug.authType,
1466
+ endpoints: serializeEndpoints(rawEndpoints),
1467
+ vars: {
1468
+ fields: fields.filter((f) => !f.hidden),
1469
+ configured: hasConfigured,
1470
+ values: varValues
1471
+ }
1472
+ });
1473
+ }
1474
+ if (variablesIdx !== -1 && variablesIdx === segments.length - 2) {
1475
+ const plugName = segments[variablesIdx + 1];
1476
+ if (!plugNames.has(plugName)) {
1477
+ return Response.json({ error: "Plug not found" }, { status: 404 });
1478
+ }
1479
+ const fields = getVarFields(plugName);
1480
+ const hasValues = await hasVars(plugName);
1481
+ let masked = {};
1482
+ if (hasValues || Object.keys(getDefaultVars(plugName)).length > 0) {
1483
+ try {
1484
+ const vars = await getVars(plugName);
1485
+ masked = maskVars(plugName, vars);
1486
+ } catch {
1487
+ masked = {};
1488
+ }
1489
+ }
1490
+ return Response.json({ fields, values: masked, configured: hasValues });
1491
+ }
1492
+ if (wiresIdx !== -1 && wiresIdx === segments.length - 2) {
1493
+ const plugName = segments[wiresIdx + 1];
1494
+ if (!plugNames.has(plugName)) {
1495
+ return Response.json({ error: "Plug not found" }, { status: 404 });
1496
+ }
1497
+ const plugReg = plugs.find((p) => p.name === plugName);
1498
+ if (!plugReg?.wires || plugReg.wires.length === 0) {
1499
+ return Response.json({ wire: null, configured: false });
1500
+ }
1501
+ const wireRecord = await wire(plugName).get();
1502
+ return Response.json({ wire: wireRecord, configured: true });
1503
+ }
1504
+ if (webhookHandlersIdx !== -1 && webhookHandlersIdx === segments.length - 2) {
1505
+ const plugName = segments[webhookHandlersIdx + 1];
1506
+ if (!plugNames.has(plugName)) {
1507
+ return Response.json({ error: "Plug not found" }, { status: 404 });
1508
+ }
1509
+ const allPlugs = await adapter.listPlugs();
1510
+ const dbPlug = allPlugs.find((p) => p["name"] === plugName);
1511
+ if (!dbPlug) {
1512
+ return Response.json([]);
1513
+ }
1514
+ const plugId = dbPlug["id"];
1515
+ const wireRecord = await adapter.getPlugWire(plugId);
1516
+ if (!wireRecord) {
1517
+ return Response.json([]);
1518
+ }
1519
+ const wireId = wireRecord["id"];
1520
+ const handlers = await adapter.listWebhookHandlers(wireId);
1521
+ const plugReg = plugs.find((p) => p.name === plugName);
1522
+ const configuredHandlerEvents = /* @__PURE__ */ new Map();
1523
+ for (const handler2 of plugReg ? getWebhookHandlersForPlug(plugReg) : []) {
1524
+ configuredHandlerEvents.set(
1525
+ `${handler2.type}:${handler2.name}`,
1526
+ handler2.events
1527
+ );
1528
+ }
1529
+ const handlersWithRuns = await Promise.all(
1530
+ handlers.map(async (handler2) => {
1531
+ const handlerId = handler2["id"];
1532
+ if (typeof handlerId !== "string") return handler2;
1533
+ const latestRun = await adapter.getLatestWebhookHandlerRun(handlerId);
1534
+ return {
1535
+ ...handler2,
1536
+ events: configuredHandlerEvents.get(
1537
+ `${String(handler2["type"])}:${String(handler2["name"])}`
1538
+ ) ?? null,
1539
+ lastRunStatus: latestRun?.["status"] ?? null,
1540
+ lastRunAt: latestRun?.["startedAt"] ?? null
1541
+ };
1542
+ })
1543
+ );
1544
+ return Response.json(handlersWithRuns);
1545
+ }
1546
+ if (plugsIdx !== -1 && plugsIdx === segments.length - 1) {
1547
+ const data = await adapter.listPlugs();
1548
+ const filtered = data.filter(
1549
+ (p) => typeof p["name"] === "string" && plugNames.has(p["name"])
1550
+ );
1551
+ const withVarState = await Promise.all(
1552
+ filtered.map(async (plug) => {
1553
+ const plugName = plug["name"];
1554
+ let varsConfigured = false;
1555
+ try {
1556
+ varsConfigured = await hasVars(plugName);
1557
+ } catch {
1558
+ varsConfigured = false;
1559
+ }
1560
+ return { ...plug, varsConfigured };
1561
+ })
1562
+ );
1563
+ return Response.json(withVarState);
1564
+ }
1565
+ if (plugsIdx !== -1 && plugsIdx === segments.length - 2) {
1566
+ const plugId = segments[plugsIdx + 1];
1567
+ const plug = await adapter.getPlug(plugId);
1568
+ if (!plug || typeof plug["name"] !== "string" || !plugNames.has(plug["name"])) {
1569
+ return Response.json({ error: "Plug not found" }, { status: 404 });
1570
+ }
1571
+ const flows = await adapter.getPlugFlows(plugId);
1572
+ return Response.json({ ...plug, flows });
1573
+ }
1574
+ if (flowsIdx !== -1 && flowsIdx === segments.length - 1) {
1575
+ const data = await adapter.listFlows();
1576
+ const filtered = data.filter((flow2) => isRegisteredFlowRecord(flow2));
1577
+ return Response.json(filtered);
1578
+ }
1579
+ if (flowsIdx !== -1 && flowsIdx === segments.length - 3 && segments[flowsIdx + 2] === "runs") {
1580
+ const flowId = segments[flowsIdx + 1];
1581
+ const data = await adapter.listRuns(flowId);
1582
+ return Response.json(data);
1583
+ }
1584
+ if (runsIdx !== -1 && runsIdx === segments.length - 1) {
1585
+ const page = await adapter.listRunsPage({ limit, offset });
1586
+ return Response.json({
1587
+ items: page.items,
1588
+ page: {
1589
+ limit,
1590
+ offset,
1591
+ hasMore: page.hasMore,
1592
+ prevOffset: Math.max(offset - limit, 0),
1593
+ nextOffset: offset + limit
1594
+ }
1595
+ });
1596
+ }
1597
+ if (runsIdx !== -1 && runsIdx === segments.length - 3 && segments[runsIdx + 2] === "stream") {
1598
+ const runId = segments[runsIdx + 1];
1599
+ const run = await adapter.getRun(runId);
1600
+ if (!run) {
1601
+ return Response.json({ error: "Run not found" }, { status: 404 });
1602
+ }
1603
+ const workflowRunId = getRunWorkflowId(run);
1604
+ if (!workflowRunId) {
1605
+ return Response.json(
1606
+ { error: "Run does not have a Workflow run ID" },
1607
+ { status: 400 }
1608
+ );
1609
+ }
1610
+ const startIndexParam = url.searchParams.get("startIndex");
1611
+ const parsedStartIndex = startIndexParam == null ? null : Number.parseInt(startIndexParam, 10);
1612
+ const namespace = url.searchParams.get("namespace") ?? void 0;
1613
+ const getRun = await importWorkflowGetRun();
1614
+ const workflowRun = getRun(workflowRunId);
1615
+ const streamOptions = {};
1616
+ if (typeof parsedStartIndex === "number" && Number.isFinite(parsedStartIndex)) {
1617
+ streamOptions.startIndex = parsedStartIndex;
1618
+ }
1619
+ if (namespace) streamOptions.namespace = namespace;
1620
+ const stream = workflowRun.getReadable?.(streamOptions);
1621
+ if (!stream) {
1622
+ return Response.json(
1623
+ { error: "Workflow run does not expose a readable stream" },
1624
+ { status: 400 }
1625
+ );
1626
+ }
1627
+ return new Response(stream, {
1628
+ headers: {
1629
+ "Content-Type": "application/x-ndjson; charset=utf-8",
1630
+ "Cache-Control": "no-cache, no-transform"
1631
+ }
1632
+ });
1633
+ }
1634
+ if (runsIdx !== -1 && runsIdx === segments.length - 2) {
1635
+ const runId = segments[runsIdx + 1];
1636
+ const run = await getRunWithWorkflowStatus(runId);
1637
+ if (!run) {
1638
+ return Response.json({ error: "Run not found" }, { status: 404 });
1639
+ }
1640
+ return Response.json(run);
1641
+ }
1642
+ if (webhookEventsIdx !== -1 && webhookEventsIdx === segments.length - 1) {
1643
+ const page = await adapter.listWebhookEventsPage({ limit, offset });
1644
+ return Response.json({
1645
+ items: page.items,
1646
+ page: {
1647
+ limit,
1648
+ offset,
1649
+ hasMore: page.hasMore,
1650
+ prevOffset: Math.max(offset - limit, 0),
1651
+ nextOffset: offset + limit
1652
+ }
1653
+ });
1654
+ }
1655
+ if (resourcesIdx !== -1 && resourcesIdx === segments.length - 1) {
1656
+ const data = await adapter.listResources();
1657
+ const filtered = data.filter(
1658
+ (r) => typeof r["name"] === "string" && resourceNames.has(r["name"])
1659
+ );
1660
+ return Response.json(filtered);
1661
+ }
1662
+ if (resourcesIdx !== -1 && resourcesIdx === segments.length - 3 && segments[resourcesIdx + 2] === "mappings") {
1663
+ const resourceId = segments[resourcesIdx + 1];
1664
+ const data = await adapter.listMappings(resourceId);
1665
+ return Response.json(data);
1666
+ }
1667
+ if (resourcesIdx !== -1 && resourcesIdx === segments.length - 2) {
1668
+ const resourceId = segments[resourcesIdx + 1];
1669
+ const resource = await adapter.getResource(resourceId);
1670
+ if (!resource || typeof resource["name"] !== "string" || !resourceNames.has(resource["name"])) {
1671
+ return Response.json(
1672
+ { error: "Resource not found" },
1673
+ { status: 404 }
1674
+ );
1675
+ }
1676
+ const flows = await adapter.getResourceFlows(resourceId);
1677
+ return Response.json({ ...resource, flows });
1678
+ }
1679
+ if (mappingsIdx !== -1 && mappingsIdx === segments.length - 2) {
1680
+ const mappingId = segments[mappingsIdx + 1];
1681
+ const mapping = await adapter.getMapping(mappingId);
1682
+ if (!mapping) {
1683
+ return Response.json({ error: "Mapping not found" }, { status: 404 });
1684
+ }
1685
+ return Response.json(mapping);
1686
+ }
1687
+ }
1688
+ if (request.method === "POST") {
1689
+ const webhookIdx = segments.indexOf("webhook");
1690
+ if (webhookIdx !== -1 && webhookIdx === segments.length - 2) {
1691
+ const plugName = segments[webhookIdx + 1];
1692
+ const plugReg = plugs.find((p) => p.name === plugName);
1693
+ if (!plugReg) {
1694
+ return Response.json(
1695
+ { error: `Unknown plug: ${plugName}` },
1696
+ { status: 404 }
1697
+ );
1698
+ }
1699
+ const wireConfig = plugReg.wires?.[0];
1700
+ if (!wireConfig?.onVerify) {
1701
+ return Response.json(
1702
+ { error: `No active wire for plug: ${plugName}` },
1703
+ { status: 404 }
1704
+ );
1705
+ }
1706
+ const rawBody = await request.text();
1707
+ const allPlugs = await adapter.listPlugs();
1708
+ const dbPlug = allPlugs.find((p) => p["name"] === plugName);
1709
+ if (!dbPlug) {
1710
+ return Response.json(
1711
+ { error: `Plug "${plugName}" not found in database` },
1712
+ { status: 404 }
1713
+ );
1714
+ }
1715
+ const plugId = dbPlug["id"];
1716
+ const wireRecord = await adapter.getPlugWire(plugId);
1717
+ const wireId = wireRecord ? wireRecord["id"] : null;
1718
+ let wireVars = {};
1719
+ if (wireId) {
1720
+ const raw = await adapter.getWireMetadata(wireId);
1721
+ if (raw) {
1722
+ if (secret) {
1723
+ try {
1724
+ const decrypted = await decryptVars(raw, secret);
1725
+ wireVars = JSON.parse(decrypted);
1726
+ } catch {
1727
+ try {
1728
+ wireVars = JSON.parse(raw);
1729
+ } catch {
1730
+ }
1731
+ }
1732
+ } else {
1733
+ try {
1734
+ wireVars = JSON.parse(raw);
1735
+ } catch {
1736
+ }
1737
+ }
1738
+ }
1739
+ }
1740
+ const headers = {};
1741
+ request.headers.forEach((value, key) => {
1742
+ headers[key] = value;
1743
+ });
1744
+ const verified = await wireConfig.onVerify({
1745
+ headers,
1746
+ body: rawBody,
1747
+ wireVars
1748
+ });
1749
+ if (!verified) {
1750
+ return Response.json(
1751
+ { error: "Webhook verification failed" },
1752
+ { status: 401 }
1753
+ );
1754
+ }
1755
+ let event;
1756
+ try {
1757
+ event = JSON.parse(rawBody);
1758
+ } catch {
1759
+ event = {};
1760
+ }
1761
+ const eventType = typeof event["type"] === "string" ? event["type"] : "unknown";
1762
+ const webhookHandlers = getWebhookHandlersForPlug(plugReg);
1763
+ const catches = webhookHandlers.filter(
1764
+ (handler2) => handler2.type === "catch"
1765
+ );
1766
+ const passes = webhookHandlers.filter(
1767
+ (handler2) => handler2.type === "pass"
1768
+ );
1769
+ void Promise.resolve().then(async () => {
1770
+ try {
1771
+ const startWorkflow = await importWorkflowStart();
1772
+ const dbHandlers = wireId ? await adapter.listWebhookHandlers(wireId) : [];
1773
+ for (const c of catches) {
1774
+ if (Array.isArray(c.events) && c.events.length > 0 && !c.events.includes(eventType)) {
1775
+ continue;
1776
+ }
1777
+ const handlerRow = dbHandlers.find(
1778
+ (h) => h["name"] === c.name && h["type"] === "catch"
1779
+ );
1780
+ if (handlerRow?.["enabled"] === false) {
1781
+ continue;
1782
+ }
1783
+ const handlerId = handlerRow ? handlerRow["id"] : null;
1784
+ const { id: khotanRunId } = await adapter.insertRun({
1785
+ webhookHandlerId: handlerId,
1786
+ wireId,
1787
+ workflowRunId: null,
1788
+ runType: "webhook",
1789
+ status: "running"
1790
+ });
1791
+ if (handlerId && wireId) {
1792
+ await adapter.insertWebhookEvent({
1793
+ wireId,
1794
+ webhookHandlerId: handlerId,
1795
+ khotanRunId,
1796
+ eventType,
1797
+ payload: event,
1798
+ headers
1799
+ });
1800
+ }
1801
+ try {
1802
+ const result = await startWorkflow(c.workflow, [
1803
+ { event, eventType, headers, khotanRunId }
1804
+ ]);
1805
+ const workflowRunId = result && typeof result === "object" ? "runId" in result ? String(result.runId) : "id" in result ? String(result.id) : null : null;
1806
+ if (workflowRunId) {
1807
+ await adapter.updateRun(khotanRunId, {
1808
+ status: "running",
1809
+ workflowRunId
1810
+ });
1811
+ }
1812
+ } catch (err) {
1813
+ const message = err instanceof Error ? err.message : "Unknown error";
1814
+ await adapter.updateRun(khotanRunId, {
1815
+ status: "failed",
1816
+ completedAt: /* @__PURE__ */ new Date(),
1817
+ failed: 1,
1818
+ error: message
1819
+ });
1820
+ throw err;
1821
+ }
1822
+ }
1823
+ for (const p of passes) {
1824
+ if (Array.isArray(p.events) && p.events.length > 0 && !p.events.includes(eventType)) {
1825
+ continue;
1826
+ }
1827
+ const handlerRow = dbHandlers.find(
1828
+ (h) => h["name"] === p.name && h["type"] === "pass"
1829
+ );
1830
+ if (handlerRow?.["enabled"] === false) {
1831
+ continue;
1832
+ }
1833
+ let destVars = {};
1834
+ const destPlug = allPlugs.find((dp) => dp["name"] === p.to);
1835
+ if (destPlug) {
1836
+ const destPlugId = destPlug["id"];
1837
+ const encrypted = await adapter.getEncryptedVariables(destPlugId);
1838
+ if (encrypted && secret) {
1839
+ try {
1840
+ const json = await decryptVars(encrypted, secret);
1841
+ destVars = JSON.parse(json);
1842
+ } catch {
1843
+ }
1844
+ }
1845
+ }
1846
+ const handlerId = handlerRow ? handlerRow["id"] : null;
1847
+ const { id: khotanRunId } = await adapter.insertRun({
1848
+ webhookHandlerId: handlerId,
1849
+ wireId,
1850
+ workflowRunId: null,
1851
+ runType: "webhook",
1852
+ status: "running"
1853
+ });
1854
+ if (handlerId && wireId) {
1855
+ await adapter.insertWebhookEvent({
1856
+ wireId,
1857
+ webhookHandlerId: handlerId,
1858
+ khotanRunId,
1859
+ eventType,
1860
+ payload: event,
1861
+ headers
1862
+ });
1863
+ }
1864
+ try {
1865
+ const result = await startWorkflow(p.workflow, [
1866
+ { event, eventType, headers, destVars, khotanRunId }
1867
+ ]);
1868
+ const workflowRunId = result && typeof result === "object" ? "runId" in result ? String(result.runId) : "id" in result ? String(result.id) : null : null;
1869
+ if (workflowRunId) {
1870
+ await adapter.updateRun(khotanRunId, {
1871
+ status: "running",
1872
+ workflowRunId
1873
+ });
1874
+ }
1875
+ } catch (err) {
1876
+ const message = err instanceof Error ? err.message : "Unknown error";
1877
+ await adapter.updateRun(khotanRunId, {
1878
+ status: "failed",
1879
+ completedAt: /* @__PURE__ */ new Date(),
1880
+ failed: 1,
1881
+ error: message
1882
+ });
1883
+ throw err;
1884
+ }
1885
+ }
1886
+ } catch (err) {
1887
+ kd("webhook", `${plugName}: workflow start failed:`, err);
1888
+ }
1889
+ });
1890
+ return Response.json({ received: true }, { status: 202 });
1891
+ }
1892
+ if (debugIdx !== -1 && debugIdx === segments.length - 2) {
1893
+ const debugActive = process?.env?.["KHOTAN_DEBUG"];
1894
+ if (!debugActive) {
1895
+ return Response.json({ error: "Not found" }, { status: 404 });
1896
+ }
1897
+ const plugName = segments[debugIdx + 1];
1898
+ const plugReg = plugs.find((p) => p.name === plugName);
1899
+ if (!plugReg) {
1900
+ return Response.json({ error: "Plug not found" }, { status: 404 });
1901
+ }
1902
+ const body = await request.json();
1903
+ const method = (body.method ?? "GET").toUpperCase();
1904
+ const reqPath = body.path ?? "/";
1905
+ const start = Date.now();
1906
+ try {
1907
+ const plug = plugReg.plug;
1908
+ const vars = secret ? await getVars(plugName).catch(() => ({})) : {};
1909
+ const _setVars = secret ? (updates) => setVars(plugName, { ...vars, ...updates }) : void 0;
1910
+ const opts = { vars };
1911
+ if (_setVars) opts._setVars = _setVars;
1912
+ if (body.params) opts.params = body.params;
1913
+ if (body.headers) opts.headers = body.headers;
1914
+ if (body.body) opts.body = body.body;
1915
+ let result;
1916
+ switch (method) {
1917
+ case "GET":
1918
+ result = await plug.get(reqPath, opts);
1919
+ break;
1920
+ case "POST":
1921
+ result = await plug.post(reqPath, opts);
1922
+ break;
1923
+ case "PUT":
1924
+ result = await plug.put(reqPath, opts);
1925
+ break;
1926
+ case "PATCH":
1927
+ result = await plug.patch(reqPath, opts);
1928
+ break;
1929
+ case "DELETE":
1930
+ result = await plug.delete(reqPath, opts);
1931
+ break;
1932
+ default:
1933
+ result = await plug.get(reqPath, opts);
1934
+ }
1935
+ const timing = Date.now() - start;
1936
+ const response = {
1937
+ status: 200,
1938
+ statusText: "OK",
1939
+ headers: {},
1940
+ body: result,
1941
+ timing
1942
+ };
1943
+ const allEndpoints = plugReg.plug.endpoints ?? plugReg.endpoints;
1944
+ if (allEndpoints) {
1945
+ const matched = Object.entries(allEndpoints).find(
1946
+ ([, ep]) => ep.method.toUpperCase() === method && ep.path === reqPath
1947
+ );
1948
+ if (matched) {
1949
+ response["endpoint"] = {
1950
+ name: matched[0],
1951
+ method: matched[1].method,
1952
+ path: matched[1].path
1953
+ };
1954
+ }
1955
+ }
1956
+ return Response.json(response);
1957
+ } catch (err) {
1958
+ const timing = Date.now() - start;
1959
+ const error = err instanceof Error ? err.message : "Unknown error";
1960
+ const errBody = err && typeof err === "object" && "body" in err ? err.body : null;
1961
+ const errStatus = err && typeof err === "object" && "status" in err ? err.status : 500;
1962
+ return Response.json({
1963
+ status: errStatus,
1964
+ statusText: "Error",
1965
+ headers: {},
1966
+ body: errBody,
1967
+ timing,
1968
+ error
1969
+ });
1970
+ }
1971
+ }
1972
+ if (variablesIdx !== -1 && variablesIdx === segments.length - 2) {
1973
+ const plugName = segments[variablesIdx + 1];
1974
+ if (!plugNames.has(plugName)) {
1975
+ return Response.json({ error: "Plug not found" }, { status: 404 });
1976
+ }
1977
+ const body = await request.json();
1978
+ const fields = getVarFields(plugName);
1979
+ const merged = {
1980
+ ...await getVars(plugName).catch(() => ({}))
1981
+ };
1982
+ for (const field of fields) {
1983
+ const value = body[field.key];
1984
+ if (value !== void 0) {
1985
+ merged[field.key] = value;
1986
+ }
1987
+ }
1988
+ const missing = fields.filter((f) => f.required !== false && !merged[f.key]).map((f) => f.key);
1989
+ if (missing.length > 0) {
1990
+ return Response.json(
1991
+ { error: `Missing required fields: ${missing.join(", ")}` },
1992
+ { status: 400 }
1993
+ );
1994
+ }
1995
+ const vars = {};
1996
+ for (const field of fields) {
1997
+ const value = merged[field.key];
1998
+ if (value !== void 0) {
1999
+ vars[field.key] = value;
2000
+ }
2001
+ }
2002
+ try {
2003
+ await setVars(plugName, vars);
2004
+ return Response.json({ ok: true });
2005
+ } catch (error) {
2006
+ const message = error instanceof Error ? error.message : "Unknown error";
2007
+ return Response.json({ error: message }, { status: 500 });
2008
+ }
2009
+ }
2010
+ if (runsIdx !== -1 && runsIdx === segments.length - 3 && segments[runsIdx + 2] === "cancel") {
2011
+ const runId = segments[runsIdx + 1];
2012
+ const run = await adapter.getRun(runId);
2013
+ if (!run) {
2014
+ return Response.json({ error: "Run not found" }, { status: 404 });
2015
+ }
2016
+ const workflowRunId = getRunWorkflowId(run);
2017
+ if (!workflowRunId) {
2018
+ return Response.json(
2019
+ { error: "Run does not have a Workflow run ID" },
2020
+ { status: 400 }
2021
+ );
2022
+ }
2023
+ const getRun = await importWorkflowGetRun();
2024
+ const workflowRun = getRun(workflowRunId);
2025
+ await workflowRun.cancel?.();
2026
+ const completedAt = /* @__PURE__ */ new Date();
2027
+ await adapter.updateRun(runId, {
2028
+ status: "cancelled",
2029
+ completedAt,
2030
+ error: "Cancelled"
2031
+ });
2032
+ const flowId = typeof run["flowId"] === "string" ? run["flowId"] : null;
2033
+ if (flowId) {
2034
+ await adapter.updateFlowLastRun(flowId, {
2035
+ lastRunAt: completedAt,
2036
+ lastRunStatus: "cancelled"
2037
+ });
2038
+ }
2039
+ return Response.json({
2040
+ ok: true,
2041
+ id: runId,
2042
+ workflowRunId,
2043
+ status: "cancelled",
2044
+ error: "Cancelled"
2045
+ });
2046
+ }
2047
+ if (runsIdx !== -1 && runsIdx === segments.length - 3 && segments[runsIdx + 2] === "retry") {
2048
+ const runId = segments[runsIdx + 1];
2049
+ const run = await adapter.getRun(runId);
2050
+ if (!run) {
2051
+ return Response.json({ error: "Run not found" }, { status: 404 });
2052
+ }
2053
+ const flowId = typeof run["flowId"] === "string" ? run["flowId"] : null;
2054
+ if (!flowId) {
2055
+ return Response.json(
2056
+ { error: "Only flow runs can be retried from the Hub" },
2057
+ { status: 400 }
2058
+ );
2059
+ }
2060
+ const runType = typeof run["runType"] === "string" ? run["runType"] : "full";
2061
+ return triggerFlowRun(flowId, { runType });
2062
+ }
2063
+ if (flowsIdx !== -1 && flowsIdx === segments.length - 3 && segments[flowsIdx + 2] === "runs") {
2064
+ const flowId = segments[flowsIdx + 1];
2065
+ const body = await request.json().catch(() => ({}));
2066
+ return triggerFlowRun(flowId, body);
2067
+ }
2068
+ if (wiresIdx !== -1 && wiresIdx === segments.length - 2) {
2069
+ const plugName = segments[wiresIdx + 1];
2070
+ if (!plugNames.has(plugName)) {
2071
+ return Response.json({ error: "Plug not found" }, { status: 404 });
2072
+ }
2073
+ const body = await request.json();
2074
+ if (!body.callbackUrl) {
2075
+ return Response.json(
2076
+ { error: "callbackUrl is required" },
2077
+ { status: 400 }
2078
+ );
2079
+ }
2080
+ try {
2081
+ const record = await wire(plugName).create(body.callbackUrl);
2082
+ return Response.json({ wire: record }, { status: 201 });
2083
+ } catch (error) {
2084
+ const message = error instanceof Error ? error.message : "Unknown error";
2085
+ kd("wire", `${plugName}: create failed:`, message);
2086
+ if (error && typeof error === "object" && "body" in error) {
2087
+ kd("wire", `${plugName}: response body:`, error.body);
2088
+ }
2089
+ return Response.json({ error: message }, { status: 500 });
2090
+ }
2091
+ }
2092
+ if (mappingsIdx !== -1 && mappingsIdx === segments.length - 2 && segments[mappingsIdx + 1] === "lookup") {
2093
+ const body = await request.json();
2094
+ const mapping = await adapter.lookupMapping(body);
2095
+ if (!mapping) {
2096
+ return Response.json({ error: "Mapping not found" }, { status: 404 });
2097
+ }
2098
+ return Response.json(mapping);
2099
+ }
2100
+ if (mappingsIdx !== -1 && mappingsIdx === segments.length - 1) {
2101
+ const body = await request.json();
2102
+ const result = await adapter.upsertMapping(body);
2103
+ return Response.json(
2104
+ { id: result.id },
2105
+ { status: result.created ? 201 : 200 }
2106
+ );
2107
+ }
2108
+ }
2109
+ if (request.method === "PATCH") {
2110
+ if (plugsIdx !== -1 && plugsIdx === segments.length - 2) {
2111
+ const plugId = segments[plugsIdx + 1];
2112
+ const plug = await adapter.getPlug(plugId);
2113
+ if (!plug || typeof plug["name"] !== "string" || !plugNames.has(plug["name"])) {
2114
+ return Response.json({ error: "Plug not found" }, { status: 404 });
2115
+ }
2116
+ const body = await request.json();
2117
+ if (typeof body.enabled === "boolean") {
2118
+ await adapter.togglePlugEnabled(plugId, body.enabled);
2119
+ }
2120
+ const updated = await adapter.getPlug(plugId);
2121
+ return Response.json(updated);
2122
+ }
2123
+ if (flowsIdx !== -1 && flowsIdx === segments.length - 2) {
2124
+ const flowId = segments[flowsIdx + 1];
2125
+ const body = await request.json();
2126
+ if (typeof body.enabled === "boolean") {
2127
+ await adapter.toggleFlowEnabled(flowId, body.enabled);
2128
+ }
2129
+ return Response.json({ id: flowId, ...body });
2130
+ }
2131
+ if (webhookHandlersIdx !== -1 && webhookHandlersIdx === segments.length - 2) {
2132
+ const handlerId = segments[webhookHandlersIdx + 1];
2133
+ const body = await request.json();
2134
+ if (typeof body.enabled === "boolean") {
2135
+ await adapter.toggleWebhookHandlerEnabled(handlerId, body.enabled);
2136
+ }
2137
+ return Response.json({ id: handlerId, ...body });
2138
+ }
2139
+ }
2140
+ if (request.method === "PUT") {
2141
+ if (mappingsIdx !== -1 && mappingsIdx === segments.length - 2) {
2142
+ const mappingId = segments[mappingsIdx + 1];
2143
+ const body = await request.json();
2144
+ const result = await adapter.upsertMapping({ ...body, id: mappingId });
2145
+ return Response.json(result);
2146
+ }
2147
+ }
2148
+ if (request.method === "DELETE") {
2149
+ if (variablesIdx !== -1 && variablesIdx === segments.length - 2) {
2150
+ const plugName = segments[variablesIdx + 1];
2151
+ if (!plugNames.has(plugName)) {
2152
+ return Response.json({ error: "Plug not found" }, { status: 404 });
2153
+ }
2154
+ await clearVars(plugName);
2155
+ return new Response(null, { status: 204 });
2156
+ }
2157
+ if (wiresIdx !== -1 && wiresIdx === segments.length - 2) {
2158
+ const plugName = segments[wiresIdx + 1];
2159
+ if (!plugNames.has(plugName)) {
2160
+ return Response.json({ error: "Plug not found" }, { status: 404 });
2161
+ }
2162
+ const body = await request.json();
2163
+ if (!body.wireId) {
2164
+ return Response.json(
2165
+ { error: "wireId is required" },
2166
+ { status: 400 }
2167
+ );
2168
+ }
2169
+ try {
2170
+ await wire(plugName).delete(body.wireId);
2171
+ return new Response(null, { status: 204 });
2172
+ } catch (error) {
2173
+ const message = error instanceof Error ? error.message : "Unknown error";
2174
+ kd("wire", `${plugName}: delete failed: ${message}`);
2175
+ return Response.json({ error: message }, { status: 500 });
2176
+ }
2177
+ }
2178
+ if (mappingsIdx !== -1 && mappingsIdx === segments.length - 2) {
2179
+ const mappingId = segments[mappingsIdx + 1];
2180
+ await adapter.deleteMapping(mappingId);
2181
+ return new Response(null, { status: 204 });
2182
+ }
2183
+ }
2184
+ return Response.json({ error: "Not found" }, { status: 404 });
2185
+ }
2186
+ const secret = config.secret ?? process.env["KHOTAN_SECRET"] ?? "";
2187
+ async function resolvePlugId(plugName) {
2188
+ await init();
2189
+ const allPlugs = await adapter.listPlugs();
2190
+ const dbPlug = allPlugs.find((p) => p["name"] === plugName);
2191
+ if (!dbPlug) {
2192
+ throw new Error(`Plug "${plugName}" not found in database`);
2193
+ }
2194
+ return dbPlug["id"];
2195
+ }
2196
+ function getDefaultVars(plugName) {
2197
+ const defaults = {};
2198
+ for (const field of getVarFields(plugName)) {
2199
+ if (field.defaultValue !== void 0) {
2200
+ defaults[field.key] = field.defaultValue;
2201
+ }
2202
+ }
2203
+ return defaults;
2204
+ }
2205
+ async function getStoredVarsByPlugId(plugId) {
2206
+ if (!secret) {
2207
+ throw new Error("KHOTAN_SECRET is required for var operations");
2208
+ }
2209
+ const encrypted = await adapter.getEncryptedVariables(plugId);
2210
+ if (!encrypted) return {};
2211
+ const json = await decryptVars(encrypted, secret);
2212
+ return JSON.parse(json);
2213
+ }
2214
+ async function setVarsByPlugId(plugId, vars) {
2215
+ if (!secret) {
2216
+ throw new Error("KHOTAN_SECRET is required for var operations");
2217
+ }
2218
+ const json = JSON.stringify(vars);
2219
+ const encrypted = await encryptVars(json, secret);
2220
+ await adapter.setEncryptedVariables(plugId, encrypted);
2221
+ }
2222
+ async function seedDefaultVarsForPlug(plugId, plugName) {
2223
+ const defaults = getDefaultVars(plugName);
2224
+ if (!secret || Object.keys(defaults).length === 0) {
2225
+ return;
2226
+ }
2227
+ const storedVars = await getStoredVarsByPlugId(
2228
+ plugId
2229
+ ).catch(() => ({}));
2230
+ const seededVars = { ...defaults, ...storedVars };
2231
+ const hasChanges = Object.keys(seededVars).some(
2232
+ (key) => seededVars[key] !== storedVars[key]
2233
+ );
2234
+ if (hasChanges) {
2235
+ await setVarsByPlugId(plugId, seededVars);
2236
+ }
2237
+ }
2238
+ async function getVars(plugName) {
2239
+ const plugId = await resolvePlugId(plugName);
2240
+ const defaults = getDefaultVars(plugName);
2241
+ const stored = await getStoredVarsByPlugId(plugId);
2242
+ return { ...defaults, ...stored };
2243
+ }
2244
+ async function setVars(plugName, vars) {
2245
+ const plugId = await resolvePlugId(plugName);
2246
+ await setVarsByPlugId(plugId, vars);
2247
+ }
2248
+ async function clearVars(plugName) {
2249
+ const plugId = await resolvePlugId(plugName);
2250
+ await adapter.clearEncryptedVariables(plugId);
2251
+ }
2252
+ async function hasVars(plugName) {
2253
+ const plugId = await resolvePlugId(plugName);
2254
+ const encrypted = await adapter.getEncryptedVariables(plugId);
2255
+ return encrypted !== null && encrypted !== "";
2256
+ }
2257
+ function getVarFields(plugName) {
2258
+ const plugReg = plugs.find((p) => p.name === plugName);
2259
+ if (!plugReg) {
2260
+ throw new Error(`Plug "${plugName}" not registered`);
2261
+ }
2262
+ return plugReg.vars ?? plugReg.plug.varFields ?? [];
2263
+ }
2264
+ function maskVars(plugName, vars) {
2265
+ const fields = getVarFields(plugName);
2266
+ return Object.fromEntries(
2267
+ Object.entries(vars).map(([key, value]) => {
2268
+ const field = fields.find((f) => f.key === key);
2269
+ if (field?.secret) {
2270
+ return [key, value ? "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" : ""];
2271
+ }
2272
+ return [key, value];
2273
+ })
2274
+ );
2275
+ }
2276
+ function getPlug(plugName) {
2277
+ const plugReg = plugs.find((p) => p.name === plugName);
2278
+ if (!plugReg) {
2279
+ throw new Error(`Plug "${plugName}" not registered`);
2280
+ }
2281
+ return plugReg.plug;
2282
+ }
2283
+ return {
2284
+ handler,
2285
+ init,
2286
+ flow,
2287
+ wire,
2288
+ getVars,
2289
+ setVars,
2290
+ clearVars,
2291
+ hasVars,
2292
+ getVarFields,
2293
+ getPlug
2294
+ };
2295
+ }
2296
+ function toNextJsHandler(factoryHandler) {
2297
+ function handle(req) {
2298
+ return factoryHandler(req);
2299
+ }
2300
+ return {
2301
+ GET: handle,
2302
+ POST: handle,
2303
+ PUT: handle,
2304
+ PATCH: handle,
2305
+ DELETE: handle
2306
+ };
2307
+ }
2308
+
2309
+ export { __setWorkflowGetRunForTests, __setWorkflowGetWritableForTests, __setWorkflowStartForTests, drizzleAdapter, khotan, sendUpdate, toNextJsHandler };
2310
+ //# sourceMappingURL=factory.js.map
2311
+ //# sourceMappingURL=factory.js.map