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,52 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Example: Outflow
|
|
3
|
+
// Generated by khotan CLI · https://github.com/khotan-data
|
|
4
|
+
//
|
|
5
|
+
// Copy this file, rename it for your destination service/resource, and register
|
|
6
|
+
// the exported flow in {outputDir}/khotan.ts.
|
|
7
|
+
// ============================================================================
|
|
8
|
+
|
|
9
|
+
import { outflow, type OutflowContext } from "./outflow";
|
|
10
|
+
|
|
11
|
+
async function hubspotProductsWorkflow(ctx: OutflowContext) {
|
|
12
|
+
"use workflow";
|
|
13
|
+
|
|
14
|
+
async function loadAndPush() {
|
|
15
|
+
"use step";
|
|
16
|
+
console.log("Starting outflow", {
|
|
17
|
+
flow: ctx.flow.name,
|
|
18
|
+
khotanRunId: ctx.khotanRunId,
|
|
19
|
+
runType: ctx.runType,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Replace this with your app-specific DB query.
|
|
23
|
+
const records: Array<Record<string, unknown>> = [];
|
|
24
|
+
|
|
25
|
+
for (const record of records) {
|
|
26
|
+
await fetch("https://api.example.com/products", {
|
|
27
|
+
method: "POST",
|
|
28
|
+
headers: {
|
|
29
|
+
Authorization: `Bearer ${ctx.vars["apiToken"] ?? ""}`,
|
|
30
|
+
"Content-Type": "application/json",
|
|
31
|
+
},
|
|
32
|
+
body: JSON.stringify(record),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
extracted: records.length,
|
|
38
|
+
transformed: records.length,
|
|
39
|
+
created: records.length,
|
|
40
|
+
metadata: { destination: ctx.flow.name },
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return loadAndPush();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const hubspotProductsOutflow = outflow({
|
|
48
|
+
name: "hubspot-products-outflow",
|
|
49
|
+
resource: "products",
|
|
50
|
+
schedule: "0 * * * *",
|
|
51
|
+
workflow: hubspotProductsWorkflow,
|
|
52
|
+
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Outflow — durable app → external service data movement
|
|
3
|
+
// Generated by khotan CLI · https://github.com/khotan-data
|
|
4
|
+
//
|
|
5
|
+
// This file defines the outflow() builder and types. Create per-service flow
|
|
6
|
+
// files (e.g. crm-audiences.ts) using this builder to read app data and push it
|
|
7
|
+
// to an external service with durable, retryable Vercel Workflow steps.
|
|
8
|
+
// Outflow workflows can also use khotanCache(ctx, "name") for checkpoints, cursor
|
|
9
|
+
// state, or dedupe markers between runs.
|
|
10
|
+
// ============================================================================
|
|
11
|
+
|
|
12
|
+
import type {
|
|
13
|
+
FlowRegistration,
|
|
14
|
+
FlowRunResult,
|
|
15
|
+
FlowWorkflowContext,
|
|
16
|
+
} from "khotan-data/factory";
|
|
17
|
+
|
|
18
|
+
export type OutflowContext = FlowWorkflowContext & {
|
|
19
|
+
flow: FlowWorkflowContext["flow"] & { type: "outflow" };
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type OutflowWorkflow = (
|
|
23
|
+
ctx: OutflowContext,
|
|
24
|
+
) => Promise<FlowRunResult | void>;
|
|
25
|
+
|
|
26
|
+
export interface OutflowConfig {
|
|
27
|
+
/** Unique name for this flow (used for DB tracking and Hub display) */
|
|
28
|
+
name: string;
|
|
29
|
+
/** Logical resource this flow publishes, e.g. "products" */
|
|
30
|
+
resource?: string;
|
|
31
|
+
/** Optional cron schedule used by scheduler integrations */
|
|
32
|
+
schedule?: string;
|
|
33
|
+
/** Durable workflow that reads app data and writes it to the plug */
|
|
34
|
+
workflow: OutflowWorkflow;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function outflow(config: OutflowConfig): FlowRegistration {
|
|
38
|
+
return {
|
|
39
|
+
name: config.name,
|
|
40
|
+
type: "outflow",
|
|
41
|
+
resource: config.resource,
|
|
42
|
+
schedule: config.schedule,
|
|
43
|
+
workflow: config.workflow as FlowRegistration["workflow"],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Usage Example (create a file like flows/hubspot-products.ts)
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
//
|
|
51
|
+
// import { bindWorkflowPlug, outflow, type OutflowContext } from "khotan-data/factory";
|
|
52
|
+
// import { db } from "@/db";
|
|
53
|
+
// import { products } from "@/db/schema";
|
|
54
|
+
// import { hubspotPlug } from "../plugs/hubspot";
|
|
55
|
+
//
|
|
56
|
+
// async function hubspotProductsWorkflow(ctx: OutflowContext) {
|
|
57
|
+
// "use workflow";
|
|
58
|
+
//
|
|
59
|
+
// async function loadAndPush() {
|
|
60
|
+
// "use step";
|
|
61
|
+
// console.log("Starting outflow", {
|
|
62
|
+
// flow: ctx.flow.name,
|
|
63
|
+
// khotanRunId: ctx.khotanRunId,
|
|
64
|
+
// runType: ctx.runType,
|
|
65
|
+
// });
|
|
66
|
+
// const hubspot = bindWorkflowPlug(hubspotPlug, ctx);
|
|
67
|
+
//
|
|
68
|
+
// const records = await db.select().from(products);
|
|
69
|
+
//
|
|
70
|
+
// for (const record of records) {
|
|
71
|
+
// await hubspot.post("/products", { body: record });
|
|
72
|
+
// }
|
|
73
|
+
//
|
|
74
|
+
// return {
|
|
75
|
+
// extracted: records.length,
|
|
76
|
+
// transformed: records.length,
|
|
77
|
+
// created: records.length,
|
|
78
|
+
// metadata: { destination: ctx.flow.name },
|
|
79
|
+
// };
|
|
80
|
+
// }
|
|
81
|
+
//
|
|
82
|
+
// return loadAndPush();
|
|
83
|
+
// }
|
|
84
|
+
//
|
|
85
|
+
// export const hubspotProductsOutflow = outflow({
|
|
86
|
+
// name: "hubspot-products-outflow",
|
|
87
|
+
// resource: "products",
|
|
88
|
+
// schedule: "0 * * * *",
|
|
89
|
+
// workflow: hubspotProductsWorkflow,
|
|
90
|
+
// });
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Example: Pass
|
|
3
|
+
// Generated by khotan CLI · https://github.com/khotan-data
|
|
4
|
+
//
|
|
5
|
+
// Copy this file, rename it for your source/destination pair, and register the
|
|
6
|
+
// exported pass handler in {outputDir}/khotan.ts.
|
|
7
|
+
// ============================================================================
|
|
8
|
+
|
|
9
|
+
import { pass, type PassContext } from "./pass";
|
|
10
|
+
|
|
11
|
+
async function stripeToSlackWorkflow(ctx: PassContext) {
|
|
12
|
+
"use workflow";
|
|
13
|
+
|
|
14
|
+
async function forwardEvent() {
|
|
15
|
+
"use step";
|
|
16
|
+
console.log("Forwarding webhook event", {
|
|
17
|
+
eventType: ctx.eventType,
|
|
18
|
+
khotanRunId: ctx.khotanRunId,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
await fetch("https://slack.com/api/chat.postMessage", {
|
|
22
|
+
method: "POST",
|
|
23
|
+
headers: {
|
|
24
|
+
Authorization: `Bearer ${ctx.destVars["botToken"] ?? ""}`,
|
|
25
|
+
"Content-Type": "application/json",
|
|
26
|
+
},
|
|
27
|
+
body: JSON.stringify({
|
|
28
|
+
channel: ctx.destVars["channelId"],
|
|
29
|
+
text: `Received ${ctx.eventType}`,
|
|
30
|
+
blocks: [
|
|
31
|
+
{
|
|
32
|
+
type: "section",
|
|
33
|
+
text: {
|
|
34
|
+
type: "mrkdwn",
|
|
35
|
+
text: `Received ${ctx.eventType} from Stripe`,
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
}),
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
await forwardEvent();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const stripeToSlack = pass({
|
|
47
|
+
name: "stripe-to-slack",
|
|
48
|
+
to: "slack",
|
|
49
|
+
events: ["invoice.paid"],
|
|
50
|
+
workflow: stripeToSlackWorkflow,
|
|
51
|
+
});
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Pass — durable webhook event forwarding via Vercel Workflow
|
|
3
|
+
// Generated by khotan CLI · https://github.com/khotan-data
|
|
4
|
+
//
|
|
5
|
+
// This file defines the pass() builder and types. Create per-service pass
|
|
6
|
+
// files (e.g. pollinate-to-slack.ts) using this builder to forward verified
|
|
7
|
+
// webhook events to another service with durable, retryable workflow steps.
|
|
8
|
+
//
|
|
9
|
+
// Pass workflows receive a PassContext with the parsed event, event type,
|
|
10
|
+
// headers, and destVars (variables for the destination plug). Steps have
|
|
11
|
+
// full Node.js access — construct destination plugs from destVars.
|
|
12
|
+
// ============================================================================
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Context — serializable data passed to the workflow
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
export interface PassContext {
|
|
19
|
+
/** Parsed webhook payload */
|
|
20
|
+
event: Record<string, unknown>;
|
|
21
|
+
/** Event type extracted from payload (e.g. "order.created") */
|
|
22
|
+
eventType: string;
|
|
23
|
+
/** Incoming request headers */
|
|
24
|
+
headers: Record<string, string>;
|
|
25
|
+
/** Decrypted variables for the destination plug (auto-injected by factory) */
|
|
26
|
+
destVars: Record<string, string>;
|
|
27
|
+
/** Khotan run ID created for this webhook handler execution */
|
|
28
|
+
khotanRunId: string;
|
|
29
|
+
/** Internal Khotan instance identifier for helper APIs */
|
|
30
|
+
khotanInstanceId: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Workflow type — the function signature your workflow must conform to
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
export type PassWorkflow = (ctx: PassContext) => Promise<void>;
|
|
38
|
+
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// Config — passed to the pass() builder
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
export interface PassConfig {
|
|
44
|
+
/** Unique name for this pass handler (used for DB tracking and Hub display) */
|
|
45
|
+
name: string;
|
|
46
|
+
/** Name of the destination plug to forward events to */
|
|
47
|
+
to: string;
|
|
48
|
+
/** Event types this pass should receive */
|
|
49
|
+
events?: string[];
|
|
50
|
+
/** Workflow function that handles the forwarding */
|
|
51
|
+
workflow: PassWorkflow;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// Registration — returned by pass(), consumed by factory config
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
export interface PassRegistration {
|
|
59
|
+
type: "pass";
|
|
60
|
+
name: string;
|
|
61
|
+
to: string;
|
|
62
|
+
events?: string[];
|
|
63
|
+
workflow: PassWorkflow;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// Builder
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
export function pass(config: PassConfig): PassRegistration {
|
|
71
|
+
return {
|
|
72
|
+
type: "pass",
|
|
73
|
+
name: config.name,
|
|
74
|
+
to: config.to,
|
|
75
|
+
events: config.events,
|
|
76
|
+
workflow: config.workflow,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// Usage Example (create a file like webhooks/pollinate-to-slack.ts)
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
//
|
|
84
|
+
// import { khotanCache } from "khotan-data/factory";
|
|
85
|
+
// import { pass, type PassContext } from "./pass";
|
|
86
|
+
// import { plug } from "../plugs/plug";
|
|
87
|
+
//
|
|
88
|
+
// async function pollinateToSlackWorkflow(ctx: PassContext) {
|
|
89
|
+
// "use workflow";
|
|
90
|
+
//
|
|
91
|
+
// async function forwardEvent() {
|
|
92
|
+
// "use step";
|
|
93
|
+
// const cache = khotanCache(ctx, "pollinate-forwarded-events");
|
|
94
|
+
// const eventId = String(ctx.event["id"] ?? "");
|
|
95
|
+
// if (eventId && (await cache.get<boolean>(eventId))) return;
|
|
96
|
+
//
|
|
97
|
+
// // Construct destination plug from destVars
|
|
98
|
+
// const slackPlug = plug({
|
|
99
|
+
// name: "slack",
|
|
100
|
+
// baseUrl: "https://hooks.slack.com",
|
|
101
|
+
// authType: "bearer",
|
|
102
|
+
// auth: { bearer: { token: ctx.destVars["token"] ?? "" } },
|
|
103
|
+
// });
|
|
104
|
+
//
|
|
105
|
+
// await slackPlug.post("/services/webhook", {
|
|
106
|
+
// body: {
|
|
107
|
+
// text: `Received ${ctx.eventType} event from pollinate`,
|
|
108
|
+
// event: ctx.event,
|
|
109
|
+
// },
|
|
110
|
+
// });
|
|
111
|
+
//
|
|
112
|
+
// if (eventId) {
|
|
113
|
+
// await cache.set(eventId, true);
|
|
114
|
+
// }
|
|
115
|
+
// }
|
|
116
|
+
//
|
|
117
|
+
// await forwardEvent();
|
|
118
|
+
// }
|
|
119
|
+
//
|
|
120
|
+
// export const pollinateToSlack = pass({
|
|
121
|
+
// name: "pollinate-to-slack",
|
|
122
|
+
// to: "slack",
|
|
123
|
+
// events: ["order.created"],
|
|
124
|
+
// workflow: pollinateToSlackWorkflow,
|
|
125
|
+
// });
|
|
126
|
+
//
|
|
127
|
+
// Then register in your khotan config:
|
|
128
|
+
//
|
|
129
|
+
// plugs: [{
|
|
130
|
+
// name: "pollinate",
|
|
131
|
+
// plug: pollinatePlug,
|
|
132
|
+
// wires: [pollinateWire],
|
|
133
|
+
// passes: [pollinateToSlack],
|
|
134
|
+
// }]
|