khotan-data 0.1.1 → 0.3.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.
- package/README.md +60 -19
- package/dist/cli.js +183 -46
- package/dist/factory.cjs +86 -9
- package/dist/factory.cjs.map +1 -1
- package/dist/factory.d.cts +46 -1
- package/dist/factory.d.ts +46 -1
- package/dist/factory.js +86 -10
- package/dist/factory.js.map +1 -1
- package/dist/templates/api-state.tsx +249 -0
- package/dist/templates/catch.example.ts +25 -17
- package/dist/templates/catch.ts +20 -15
- package/dist/templates/debug-index-page.tsx +56 -36
- package/dist/templates/hub.tsx +105 -36
- package/dist/templates/inflow.example.ts +46 -38
- package/dist/templates/inflow.ts +37 -31
- package/dist/templates/khotan-config.ts +28 -0
- package/dist/templates/mapping-browser.tsx +56 -44
- package/dist/templates/outflow.example.ts +39 -31
- package/dist/templates/outflow.ts +28 -23
- package/dist/templates/pass.example.ts +38 -30
- package/dist/templates/pass.ts +29 -24
- package/dist/templates/plug-debugger.tsx +15 -7
- package/dist/templates/relay.example.ts +52 -44
- package/dist/templates/relay.ts +38 -33
- package/dist/templates/runs-table.tsx +133 -130
- package/dist/templates/skill-dashboard.md +2 -1
- package/dist/templates/skill-setup.md +113 -2
- package/dist/templates/skill-webhook.md +45 -23
- package/dist/templates/topology-canvas.tsx +19 -30
- package/dist/templates/var-panel.tsx +33 -10
- package/dist/templates/webhook-events-table.tsx +105 -102
- package/dist/templates/wire-panel.tsx +30 -8
- package/package.json +1 -1
|
@@ -4,44 +4,52 @@
|
|
|
4
4
|
//
|
|
5
5
|
// Copy this file, rename it for your destination service/resource, and register
|
|
6
6
|
// the exported flow in {outputDir}/khotan.ts.
|
|
7
|
+
//
|
|
8
|
+
// IMPORTANT — Workflow step structure:
|
|
9
|
+
// Declare "use step" functions at module top level and pass them only
|
|
10
|
+
// serializable values (the `ctx` object is plain data and is safe to pass).
|
|
11
|
+
// Do NOT nest step functions inside the "use workflow" function — the Workflow
|
|
12
|
+
// compiler cannot hoist closures that capture workflow scope, and they fail at
|
|
13
|
+
// runtime in the sandbox. Keep the workflow body limited to orchestration.
|
|
7
14
|
// ============================================================================
|
|
8
15
|
|
|
9
16
|
import { outflow, type OutflowContext } from "./outflow";
|
|
10
17
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
// Step: full Node.js access, retried independently. Receives serializable ctx.
|
|
19
|
+
async function loadAndPush(ctx: OutflowContext) {
|
|
20
|
+
"use step";
|
|
21
|
+
console.log("Starting outflow", {
|
|
22
|
+
flow: ctx.flow.name,
|
|
23
|
+
khotanRunId: ctx.khotanRunId,
|
|
24
|
+
runType: ctx.runType,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Replace this with your app-specific DB query.
|
|
28
|
+
const records: Array<Record<string, unknown>> = [];
|
|
29
|
+
|
|
30
|
+
for (const record of records) {
|
|
31
|
+
await fetch("https://api.example.com/products", {
|
|
32
|
+
method: "POST",
|
|
33
|
+
headers: {
|
|
34
|
+
Authorization: `Bearer ${ctx.vars["apiToken"] ?? ""}`,
|
|
35
|
+
"Content-Type": "application/json",
|
|
36
|
+
},
|
|
37
|
+
body: JSON.stringify(record),
|
|
20
38
|
});
|
|
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
39
|
}
|
|
43
40
|
|
|
44
|
-
return
|
|
41
|
+
return {
|
|
42
|
+
extracted: records.length,
|
|
43
|
+
transformed: records.length,
|
|
44
|
+
created: records.length,
|
|
45
|
+
metadata: { destination: ctx.flow.name },
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Workflow: orchestration only. Calls top-level steps with serializable args.
|
|
50
|
+
async function hubspotProductsWorkflow(ctx: OutflowContext) {
|
|
51
|
+
"use workflow";
|
|
52
|
+
return loadAndPush(ctx);
|
|
45
53
|
}
|
|
46
54
|
|
|
47
55
|
export const hubspotProductsOutflow = outflow({
|
|
@@ -48,38 +48,43 @@ export function outflow(config: OutflowConfig): FlowRegistration {
|
|
|
48
48
|
// Usage Example (create a file like flows/hubspot-products.ts)
|
|
49
49
|
// ---------------------------------------------------------------------------
|
|
50
50
|
//
|
|
51
|
+
// Declare "use step" functions at MODULE TOP LEVEL and pass them serializable
|
|
52
|
+
// values only (`ctx` is plain data). Do NOT nest steps inside the "use workflow"
|
|
53
|
+
// function — closures over workflow scope cannot be hoisted and fail at runtime.
|
|
54
|
+
//
|
|
51
55
|
// import { bindWorkflowPlug, outflow, type OutflowContext } from "khotan-data/factory";
|
|
52
56
|
// import { db } from "@/db";
|
|
53
57
|
// import { products } from "@/db/schema";
|
|
54
58
|
// import { hubspotPlug } from "../plugs/hubspot";
|
|
55
59
|
//
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
// });
|
|
66
|
-
// const hubspot = bindWorkflowPlug(hubspotPlug, ctx);
|
|
67
|
-
//
|
|
68
|
-
// const records = await db.select().from(products);
|
|
60
|
+
// // Step: top-level, full Node.js access, retried independently.
|
|
61
|
+
// async function loadAndPush(ctx: OutflowContext) {
|
|
62
|
+
// "use step";
|
|
63
|
+
// console.log("Starting outflow", {
|
|
64
|
+
// flow: ctx.flow.name,
|
|
65
|
+
// khotanRunId: ctx.khotanRunId,
|
|
66
|
+
// runType: ctx.runType,
|
|
67
|
+
// });
|
|
68
|
+
// const hubspot = bindWorkflowPlug(hubspotPlug, ctx);
|
|
69
69
|
//
|
|
70
|
-
//
|
|
71
|
-
// await hubspot.post("/products", { body: record });
|
|
72
|
-
// }
|
|
70
|
+
// const records = await db.select().from(products);
|
|
73
71
|
//
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
// transformed: records.length,
|
|
77
|
-
// created: records.length,
|
|
78
|
-
// metadata: { destination: ctx.flow.name },
|
|
79
|
-
// };
|
|
72
|
+
// for (const record of records) {
|
|
73
|
+
// await hubspot.post("/products", { body: record });
|
|
80
74
|
// }
|
|
81
75
|
//
|
|
82
|
-
// return
|
|
76
|
+
// return {
|
|
77
|
+
// extracted: records.length,
|
|
78
|
+
// transformed: records.length,
|
|
79
|
+
// created: records.length,
|
|
80
|
+
// metadata: { destination: ctx.flow.name },
|
|
81
|
+
// };
|
|
82
|
+
// }
|
|
83
|
+
//
|
|
84
|
+
// // Workflow: orchestration only.
|
|
85
|
+
// async function hubspotProductsWorkflow(ctx: OutflowContext) {
|
|
86
|
+
// "use workflow";
|
|
87
|
+
// return loadAndPush(ctx);
|
|
83
88
|
// }
|
|
84
89
|
//
|
|
85
90
|
// export const hubspotProductsOutflow = outflow({
|
|
@@ -4,43 +4,51 @@
|
|
|
4
4
|
//
|
|
5
5
|
// Copy this file, rename it for your source/destination pair, and register the
|
|
6
6
|
// exported pass handler in {outputDir}/khotan.ts.
|
|
7
|
+
//
|
|
8
|
+
// IMPORTANT — Workflow step structure:
|
|
9
|
+
// Declare "use step" functions at module top level and pass them only
|
|
10
|
+
// serializable values (the `ctx` object is plain data and is safe to pass).
|
|
11
|
+
// Do NOT nest step functions inside the "use workflow" function — the Workflow
|
|
12
|
+
// compiler cannot hoist closures that capture workflow scope, and they fail at
|
|
13
|
+
// runtime in the sandbox. Keep the workflow body limited to orchestration.
|
|
7
14
|
// ============================================================================
|
|
8
15
|
|
|
9
16
|
import { pass, type PassContext } from "./pass";
|
|
10
17
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
khotanRunId: ctx.khotanRunId,
|
|
19
|
-
});
|
|
18
|
+
// Step: full Node.js access, retried independently. Receives serializable ctx.
|
|
19
|
+
async function forwardEvent(ctx: PassContext) {
|
|
20
|
+
"use step";
|
|
21
|
+
console.log("Forwarding webhook event", {
|
|
22
|
+
eventType: ctx.eventType,
|
|
23
|
+
khotanRunId: ctx.khotanRunId,
|
|
24
|
+
});
|
|
20
25
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
},
|
|
26
|
+
await fetch("https://slack.com/api/chat.postMessage", {
|
|
27
|
+
method: "POST",
|
|
28
|
+
headers: {
|
|
29
|
+
Authorization: `Bearer ${ctx.destVars["botToken"] ?? ""}`,
|
|
30
|
+
"Content-Type": "application/json",
|
|
31
|
+
},
|
|
32
|
+
body: JSON.stringify({
|
|
33
|
+
channel: ctx.destVars["channelId"],
|
|
34
|
+
text: `Received ${ctx.eventType}`,
|
|
35
|
+
blocks: [
|
|
36
|
+
{
|
|
37
|
+
type: "section",
|
|
38
|
+
text: {
|
|
39
|
+
type: "mrkdwn",
|
|
40
|
+
text: `Received ${ctx.eventType} from Stripe`,
|
|
37
41
|
},
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
})
|
|
41
|
-
}
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
}),
|
|
45
|
+
});
|
|
46
|
+
}
|
|
42
47
|
|
|
43
|
-
|
|
48
|
+
// Workflow: orchestration only. Calls top-level steps with serializable args.
|
|
49
|
+
async function stripeToSlackWorkflow(ctx: PassContext) {
|
|
50
|
+
"use workflow";
|
|
51
|
+
await forwardEvent(ctx);
|
|
44
52
|
}
|
|
45
53
|
|
|
46
54
|
export const stripeToSlack = pass({
|
package/dist/templates/pass.ts
CHANGED
|
@@ -85,36 +85,41 @@ export function pass(config: PassConfig): PassRegistration {
|
|
|
85
85
|
// import { pass, type PassContext } from "./pass";
|
|
86
86
|
// import { plug } from "../plugs/plug";
|
|
87
87
|
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
88
|
+
// Declare "use step" functions at MODULE TOP LEVEL and pass them serializable
|
|
89
|
+
// values only (`ctx` is plain data). Do NOT nest steps inside the "use workflow"
|
|
90
|
+
// function — closures over workflow scope cannot be hoisted and fail at runtime.
|
|
90
91
|
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
//
|
|
92
|
+
// // Step: top-level, full Node.js access, retried independently.
|
|
93
|
+
// async function forwardEvent(ctx: PassContext) {
|
|
94
|
+
// "use step";
|
|
95
|
+
// const cache = khotanCache(ctx, "pollinate-forwarded-events");
|
|
96
|
+
// const eventId = String(ctx.event["id"] ?? "");
|
|
97
|
+
// if (eventId && (await cache.get<boolean>(eventId))) return;
|
|
96
98
|
//
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
//
|
|
99
|
+
// // Construct destination plug from destVars
|
|
100
|
+
// const slackPlug = plug({
|
|
101
|
+
// name: "slack",
|
|
102
|
+
// baseUrl: "https://hooks.slack.com",
|
|
103
|
+
// authType: "bearer",
|
|
104
|
+
// auth: { bearer: { token: ctx.destVars["token"] ?? "" } },
|
|
105
|
+
// });
|
|
104
106
|
//
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
//
|
|
107
|
+
// await slackPlug.post("/services/webhook", {
|
|
108
|
+
// body: {
|
|
109
|
+
// text: `Received ${ctx.eventType} event from pollinate`,
|
|
110
|
+
// event: ctx.event,
|
|
111
|
+
// },
|
|
112
|
+
// });
|
|
111
113
|
//
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
// }
|
|
114
|
+
// if (eventId) {
|
|
115
|
+
// await cache.set(eventId, true);
|
|
115
116
|
// }
|
|
117
|
+
// }
|
|
116
118
|
//
|
|
117
|
-
//
|
|
119
|
+
// // Workflow: orchestration only.
|
|
120
|
+
// async function pollinateToSlackWorkflow(ctx: PassContext) {
|
|
121
|
+
// "use workflow";
|
|
122
|
+
// await forwardEvent(ctx);
|
|
118
123
|
// }
|
|
119
124
|
//
|
|
120
125
|
// export const pollinateToSlack = pass({
|
|
@@ -5,6 +5,7 @@ import { Badge } from "@/components/ui/badge";
|
|
|
5
5
|
import { Button } from "@/components/ui/button";
|
|
6
6
|
import { Input } from "@/components/ui/input";
|
|
7
7
|
import { Label } from "@/components/ui/label";
|
|
8
|
+
import { khotanFetch, isKhotanApiError, ApiErrorState } from "./api-state";
|
|
8
9
|
|
|
9
10
|
// ============================================================================
|
|
10
11
|
// Plug Debugger — Lightweight Postman for your plugs
|
|
@@ -331,6 +332,7 @@ export function PlugDebugger({
|
|
|
331
332
|
}: PlugDebuggerProps) {
|
|
332
333
|
const [meta, setMeta] = useState<PlugMeta | null>(null);
|
|
333
334
|
const [loading, setLoading] = useState(true);
|
|
335
|
+
const [metaError, setMetaError] = useState<unknown>(null);
|
|
334
336
|
|
|
335
337
|
const [method, setMethod] = useState<string>("GET");
|
|
336
338
|
const [path, setPath] = useState("");
|
|
@@ -353,15 +355,17 @@ export function PlugDebugger({
|
|
|
353
355
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
354
356
|
|
|
355
357
|
const fetchMeta = useCallback(async () => {
|
|
358
|
+
setLoading(true);
|
|
359
|
+
setMetaError(null);
|
|
356
360
|
try {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
setMeta(null);
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
setMeta(await res.json());
|
|
363
|
-
} catch {
|
|
361
|
+
setMeta(await khotanFetch<PlugMeta>(`${basePath}/debug/${plugName}`));
|
|
362
|
+
} catch (err) {
|
|
364
363
|
setMeta(null);
|
|
364
|
+
// A 404 means debug is off or the plug isn't registered — handled by the
|
|
365
|
+
// dedicated "not available" message below. Surface everything else.
|
|
366
|
+
if (!(isKhotanApiError(err) && err.status === 404)) {
|
|
367
|
+
setMetaError(err);
|
|
368
|
+
}
|
|
365
369
|
} finally {
|
|
366
370
|
setLoading(false);
|
|
367
371
|
}
|
|
@@ -401,6 +405,10 @@ export function PlugDebugger({
|
|
|
401
405
|
);
|
|
402
406
|
}
|
|
403
407
|
|
|
408
|
+
if (metaError) {
|
|
409
|
+
return <ApiErrorState error={metaError} onRetry={() => void fetchMeta()} />;
|
|
410
|
+
}
|
|
411
|
+
|
|
404
412
|
if (!meta) {
|
|
405
413
|
return (
|
|
406
414
|
<div className="rounded-lg border border-border p-6 text-center">
|
|
@@ -4,62 +4,70 @@
|
|
|
4
4
|
//
|
|
5
5
|
// Copy this file, rename it for your source/destination pair, and register the
|
|
6
6
|
// exported flow in {outputDir}/khotan.ts.
|
|
7
|
+
//
|
|
8
|
+
// IMPORTANT — Workflow step structure:
|
|
9
|
+
// Declare "use step" functions at module top level and pass them only
|
|
10
|
+
// serializable values (the `ctx` object is plain data and is safe to pass).
|
|
11
|
+
// Do NOT nest step functions inside the "use workflow" function — the Workflow
|
|
12
|
+
// compiler cannot hoist closures that capture workflow scope, and they fail at
|
|
13
|
+
// runtime in the sandbox. Keep the workflow body limited to orchestration.
|
|
7
14
|
// ============================================================================
|
|
8
15
|
|
|
9
16
|
import { khotanCache } from "khotan-data/factory";
|
|
10
17
|
import { relay, type RelayContext } from "./relay";
|
|
11
18
|
|
|
12
|
-
|
|
13
|
-
|
|
19
|
+
// Step: full Node.js access, retried independently. Receives serializable ctx.
|
|
20
|
+
async function forwardProducts(ctx: RelayContext) {
|
|
21
|
+
"use step";
|
|
22
|
+
console.log("Starting relay", {
|
|
23
|
+
flow: ctx.flow.name,
|
|
24
|
+
to: ctx.flow.to,
|
|
25
|
+
khotanRunId: ctx.khotanRunId,
|
|
26
|
+
runType: ctx.runType,
|
|
27
|
+
});
|
|
14
28
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
29
|
+
const sourceResponse = await fetch("https://source.example.com/products", {
|
|
30
|
+
headers: {
|
|
31
|
+
Authorization: `Bearer ${ctx.vars["sourceToken"] ?? ""}`,
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
const payload = (await sourceResponse.json()) as {
|
|
35
|
+
data?: Array<Record<string, unknown>>;
|
|
36
|
+
};
|
|
37
|
+
const records = Array.isArray(payload.data) ? payload.data : [];
|
|
38
|
+
const snapshotCache = khotanCache(ctx, "shopify-products-snapshot");
|
|
39
|
+
const previousRecords =
|
|
40
|
+
(await snapshotCache.get<Array<Record<string, unknown>>>("latest")) ?? [];
|
|
41
|
+
|
|
42
|
+
await snapshotCache.set("latest", records, { ttl: "6h" });
|
|
23
43
|
|
|
24
|
-
|
|
44
|
+
for (const record of records) {
|
|
45
|
+
await fetch("https://destination.example.com/products", {
|
|
46
|
+
method: "POST",
|
|
25
47
|
headers: {
|
|
26
|
-
Authorization: `Bearer ${ctx.vars["
|
|
48
|
+
Authorization: `Bearer ${ctx.vars["destinationToken"] ?? ""}`,
|
|
49
|
+
"Content-Type": "application/json",
|
|
27
50
|
},
|
|
51
|
+
body: JSON.stringify(record),
|
|
28
52
|
});
|
|
29
|
-
const payload = (await sourceResponse.json()) as {
|
|
30
|
-
data?: Array<Record<string, unknown>>;
|
|
31
|
-
};
|
|
32
|
-
const records = Array.isArray(payload.data) ? payload.data : [];
|
|
33
|
-
const snapshotCache = khotanCache(ctx, "shopify-products-snapshot");
|
|
34
|
-
const previousRecords =
|
|
35
|
-
(await snapshotCache.get<Array<Record<string, unknown>>>("latest")) ?? [];
|
|
36
|
-
|
|
37
|
-
await snapshotCache.set("latest", records, { ttl: "6h" });
|
|
38
|
-
|
|
39
|
-
for (const record of records) {
|
|
40
|
-
await fetch("https://destination.example.com/products", {
|
|
41
|
-
method: "POST",
|
|
42
|
-
headers: {
|
|
43
|
-
Authorization: `Bearer ${ctx.vars["destinationToken"] ?? ""}`,
|
|
44
|
-
"Content-Type": "application/json",
|
|
45
|
-
},
|
|
46
|
-
body: JSON.stringify(record),
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return {
|
|
51
|
-
extracted: records.length,
|
|
52
|
-
transformed: records.length,
|
|
53
|
-
created: records.length,
|
|
54
|
-
metadata: {
|
|
55
|
-
relay: ctx.flow.name,
|
|
56
|
-
to: ctx.flow.to,
|
|
57
|
-
previousCount: previousRecords.length,
|
|
58
|
-
},
|
|
59
|
-
};
|
|
60
53
|
}
|
|
61
54
|
|
|
62
|
-
return
|
|
55
|
+
return {
|
|
56
|
+
extracted: records.length,
|
|
57
|
+
transformed: records.length,
|
|
58
|
+
created: records.length,
|
|
59
|
+
metadata: {
|
|
60
|
+
relay: ctx.flow.name,
|
|
61
|
+
to: ctx.flow.to,
|
|
62
|
+
previousCount: previousRecords.length,
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Workflow: orchestration only. Calls top-level steps with serializable args.
|
|
68
|
+
async function shopifyToHubspotWorkflow(ctx: RelayContext) {
|
|
69
|
+
"use workflow";
|
|
70
|
+
return forwardProducts(ctx);
|
|
63
71
|
}
|
|
64
72
|
|
|
65
73
|
export const shopifyToHubspotRelay = relay({
|
package/dist/templates/relay.ts
CHANGED
|
@@ -51,48 +51,53 @@ export function relay(config: RelayConfig): FlowRegistration {
|
|
|
51
51
|
// Usage Example (create a file like flows/shopify-to-hubspot.ts)
|
|
52
52
|
// ---------------------------------------------------------------------------
|
|
53
53
|
//
|
|
54
|
+
// Declare "use step" functions at MODULE TOP LEVEL and pass them serializable
|
|
55
|
+
// values only (`ctx` is plain data). Do NOT nest steps inside the "use workflow"
|
|
56
|
+
// function — closures over workflow scope cannot be hoisted and fail at runtime.
|
|
57
|
+
//
|
|
54
58
|
// import { bindWorkflowPlug, khotanCache, relay, type RelayContext } from "khotan-data/factory";
|
|
55
59
|
// import { shopifyPlug } from "../plugs/shopify";
|
|
56
60
|
// import { hubspotPlug } from "../plugs/hubspot";
|
|
57
61
|
//
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
// const shopify = bindWorkflowPlug(shopifyPlug, ctx);
|
|
70
|
-
// const hubspot = bindWorkflowPlug(hubspotPlug, ctx, "hubspot");
|
|
71
|
-
//
|
|
72
|
-
// const snapshotCache = khotanCache(ctx, "shopify-products-snapshot");
|
|
73
|
-
// const previous = await snapshotCache.get<Array<Record<string, unknown>>>("latest");
|
|
62
|
+
// // Step: top-level, full Node.js access, retried independently.
|
|
63
|
+
// async function forwardProducts(ctx: RelayContext) {
|
|
64
|
+
// "use step";
|
|
65
|
+
// console.log("Starting relay", {
|
|
66
|
+
// flow: ctx.flow.name,
|
|
67
|
+
// to: ctx.flow.to,
|
|
68
|
+
// khotanRunId: ctx.khotanRunId,
|
|
69
|
+
// runType: ctx.runType,
|
|
70
|
+
// });
|
|
71
|
+
// const shopify = bindWorkflowPlug(shopifyPlug, ctx);
|
|
72
|
+
// const hubspot = bindWorkflowPlug(hubspotPlug, ctx, "hubspot");
|
|
74
73
|
//
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
// await snapshotCache.set("latest", records);
|
|
74
|
+
// const snapshotCache = khotanCache(ctx, "shopify-products-snapshot");
|
|
75
|
+
// const previous = await snapshotCache.get<Array<Record<string, unknown>>>("latest");
|
|
78
76
|
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
77
|
+
// const response = await shopify.get<{ data?: Array<Record<string, unknown>> }>("/products");
|
|
78
|
+
// const records = Array.isArray(response.data) ? response.data : [];
|
|
79
|
+
// await snapshotCache.set("latest", records);
|
|
82
80
|
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
// transformed: records.length,
|
|
86
|
-
// created: records.length,
|
|
87
|
-
// metadata: {
|
|
88
|
-
// relay: ctx.flow.name,
|
|
89
|
-
// to: ctx.flow.to,
|
|
90
|
-
// previousCount: previous?.length ?? 0,
|
|
91
|
-
// },
|
|
92
|
-
// };
|
|
81
|
+
// for (const record of records) {
|
|
82
|
+
// await hubspot.post("/products", { body: record });
|
|
93
83
|
// }
|
|
94
84
|
//
|
|
95
|
-
// return
|
|
85
|
+
// return {
|
|
86
|
+
// extracted: records.length,
|
|
87
|
+
// transformed: records.length,
|
|
88
|
+
// created: records.length,
|
|
89
|
+
// metadata: {
|
|
90
|
+
// relay: ctx.flow.name,
|
|
91
|
+
// to: ctx.flow.to,
|
|
92
|
+
// previousCount: previous?.length ?? 0,
|
|
93
|
+
// },
|
|
94
|
+
// };
|
|
95
|
+
// }
|
|
96
|
+
//
|
|
97
|
+
// // Workflow: orchestration only.
|
|
98
|
+
// async function shopifyToHubspotWorkflow(ctx: RelayContext) {
|
|
99
|
+
// "use workflow";
|
|
100
|
+
// return forwardProducts(ctx);
|
|
96
101
|
// }
|
|
97
102
|
//
|
|
98
103
|
// export const shopifyToHubspotRelay = relay({
|