File size: 9,831 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 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 |
/**
* Solution 3: Pipeline Composition
*
* This solution demonstrates:
* - Creating multiple Runnable components
* - Composing them with pipe()
* - Building reusable pipelines
* - Testing each component individually
*/
import {Runnable} from '../../../../src/index.js';
/**
* MultiplierRunnable - Multiplies by a factor
*/
class MultiplierRunnable extends Runnable {
constructor(factor) {
super();
this.factor = factor;
}
async _call(input, config) {
if (typeof input !== 'number') {
throw new Error('Input must be a number');
}
return input * this.factor;
}
toString() {
return `Multiply(Γ${this.factor})`;
}
}
/**
* ObjectWrapperRunnable - Wraps value in an object
*/
class ObjectWrapperRunnable extends Runnable {
constructor(key = 'result') {
super();
this.key = key;
}
async _call(input, config) {
// Wrap any input in an object with the specified key
return { [this.key]: input };
}
toString() {
return `Wrap({${this.key}: ...})`;
}
}
/**
* JsonStringifyRunnable - Converts object to JSON string
*/
class JsonStringifyRunnable extends Runnable {
constructor(options = {}) {
super();
this.indent = options.indent ?? 0; // 0 = compact, 2 = pretty
}
async _call(input, config) {
try {
if (this.indent > 0) {
return JSON.stringify(input, null, this.indent);
}
return JSON.stringify(input);
} catch (error) {
throw new Error(`Failed to stringify: ${error.message}`);
}
}
toString() {
return 'Stringify(JSON)';
}
}
// ============================================================================
// Pipeline Creation
// ============================================================================
/**
* Create a pipeline: number β multiply β wrap β stringify
*/
function createPipeline(factor = 3) {
const multiplier = new MultiplierRunnable(factor);
const wrapper = new ObjectWrapperRunnable('result');
const stringifier = new JsonStringifyRunnable();
// Compose the pipeline using pipe()
return multiplier
.pipe(wrapper)
.pipe(stringifier);
}
/**
* Bonus: Create a reverse pipeline (parse β extract β process)
*/
function createReversePipeline(factor = 2) {
// Parse JSON string
class JsonParserRunnable extends Runnable {
async _call(input, config) {
return JSON.parse(input);
}
}
// Extract a value from object
class ExtractorRunnable extends Runnable {
constructor(key) {
super();
this.key = key;
}
async _call(input, config) {
return input[this.key];
}
}
const parser = new JsonParserRunnable();
const extractor = new ExtractorRunnable('result');
const multiplier = new MultiplierRunnable(factor);
const wrapper = new ObjectWrapperRunnable('doubled');
const stringifier = new JsonStringifyRunnable();
return parser
.pipe(extractor)
.pipe(multiplier)
.pipe(wrapper)
.pipe(stringifier);
}
// ============================================================================
// Tests
// ============================================================================
async function runTests() {
console.log('π§ͺ Testing Pipeline Composition Solution...\n');
try {
// Test 1: Basic pipeline
console.log('Test 1: Basic pipeline (multiply β wrap β stringify)');
const pipeline = createPipeline(3);
console.log(` Pipeline: ${pipeline.toString()}`);
const result1 = await pipeline.invoke(5);
console.log(` Input: 5`);
console.log(` Output: ${result1}`);
console.assert(result1 === '{"result":15}', `Expected '{"result":15}', got '${result1}'`);
console.log('β
Pipeline works!\n');
// Test 2: Different factor
console.log('Test 2: Different factor');
const pipeline2 = createPipeline(10);
const result2 = await pipeline2.invoke(4);
console.log(` Input: 4`);
console.log(` Output: ${result2}`);
console.assert(result2 === '{"result":40}', `Expected '{"result":40}', got '${result2}'`);
console.log('β
Works with different factors!\n');
// Test 3: Pipeline with batch
console.log('Test 3: Batch processing through pipeline');
const pipeline3 = createPipeline(2);
const results3 = await pipeline3.batch([1, 2, 3]);
console.log(` Inputs: [1, 2, 3]`);
console.log(` Outputs:`);
results3.forEach((r, i) => console.log(` [${i}]: ${r}`));
console.assert(results3[0] === '{"result":2}', 'First result should be correct');
console.assert(results3[1] === '{"result":4}', 'Second result should be correct');
console.assert(results3[2] === '{"result":6}', 'Third result should be correct');
console.log('β
Batch processing works!\n');
// Test 4: Individual components
console.log('Test 4: Testing individual components');
const multiplier = new MultiplierRunnable(5);
const wrapper = new ObjectWrapperRunnable();
const stringifier = new JsonStringifyRunnable();
const step1 = await multiplier.invoke(3);
console.log(` After multiply: ${step1}`);
console.assert(step1 === 15, 'Multiplier should work');
const step2 = await wrapper.invoke(step1);
console.log(` After wrap: ${JSON.stringify(step2)}`);
console.assert(step2.result === 15, 'Wrapper should work');
const step3 = await stringifier.invoke(step2);
console.log(` After stringify: ${step3}`);
console.assert(step3 === '{"result":15}', 'Stringifier should work');
console.log('β
All components work individually!\n');
// Test 5: Pretty printing
console.log('Test 5: Pretty printing with indent');
const prettyStringifier = new JsonStringifyRunnable({ indent: 2 });
const prettyPipeline = new MultiplierRunnable(5)
.pipe(new ObjectWrapperRunnable())
.pipe(prettyStringifier);
const result5 = await prettyPipeline.invoke(3);
console.log(' Output with indent:');
console.log(result5);
console.assert(result5.includes('\n'), 'Should have newlines');
console.log('β
Pretty printing works!\n');
// Test 6: Complex pipeline with multiple steps
console.log('Test 6: Complex pipeline (5 steps)');
class AddConstantRunnable extends Runnable {
constructor(constant) {
super();
this.constant = constant;
}
async _call(input, config) {
return input + this.constant;
}
}
const complexPipeline = new MultiplierRunnable(2) // 5 * 2 = 10
.pipe(new AddConstantRunnable(5)) // 10 + 5 = 15
.pipe(new MultiplierRunnable(3)) // 15 * 3 = 45
.pipe(new ObjectWrapperRunnable('finalResult')) // { finalResult: 45 }
.pipe(new JsonStringifyRunnable()); // JSON string
const result6 = await complexPipeline.invoke(5);
console.log(` Input: 5`);
console.log(` Steps: Γ2 β +5 β Γ3 β wrap β stringify`);
console.log(` Output: ${result6}`);
console.assert(result6 === '{"finalResult":45}', 'Complex pipeline should work');
console.log('β
Complex pipeline works!\n');
// Test 7: Bonus - Reverse pipeline
console.log('Test 7: Bonus - Reverse pipeline (parse β extract β process)');
const reversePipeline = createReversePipeline(2);
const result7 = await reversePipeline.invoke('{"result":10}');
console.log(` Input: '{"result":10}'`);
console.log(` Steps: parse β extract β Γ2 β wrap β stringify`);
console.log(` Output: ${result7}`);
console.assert(result7 === '{"doubled":20}', 'Reverse pipeline should work');
console.log('β
Reverse pipeline works!\n');
// Test 8: Error propagation through pipeline
console.log('Test 8: Error propagation');
try {
const errorPipeline = createPipeline(5);
await errorPipeline.invoke('not a number'); // Should fail at multiply step
console.error('β Should have thrown error');
} catch (error) {
console.log(` Caught error: ${error.message}`);
console.log('β
Errors propagate correctly!\n');
}
console.log('π All tests passed!');
console.log('\nπ‘ Key Learnings:');
console.log(' β’ Runnables can be composed with pipe()');
console.log(' β’ Each step transforms the data');
console.log(' β’ Pipelines are themselves Runnables');
console.log(' β’ Errors propagate through the pipeline');
console.log(' β’ You can test each step individually');
} catch (error) {
console.error('β Test failed:', error.message);
console.error(error.stack);
}
}
// Run tests
if (import.meta.url === `file://${process.argv[1]}`) {
runTests();
}
export {
MultiplierRunnable,
ObjectWrapperRunnable,
JsonStringifyRunnable,
createPipeline,
createReversePipeline
}; |