From 6110dd8e71f5654d6be694971309851f4efa22f2 Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Mon, 26 Jan 2026 00:54:12 +0000 Subject: [PATCH] fix(ts_web): fix web dashboard typings and update generated commit info --- changelog.md | 7 + readme.md | 972 ++++++++++++++++++++--------------- ts/00_commitinfo_data.ts | 2 +- ts_web/00_commitinfo_data.ts | 2 +- 4 files changed, 560 insertions(+), 423 deletions(-) diff --git a/changelog.md b/changelog.md index 8d98c57..72eaa5c 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,12 @@ # Changelog +## 2026-01-26 - 4.1.1 - fix(ts_web) +fix web dashboard typings and update generated commit info + +- Updated generated commit info file ts_web/00_commitinfo_data.ts to version 4.1.0 +- Large changes applied to web/TS build files (net +529 additions, -399 deletions) โ€” likely fixes and typing/refactor improvements in ts_web/dashboard +- package.json remains at 4.1.0; recommend a patch bump to 4.1.1 for these fixes + ## 2026-01-26 - 4.1.0 - feat(task) add task labels and push-based task events diff --git a/readme.md b/readme.md index 6d64142..6a13ff7 100644 --- a/readme.md +++ b/readme.md @@ -1,163 +1,169 @@ # @push.rocks/taskbuffer ๐Ÿš€ -> **Modern TypeScript task orchestration with smart buffering, scheduling, and real-time progress tracking** +> **Modern TypeScript task orchestration with smart buffering, scheduling, labels, and real-time event streaming** [![npm version](https://img.shields.io/npm/v/@push.rocks/taskbuffer.svg)](https://www.npmjs.com/package/@push.rocks/taskbuffer) [![TypeScript](https://img.shields.io/badge/TypeScript-5.x-blue.svg)](https://www.typescriptlang.org/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +## Issue Reporting and Security + +For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly. + ## ๐ŸŒŸ Features -- **๐ŸŽฏ Type-Safe Task Management** - Full TypeScript support with generics and type inference -- **๐Ÿ“Š Real-Time Progress Tracking** - Step-based progress with percentage weights -- **โšก Smart Buffering** - Intelligent request debouncing and batching -- **โฐ Cron Scheduling** - Schedule tasks with cron expressions -- **๐Ÿ”„ Task Chains & Parallel Execution** - Sequential and parallel task orchestration -- **๐ŸŽจ Web Component Dashboard** - Real-time visualization of task execution -- **๐Ÿ“ˆ Comprehensive Metadata** - Track execution history, duration, and status -- **๐Ÿ”’ Thread-Safe Operations** - Concurrency control and execution limits -- **๐ŸŽญ Event-Driven Architecture** - Observable task lifecycle events +- **๐ŸŽฏ Type-Safe Task Management** โ€” Full TypeScript support with generics and type inference +- **๐Ÿ“Š Real-Time Progress Tracking** โ€” Step-based progress with percentage weights +- **โšก Smart Buffering** โ€” Intelligent request debouncing and batching +- **โฐ Cron Scheduling** โ€” Schedule tasks with cron expressions +- **๐Ÿ”— Task Chains & Parallel Execution** โ€” Sequential and parallel task orchestration +- **๐Ÿท๏ธ Labels** โ€” Attach arbitrary `Record` metadata (userId, tenantId, etc.) for multi-tenant filtering +- **๐Ÿ“ก Push-Based Events** โ€” rxjs `Subject` on every Task and TaskManager for real-time state change notifications +- **๐Ÿ›ก๏ธ Error Handling** โ€” Configurable error propagation with `catchErrors`, error tracking, and clear error state +- **๐ŸŽจ Web Component Dashboard** โ€” Built-in Lit-based dashboard for real-time task visualization +- **๐ŸŒ Distributed Coordination** โ€” Abstract coordinator for multi-instance task deduplication ## ๐Ÿ“ฆ Installation ```bash -npm install @push.rocks/taskbuffer -# or pnpm add @push.rocks/taskbuffer # or -yarn add @push.rocks/taskbuffer +npm install @push.rocks/taskbuffer ``` ## ๐Ÿš€ Quick Start -### Basic Task Creation +### Basic Task ```typescript -import { Task, TaskManager } from '@push.rocks/taskbuffer'; +import { Task } from '@push.rocks/taskbuffer'; -// Create a simple task -const dataProcessor = new Task({ - name: 'ProcessData', - taskFunction: async (data) => { - console.log(`Processing: ${data}`); - // Your async logic here - return `Processed: ${data}`; - } +const greetTask = new Task({ + name: 'Greet', + taskFunction: async (name) => { + return `Hello, ${name}!`; + }, }); -// Execute the task -const result = await dataProcessor.trigger('my-data'); -console.log(result); // "Processed: my-data" +const result = await greetTask.trigger('World'); +console.log(result); // "Hello, World!" ``` -### Tasks with Progress Tracking ๐Ÿ“Š +### Task with Steps & Progress ๐Ÿ“Š ```typescript -const deploymentTask = new Task({ - name: 'DeployApplication', +const deployTask = new Task({ + name: 'Deploy', steps: [ - { name: 'build', description: 'Building application', percentage: 30 }, + { name: 'build', description: 'Building app', percentage: 30 }, { name: 'test', description: 'Running tests', percentage: 20 }, { name: 'deploy', description: 'Deploying to server', percentage: 40 }, - { name: 'verify', description: 'Verifying deployment', percentage: 10 } - ] as const, // Use 'as const' for type inference - taskFunction: async function() { - // TypeScript knows these step names! - this.notifyStep('build'); - await buildApplication(); - - this.notifyStep('test'); + { name: 'verify', description: 'Verifying deployment', percentage: 10 }, + ] as const, + taskFunction: async () => { + deployTask.notifyStep('build'); + await buildApp(); + + deployTask.notifyStep('test'); await runTests(); - - this.notifyStep('deploy'); + + deployTask.notifyStep('deploy'); await deployToServer(); - - this.notifyStep('verify'); + + deployTask.notifyStep('verify'); await verifyDeployment(); - + return 'Deployment successful!'; - } + }, }); -// Monitor progress -console.log(deploymentTask.getProgress()); // 0-100 -console.log(deploymentTask.getStepsMetadata()); // Detailed step info +await deployTask.trigger(); +console.log(deployTask.getProgress()); // 100 +console.log(deployTask.getStepsMetadata()); // Step details with status ``` +> **Note:** `notifyStep()` is fully type-safe โ€” TypeScript only accepts step names you declared in the `steps` array when you use `as const`. + ## ๐ŸŽฏ Core Concepts -### Task Buffering - Intelligent Request Management +### Task Buffering โ€” Intelligent Request Management -TaskBuffer's buffering system prevents overwhelming your system with rapid-fire requests: +Prevent overwhelming your system with rapid-fire requests: ```typescript const apiTask = new Task({ name: 'APIRequest', - taskFunction: async (endpoint) => { - return await fetch(endpoint).then(r => r.json()); - }, buffered: true, - bufferMax: 5, // Maximum 5 concurrent executions - execDelay: 100 // Minimum 100ms between executions + bufferMax: 5, // Maximum 5 concurrent executions + execDelay: 100, // Minimum 100ms between executions + taskFunction: async (endpoint) => { + return await fetch(endpoint).then((r) => r.json()); + }, }); -// Rapid fire 100 calls - only 5 will execute concurrently +// Rapid fire 100 calls โ€” only bufferMax execute concurrently for (let i = 0; i < 100; i++) { apiTask.trigger(`/api/data/${i}`); } ``` **Buffer Behavior:** + - First `bufferMax` calls execute immediately - Additional calls are queued - When buffer is full, new calls overwrite the last queued item - Perfect for real-time data streams where only recent data matters -### Task Chains - Sequential Workflows +### Task Chains โ€” Sequential Workflows ๐Ÿ”— -Build complex workflows with automatic data flow: +Build complex workflows with automatic data flow between tasks: ```typescript -import { Task, Taskchain } from '@push.rocks/taskbuffer'; +import { Taskchain } from '@push.rocks/taskbuffer'; const fetchTask = new Task({ - name: 'FetchData', + name: 'Fetch', taskFunction: async () => { - const response = await fetch('/api/data'); - return response.json(); - } + const res = await fetch('/api/data'); + return res.json(); + }, }); const transformTask = new Task({ - name: 'TransformData', + name: 'Transform', taskFunction: async (data) => { - return data.map(item => ({ - ...item, - transformed: true, - timestamp: Date.now() - })); - } + return data.map((item) => ({ ...item, transformed: true })); + }, }); const saveTask = new Task({ - name: 'SaveData', + name: 'Save', taskFunction: async (transformedData) => { await database.save(transformedData); return transformedData.length; - } + }, }); -// Create and execute chain -const dataChain = new Taskchain({ +const pipeline = new Taskchain({ name: 'DataPipeline', - tasks: [fetchTask, transformTask, saveTask] + taskArray: [fetchTask, transformTask, saveTask], }); -const savedCount = await dataChain.trigger(); +const savedCount = await pipeline.trigger(); console.log(`Saved ${savedCount} items`); ``` -### Parallel Execution - Concurrent Processing +Taskchain also supports dynamic mutation: + +```typescript +pipeline.addTask(newTask); // Append to chain +pipeline.removeTask(oldTask); // Remove by reference (returns boolean) +pipeline.shiftTask(); // Remove & return first task +``` + +Error context is rich โ€” a chain failure includes the chain name, failing task name, task index, and preserves the original error as `.cause`. + +### Parallel Execution โ€” Concurrent Processing โšก Execute multiple tasks simultaneously: @@ -165,21 +171,13 @@ Execute multiple tasks simultaneously: import { Taskparallel } from '@push.rocks/taskbuffer'; const parallel = new Taskparallel({ - name: 'ParallelProcessor', - tasks: [ - emailTask, - smsTask, - pushNotificationTask, - webhookTask - ] + taskArray: [emailTask, smsTask, pushNotificationTask, webhookTask], }); -// All tasks execute concurrently -const results = await parallel.trigger(notificationData); -// results = [emailResult, smsResult, pushResult, webhookResult] +await parallel.trigger(notificationData); ``` -### Debounced Tasks - Smart Trigger Coalescing +### Debounced Tasks โ€” Smart Trigger Coalescing ๐Ÿ• Coalesce rapid triggers into a single execution after a quiet period: @@ -187,441 +185,573 @@ Coalesce rapid triggers into a single execution after a quiet period: import { TaskDebounced } from '@push.rocks/taskbuffer'; const searchTask = new TaskDebounced({ - name: 'SearchQuery', - debounceTimeInMillis: 300, // Wait 300ms after last trigger + name: 'Search', + debounceTimeInMillis: 300, taskFunction: async (query) => { - const results = await searchAPI(query); - return results; - } + return await searchAPI(query); + }, }); -// Rapid typing - only the last query executes +// Rapid calls โ€” only the last triggers after 300ms of quiet searchTask.trigger('h'); searchTask.trigger('he'); searchTask.trigger('hel'); -searchTask.trigger('hello'); // Only this one executes after 300ms pause +searchTask.trigger('hello'); // โ† this one fires ``` -### TaskManager - Centralized Orchestration +### TaskOnce โ€” Single-Execution Guard -Manage all your tasks from a single point: +Ensure a task only runs once, regardless of how many times it's triggered: ```typescript -const taskManager = new TaskManager(); +import { TaskOnce } from '@push.rocks/taskbuffer'; + +const initTask = new TaskOnce({ + name: 'Init', + taskFunction: async () => { + await setupDatabase(); + console.log('Initialized!'); + }, +}); + +await initTask.trigger(); // Runs +await initTask.trigger(); // No-op +await initTask.trigger(); // No-op +console.log(initTask.hasTriggered); // true +``` + +### TaskRunner โ€” Managed Queue with Concurrency Control + +Process a queue of tasks with a configurable parallelism limit: + +```typescript +import { TaskRunner } from '@push.rocks/taskbuffer'; + +const runner = new TaskRunner(); +runner.setMaxParallelJobs(3); // Run up to 3 tasks concurrently + +await runner.start(); + +runner.addTask(taskA); +runner.addTask(taskB); +runner.addTask(taskC); +runner.addTask(taskD); // Queued until a slot opens + +// When done: +await runner.stop(); +``` + +## ๐Ÿท๏ธ Labels โ€” Multi-Tenant Task Filtering + +Attach arbitrary key-value labels to any task for filtering, grouping, or multi-tenant isolation: + +```typescript +const task = new Task({ + name: 'ProcessOrder', + labels: { userId: 'u-42', tenantId: 'acme-corp', priority: 'high' }, + taskFunction: async (order) => { + /* ... */ + }, +}); + +// Manipulate labels at runtime +task.setLabel('region', 'eu-west'); +task.getLabel('userId'); // 'u-42' +task.hasLabel('tenantId', 'acme-corp'); // true +task.removeLabel('priority'); // true + +// Labels are included in metadata snapshots +const meta = task.getMetadata(); +console.log(meta.labels); // { userId: 'u-42', tenantId: 'acme-corp', region: 'eu-west' } +``` + +### Filtering Tasks by Label in TaskManager + +```typescript +const manager = new TaskManager(); +manager.addTask(orderTask1); // labels: { tenantId: 'acme' } +manager.addTask(orderTask2); // labels: { tenantId: 'globex' } +manager.addTask(orderTask3); // labels: { tenantId: 'acme' } + +const acmeTasks = manager.getTasksByLabel('tenantId', 'acme'); +// โ†’ [orderTask1, orderTask3] + +const acmeMetadata = manager.getTasksMetadataByLabel('tenantId', 'acme'); +// โ†’ [ITaskMetadata, ITaskMetadata] +``` + +## ๐Ÿ“ก Push-Based Events โ€” Real-Time Task Lifecycle + +Every `Task` exposes an rxjs `Subject` that emits events as the task progresses through its lifecycle: + +```typescript +import type { ITaskEvent } from '@push.rocks/taskbuffer'; + +const task = new Task({ + name: 'DataSync', + steps: [ + { name: 'fetch', description: 'Fetching data', percentage: 50 }, + { name: 'save', description: 'Saving data', percentage: 50 }, + ] as const, + taskFunction: async () => { + task.notifyStep('fetch'); + const data = await fetchData(); + task.notifyStep('save'); + await saveData(data); + }, +}); + +// Subscribe to individual task events +task.eventSubject.subscribe((event: ITaskEvent) => { + console.log(`[${event.type}] ${event.task.name} @ ${new Date(event.timestamp).toISOString()}`); + if (event.type === 'step') console.log(` Step: ${event.stepName}`); + if (event.type === 'failed') console.log(` Error: ${event.error}`); +}); + +await task.trigger(); +// [started] DataSync @ 2025-01-26T... +// [step] DataSync @ 2025-01-26T... +// Step: fetch +// [step] DataSync @ 2025-01-26T... +// Step: save +// [completed] DataSync @ 2025-01-26T... +``` + +### Event Types + +| Type | When | Extra Fields | +| --- | --- | --- | +| `'started'` | Task begins execution | โ€” | +| `'step'` | `notifyStep()` is called | `stepName` | +| `'completed'` | Task finishes successfully | โ€” | +| `'failed'` | Task throws an error | `error` (message string) | + +Every event includes a full `ITaskMetadata` snapshot (including labels) at the time of emission. + +### Aggregated Events on TaskManager + +`TaskManager` automatically aggregates events from all added tasks into a single `taskSubject`: + +```typescript +const manager = new TaskManager(); +manager.addTask(syncTask); +manager.addTask(reportTask); +manager.addTask(cleanupTask); + +// Single subscription for ALL task events +manager.taskSubject.subscribe((event) => { + sendToMonitoringDashboard(event); +}); + +// Events stop flowing for a task after removal +manager.removeTask(syncTask); +``` + +`manager.stop()` automatically cleans up all event subscriptions. + +## ๐Ÿ›ก๏ธ Error Handling + +By default, `trigger()` **rejects** when the task function throws โ€” errors propagate naturally: + +```typescript +const task = new Task({ + name: 'RiskyOp', + taskFunction: async () => { + throw new Error('something broke'); + }, +}); + +try { + await task.trigger(); +} catch (err) { + console.error(err.message); // "something broke" +} +``` + +### Swallowing Errors with `catchErrors` + +Set `catchErrors: true` to swallow errors and return `undefined` instead of rejecting: + +```typescript +const task = new Task({ + name: 'BestEffort', + catchErrors: true, + taskFunction: async () => { + throw new Error('non-critical'); + }, +}); + +const result = await task.trigger(); // undefined (no throw) +``` + +### Error State Tracking + +Regardless of `catchErrors`, the task tracks errors: + +```typescript +console.log(task.lastError); // Error object (or undefined) +console.log(task.errorCount); // Number of failures across all runs +console.log(task.getMetadata().status); // 'failed' + +task.clearError(); // Resets lastError to undefined (errorCount stays) +``` + +On a subsequent successful run, `lastError` is automatically cleared. + +## ๐Ÿ“‹ TaskManager โ€” Centralized Orchestration + +```typescript +const manager = new TaskManager(); // Add tasks -taskManager.addTask(dataProcessor); -taskManager.addTask(deploymentTask); +manager.addTask(dataProcessor); +manager.addTask(deployTask); -// Schedule tasks with cron -taskManager.addAndScheduleTask(backupTask, '0 2 * * *'); // Daily at 2 AM -taskManager.addAndScheduleTask(healthCheck, '*/5 * * * *'); // Every 5 minutes +// Schedule with cron expressions +manager.addAndScheduleTask(backupTask, '0 2 * * *'); // Daily at 2 AM +manager.addAndScheduleTask(healthCheck, '*/5 * * * *'); // Every 5 minutes -// Get task metadata -const metadata = taskManager.getTaskMetadata('DeployApplication'); -console.log(metadata); +// Query metadata +const meta = manager.getTaskMetadata('Deploy'); +console.log(meta); // { -// name: 'DeployApplication', -// status: 'idle' | 'running' | 'completed' | 'failed', +// name: 'Deploy', +// status: 'completed', // steps: [...], -// currentProgress: 75, -// runCount: 12, -// lastRun: Date, -// buffered: false, -// bufferMax: undefined, -// version: '1.0.0', -// timeout: 30000 +// currentProgress: 100, +// runCount: 3, +// labels: { env: 'production' }, +// lastError: undefined, +// errorCount: 0, +// ... // } -// Get all scheduled tasks -const scheduled = taskManager.getScheduledTasks(); -scheduled.forEach(task => { - console.log(`${task.name}: Next run at ${task.nextRun}`); -}); +// All tasks at once +const allMeta = manager.getAllTasksMetadata(); -// Execute and remove pattern -const report = await taskManager.addExecuteRemoveTask(temporaryTask, { - trackProgress: true -}); +// Scheduled task info +const scheduled = manager.getScheduledTasks(); +const nextRuns = manager.getNextScheduledRuns(5); + +// Trigger by name +await manager.triggerTaskByName('Deploy'); + +// One-shot: add, execute, collect report, remove +const report = await manager.addExecuteRemoveTask(temporaryTask); console.log(report); // { // taskName: 'TempTask', -// startTime: Date, -// endTime: Date, +// startTime: 1706284800000, +// endTime: 1706284801523, // duration: 1523, // steps: [...], // stepsCompleted: ['step1', 'step2'], // progress: 100, -// result: any, -// error?: Error +// result: any // } + +// Lifecycle +await manager.start(); // Starts cron scheduling + distributed coordinator +await manager.stop(); // Stops scheduling, cleans up event subscriptions +``` + +### Remove Tasks + +```typescript +manager.removeTask(task); // Removes from map and unsubscribes event forwarding +manager.descheduleTaskByName('Deploy'); // Remove cron schedule only ``` ## ๐ŸŽจ Web Component Dashboard -Visualize your tasks in real-time with the included web component: +Visualize your tasks in real-time with the included Lit-based web component: ```html - - - - - - - - - + + + ``` The dashboard provides: + - ๐Ÿ“Š Real-time progress bars with step indicators -- ๐Ÿ“ˆ Task execution history -- โฐ Scheduled task information -- ๐ŸŽฏ Interactive task controls +- ๐Ÿ“ˆ Task execution history and metadata +- โฐ Scheduled task information with next-run times - ๐ŸŒ“ Light/dark theme support +## ๐ŸŒ Distributed Coordination + +For multi-instance deployments, extend `AbstractDistributedCoordinator` to prevent duplicate task execution: + +```typescript +import { TaskManager, distributedCoordination } from '@push.rocks/taskbuffer'; + +class RedisCoordinator extends distributedCoordination.AbstractDistributedCoordinator { + async fireDistributedTaskRequest(request) { + // Implement leader election / distributed lock via Redis + return { shouldTrigger: true, considered: true, rank: 1, reason: 'elected', ...request }; + } + async updateDistributedTaskRequest(request) { + /* update status */ + } + async start() { + /* connect */ + } + async stop() { + /* disconnect */ + } +} + +const manager = new TaskManager({ + distributedCoordinator: new RedisCoordinator(), +}); +``` + +When a distributed coordinator is configured, scheduled tasks consult it before executing โ€” only the elected instance runs the task. + ## ๐Ÿงฉ Advanced Patterns -### Dynamic Task Routing +### Pre-Task & After-Task Hooks -Route tasks based on conditions: +Run setup/teardown tasks automatically: ```typescript -const routerTask = new Task({ - name: 'Router', - taskFunction: async (request) => { - if (request.priority === 'high') { - return await expressProcessor.trigger(request); - } else if (request.size > 1000000) { - return await bulkProcessor.trigger(request); - } else { - return await standardProcessor.trigger(request); - } - } +const mainTask = new Task({ + name: 'MainWork', + preTask: new Task({ + name: 'Setup', + taskFunction: async () => { + console.log('Setting up...'); + }, + }), + afterTask: new Task({ + name: 'Cleanup', + taskFunction: async () => { + console.log('Cleaning up...'); + }, + }), + taskFunction: async () => { + console.log('Doing work...'); + return 'done'; + }, }); + +await mainTask.trigger(); +// Setting up... โ†’ Doing work... โ†’ Cleaning up... ``` -### Task Pools +### One-Time Setup Functions -Create reusable task pools for load distribution: +Run an expensive initialization exactly once, before the first execution: ```typescript -class TaskPool { - private tasks: Task[] = []; - private currentIndex = 0; - - constructor(poolSize: number, taskConfig: any) { - for (let i = 0; i < poolSize; i++) { - this.tasks.push(new Task({ - ...taskConfig, - name: `${taskConfig.name}_${i}` - })); - } - } - - async execute(data: any) { - const task = this.tasks[this.currentIndex]; - this.currentIndex = (this.currentIndex + 1) % this.tasks.length; - return await task.trigger(data); - } -} - -const processorPool = new TaskPool(5, { - name: 'DataProcessor', - taskFunction: async (data) => processData(data) +const task = new Task({ + name: 'DBQuery', + taskSetup: async () => { + const pool = await createConnectionPool(); + return pool; // This becomes `setupValue` + }, + taskFunction: async (input, pool) => { + return await pool.query(input); + }, }); + +await task.trigger('SELECT * FROM users'); // Setup runs here +await task.trigger('SELECT * FROM orders'); // Setup skipped, pool reused ``` -### Error Recovery & Retry +### Blocking Tasks -Implement robust error handling: +Make one task wait for another to finish before executing: ```typescript -const resilientTask = new Task({ - name: 'ResilientTask', - taskFunction: async (data, retryCount = 0) => { - try { - return await riskyOperation(data); - } catch (error) { - if (retryCount < 3) { - console.log(`Retry ${retryCount + 1}/3`); - await new Promise(r => setTimeout(r, 1000 * Math.pow(2, retryCount))); - return await resilientTask.trigger(data, retryCount + 1); - } - throw error; - } - } +const initTask = new Task({ + name: 'Init', + taskFunction: async () => { + await initializeSystem(); + }, }); -``` -### Task Composition - -Compose complex behaviors from simple tasks: - -```typescript -const compositeTask = new Task({ - name: 'CompositeOperation', - steps: [ - { name: 'validate', description: 'Validating input', percentage: 20 }, - { name: 'process', description: 'Processing data', percentage: 60 }, - { name: 'notify', description: 'Sending notifications', percentage: 20 } - ] as const, - taskFunction: async function(data) { - this.notifyStep('validate'); - const validated = await validationTask.trigger(data); - - this.notifyStep('process'); - const processed = await processingTask.trigger(validated); - - this.notifyStep('notify'); - await notificationTask.trigger(processed); - - return processed; - } +const workerTask = new Task({ + name: 'Worker', + taskFunction: async () => { + await doWork(); + }, }); -``` -## ๐Ÿ”ง Configuration +workerTask.blockingTasks.push(initTask); -### Task Options - -```typescript -interface TaskOptions { - name?: string; // Task identifier - taskFunction: Function; // Async function to execute - buffered?: boolean; // Enable buffering - bufferMax?: number; // Max concurrent executions - execDelay?: number; // Min delay between executions - timeout?: number; // Task timeout in ms - version?: string; // Task version - steps?: TSteps; // Progress steps configuration - taskSetup?: Function; // One-time setup function - beforeTask?: Function; // Runs before each execution - afterTask?: Function; // Runs after each execution -} -``` - -### TaskManager Options - -```typescript -const taskManager = new TaskManager({ - maxConcurrentTasks: 10, // Global concurrency limit - defaultTimeout: 30000, // Default task timeout - logLevel: 'info' // Logging verbosity -}); -``` - -## ๐Ÿ“Š Monitoring & Observability - -### Task Events - -```typescript -task.on('started', () => console.log('Task started')); -task.on('completed', (result) => console.log('Task completed:', result)); -task.on('failed', (error) => console.error('Task failed:', error)); -task.on('stepChange', (step) => console.log('Step:', step.name)); -``` - -### Execution Metrics - -```typescript -const metrics = task.getMetrics(); -console.log({ - totalRuns: metrics.runCount, - averageDuration: metrics.avgDuration, - successRate: metrics.successRate, - lastError: metrics.lastError -}); -``` - -## ๐Ÿงช Testing - -```typescript -import { expect, tap } from '@git.zone/tstest'; -import { Task } from '@push.rocks/taskbuffer'; - -tap.test('Task should execute with progress tracking', async () => { - const task = new Task({ - name: 'TestTask', - steps: [ - { name: 'step1', description: 'Step 1', percentage: 50 }, - { name: 'step2', description: 'Step 2', percentage: 50 } - ] as const, - taskFunction: async function() { - this.notifyStep('step1'); - await new Promise(r => setTimeout(r, 100)); - this.notifyStep('step2'); - return 'done'; - } - }); - - const result = await task.trigger(); - expect(result).toEqual('done'); - expect(task.getProgress()).toEqual(100); -}); -``` - -## ๐ŸŒ Real-World Examples - -### API Rate Limiter - -```typescript -const apiLimiter = new Task({ - name: 'APIRateLimiter', - buffered: true, - bufferMax: 10, // Max 10 requests per second - execDelay: 100, // 100ms between requests - taskFunction: async (endpoint, data) => { - return await fetch(endpoint, { - method: 'POST', - body: JSON.stringify(data) - }); - } -}); +// Triggering worker will automatically wait for init to complete +initTask.trigger(); +workerTask.trigger(); // Waits until initTask.finished resolves ``` ### Database Migration Pipeline ```typescript -const migrationChain = new Taskchain({ +const migration = new Taskchain({ name: 'DatabaseMigration', - tasks: [ - backupDatabaseTask, - validateSchemaTask, - runMigrationsTask, - verifyIntegrityTask, - updateIndexesTask - ] + taskArray: [backupTask, validateSchemaTask, runMigrationsTask, verifyIntegrityTask], }); -// Execute with rollback on failure try { - await migrationChain.trigger(); + await migration.trigger(); console.log('Migration successful!'); } catch (error) { + // error includes chain name, failing task name, index, and original cause + console.error(error.message); await rollbackTask.trigger(); - throw error; } ``` -### Distributed Job Queue +### Multi-Tenant SaaS Monitoring + +Combine labels + events for a real-time multi-tenant dashboard: ```typescript -const jobQueue = new TaskManager(); +const manager = new TaskManager(); -// Worker tasks -const imageProcessor = new Task({ - name: 'ImageProcessor', - buffered: true, - bufferMax: 5, - steps: [ - { name: 'download', description: 'Downloading', percentage: 20 }, - { name: 'resize', description: 'Resizing', percentage: 40 }, - { name: 'optimize', description: 'Optimizing', percentage: 30 }, - { name: 'upload', description: 'Uploading', percentage: 10 } - ] as const, - taskFunction: async function(job) { - this.notifyStep('download'); - const image = await downloadImage(job.url); - - this.notifyStep('resize'); - const resized = await resizeImage(image, job.dimensions); - - this.notifyStep('optimize'); - const optimized = await optimizeImage(resized); - - this.notifyStep('upload'); - return await uploadToCDN(optimized); +// Create tenant-scoped tasks +function createTenantTask(tenantId: string, taskName: string, fn: () => Promise) { + const task = new Task({ + name: `${tenantId}:${taskName}`, + labels: { tenantId }, + taskFunction: fn, + }); + manager.addTask(task); + return task; +} + +createTenantTask('acme', 'sync', async () => syncData('acme')); +createTenantTask('globex', 'sync', async () => syncData('globex')); + +// Stream events to tenant-specific WebSocket channels +manager.taskSubject.subscribe((event) => { + const tenantId = event.task.labels?.tenantId; + if (tenantId) { + wss.broadcast(tenantId, JSON.stringify(event)); } }); -jobQueue.addTask(imageProcessor); - -// Process incoming jobs -messageQueue.on('job', async (job) => { - const result = await jobQueue.getTaskByName('ImageProcessor').trigger(job); - await messageQueue.ack(job.id, result); -}); -``` - -## ๐Ÿš€ Performance Tips - -1. **Use Buffering Wisely** - Enable buffering for I/O-bound tasks -2. **Set Appropriate Delays** - Use `execDelay` to prevent API rate limits -3. **Leverage Task Pools** - Distribute load across multiple task instances -4. **Monitor Progress** - Use step tracking for long-running operations -5. **Clean Up** - Use `addExecuteRemoveTask` for one-time operations - -## ๐Ÿ” Debugging - -Enable detailed logging: - -```typescript -import { logger } from '@push.rocks/smartlog'; - -logger.enableConsole(); -logger.level = 'debug'; - -// Tasks will now output detailed execution logs +// Query tasks for a specific tenant +const acmeTasks = manager.getTasksMetadataByLabel('tenantId', 'acme'); ``` ## ๐Ÿ“š API Reference -### Core Classes +### Classes -- **`Task`** - Basic task unit with optional step tracking -- **`TaskManager`** - Central orchestrator for task management -- **`Taskchain`** - Sequential task executor -- **`Taskparallel`** - Concurrent task executor -- **`TaskOnce`** - Single-execution task -- **`TaskDebounced`** - Debounced task that waits for a pause in triggers -- **`TaskRunner`** - Sequential task runner with scheduling support -- **`distributedCoordination`** - Namespace for distributed task coordination +| Class | Description | +| --- | --- | +| `Task` | Core task unit with optional step tracking, labels, and event streaming | +| `TaskManager` | Centralized orchestrator with scheduling, label queries, and aggregated events | +| `Taskchain` | Sequential task executor with data flow between tasks | +| `Taskparallel` | Concurrent task executor via `Promise.all()` | +| `TaskOnce` | Single-execution guard | +| `TaskDebounced` | Debounced task using rxjs | +| `TaskRunner` | Sequential queue with configurable parallelism | +| `TaskStep` | Step tracking unit (internal, exposed via metadata) | -### Key Methods +### Task Methods -#### Task Methods -- `trigger(input?: T): Promise` - Execute the task -- `notifyStep(stepName: StepNames): void` - Update current step -- `getProgress(): number` - Get progress percentage (0-100) -- `getStepsMetadata(): ITaskStep[]` - Get detailed step information -- `getMetadata(): ITaskMetadata` - Get complete task metadata +| Method | Returns | Description | +| --- | --- | --- | +| `trigger(input?)` | `Promise` | Execute the task | +| `notifyStep(name)` | `void` | Advance to named step (type-safe) | +| `getProgress()` | `number` | Current progress 0โ€“100 | +| `getStepsMetadata()` | `ITaskStep[]` | Step details with status | +| `getMetadata()` | `ITaskMetadata` | Full task metadata snapshot | +| `setLabel(key, value)` | `void` | Set a label | +| `getLabel(key)` | `string \| undefined` | Get a label value | +| `removeLabel(key)` | `boolean` | Remove a label | +| `hasLabel(key, value?)` | `boolean` | Check label existence / value | +| `clearError()` | `void` | Reset `lastError` to undefined | -#### TaskManager Methods -- `addTask(task: Task): void` - Register a task -- `getTaskByName(name: string): Task | undefined` - Retrieve task by name -- `addAndScheduleTask(task: Task, cronExpression: string): void` - Schedule task -- `descheduleTaskByName(name: string): void` - Remove scheduling -- `getTaskMetadata(name: string): ITaskMetadata | null` - Get task metadata -- `getAllTasksMetadata(): ITaskMetadata[]` - Get all tasks metadata -- `getScheduledTasks(): IScheduledTaskInfo[]` - List scheduled tasks -- `addExecuteRemoveTask(task, options?): Promise` - Execute once +### Task Properties + +| Property | Type | Description | +| --- | --- | --- | +| `name` | `string` | Task identifier | +| `running` | `boolean` | Whether the task is currently executing | +| `idle` | `boolean` | Inverse of `running` | +| `labels` | `Record` | Attached labels | +| `eventSubject` | `Subject` | rxjs Subject emitting lifecycle events | +| `lastError` | `Error \| undefined` | Last error encountered | +| `errorCount` | `number` | Total error count across all runs | +| `runCount` | `number` | Total execution count | +| `lastRun` | `Date \| undefined` | Timestamp of last execution | +| `blockingTasks` | `Task[]` | Tasks that must finish before this one starts | + +### TaskManager Methods + +| Method | Returns | Description | +| --- | --- | --- | +| `addTask(task)` | `void` | Register a task (wires event forwarding) | +| `removeTask(task)` | `void` | Remove task and unsubscribe events | +| `getTaskByName(name)` | `Task \| undefined` | Look up by name | +| `triggerTaskByName(name)` | `Promise` | Trigger by name | +| `addAndScheduleTask(task, cron)` | `void` | Register + schedule | +| `scheduleTaskByName(name, cron)` | `void` | Schedule existing task | +| `descheduleTaskByName(name)` | `void` | Remove schedule | +| `getTaskMetadata(name)` | `ITaskMetadata \| null` | Single task metadata | +| `getAllTasksMetadata()` | `ITaskMetadata[]` | All tasks metadata | +| `getScheduledTasks()` | `IScheduledTaskInfo[]` | Scheduled task info | +| `getNextScheduledRuns(limit?)` | `Array<{...}>` | Upcoming scheduled runs | +| `getTasksByLabel(key, value)` | `Task[]` | Filter tasks by label | +| `getTasksMetadataByLabel(key, value)` | `ITaskMetadata[]` | Filter metadata by label | +| `addExecuteRemoveTask(task, opts?)` | `Promise` | One-shot execution with report | +| `start()` | `Promise` | Start cron + coordinator | +| `stop()` | `Promise` | Stop cron + clean up subscriptions | + +### TaskManager Properties + +| Property | Type | Description | +| --- | --- | --- | +| `taskSubject` | `Subject` | Aggregated events from all added tasks | +| `taskMap` | `ObjectMap` | Internal task registry | + +### Exported Types + +```typescript +import type { + ITaskMetadata, + ITaskExecutionReport, + IScheduledTaskInfo, + ITaskEvent, + TTaskEventType, + ITaskStep, + ITaskFunction, + StepNames, +} from '@push.rocks/taskbuffer'; +``` ## License and Legal Information -This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. +This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file. **Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file. ### Trademarks -This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH. +This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein. + +Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar. ### Company Information -Task Venture Capital GmbH -Registered at District court Bremen HRB 35230 HB, Germany +Task Venture Capital GmbH +Registered at District Court Bremen HRB 35230 HB, Germany -For any legal inquiries or if you require further information, please contact us via email at hello@task.vc. +For any legal inquiries or further information, please contact us via email at hello@task.vc. -By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works. \ No newline at end of file +By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works. diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 943ad35..bf3d3cd 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@push.rocks/taskbuffer', - version: '4.1.0', + version: '4.1.1', description: 'A flexible task management library supporting TypeScript, allowing for task buffering, scheduling, and execution with dependency management.' } diff --git a/ts_web/00_commitinfo_data.ts b/ts_web/00_commitinfo_data.ts index 943ad35..bf3d3cd 100644 --- a/ts_web/00_commitinfo_data.ts +++ b/ts_web/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@push.rocks/taskbuffer', - version: '4.1.0', + version: '4.1.1', description: 'A flexible task management library supporting TypeScript, allowing for task buffering, scheduling, and execution with dependency management.' }