/** * Exercise 15: Config Merging and Child Configs * * Goal: Understand how configs inherit and merge * * In this exercise, you'll: * 1. Create parent and child configs * 2. See how child configs inherit from parents * 3. Understand callback accumulation * 4. Learn when to use config.merge() vs config.child() * * This is crucial for nested Runnable calls! */ import {RunnableConfig} from '../../../../src/core/context.js'; import {Runnable} from '../../../../src/index.js'; import {BaseCallback} from '../../../../src/utils/callbacks.js'; // Simple callback to show when it's called class TagLoggerCallback extends BaseCallback { constructor(name) { super(); this.name = name; } async onStart(runnable, input, config) { console.log(`[${this.name}] Starting ${runnable.name}`); console.log(` Tags: [${config.tags.join(', ')}]`); console.log(` Callback count: ${config.callbacks.length}`); } async onEnd(runnable, output, config) { console.log(`[${this.name}] Completed ${runnable.name}`); } } // Test Runnables that create child configs class Step1Runnable extends Runnable { async _call(input, config) { console.log('\n--- Inside Step1 ---'); // TODO: Create a child config with additional tag const childConfig = null; // Use config.child({ tags: ['step1'] }) // TODO: Log how many callbacks childConfig has console.log(`Step1 child has ___ callbacks`); // Simulate nested work return `Step1(${input})`; } } class Step2Runnable extends Runnable { async _call(input, config) { console.log('\n--- Inside Step2 ---'); // TODO: Create a child config with different tag const childConfig = null; // Use config.child({ tags: ['step2'] }) // TODO: Log how many callbacks childConfig has console.log(`Step2 child has ___ callbacks`); return `Step2(${input})`; } } class Step3Runnable extends Runnable { async _call(input, config) { console.log('\n--- Inside Step3 ---'); // TODO: Create a child config with: // - Additional tag: 'step3' // - Additional metadata: { nested: true } const childConfig = null; // Use config.child({ ... }) // TODO: Log the tags and metadata console.log(`Step3 tags: [${childConfig.tags.join(', ')}]`); console.log(`Step3 metadata:`, childConfig.metadata); return `Step3(${input})`; } } async function exercise() { console.log('=== Exercise 15: Config Merging and Child Configs ===\n'); // Part 1: Basic config inheritance console.log('--- Part 1: Basic Inheritance ---\n'); // TODO: Create parent config with one callback const parentConfig = null; // new RunnableConfig({ ... }) // TODO: Create child config with additional callback const childConfig = null; // parentConfig.child({ ... }) // TODO: Check what's in each config console.log('Parent callbacks:', 0); // parentConfig.callbacks.length console.log('Child callbacks:', 0); // childConfig.callbacks.length console.log('Parent tags:', []); // parentConfig.tags console.log('Child tags:', []); // childConfig.tags // Part 2: Config in pipelines console.log('\n--- Part 2: Config in Pipelines ---\n'); // TODO: Create a parent config with callbacks and tags const pipelineConfig = null; // new RunnableConfig({ ... }) // TODO: Create pipeline const step1 = new Step1Runnable(); const step2 = new Step2Runnable(); const pipeline = null; // step1.pipe(step2) // TODO: Invoke pipeline with config // await pipeline.invoke("test", pipelineConfig); // Part 3: Multiple levels of nesting console.log('\n--- Part 3: Multiple Nesting Levels ---\n'); const level1Config = new RunnableConfig({ callbacks: [new TagLoggerCallback('Level1')], tags: ['level1'], metadata: {level: 1} }); // TODO: Create level2 config as child of level1 const level2Config = null; // level1Config.child({ ... }) // TODO: Create level3 config as child of level2 const level3Config = null; // level2Config.child({ ... }) // TODO: Check the accumulation console.log('Level 1 - Callbacks:', 0, 'Tags:', []); // level1Config console.log('Level 2 - Callbacks:', 0, 'Tags:', []); // level2Config console.log('Level 3 - Callbacks:', 0, 'Tags:', []); // level3Config // Part 4: merge() vs child() console.log('\n--- Part 4: merge() vs child() ---\n'); const configA = new RunnableConfig({ tags: ['a'], metadata: {source: 'A'} }); const configB = new RunnableConfig({ tags: ['b'], metadata: {source: 'B', extra: 'data'} }); // TODO: Use merge() - combines two configs as equals const merged = null; // configA.merge(configB) // TODO: Use child() - B inherits from A const child = null; // configA.child({ tags: ['b'], metadata: { extra: 'data' } }) // TODO: Compare results console.log('Merged metadata:', {}); // merged.metadata console.log('Child metadata:', {}); // child.metadata console.log('\n✓ Exercise 3 complete!'); } // Run the exercise exercise().catch(console.error); /** * Expected Output Snippets: * * --- Part 1: Basic Inheritance --- * Parent callbacks: 1 * Child callbacks: 2 * Parent tags: ['base'] * Child tags: ['base', 'child'] * * --- Part 2: Config in Pipelines --- * [Parent] Starting Step1Runnable * Tags: [base] * Callback count: 1 * * --- Inside Step1 --- * Step1 child has 1 callbacks * [Parent] Completed Step1Runnable * * --- Part 3: Multiple Nesting Levels --- * Level 1 - Callbacks: 1, Tags: ['level1'] * Level 2 - Callbacks: 2, Tags: ['level1', 'level2'] * Level 3 - Callbacks: 3, Tags: ['level1', 'level2', 'level3'] * * --- Part 4: merge() vs child() --- * Merged metadata: { source: 'B', extra: 'data' } * Child metadata: { source: 'A', extra: 'data' } * * Learning Points: * 1. child() creates a new config inheriting from parent * 2. Callbacks accumulate (child has parent's + its own) * 3. Tags accumulate (arrays concatenate) * 4. Metadata merges (child overrides parent keys) * 5. merge() treats both configs equally * 6. child() treats parent as base, child as override */