@donkeylabs/cli 0.5.0 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,197 @@
1
+ // Workflow Demo Plugin - Demonstrates step function orchestration
2
+ import { createPlugin, workflow, type WorkflowContext, type CoreServices } from "@donkeylabs/server";
3
+ import { z } from "zod";
4
+
5
+ // Helper to simulate async work with delay
6
+ const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
7
+
8
+ // Input/output types for better type safety
9
+ type OrderInput = {
10
+ orderId: string;
11
+ items: { name: string; qty: number }[];
12
+ customerEmail: string;
13
+ };
14
+
15
+ type ValidateOutput = { valid: boolean; total: number; itemCount: number };
16
+ type PaymentOutput = { paymentId: string; status: string };
17
+
18
+ // Define an example order processing workflow with placeholder tasks
19
+ export const orderWorkflow = workflow("process-order")
20
+ .timeout(60000) // 1 minute max
21
+ .defaultRetry({ maxAttempts: 2 })
22
+
23
+ // Step 1: Validate Order
24
+ .task("validate", {
25
+ inputSchema: z.object({
26
+ orderId: z.string(),
27
+ items: z.array(z.object({ name: z.string(), qty: z.number() })),
28
+ customerEmail: z.string().email(),
29
+ }),
30
+ outputSchema: z.object({
31
+ valid: z.boolean(),
32
+ total: z.number(),
33
+ itemCount: z.number(),
34
+ }),
35
+ handler: async (input: OrderInput, ctx: { core: CoreServices }): Promise<ValidateOutput> => {
36
+ ctx.core.logger.info("Validating order", { orderId: input.orderId });
37
+ await sleep(1000); // Simulate validation work
38
+ const total = input.items.reduce((sum: number, item: { qty: number }) => sum + item.qty * 10, 0);
39
+ return { valid: true, total, itemCount: input.items.length };
40
+ },
41
+ })
42
+
43
+ // Step 2: Process Payment
44
+ .task("payment", {
45
+ inputSchema: (prev: ValidateOutput, workflowInput: OrderInput) => ({
46
+ orderId: workflowInput.orderId,
47
+ amount: prev.total,
48
+ email: workflowInput.customerEmail,
49
+ }),
50
+ outputSchema: z.object({
51
+ paymentId: z.string(),
52
+ status: z.string(),
53
+ }),
54
+ handler: async (input: { orderId: string; amount: number; email: string }, ctx: { core: CoreServices }): Promise<PaymentOutput> => {
55
+ ctx.core.logger.info("Processing payment", {
56
+ orderId: input.orderId,
57
+ amount: input.amount,
58
+ });
59
+ await sleep(2000); // Simulate payment processing
60
+ return {
61
+ paymentId: `PAY-${Date.now().toString(36).toUpperCase()}`,
62
+ status: "completed",
63
+ };
64
+ },
65
+ })
66
+
67
+ // Step 3: Parallel - Send Notification + Prepare Shipment
68
+ .parallel("fulfill", {
69
+ branches: [
70
+ workflow
71
+ .branch("notification")
72
+ .task("send-email", {
73
+ handler: async (_input: unknown, ctx: { core: CoreServices }) => {
74
+ ctx.core.logger.info("Sending confirmation email");
75
+ await sleep(800); // Simulate email send
76
+ return { emailSent: true, sentAt: new Date().toISOString() };
77
+ },
78
+ })
79
+ .build(),
80
+
81
+ workflow
82
+ .branch("shipping")
83
+ .task("prepare-shipment", {
84
+ handler: async (_input: unknown, ctx: { core: CoreServices }) => {
85
+ ctx.core.logger.info("Preparing shipment");
86
+ await sleep(1500); // Simulate shipment prep
87
+ return {
88
+ trackingId: `SHIP-${Date.now().toString(36).toUpperCase()}`,
89
+ carrier: "FastShip",
90
+ estimatedDelivery: new Date(
91
+ Date.now() + 3 * 24 * 60 * 60 * 1000
92
+ ).toISOString(),
93
+ };
94
+ },
95
+ })
96
+ .build(),
97
+ ],
98
+ next: "complete",
99
+ })
100
+
101
+ // Step 4: Complete
102
+ .pass("complete", {
103
+ transform: (ctx: WorkflowContext) => ({
104
+ orderId: ctx.input.orderId,
105
+ paymentId: ctx.steps["payment"].paymentId,
106
+ tracking: ctx.steps["fulfill"].shipping["prepare-shipment"].trackingId,
107
+ emailSent: ctx.steps["fulfill"].notification["send-email"].emailSent,
108
+ completedAt: new Date().toISOString(),
109
+ }),
110
+ end: true,
111
+ })
112
+ .build();
113
+
114
+ // Plugin that registers the workflow and provides service methods
115
+ export const workflowDemoPlugin = createPlugin.define({
116
+ name: "workflowDemo",
117
+ service: async (ctx) => ({
118
+ // Start a new order processing workflow
119
+ startOrder: async (input: {
120
+ orderId: string;
121
+ items: { name: string; qty: number }[];
122
+ customerEmail: string;
123
+ }) => {
124
+ const instanceId = await ctx.core.workflows.start("process-order", input);
125
+ return { instanceId };
126
+ },
127
+
128
+ // Get workflow instance status
129
+ getStatus: async (instanceId: string) => {
130
+ const instance = await ctx.core.workflows.getInstance(instanceId);
131
+ if (!instance) return null;
132
+
133
+ return {
134
+ id: instance.id,
135
+ status: instance.status,
136
+ currentStep: instance.currentStep,
137
+ input: instance.input,
138
+ output: instance.output,
139
+ error: instance.error,
140
+ stepResults: instance.stepResults,
141
+ createdAt: instance.createdAt.toISOString(),
142
+ startedAt: instance.startedAt?.toISOString(),
143
+ completedAt: instance.completedAt?.toISOString(),
144
+ };
145
+ },
146
+
147
+ // List all workflow instances
148
+ listInstances: async (status?: string) => {
149
+ const instances = await ctx.core.workflows.getInstances(
150
+ "process-order",
151
+ status as any
152
+ );
153
+ return instances.map((i) => ({
154
+ id: i.id,
155
+ status: i.status,
156
+ currentStep: i.currentStep,
157
+ createdAt: i.createdAt.toISOString(),
158
+ completedAt: i.completedAt?.toISOString(),
159
+ }));
160
+ },
161
+
162
+ // Cancel a running workflow
163
+ cancel: async (instanceId: string) => {
164
+ const success = await ctx.core.workflows.cancel(instanceId);
165
+ return { success };
166
+ },
167
+ }),
168
+
169
+ init: async (ctx) => {
170
+ // Register the workflow definition
171
+ ctx.core.workflows.register(orderWorkflow);
172
+
173
+ // Broadcast workflow events to SSE for real-time UI updates
174
+ const workflowEvents = [
175
+ "workflow.started",
176
+ "workflow.progress",
177
+ "workflow.completed",
178
+ "workflow.failed",
179
+ "workflow.cancelled",
180
+ "workflow.step.started",
181
+ "workflow.step.completed",
182
+ "workflow.step.failed",
183
+ ];
184
+
185
+ for (const event of workflowEvents) {
186
+ ctx.core.events.on(event, (data: any) => {
187
+ // Broadcast to workflow-specific channel
188
+ ctx.core.sse.broadcast(`workflow:${data.instanceId}`, event, data);
189
+
190
+ // Also broadcast to general workflow-updates channel for the demo list
191
+ ctx.core.sse.broadcast("workflow-updates", event, data);
192
+ });
193
+ }
194
+
195
+ ctx.core.logger.info("Workflow demo plugin initialized with order workflow");
196
+ },
197
+ });
@@ -234,4 +234,86 @@ demo.route("cron.list").typed(
234
234
  })
235
235
  );
236
236
 
237
+ // =============================================================================
238
+ // WORKFLOWS - Step function orchestration (via workflowDemo plugin)
239
+ // =============================================================================
240
+
241
+ demo.route("workflow.start").typed(
242
+ defineRoute({
243
+ input: z.object({
244
+ orderId: z.string().default(() => `ORD-${Date.now().toString(36).toUpperCase()}`),
245
+ items: z
246
+ .array(z.object({ name: z.string(), qty: z.number() }))
247
+ .default([
248
+ { name: "Widget A", qty: 2 },
249
+ { name: "Gadget B", qty: 1 },
250
+ ]),
251
+ customerEmail: z.string().email().default("demo@example.com"),
252
+ }),
253
+ output: z.object({ instanceId: z.string() }),
254
+ handle: async (input, ctx) => {
255
+ return ctx.plugins.workflowDemo.startOrder({
256
+ orderId: input.orderId ?? `ORD-${Date.now().toString(36).toUpperCase()}`,
257
+ items: input.items ?? [
258
+ { name: "Widget A", qty: 2 },
259
+ { name: "Gadget B", qty: 1 },
260
+ ],
261
+ customerEmail: input.customerEmail ?? "demo@example.com",
262
+ });
263
+ },
264
+ })
265
+ );
266
+
267
+ demo.route("workflow.status").typed(
268
+ defineRoute({
269
+ input: z.object({ instanceId: z.string() }),
270
+ output: z.object({
271
+ id: z.string(),
272
+ status: z.string(),
273
+ currentStep: z.string().optional(),
274
+ input: z.any(),
275
+ output: z.any().optional(),
276
+ error: z.string().optional(),
277
+ stepResults: z.record(z.any()),
278
+ createdAt: z.string(),
279
+ startedAt: z.string().optional(),
280
+ completedAt: z.string().optional(),
281
+ }).nullable(),
282
+ handle: async (input, ctx) => {
283
+ return ctx.plugins.workflowDemo.getStatus(input.instanceId);
284
+ },
285
+ })
286
+ );
287
+
288
+ demo.route("workflow.list").typed(
289
+ defineRoute({
290
+ input: z.object({ status: z.string().optional() }),
291
+ output: z.object({
292
+ instances: z.array(
293
+ z.object({
294
+ id: z.string(),
295
+ status: z.string(),
296
+ currentStep: z.string().optional(),
297
+ createdAt: z.string(),
298
+ completedAt: z.string().optional(),
299
+ })
300
+ ),
301
+ }),
302
+ handle: async (input, ctx) => {
303
+ const instances = await ctx.plugins.workflowDemo.listInstances(input.status);
304
+ return { instances };
305
+ },
306
+ })
307
+ );
308
+
309
+ demo.route("workflow.cancel").typed(
310
+ defineRoute({
311
+ input: z.object({ instanceId: z.string() }),
312
+ output: z.object({ success: z.boolean() }),
313
+ handle: async (input, ctx) => {
314
+ return ctx.plugins.workflowDemo.cancel(input.instanceId);
315
+ },
316
+ })
317
+ );
318
+
237
319
  export default demo;