khotan-data 0.0.1 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +54 -0
- package/README.md +117 -1
- package/dist/cli.js +2869 -0
- package/dist/factory.cjs +3303 -0
- package/dist/factory.cjs.map +1 -0
- package/dist/factory.d.cts +662 -0
- package/dist/factory.d.ts +662 -0
- package/dist/factory.js +3292 -0
- package/dist/factory.js.map +1 -0
- package/dist/plug-client.cjs +99 -0
- package/dist/plug-client.cjs.map +1 -0
- package/dist/plug-client.d.cts +71 -0
- package/dist/plug-client.d.ts +71 -0
- package/dist/plug-client.js +96 -0
- package/dist/plug-client.js.map +1 -0
- package/dist/templates/agent-skill.md +73 -0
- package/dist/templates/agents.md +41 -0
- package/dist/templates/cache.example.ts +11 -0
- package/dist/templates/cache.ts +58 -0
- package/dist/templates/catch.example.ts +36 -0
- package/dist/templates/catch.ts +119 -0
- package/dist/templates/config-page.tsx +20 -0
- package/dist/templates/debug-index-page.tsx +101 -0
- package/dist/templates/debug-page.tsx +48 -0
- package/dist/templates/graph-page.tsx +11 -0
- package/dist/templates/hub.tsx +450 -0
- package/dist/templates/inflow.example.ts +61 -0
- package/dist/templates/inflow.ts +98 -0
- package/dist/templates/khotan-config.ts +49 -0
- package/dist/templates/khotan-route.ts +13 -0
- package/dist/templates/logs-page.tsx +9 -0
- package/dist/templates/logs.tsx +20 -0
- package/dist/templates/mapping-browser.tsx +761 -0
- package/dist/templates/mappings-page.tsx +9 -0
- package/dist/templates/outflow.example.ts +52 -0
- package/dist/templates/outflow.ts +90 -0
- package/dist/templates/pass.example.ts +51 -0
- package/dist/templates/pass.ts +134 -0
- package/dist/templates/plug-debugger.tsx +1185 -0
- package/dist/templates/plug.example.ts +93 -0
- package/dist/templates/plug.ts +806 -0
- package/dist/templates/relay.example.ts +71 -0
- package/dist/templates/relay.ts +104 -0
- package/dist/templates/runs-table.tsx +592 -0
- package/dist/templates/schema.ts +505 -0
- package/dist/templates/skill-dashboard.md +144 -0
- package/dist/templates/skill-plug.md +216 -0
- package/dist/templates/skill-setup.md +161 -0
- package/dist/templates/skill-webhook.md +196 -0
- package/dist/templates/topology-canvas.tsx +1406 -0
- package/dist/templates/var-panel.tsx +276 -0
- package/dist/templates/webhook-events-table.tsx +241 -0
- package/dist/templates/wire-panel.tsx +216 -0
- package/dist/templates/wire.ts +155 -0
- package/package.json +46 -5
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Schema — Drizzle table definitions for khotan plugs, flows, and runs
|
|
3
|
+
// Generated by khotan CLI · https://github.com/khotan-data
|
|
4
|
+
//
|
|
5
|
+
// This file is yours. Edit anything — column types, defaults, indexes,
|
|
6
|
+
// relations. It has zero runtime dependencies on khotan-data.
|
|
7
|
+
//
|
|
8
|
+
// Re-export from your Drizzle schema barrel file:
|
|
9
|
+
// export * from "@/lib/khotan/schema";
|
|
10
|
+
// ============================================================================
|
|
11
|
+
|
|
12
|
+
import { relations } from "drizzle-orm";
|
|
13
|
+
import {
|
|
14
|
+
boolean,
|
|
15
|
+
index,
|
|
16
|
+
integer,
|
|
17
|
+
jsonb,
|
|
18
|
+
pgTable,
|
|
19
|
+
text,
|
|
20
|
+
timestamp,
|
|
21
|
+
unique,
|
|
22
|
+
} from "drizzle-orm/pg-core";
|
|
23
|
+
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// khotan_plugs — one row per configured external service connection
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
export const khotanPlugs = pgTable("khotan_plugs", {
|
|
29
|
+
id: text("id")
|
|
30
|
+
.primaryKey()
|
|
31
|
+
.$defaultFn(() => crypto.randomUUID()),
|
|
32
|
+
name: text("name").notNull().unique(),
|
|
33
|
+
baseUrl: text("base_url").notNull(),
|
|
34
|
+
authType: text("auth_type", {
|
|
35
|
+
enum: ["bearer", "basic", "apiKey", "custom"],
|
|
36
|
+
}).notNull(),
|
|
37
|
+
enabled: boolean("enabled").default(true).notNull(),
|
|
38
|
+
status: text("status", {
|
|
39
|
+
enum: ["connected", "error", "idle"],
|
|
40
|
+
})
|
|
41
|
+
.default("idle")
|
|
42
|
+
.notNull(),
|
|
43
|
+
statusMessage: text("status_message"),
|
|
44
|
+
encryptedVars: text("encrypted_vars"),
|
|
45
|
+
createdAt: timestamp("created_at", { withTimezone: true })
|
|
46
|
+
.defaultNow()
|
|
47
|
+
.notNull(),
|
|
48
|
+
updatedAt: timestamp("updated_at", { withTimezone: true })
|
|
49
|
+
.defaultNow()
|
|
50
|
+
.notNull(),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// khotan_resources — one row per logical entity type (e.g. products, orders)
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
export const khotanResources = pgTable("khotan_resources", {
|
|
58
|
+
id: text("id")
|
|
59
|
+
.primaryKey()
|
|
60
|
+
.$defaultFn(() => crypto.randomUUID()),
|
|
61
|
+
name: text("name").notNull().unique(),
|
|
62
|
+
connectField: text("connect_field").notNull(),
|
|
63
|
+
description: text("description"),
|
|
64
|
+
createdAt: timestamp("created_at", { withTimezone: true })
|
|
65
|
+
.defaultNow()
|
|
66
|
+
.notNull(),
|
|
67
|
+
updatedAt: timestamp("updated_at", { withTimezone: true })
|
|
68
|
+
.defaultNow()
|
|
69
|
+
.notNull(),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// khotan_flows — one row per data flow tied to a plug
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
export const khotanFlows = pgTable(
|
|
77
|
+
"khotan_flows",
|
|
78
|
+
{
|
|
79
|
+
id: text("id")
|
|
80
|
+
.primaryKey()
|
|
81
|
+
.$defaultFn(() => crypto.randomUUID()),
|
|
82
|
+
plugId: text("plug_id")
|
|
83
|
+
.notNull()
|
|
84
|
+
.references(() => khotanPlugs.id),
|
|
85
|
+
name: text("name").notNull(),
|
|
86
|
+
type: text("type", {
|
|
87
|
+
enum: ["inflow", "outflow", "relay", "webhook"],
|
|
88
|
+
}).notNull(),
|
|
89
|
+
enabled: boolean("enabled").default(true).notNull(),
|
|
90
|
+
schedule: text("schedule"),
|
|
91
|
+
resourceId: text("resource_id").references(() => khotanResources.id),
|
|
92
|
+
lastRunAt: timestamp("last_run_at", { withTimezone: true }),
|
|
93
|
+
lastRunStatus: text("last_run_status", {
|
|
94
|
+
enum: ["completed", "partial", "failed", "cancelled"],
|
|
95
|
+
}),
|
|
96
|
+
createdAt: timestamp("created_at", { withTimezone: true })
|
|
97
|
+
.defaultNow()
|
|
98
|
+
.notNull(),
|
|
99
|
+
updatedAt: timestamp("updated_at", { withTimezone: true })
|
|
100
|
+
.defaultNow()
|
|
101
|
+
.notNull(),
|
|
102
|
+
},
|
|
103
|
+
(table) => [
|
|
104
|
+
unique("khotan_flows_plug_id_name_unique").on(table.plugId, table.name),
|
|
105
|
+
index("khotan_flows_plug_id_idx").on(table.plugId),
|
|
106
|
+
index("khotan_flows_resource_id_idx").on(table.resourceId),
|
|
107
|
+
],
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
// khotan_wires — one row per webhook subscription managed by a plug
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
|
|
114
|
+
export const khotanWires = pgTable(
|
|
115
|
+
"khotan_wires",
|
|
116
|
+
{
|
|
117
|
+
id: text("id")
|
|
118
|
+
.primaryKey()
|
|
119
|
+
.$defaultFn(() => crypto.randomUUID()),
|
|
120
|
+
plugId: text("plug_id")
|
|
121
|
+
.notNull()
|
|
122
|
+
.references(() => khotanPlugs.id),
|
|
123
|
+
remoteId: text("remote_id").notNull(),
|
|
124
|
+
callbackUrl: text("callback_url").notNull(),
|
|
125
|
+
eventTypes: jsonb("event_types").notNull().$type<string[]>(),
|
|
126
|
+
status: text("status", {
|
|
127
|
+
enum: ["active", "disabled"],
|
|
128
|
+
})
|
|
129
|
+
.default("active")
|
|
130
|
+
.notNull(),
|
|
131
|
+
metadata: jsonb("metadata"),
|
|
132
|
+
createdAt: timestamp("created_at", { withTimezone: true })
|
|
133
|
+
.defaultNow()
|
|
134
|
+
.notNull(),
|
|
135
|
+
updatedAt: timestamp("updated_at", { withTimezone: true })
|
|
136
|
+
.defaultNow()
|
|
137
|
+
.notNull(),
|
|
138
|
+
},
|
|
139
|
+
(table) => [
|
|
140
|
+
index("khotan_wires_plug_id_idx").on(table.plugId),
|
|
141
|
+
index("khotan_wires_status_idx").on(table.status),
|
|
142
|
+
],
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
// khotan_webhook_handlers — catches and passes attached to a wire
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
export const khotanWebhookHandlers = pgTable(
|
|
150
|
+
"khotan_webhook_handlers",
|
|
151
|
+
{
|
|
152
|
+
id: text("id")
|
|
153
|
+
.primaryKey()
|
|
154
|
+
.$defaultFn(() => crypto.randomUUID()),
|
|
155
|
+
wireId: text("wire_id")
|
|
156
|
+
.notNull()
|
|
157
|
+
.references(() => khotanWires.id),
|
|
158
|
+
name: text("name").notNull(),
|
|
159
|
+
type: text("type", {
|
|
160
|
+
enum: ["catch", "pass"],
|
|
161
|
+
}).notNull(),
|
|
162
|
+
destinationPlugId: text("destination_plug_id"),
|
|
163
|
+
enabled: boolean("enabled").default(true).notNull(),
|
|
164
|
+
createdAt: timestamp("created_at", { withTimezone: true })
|
|
165
|
+
.defaultNow()
|
|
166
|
+
.notNull(),
|
|
167
|
+
updatedAt: timestamp("updated_at", { withTimezone: true })
|
|
168
|
+
.defaultNow()
|
|
169
|
+
.notNull(),
|
|
170
|
+
},
|
|
171
|
+
(table) => [
|
|
172
|
+
unique("khotan_webhook_handlers_wire_id_name_unique").on(
|
|
173
|
+
table.wireId,
|
|
174
|
+
table.name,
|
|
175
|
+
),
|
|
176
|
+
index("khotan_webhook_handlers_wire_id_idx").on(table.wireId),
|
|
177
|
+
],
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
// khotan_webhook_events — one row per webhook event routed to a handler
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
|
|
184
|
+
export const khotanWebhookEvents = pgTable(
|
|
185
|
+
"khotan_webhook_events",
|
|
186
|
+
{
|
|
187
|
+
id: text("id")
|
|
188
|
+
.primaryKey()
|
|
189
|
+
.$defaultFn(() => crypto.randomUUID()),
|
|
190
|
+
wireId: text("wire_id")
|
|
191
|
+
.notNull()
|
|
192
|
+
.references(() => khotanWires.id),
|
|
193
|
+
webhookHandlerId: text("webhook_handler_id")
|
|
194
|
+
.notNull()
|
|
195
|
+
.references(() => khotanWebhookHandlers.id),
|
|
196
|
+
khotanRunId: text("khotan_run_id")
|
|
197
|
+
.notNull()
|
|
198
|
+
.references(() => khotanRuns.id),
|
|
199
|
+
eventType: text("event_type").notNull(),
|
|
200
|
+
payload: jsonb("payload").notNull().$type<Record<string, unknown>>(),
|
|
201
|
+
headers: jsonb("headers").notNull().$type<Record<string, string>>(),
|
|
202
|
+
receivedAt: timestamp("received_at", { withTimezone: true })
|
|
203
|
+
.defaultNow()
|
|
204
|
+
.notNull(),
|
|
205
|
+
},
|
|
206
|
+
(table) => [
|
|
207
|
+
index("khotan_webhook_events_wire_id_idx").on(table.wireId),
|
|
208
|
+
index("khotan_webhook_events_webhook_handler_id_idx").on(
|
|
209
|
+
table.webhookHandlerId,
|
|
210
|
+
),
|
|
211
|
+
index("khotan_webhook_events_khotan_run_id_idx").on(table.khotanRunId),
|
|
212
|
+
index("khotan_webhook_events_received_at_idx").on(table.receivedAt.desc()),
|
|
213
|
+
],
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
// khotan_runs — one row per execution of a flow, wire, or webhook handler
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
|
|
220
|
+
export const khotanRuns = pgTable(
|
|
221
|
+
"khotan_runs",
|
|
222
|
+
{
|
|
223
|
+
id: text("id")
|
|
224
|
+
.primaryKey()
|
|
225
|
+
.$defaultFn(() => crypto.randomUUID()),
|
|
226
|
+
flowId: text("flow_id").references(() => khotanFlows.id),
|
|
227
|
+
wireId: text("wire_id").references(() => khotanWires.id),
|
|
228
|
+
webhookHandlerId: text("webhook_handler_id").references(
|
|
229
|
+
() => khotanWebhookHandlers.id,
|
|
230
|
+
),
|
|
231
|
+
workflowRunId: text("workflow_run_id"),
|
|
232
|
+
runType: text("run_type", {
|
|
233
|
+
enum: ["full", "delta", "backfill", "reconcile", "dry-run", "webhook"],
|
|
234
|
+
}).notNull(),
|
|
235
|
+
status: text("status", {
|
|
236
|
+
enum: [
|
|
237
|
+
"pending",
|
|
238
|
+
"running",
|
|
239
|
+
"completed",
|
|
240
|
+
"partial",
|
|
241
|
+
"failed",
|
|
242
|
+
"cancelled",
|
|
243
|
+
],
|
|
244
|
+
})
|
|
245
|
+
.default("pending")
|
|
246
|
+
.notNull(),
|
|
247
|
+
startedAt: timestamp("started_at", { withTimezone: true })
|
|
248
|
+
.defaultNow()
|
|
249
|
+
.notNull(),
|
|
250
|
+
completedAt: timestamp("completed_at", { withTimezone: true }),
|
|
251
|
+
durationMs: integer("duration_ms"),
|
|
252
|
+
extracted: integer("extracted").default(0).notNull(),
|
|
253
|
+
transformed: integer("transformed").default(0).notNull(),
|
|
254
|
+
created: integer("created").default(0).notNull(),
|
|
255
|
+
updated: integer("updated").default(0).notNull(),
|
|
256
|
+
deleted: integer("deleted").default(0).notNull(),
|
|
257
|
+
failed: integer("failed").default(0).notNull(),
|
|
258
|
+
error: text("error"),
|
|
259
|
+
metadata: jsonb("metadata"),
|
|
260
|
+
},
|
|
261
|
+
(table) => [
|
|
262
|
+
index("khotan_runs_flow_id_idx").on(table.flowId),
|
|
263
|
+
index("khotan_runs_wire_id_idx").on(table.wireId),
|
|
264
|
+
index("khotan_runs_webhook_handler_id_idx").on(table.webhookHandlerId),
|
|
265
|
+
index("khotan_runs_status_idx").on(table.status),
|
|
266
|
+
index("khotan_runs_flow_id_started_at_idx").on(
|
|
267
|
+
table.flowId,
|
|
268
|
+
table.startedAt.desc(),
|
|
269
|
+
),
|
|
270
|
+
],
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
// ---------------------------------------------------------------------------
|
|
274
|
+
// khotan_mappings — one row per entity instance within a resource
|
|
275
|
+
// ---------------------------------------------------------------------------
|
|
276
|
+
|
|
277
|
+
export const khotanMappings = pgTable(
|
|
278
|
+
"khotan_mappings",
|
|
279
|
+
{
|
|
280
|
+
id: text("id")
|
|
281
|
+
.primaryKey()
|
|
282
|
+
.$defaultFn(() => crypto.randomUUID()),
|
|
283
|
+
resourceId: text("resource_id")
|
|
284
|
+
.notNull()
|
|
285
|
+
.references(() => khotanResources.id),
|
|
286
|
+
connectValue: text("connect_value").notNull(),
|
|
287
|
+
refs: jsonb("refs").notNull().$type<Record<string, string>>().default({}),
|
|
288
|
+
metadata: jsonb("metadata").$type<Record<string, unknown>>(),
|
|
289
|
+
createdAt: timestamp("created_at", { withTimezone: true })
|
|
290
|
+
.defaultNow()
|
|
291
|
+
.notNull(),
|
|
292
|
+
updatedAt: timestamp("updated_at", { withTimezone: true })
|
|
293
|
+
.defaultNow()
|
|
294
|
+
.notNull(),
|
|
295
|
+
},
|
|
296
|
+
(table) => [
|
|
297
|
+
unique("khotan_mappings_resource_id_connect_value_unique").on(
|
|
298
|
+
table.resourceId,
|
|
299
|
+
table.connectValue,
|
|
300
|
+
),
|
|
301
|
+
index("khotan_mappings_resource_id_idx").on(table.resourceId),
|
|
302
|
+
index("khotan_mappings_refs_gin_idx").using("gin", table.refs),
|
|
303
|
+
],
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
// ---------------------------------------------------------------------------
|
|
307
|
+
// khotan_caches — one row per registered durable cache namespace
|
|
308
|
+
// ---------------------------------------------------------------------------
|
|
309
|
+
|
|
310
|
+
export const khotanCaches = pgTable(
|
|
311
|
+
"khotan_caches",
|
|
312
|
+
{
|
|
313
|
+
id: text("id")
|
|
314
|
+
.primaryKey()
|
|
315
|
+
.$defaultFn(() => crypto.randomUUID()),
|
|
316
|
+
name: text("name").notNull().unique(),
|
|
317
|
+
scope: jsonb("scope").$type<{
|
|
318
|
+
plug?: string;
|
|
319
|
+
resource?: string;
|
|
320
|
+
flow?: string;
|
|
321
|
+
}>(),
|
|
322
|
+
ttlSeconds: integer("ttl_seconds"),
|
|
323
|
+
createdAt: timestamp("created_at", { withTimezone: true })
|
|
324
|
+
.defaultNow()
|
|
325
|
+
.notNull(),
|
|
326
|
+
updatedAt: timestamp("updated_at", { withTimezone: true })
|
|
327
|
+
.defaultNow()
|
|
328
|
+
.notNull(),
|
|
329
|
+
},
|
|
330
|
+
(table) => [index("khotan_caches_name_idx").on(table.name)],
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
// ---------------------------------------------------------------------------
|
|
334
|
+
// khotan_cache_entries — latest-value durable cache rows keyed by cache + key
|
|
335
|
+
// ---------------------------------------------------------------------------
|
|
336
|
+
|
|
337
|
+
export const khotanCacheEntries = pgTable(
|
|
338
|
+
"khotan_cache_entries",
|
|
339
|
+
{
|
|
340
|
+
id: text("id")
|
|
341
|
+
.primaryKey()
|
|
342
|
+
.$defaultFn(() => crypto.randomUUID()),
|
|
343
|
+
cacheId: text("cache_id")
|
|
344
|
+
.notNull()
|
|
345
|
+
.references(() => khotanCaches.id),
|
|
346
|
+
key: text("key").notNull(),
|
|
347
|
+
value: jsonb("value").notNull(),
|
|
348
|
+
expiresAt: timestamp("expires_at", { withTimezone: true }),
|
|
349
|
+
createdAt: timestamp("created_at", { withTimezone: true })
|
|
350
|
+
.defaultNow()
|
|
351
|
+
.notNull(),
|
|
352
|
+
updatedAt: timestamp("updated_at", { withTimezone: true })
|
|
353
|
+
.defaultNow()
|
|
354
|
+
.notNull(),
|
|
355
|
+
},
|
|
356
|
+
(table) => [
|
|
357
|
+
unique("khotan_cache_entries_cache_id_key_unique").on(
|
|
358
|
+
table.cacheId,
|
|
359
|
+
table.key,
|
|
360
|
+
),
|
|
361
|
+
index("khotan_cache_entries_cache_id_idx").on(table.cacheId),
|
|
362
|
+
index("khotan_cache_entries_cache_id_key_idx").on(table.cacheId, table.key),
|
|
363
|
+
index("khotan_cache_entries_expires_at_idx").on(table.expiresAt),
|
|
364
|
+
],
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
// ---------------------------------------------------------------------------
|
|
368
|
+
// Relations
|
|
369
|
+
// ---------------------------------------------------------------------------
|
|
370
|
+
|
|
371
|
+
export const khotanPlugsRelations = relations(khotanPlugs, ({ many }) => ({
|
|
372
|
+
flows: many(khotanFlows),
|
|
373
|
+
wires: many(khotanWires),
|
|
374
|
+
}));
|
|
375
|
+
|
|
376
|
+
export const khotanFlowsRelations = relations(khotanFlows, ({ one, many }) => ({
|
|
377
|
+
plug: one(khotanPlugs, {
|
|
378
|
+
fields: [khotanFlows.plugId],
|
|
379
|
+
references: [khotanPlugs.id],
|
|
380
|
+
}),
|
|
381
|
+
resource: one(khotanResources, {
|
|
382
|
+
fields: [khotanFlows.resourceId],
|
|
383
|
+
references: [khotanResources.id],
|
|
384
|
+
}),
|
|
385
|
+
runs: many(khotanRuns),
|
|
386
|
+
}));
|
|
387
|
+
|
|
388
|
+
export const khotanWiresRelations = relations(khotanWires, ({ one, many }) => ({
|
|
389
|
+
plug: one(khotanPlugs, {
|
|
390
|
+
fields: [khotanWires.plugId],
|
|
391
|
+
references: [khotanPlugs.id],
|
|
392
|
+
}),
|
|
393
|
+
webhookHandlers: many(khotanWebhookHandlers),
|
|
394
|
+
webhookEvents: many(khotanWebhookEvents),
|
|
395
|
+
runs: many(khotanRuns),
|
|
396
|
+
}));
|
|
397
|
+
|
|
398
|
+
export const khotanWebhookHandlersRelations = relations(
|
|
399
|
+
khotanWebhookHandlers,
|
|
400
|
+
({ one, many }) => ({
|
|
401
|
+
wire: one(khotanWires, {
|
|
402
|
+
fields: [khotanWebhookHandlers.wireId],
|
|
403
|
+
references: [khotanWires.id],
|
|
404
|
+
}),
|
|
405
|
+
webhookEvents: many(khotanWebhookEvents),
|
|
406
|
+
runs: many(khotanRuns),
|
|
407
|
+
}),
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
export const khotanRunsRelations = relations(khotanRuns, ({ one, many }) => ({
|
|
411
|
+
flow: one(khotanFlows, {
|
|
412
|
+
fields: [khotanRuns.flowId],
|
|
413
|
+
references: [khotanFlows.id],
|
|
414
|
+
}),
|
|
415
|
+
wire: one(khotanWires, {
|
|
416
|
+
fields: [khotanRuns.wireId],
|
|
417
|
+
references: [khotanWires.id],
|
|
418
|
+
}),
|
|
419
|
+
webhookHandler: one(khotanWebhookHandlers, {
|
|
420
|
+
fields: [khotanRuns.webhookHandlerId],
|
|
421
|
+
references: [khotanWebhookHandlers.id],
|
|
422
|
+
}),
|
|
423
|
+
webhookEvents: many(khotanWebhookEvents),
|
|
424
|
+
}));
|
|
425
|
+
|
|
426
|
+
export const khotanWebhookEventsRelations = relations(
|
|
427
|
+
khotanWebhookEvents,
|
|
428
|
+
({ one }) => ({
|
|
429
|
+
wire: one(khotanWires, {
|
|
430
|
+
fields: [khotanWebhookEvents.wireId],
|
|
431
|
+
references: [khotanWires.id],
|
|
432
|
+
}),
|
|
433
|
+
webhookHandler: one(khotanWebhookHandlers, {
|
|
434
|
+
fields: [khotanWebhookEvents.webhookHandlerId],
|
|
435
|
+
references: [khotanWebhookHandlers.id],
|
|
436
|
+
}),
|
|
437
|
+
khotanRun: one(khotanRuns, {
|
|
438
|
+
fields: [khotanWebhookEvents.khotanRunId],
|
|
439
|
+
references: [khotanRuns.id],
|
|
440
|
+
}),
|
|
441
|
+
}),
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
export const khotanResourcesRelations = relations(
|
|
445
|
+
khotanResources,
|
|
446
|
+
({ many }) => ({
|
|
447
|
+
flows: many(khotanFlows),
|
|
448
|
+
mappings: many(khotanMappings),
|
|
449
|
+
}),
|
|
450
|
+
);
|
|
451
|
+
|
|
452
|
+
export const khotanMappingsRelations = relations(khotanMappings, ({ one }) => ({
|
|
453
|
+
resource: one(khotanResources, {
|
|
454
|
+
fields: [khotanMappings.resourceId],
|
|
455
|
+
references: [khotanResources.id],
|
|
456
|
+
}),
|
|
457
|
+
}));
|
|
458
|
+
|
|
459
|
+
export const khotanCachesRelations = relations(khotanCaches, ({ many }) => ({
|
|
460
|
+
entries: many(khotanCacheEntries),
|
|
461
|
+
}));
|
|
462
|
+
|
|
463
|
+
export const khotanCacheEntriesRelations = relations(
|
|
464
|
+
khotanCacheEntries,
|
|
465
|
+
({ one }) => ({
|
|
466
|
+
cache: one(khotanCaches, {
|
|
467
|
+
fields: [khotanCacheEntries.cacheId],
|
|
468
|
+
references: [khotanCaches.id],
|
|
469
|
+
}),
|
|
470
|
+
}),
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
// ---------------------------------------------------------------------------
|
|
474
|
+
// Type helpers
|
|
475
|
+
// ---------------------------------------------------------------------------
|
|
476
|
+
|
|
477
|
+
export type KhotanPlug = typeof khotanPlugs.$inferSelect;
|
|
478
|
+
export type NewKhotanPlug = typeof khotanPlugs.$inferInsert;
|
|
479
|
+
|
|
480
|
+
export type KhotanFlow = typeof khotanFlows.$inferSelect;
|
|
481
|
+
export type NewKhotanFlow = typeof khotanFlows.$inferInsert;
|
|
482
|
+
|
|
483
|
+
export type KhotanWire = typeof khotanWires.$inferSelect;
|
|
484
|
+
export type NewKhotanWire = typeof khotanWires.$inferInsert;
|
|
485
|
+
|
|
486
|
+
export type KhotanWebhookHandler = typeof khotanWebhookHandlers.$inferSelect;
|
|
487
|
+
export type NewKhotanWebhookHandler = typeof khotanWebhookHandlers.$inferInsert;
|
|
488
|
+
|
|
489
|
+
export type KhotanWebhookEvent = typeof khotanWebhookEvents.$inferSelect;
|
|
490
|
+
export type NewKhotanWebhookEvent = typeof khotanWebhookEvents.$inferInsert;
|
|
491
|
+
|
|
492
|
+
export type KhotanRun = typeof khotanRuns.$inferSelect;
|
|
493
|
+
export type NewKhotanRun = typeof khotanRuns.$inferInsert;
|
|
494
|
+
|
|
495
|
+
export type KhotanResource = typeof khotanResources.$inferSelect;
|
|
496
|
+
export type NewKhotanResource = typeof khotanResources.$inferInsert;
|
|
497
|
+
|
|
498
|
+
export type KhotanMapping = typeof khotanMappings.$inferSelect;
|
|
499
|
+
export type NewKhotanMapping = typeof khotanMappings.$inferInsert;
|
|
500
|
+
|
|
501
|
+
export type KhotanCache = typeof khotanCaches.$inferSelect;
|
|
502
|
+
export type NewKhotanCache = typeof khotanCaches.$inferInsert;
|
|
503
|
+
|
|
504
|
+
export type KhotanCacheEntry = typeof khotanCacheEntries.$inferSelect;
|
|
505
|
+
export type NewKhotanCacheEntry = typeof khotanCacheEntries.$inferInsert;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: khotan-dashboard
|
|
3
|
+
description: >
|
|
4
|
+
Set up khotan dashboard UI — the Hub for managing plugs, flows,
|
|
5
|
+
variables, and webhooks, plus the Plug Debugger for testing API
|
|
6
|
+
requests. Use when adding a management interface, configuring plug
|
|
7
|
+
variables in the browser, or setting up debug pages.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
Set up khotan dashboard UI — the Hub for managing plugs, flows, variables, and webhooks, plus the Plug Debugger for testing API requests. Use when adding a management interface, configuring plug variables in the browser, or setting up debug pages.
|
|
11
|
+
|
|
12
|
+
## Hub (Management Dashboard)
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npx khotan add hub --yes
|
|
16
|
+
npx khotan add config-page-1 --yes # Ready-made /config route
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The Hub scaffolds three components to `src/components/khotan/`:
|
|
20
|
+
|
|
21
|
+
| File | Purpose |
|
|
22
|
+
|------|---------|
|
|
23
|
+
| `hub.tsx` | Main `<KhotanHub />` — plug cards, flow table, enable/disable toggles |
|
|
24
|
+
| `var-panel.tsx` | Variables panel for configuring plug vars |
|
|
25
|
+
| `wire-panel.tsx` | Webhook subscription management (connect/disconnect) |
|
|
26
|
+
|
|
27
|
+
### Rendering the Hub
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import { KhotanHub } from "@/components/khotan/hub";
|
|
31
|
+
|
|
32
|
+
export default function ConfigPage() {
|
|
33
|
+
return (
|
|
34
|
+
<main className="container mx-auto max-w-5xl px-4 py-10">
|
|
35
|
+
<KhotanHub />
|
|
36
|
+
</main>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Or use `npx khotan add config-page-1` to scaffold a `/config` page automatically.
|
|
42
|
+
|
|
43
|
+
### Hub Features
|
|
44
|
+
|
|
45
|
+
- Lists all registered plugs with status badges (connected/error/idle)
|
|
46
|
+
- Click a plug to see its flows with enable/disable toggles
|
|
47
|
+
- VarPanel: configure plug variables (stored encrypted via `KHOTAN_SECRET`)
|
|
48
|
+
- WirePanel: manage webhook subscriptions (requires wires configured on plug)
|
|
49
|
+
- Debug button on each plug card (visible when `KHOTAN_DEBUG=1`)
|
|
50
|
+
|
|
51
|
+
### Hub Props
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
<KhotanHub
|
|
55
|
+
webhookUrl="https://your-domain.com" // Base URL for wire callbacks
|
|
56
|
+
/>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### API Endpoints Used by Hub
|
|
60
|
+
|
|
61
|
+
| Endpoint | Purpose |
|
|
62
|
+
|----------|---------|
|
|
63
|
+
| `GET /api/khotan/plugs` | List plugs with flow counts |
|
|
64
|
+
| `GET /api/khotan/flows` | List all flows |
|
|
65
|
+
| `PATCH /api/khotan/flows/:id` | Toggle flow enabled/disabled |
|
|
66
|
+
| `POST /api/khotan/flows/:id/runs` | Start a tracked flow run |
|
|
67
|
+
| `GET /api/khotan/runs/:id` | Get run detail with live Workflow status |
|
|
68
|
+
| `GET /api/khotan/runs/:id/stream` | Stream Workflow progress updates |
|
|
69
|
+
| `POST /api/khotan/runs/:id/cancel` | Cancel a running Workflow-backed run |
|
|
70
|
+
| `POST /api/khotan/runs/:id/retry` | Retry a flow run with the same run type |
|
|
71
|
+
| `PATCH /api/khotan/plugs/:id` | Toggle plug enabled/disabled |
|
|
72
|
+
| `GET /api/khotan/variables/:plugName` | Get var fields + masked values |
|
|
73
|
+
| `POST /api/khotan/variables/:plugName` | Save encrypted variables |
|
|
74
|
+
| `DELETE /api/khotan/variables/:plugName` | Clear variables |
|
|
75
|
+
| `GET /api/khotan/wires/:plugName` | Get wire status |
|
|
76
|
+
| `POST /api/khotan/wires/:plugName` | Create webhook subscription |
|
|
77
|
+
| `DELETE /api/khotan/wires/:plugName` | Remove webhook subscription |
|
|
78
|
+
|
|
79
|
+
From server code, prefer the Khotan-native starter instead of calling `workflow/api.start()` directly:
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
await khotanData.flow("products-inflow", { plugName: "shopify" }).start({
|
|
83
|
+
runType: "delta",
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Variables In Code And CLI
|
|
88
|
+
|
|
89
|
+
- Declare optional `defaultValue` on plug var fields to seed initial DB-backed values.
|
|
90
|
+
- Use `npx khotan plug vars <plugName>` to inspect masked values from the terminal.
|
|
91
|
+
- Use `npx khotan plug vars <plugName> set --json '{...}'` to update variables without opening the Hub.
|
|
92
|
+
- Use `npx khotan plug vars <plugName> clear` to remove all stored overrides for a plug.
|
|
93
|
+
|
|
94
|
+
## Plug Debugger (Dev Testing UI)
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
npx khotan add plug-debugger --yes
|
|
98
|
+
npx khotan add debug-page-1 --yes # Routes at /debug and /debug/[plugName]
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Requires `KHOTAN_DEBUG=1` in your environment.
|
|
102
|
+
|
|
103
|
+
### Features
|
|
104
|
+
|
|
105
|
+
- Postman-like interface for testing plug requests
|
|
106
|
+
- Typed endpoint sidebar showing Zod schemas
|
|
107
|
+
- Path parameter interpolation
|
|
108
|
+
- Response validation (green/amber/red diff)
|
|
109
|
+
- Request history
|
|
110
|
+
- Auto-format JSON bodies
|
|
111
|
+
- Keyboard shortcut: Cmd+Enter to send
|
|
112
|
+
|
|
113
|
+
### Debug Routes
|
|
114
|
+
|
|
115
|
+
| Route | Purpose |
|
|
116
|
+
|-------|---------|
|
|
117
|
+
| `/debug` | Index page listing all plugs |
|
|
118
|
+
| `/debug/[plugName]` | Per-plug debugger |
|
|
119
|
+
|
|
120
|
+
### Debug API Endpoints
|
|
121
|
+
|
|
122
|
+
| Endpoint | Purpose |
|
|
123
|
+
|----------|---------|
|
|
124
|
+
| `GET /api/khotan/debug` | Check if debug mode is active |
|
|
125
|
+
| `GET /api/khotan/debug/:plugName` | Plug metadata + endpoint schemas |
|
|
126
|
+
| `POST /api/khotan/debug/:plugName` | Fire request through the real plug code path |
|
|
127
|
+
|
|
128
|
+
## shadcn Dependencies
|
|
129
|
+
|
|
130
|
+
Both components require shadcn/ui. The CLI will offer to install missing components:
|
|
131
|
+
|
|
132
|
+
- **Hub**: card, badge, table, switch, button, input, label
|
|
133
|
+
- **Plug Debugger**: card, badge, button, input, label
|
|
134
|
+
|
|
135
|
+
Run `npx shadcn@latest init --defaults --yes` first if shadcn is not set up.
|
|
136
|
+
|
|
137
|
+
## Skip UI
|
|
138
|
+
|
|
139
|
+
Use `--without-ui` to scaffold only the backend code without React components:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
npx khotan add hub --without-ui
|
|
143
|
+
npx khotan add wire --without-ui
|
|
144
|
+
```
|