UNPKG

tendryl

Version:

A workflow orchestrator with chainable async interfaces, ledger integration for operation tracking, and race condition detection

278 lines (203 loc) 6.69 kB
# Tendryl A workflow orchestrator with chainable async interfaces, ledger integration for operation tracking, and race condition detection. ## Features - **Chain**: Class-based and function-based chainable async workflows - **Ledger**: Automatic operation tracking with collision detection - **Logger**: Structured logging with Pino integration - **Chain with Ledger**: Integrated chain and ledger functionality ## Installation ```bash npm install tendryl ``` ## Usage ### Core Chain The core chain provides a simple way to create chainable async workflows: ```typescript import { ChainStep, chainstep } from 'tendryl'; // Class-based approach class FetchData extends ChainStep<{ url: string; data?: any }> { async execute(state) { const response = await fetch(state.url); state.data = await response.json(); return state; } } class ProcessData extends ChainStep<{ data?: any; processed?: any }> { async execute(state) { state.processed = state.data.map(item => ({ ...item, processed: true })); return state; } } // Chain them together FetchData.then(ProcessData); // Execute the chain const result = await FetchData.invoke({ url: 'https://api.example.com/data' }); ``` ### Function-based approach ```typescript import { chainstep } from 'tendryl'; const fetchData = chainstep(async (state) => { const response = await fetch(state.url); state.data = await response.json(); return state; }, 'FetchData'); const processData = chainstep(async (state) => { state.processed = state.data.map(item => ({ ...item, processed: true })); return state; }, 'ProcessData'); // Chain them together fetchData.then(processData); // Execute const result = await fetchData.invoke({ url: 'https://api.example.com/data' }); ``` ### Ledger System The ledger system tracks all operations on an object and detects race conditions: ```typescript import { createLedger, getLedgerStats, printLedger } from 'tendryl/ledger'; // Create a ledger-wrapped object const state = createLedger({ count: 0, name: 'test' }); // All operations are tracked state.count = 1; state.name = 'updated'; const currentCount = state.count; // Get statistics const stats = getLedgerStats(state); console.log(stats); // { totalOperations: 3, totalReads: 1, totalWrites: 2, collisions: 0, duration: 123 } // Print detailed ledger printLedger(state); ``` ### Logger Structured logging with Pino: ```typescript import { logger, createChildLogger, setLogLevel } from 'tendryl/logger'; // Use the main logger logger.info('Application started'); // Create a child logger for specific context const userLogger = createChildLogger('user-service'); userLogger.info('User created', { userId: 123 }); // Set log level setLogLevel('debug'); ``` ### Chain with Ledger Combines chain functionality with automatic ledger tracking: ```typescript import { ChainStep } from 'tendryl/chain-with-ledger'; class UpdateUser extends ChainStep<{ userId: number; user?: any }> { async execute(state) { // All operations on state are automatically tracked state.user = { id: state.userId, name: 'John Doe' }; state.user.lastUpdated = new Date(); return state; } } class ValidateUser extends ChainStep<{ user?: any; valid?: boolean }> { async execute(state) { state.valid = state.user && state.user.name; return state; } } // Chain with ledger enabled UpdateUser.then(ValidateUser); // Execute with ledger tracking const result = await UpdateUser.invoke( { userId: 123 }, { enableLedger: true } ); // Access ledger information console.log(result.ledger); // Ledger log console.log(result.reconciliation); // Conflict detection results ``` ## API Reference ### Chain - `ChainStep<S>` - Abstract base class for chain steps - `chainstep(fn, name?)` - Create chainable steps from functions - `.then(next)` - Chain steps sequentially - `.when(condition, target, label?)` - Conditional branching - `.retries(n)` - Set retry attempts - `.catch(errorStep)` - Error handling - `.invoke(initialState)` - Execute the chain - `.graph(options?)` - Generate Mermaid flowchart ### Ledger - `createLedger(initial, options?)` - Create ledger-wrapped object - `getLedger(proxy)` - Get ledger from proxy - `getLedgerStats(proxy)` - Get operation statistics - `getCollisions(proxy)` - Get detected collisions - `reconcileLedger(proxy)` - Reconcile pending operations - `printLedger(proxy)` - Print detailed ledger report ### Logger - `logger` - Main Pino logger instance - `createChildLogger(name)` - Create child logger - `setLogLevel(level)` - Set log level - `getLogLevel()` - Get current log level ## Examples ### Workflow with Error Handling ```typescript import { ChainStep } from 'tendryl/chain-with-ledger'; class FetchData extends ChainStep<{ url: string; data?: any }> { async execute(state) { const response = await fetch(state.url); if (!response.ok) throw new Error('Fetch failed'); state.data = await response.json(); return state; } } class HandleError extends ChainStep<{ error?: string }> { async execute(state) { state.error = 'Data fetch failed, using fallback'; state.data = [{ fallback: true }]; return state; } } FetchData.retries(3).catch(HandleError); ``` ### Parallel Execution ```typescript import { ChainStep } from 'tendryl/chain-with-ledger'; class ProcessA extends ChainStep<{ resultA?: string }> { async execute(state) { state.resultA = 'Processed A'; return state; } } class ProcessB extends ChainStep<{ resultB?: string }> { async execute(state) { state.resultB = 'Processed B'; return state; } } class Start extends ChainStep<{}> { async execute(state) { return state; } } // Execute ProcessA and ProcessB in parallel Start.then([ProcessA, ProcessB]); ``` ## Development ### GitHub Actions This repository includes GitHub Actions workflows for automated testing and publishing: - **Test Workflow** (`test.yml`): Runs on every push to main and pull request - Type checking - Unit tests - Build verification - **Publish Workflow** (`publish.yml`): Runs when a new release is published - Automatically publishes to npm - Requires `NPM_TOKEN` secret to be configured ### Setting up NPM Token To enable automatic publishing, you need to: 1. Create an npm access token at https://www.npmjs.com/settings/tokens 2. Add the token as a repository secret named `NPM_TOKEN` in your GitHub repository settings 3. Create a new release on GitHub to trigger the publish workflow ### Local Development ```bash # Install dependencies pnpm install # Run tests pnpm test # Build package pnpm build # Type check pnpm type-check ``` ## License MIT