Last active
July 26, 2026 06:34
-
-
Save dhaupin/82252966b546e45852c8c2f087d428a6 to your computer and use it in GitHub Desktop.
Tmp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| That makes complete sense—when a project grows into a distributed agentic runtime with file/git-backed state (like Vant's multi-brain refactors), lib/do.js isn't just a utility helper. It’s a Command Dispatcher / Pipeline Execution Engine. | |
| You’re trying to build a Unified Task Strategy Enforcer (or Execution Middleware). It's essentially the Command Pattern + Strategy Pattern built specifically for Node runtime operations. | |
| Here is an architectural mental model and pattern that turns do.js into an extensible, state-aware execution engine. | |
| The Pattern: Execution Handler Engine (lib/do.js) | |
| Instead of just checking { sync: true }, do.js acts as a pipeline gatekeeper that normalizes incoming tasks, applies interceptors (like state tracking, logging, or escrow/sandboxing), and executes via strategy handlers. | |
| // lib/do.js | |
| class TaskDispatcher { | |
| constructor(globalConfig = {}) { | |
| this.config = globalConfig; | |
| this.handlers = new Map(); | |
| // Default built-in execution strategy | |
| this.registerHandler('default', (fn, args, state) => fn(state, ...args)); | |
| } | |
| /** | |
| * Register future execution pipelines (e.g. 'retried', 'isolated', 'sandboxed') | |
| */ | |
| registerHandler(name, handlerFn) { | |
| this.handlers.set(name, handlerFn); | |
| return this; | |
| } | |
| /** | |
| * Main entry point: run(fn, ...args, opts) | |
| */ | |
| run(fn, ...args) { | |
| // 1. Extract trailing options object if present | |
| let opts = {}; | |
| if (args.length > 0 && typeof args[args.length - 1] === 'object' && args[args.length - 1] !== null) { | |
| // If last arg looks like opts, slice it off so it isn't passed down as a native arg | |
| opts = args.pop(); | |
| } | |
| // 2. Resolve target execution strategy (allows future handler plugins) | |
| const strategyName = opts.mode || 'default'; | |
| const handler = this.handlers.get(strategyName); | |
| if (!handler) { | |
| throw new Error(`[do.js] Unknown execution handler strategy: '${strategyName}'`); | |
| } | |
| // 3. Construct unified execution core (capturing state + context) | |
| const execute = () => { | |
| // Optional lifecycle hooks or state injection | |
| const stateContext = { | |
| timestamp: Date.now(), | |
| state: opts.state || this.config.state || null, | |
| ...opts.context | |
| }; | |
| return handler(fn, args, stateContext); | |
| }; | |
| // 4. Synchronous Execution Gate | |
| if (opts.sync) { | |
| return execute(); | |
| } | |
| // 5. Asynchronous / Lazy Execution Gate | |
| return Promise.resolve().then(execute); | |
| } | |
| } | |
| // Instantiate default singleton instance | |
| const dispatcher = new TaskDispatcher(); | |
| // Export both the bound `run` helper and the Class for custom instances | |
| const doRun = (fn, ...args) => dispatcher.run(fn, ...args); | |
| doRun.Dispatcher = TaskDispatcher; | |
| doRun.dispatcher = dispatcher; | |
| module.exports = doRun; | |
| How this scales with your codebase architecture | |
| 1. Standard Dual-Mode Execution (Sync vs Async) | |
| Your module functions stay completely clean—no boilerplates or internal Promise.resolve() branching needed inside every single file: | |
| const `do` = require('./lib/do'); | |
| // Native pure function (doesn't care about async/sync mechanics) | |
| function mutateBrainState(stateContext, patch) { | |
| stateContext.state.apply(patch); | |
| return stateContext.state; | |
| } | |
| // Async invocation (Default) | |
| `do`(mutateBrainState, { node: 'head-01' }, { state: myState }) | |
| .then(newState => console.log("Updated via microtask queue")) | |
| .catch(err => console.error("Caught in promise chain", err)); | |
| // Sync invocation | |
| const newState = `do`(mutateBrainState, { node: 'head-01' }, { sync: true, state: myState }); | |
| 2. Plug in Future Handlers (Without rewriting core code) | |
| Because you mentioned wanting to support other function handlers in the future (e.g., retries, multi-brain execution gates, sandboxing, or transaction rollback), you can register custom strategies on the dispatcher: | |
| const `do` = require('./lib/do'); | |
| // Register a "safe-retry" handler strategy on the global engine | |
| `do`.dispatcher.registerHandler('retryable', async (fn, args, ctx) => { | |
| let attempts = 0; | |
| while (attempts < 3) { | |
| try { | |
| return await fn(ctx, ...args); | |
| } catch (err) { | |
| attempts++; | |
| if (attempts >= 3) throw err; | |
| } | |
| } | |
| }); | |
| // Execute a task using the custom handler strategy | |
| await `do`(flakyGitSyncTask, repoUrl, { mode: 'retryable' }); | |
| Why this fits an Architecture Refactor | |
| * Inversion of Control: Individual files in lib/ no longer manage their own execution mode rules. do.js acts as the single source of truth for runtime behavior. | |
| * Unified Error Pipeline: Whether a module throws synchronously or rejects asynchronously, do.js standardizes how errors bubbling out of state operations are surfaced. | |
| * Extensible Middleware: As Vant evolves across branches, do.js can hook into telemetry, step logging, or git commit-trailers without modifying any of your core domain functions. | |
| -------------------- | |
| I wasn't able to directly pull and inspect your live GitHub branch (vant/tree/nova) in real time, as I don't have direct external web-scraping access to git repos. However, I tapped into our prior context and your exact architectural requirements—and I'm really glad we hit the nail on the head! | |
| That Command / Pipeline Dispatcher pattern fits the direction you're taking with Vant's multi-brain and state architecture. | |
| Since lib/do.js is going to be such a critical backbone for the repo, here are a few additional middleware features you might want to bake into that TaskDispatcher class as you build out the nova branch: | |
| 1. Pre/Post Lifecycle Hooks (Interceptors) | |
| When managing state across agent operations, you often need to audit, log, or freeze state before and after execution: | |
| class TaskDispatcher { | |
| constructor(globalConfig = {}) { | |
| this.config = globalConfig; | |
| this.handlers = new Map(); | |
| this.beforeHooks = []; | |
| this.afterHooks = []; | |
| this.registerHandler('default', (fn, args, ctx) => fn(ctx, ...args)); | |
| } | |
| useBefore(fn) { this.beforeHooks.push(fn); return this; } | |
| useAfter(fn) { this.afterHooks.push(fn); return this; } | |
| // ... rest of run method ... | |
| } | |
| 2. Auto-Detection of Async Functions | |
| If someone passes an inherently async function (or a function returning a Promise) while setting { sync: true }, you want to catch that early so it doesn't fail silently or break sync guarantees: | |
| const execute = () => { | |
| const result = handler(fn, args, stateContext); | |
| // Warning or Guard if caller requested sync but the function returned a Promise | |
| if (opts.sync && result && typeof result.then === 'function') { | |
| throw new Error( | |
| `[do.js] Function '${fn.name || 'anonymous'}' returned a Promise, ` + | |
| `but was executed with { sync: true }. Synchronous unrolling of Promises is not supported.` | |
| ); | |
| } | |
| return result; | |
| }; | |
| 3. Preserving Function Context (this) | |
| If any of the functions passed through do.js belong to class instances or modules relying on this, binding the execution context will save you debugging headaches down the line: | |
| // Allow passing an optional context bound 'this' target | |
| const targetContext = opts.thisArg || null; | |
| const execute = () => handler(fn.bind(targetContext), args, stateContext); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment