|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import {Runnable} from '../../../../src/index.js';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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})`;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ObjectWrapperRunnable extends Runnable {
|
|
|
constructor(key = 'result') {
|
|
|
super();
|
|
|
this.key = key;
|
|
|
}
|
|
|
|
|
|
async _call(input, config) {
|
|
|
|
|
|
return { [this.key]: input };
|
|
|
}
|
|
|
|
|
|
toString() {
|
|
|
return `Wrap({${this.key}: ...})`;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class JsonStringifyRunnable extends Runnable {
|
|
|
constructor(options = {}) {
|
|
|
super();
|
|
|
this.indent = options.indent ?? 0;
|
|
|
}
|
|
|
|
|
|
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)';
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function createPipeline(factor = 3) {
|
|
|
const multiplier = new MultiplierRunnable(factor);
|
|
|
const wrapper = new ObjectWrapperRunnable('result');
|
|
|
const stringifier = new JsonStringifyRunnable();
|
|
|
|
|
|
|
|
|
return multiplier
|
|
|
.pipe(wrapper)
|
|
|
.pipe(stringifier);
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function createReversePipeline(factor = 2) {
|
|
|
|
|
|
class JsonParserRunnable extends Runnable {
|
|
|
async _call(input, config) {
|
|
|
return JSON.parse(input);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
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);
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function runTests() {
|
|
|
console.log('π§ͺ Testing Pipeline Composition Solution...\n');
|
|
|
|
|
|
try {
|
|
|
|
|
|
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');
|
|
|
|
|
|
|
|
|
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');
|
|
|
|
|
|
|
|
|
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');
|
|
|
|
|
|
|
|
|
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');
|
|
|
|
|
|
|
|
|
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');
|
|
|
|
|
|
|
|
|
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)
|
|
|
.pipe(new AddConstantRunnable(5))
|
|
|
.pipe(new MultiplierRunnable(3))
|
|
|
.pipe(new ObjectWrapperRunnable('finalResult'))
|
|
|
.pipe(new JsonStringifyRunnable());
|
|
|
|
|
|
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');
|
|
|
|
|
|
|
|
|
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');
|
|
|
|
|
|
|
|
|
console.log('Test 8: Error propagation');
|
|
|
try {
|
|
|
const errorPipeline = createPipeline(5);
|
|
|
await errorPipeline.invoke('not a number');
|
|
|
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);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
runTests();
|
|
|
}
|
|
|
|
|
|
export {
|
|
|
MultiplierRunnable,
|
|
|
ObjectWrapperRunnable,
|
|
|
JsonStringifyRunnable,
|
|
|
createPipeline,
|
|
|
createReversePipeline
|
|
|
}; |