File size: 4,491 Bytes
e706de2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
/**

 * Exercise 1: Build a Simple Logging Callback

 *

 * Goal: Understand the basic callback lifecycle

 *

 * In this exercise, you'll:

 * 1. Extend BaseCallback to create a custom logger

 * 2. Implement onStart, onEnd, and onError methods

 * 3. Format output with emoji indicators

 * 4. See how callbacks observe Runnable execution

 *

 * This is the foundation of observability in your framework!

 */

import {Runnable} from '../../../../src/index.js';
import {BaseCallback} from '../../../../src/utils/callbacks.js';

// TODO: Create SimpleLoggerCallback that extends BaseCallback
class SimpleLoggerCallback extends BaseCallback {
    constructor(options = {}) {
        super();
        this.showTimestamp = options.showTimestamp ?? false;
    }

    // TODO: Implement onStart - log when runnable starts
    // Use emoji: ▶️
    // Log to console: runnable name and input
    async onStart(runnable, input, config) {
        // Your code here
    }

    // TODO: Implement onEnd - log when runnable completes
    // Use emoji: ✔️
    // Log to console: runnable name and output
    async onEnd(runnable, output, config) {
        // Your code here
    }

    // TODO: Implement onError - log when runnable errors
    // Use emoji: ❌
    // Log to console: runnable name and error message
    async onError(runnable, error, config) {
        // Your code here
    }
}

// Test Runnables
class GreeterRunnable extends Runnable {
    async _call(input, config) {
        return `Hello, ${input}!`;
    }
}

class UpperCaseRunnable extends Runnable {
    async _call(input, config) {
        if (typeof input !== 'string') {
            throw new Error('Input must be a string');
        }
        return input.toUpperCase();
    }
}

class ErrorRunnable extends Runnable {
    async _call(input, config) {
        throw new Error('Intentional error for testing');
    }
}

// TODO: Test your callback
async function exercise1() {
    console.log('=== Exercise 1: Simple Logging Callback ===\n');

    // TODO: Create an instance of your SimpleLoggerCallback
    const logger = null; // Replace with your code

    const config = {
        callbacks: [logger]
    };

    // Test 1: Normal execution
    console.log('--- Test 1: Normal Execution ---');
    const greeter = new GreeterRunnable();
    // TODO: Invoke greeter with "World" and config
    const result1 = null; // Replace with your code
    console.log('Final result:', result1);
    console.log();

    // Test 2: Pipeline
    console.log('--- Test 2: Pipeline ---');
    const upper = new UpperCaseRunnable();
    const pipeline = greeter.pipe(upper);
    // TODO: Invoke pipeline with "claude" and config
    const result2 = null; // Replace with your code
    console.log('Final result:', result2);
    console.log();

    // Test 3: Error handling
    console.log('--- Test 3: Error Handling ---');
    const errorRunnable = new ErrorRunnable();
    try {
        // TODO: Invoke errorRunnable with "test" and config
        // Replace with your code
    } catch (error) {
        console.log('Caught error (expected):', error.message);
    }

    console.log('\n✓ Exercise 1 complete!');
}

// Run the exercise
exercise1().catch(console.error);

/**

 * Expected Output:

 *

 * --- Test 1: Normal Execution ---

 * ▶️ Starting: GreeterRunnable

 *    Input: World

 * ✔️ Completed: GreeterRunnable

 *    Output: Hello, World!

 * Final result: Hello, World!

 *

 * --- Test 2: Pipeline ---

 * ▶️  Starting: RunnableSequence

 *    Input: claude

 * ▶️  Starting: GreeterRunnable

 *    Input: claude

 * ✔️  Completed: GreeterRunnable

 *    Output: Hello, claude!

 * ▶️  Starting: UpperCaseRunnable

 *    Input: Hello, claude!

 * ✔️  Completed: UpperCaseRunnable

 *    Output: HELLO, CLAUDE!

 * ✔️  Completed: RunnableSequence

 *    Output: HELLO, CLAUDE!

 * Final result: HELLO, CLAUDE!

 *

 * --- Test 3: Error Handling ---

 * ▶️ Starting: ErrorRunnable

 *    Input: test

 * ❌ ErrorRunnable: Intentional error for testing

 * Caught error (expected): Intentional error for testing

 *

 * Learning Points:

 * 1. Callbacks see every step in execution

 * 2. onStart fires before _call()

 * 3. onEnd fires after successful _call()

 * 4. onError fires when _call() throws error

 * 5. Callbacks don't change the output - they just observe

 */