Skip to content

Commit f0e8f82

Browse files
authored
step handler (#56)
* step handler * step logging * better dx * better dx * workflow complete
1 parent 7b15741 commit f0e8f82

File tree

22 files changed

+678
-494
lines changed

22 files changed

+678
-494
lines changed

CONTRIBUTING.md

Lines changed: 65 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -361,25 +361,30 @@ export async function testMyIntegration(credentials: Record<string, string>) {
361361

362362
**File:** `plugins/my-integration/steps/send-message/step.ts`
363363

364-
This runs on the server during workflow execution:
364+
This runs on the server during workflow execution. Steps use the `withStepLogging` wrapper to automatically log execution for the workflow builder UI:
365365

366366
```typescript
367367
import "server-only";
368368

369369
import { fetchCredentials } from "@/lib/credential-fetcher";
370+
import { type StepInput, withStepLogging } from "@/lib/steps/step-handler";
370371
import { getErrorMessage } from "@/lib/utils";
371372

372-
/**
373-
* Send Message Step
374-
* Sends a message using My Integration API
375-
*/
376-
export async function sendMessageStep(input: {
373+
type SendMessageResult =
374+
| { success: true; id: string; url: string }
375+
| { success: false; error: string };
376+
377+
// Extend StepInput to get automatic logging context
378+
export type SendMessageInput = StepInput & {
377379
integrationId?: string;
378380
message: string;
379381
channel: string;
380-
}) {
381-
"use step";
382+
};
382383

384+
/**
385+
* Send message logic - separated for clarity and testability
386+
*/
387+
async function sendMessage(input: SendMessageInput): Promise<SendMessageResult> {
383388
const credentials = input.integrationId
384389
? await fetchCredentials(input.integrationId)
385390
: {};
@@ -414,9 +419,9 @@ export async function sendMessageStep(input: {
414419
const result = await response.json();
415420

416421
return {
422+
success: true,
417423
id: result.id,
418424
url: result.url,
419-
success: true,
420425
};
421426
} catch (error) {
422427
return {
@@ -425,8 +430,26 @@ export async function sendMessageStep(input: {
425430
};
426431
}
427432
}
433+
434+
/**
435+
* Send Message Step
436+
* Sends a message using My Integration API
437+
*/
438+
export async function sendMessageStep(
439+
input: SendMessageInput
440+
): Promise<SendMessageResult> {
441+
"use step";
442+
return withStepLogging(input, () => sendMessage(input));
443+
}
428444
```
429445

446+
**Key Points:**
447+
448+
1. **Extend `StepInput`**: Your input type should extend `StepInput` to include the optional `_context` for logging
449+
2. **Separate logic function**: Keep the actual logic in a separate function for clarity and testability
450+
3. **Wrap with `withStepLogging`**: The step function just wraps the logic with `withStepLogging(input, () => logic(input))`
451+
4. **Return success/error objects**: Steps should return `{ success: true, ... }` or `{ success: false, error: "..." }`
452+
430453
#### Step 6: Create Config UI Component
431454

432455
**File:** `plugins/my-integration/steps/send-message/config.tsx`
@@ -756,21 +779,42 @@ Use `TemplateBadgeInput` to allow users to reference outputs from other workflow
756779
/>
757780
```
758781

759-
### Pattern 2: Step Function Error Handling
782+
### Pattern 2: Step Function Structure
783+
784+
Steps follow a consistent structure with logging:
760785

761786
```typescript
762-
try {
763-
const response = await fetch(/* ... */);
764-
if (!response.ok) {
765-
throw new Error(`API error: ${response.statusText}`);
787+
import "server-only";
788+
789+
import { type StepInput, withStepLogging } from "@/lib/steps/step-handler";
790+
791+
type MyResult = { success: true; data: string } | { success: false; error: string };
792+
793+
export type MyInput = StepInput & {
794+
field1: string;
795+
};
796+
797+
// 1. Logic function (no "use step" needed)
798+
async function myLogic(input: MyInput): Promise<MyResult> {
799+
try {
800+
const response = await fetch(/* ... */);
801+
if (!response.ok) {
802+
throw new Error(`API error: ${response.statusText}`);
803+
}
804+
const result = await response.json();
805+
return { success: true, data: result };
806+
} catch (error) {
807+
return {
808+
success: false,
809+
error: `Failed to execute: ${getErrorMessage(error)}`,
810+
};
766811
}
767-
const result = await response.json();
768-
return { success: true, ...result };
769-
} catch (error) {
770-
return {
771-
success: false,
772-
error: `Failed to execute: ${getErrorMessage(error)}`,
773-
};
812+
}
813+
814+
// 2. Step wrapper (has "use step", wraps with logging)
815+
export async function myStep(input: MyInput): Promise<MyResult> {
816+
"use step";
817+
return withStepLogging(input, () => myLogic(input));
774818
}
775819
```
776820

0 commit comments

Comments
 (0)