@push.rocks/taskbuffer 3.1.9 โ†’ 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/readme.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @push.rocks/taskbuffer ๐Ÿš€
2
2
 
3
- A **powerful**, **flexible**, and **TypeScript-first** task management library for orchestrating asynchronous operations with style. From simple task execution to complex distributed workflows, taskbuffer has got you covered.
3
+ A **powerful**, **flexible**, and **TypeScript-first** task management library for orchestrating asynchronous operations with style. From simple task execution to complex distributed workflows with real-time progress tracking, taskbuffer has got you covered.
4
4
 
5
5
  ## Install ๐Ÿ“ฆ
6
6
 
@@ -23,35 +23,33 @@ In the modern JavaScript ecosystem, managing asynchronous tasks efficiently is c
23
23
  - **๐Ÿ”„ Smart buffering**: Control concurrent executions with intelligent buffer management
24
24
  - **โฐ Built-in scheduling**: Cron-based task scheduling without additional dependencies
25
25
  - **๐ŸŽญ Multiple paradigms**: Support for debounced, throttled, and one-time execution patterns
26
+ - **๐Ÿ“Š Progress tracking**: Real-time step-by-step progress monitoring for UI integration
26
27
  - **๐Ÿ”Œ Extensible**: Clean architecture that's easy to extend and customize
27
28
  - **๐Ÿƒ Zero dependencies on external schedulers**: Everything you need is included
28
29
 
29
30
  ## Core Concepts ๐ŸŽ“
30
31
 
31
32
  ### Task
32
-
33
- The fundamental unit of work. A task wraps an asynchronous function and provides powerful execution control.
33
+ The fundamental unit of work. A task wraps an asynchronous function and provides powerful execution control, now with step-by-step progress tracking.
34
34
 
35
35
  ### Taskchain
36
-
37
36
  Sequential task execution - tasks run one after another, with results passed along the chain.
38
37
 
39
38
  ### Taskparallel
40
-
41
39
  Parallel task execution - multiple tasks run simultaneously for maximum performance.
42
40
 
43
41
  ### TaskManager
44
-
45
- Centralized task scheduling and management using cron expressions.
42
+ Centralized task scheduling and management using cron expressions, with rich metadata collection.
46
43
 
47
44
  ### TaskDebounced
48
-
49
45
  Debounced task execution - prevents rapid repeated executions, only running after a quiet period.
50
46
 
51
47
  ### TaskOnce
52
-
53
48
  Singleton task execution - ensures a task runs exactly once, perfect for initialization routines.
54
49
 
50
+ ### TaskStep ๐Ÿ†•
51
+ Granular progress tracking - define named steps with percentage weights for real-time progress monitoring.
52
+
55
53
  ## Quick Start ๐Ÿ
56
54
 
57
55
  ### Basic Task Execution
@@ -72,27 +70,558 @@ const myTask = new Task({
72
70
  const result = await myTask.trigger();
73
71
  ```
74
72
 
75
- ### Buffered Execution (Rate Limiting)
73
+ ### Task with Progress Steps ๐Ÿ†•
76
74
 
77
- Perfect for API calls or database operations that need throttling:
75
+ Track granular progress for complex operations - perfect for UI progress bars:
78
76
 
79
77
  ```typescript
80
- const apiTask = new Task({
81
- name: 'APICall',
82
- taskFunction: async (endpoint: string) => {
83
- return await fetch(endpoint);
78
+ const dataProcessingTask = new Task({
79
+ name: 'DataProcessor',
80
+ steps: [
81
+ { name: 'validate', description: 'Validating input data', percentage: 15 },
82
+ { name: 'fetch', description: 'Fetching external resources', percentage: 25 },
83
+ { name: 'transform', description: 'Transforming data', percentage: 35 },
84
+ { name: 'save', description: 'Saving to database', percentage: 25 }
85
+ ] as const, // Use 'as const' for full type safety
86
+ taskFunction: async (inputData) => {
87
+ // TypeScript knows these step names!
88
+ dataProcessingTask.notifyStep('validate');
89
+ const validated = await validateData(inputData);
90
+
91
+ dataProcessingTask.notifyStep('fetch');
92
+ const external = await fetchExternalData();
93
+
94
+ dataProcessingTask.notifyStep('transform');
95
+ const transformed = await transformData(validated, external);
96
+
97
+ dataProcessingTask.notifyStep('save');
98
+ const result = await saveToDatabase(transformed);
99
+
100
+ return result;
101
+ }
102
+ });
103
+
104
+ // Monitor progress in real-time
105
+ const result = await dataProcessingTask.trigger();
106
+ console.log(`Final progress: ${dataProcessingTask.getProgress()}%`); // 100%
107
+ ```
108
+
109
+ ## TypeScript Generics Support ๐Ÿ”ฌ
110
+
111
+ TaskBuffer leverages TypeScript's powerful generics system for complete type safety across your task chains and workflows.
112
+
113
+ ### Generic Task Functions
114
+
115
+ Tasks support generic type parameters for both input and output types:
116
+
117
+ ```typescript
118
+ import { Task, ITaskFunction } from '@push.rocks/taskbuffer';
119
+
120
+ // Define typed interfaces
121
+ interface UserData {
122
+ id: string;
123
+ name: string;
124
+ email: string;
125
+ }
126
+
127
+ interface ProcessedUser {
128
+ userId: string;
129
+ displayName: string;
130
+ normalized: boolean;
131
+ }
132
+
133
+ // Create strongly typed tasks
134
+ const processUserTask = new Task<ProcessedUser>({
135
+ name: 'ProcessUser',
136
+ taskFunction: async (user: UserData): Promise<ProcessedUser> => {
137
+ return {
138
+ userId: user.id,
139
+ displayName: user.name.toUpperCase(),
140
+ normalized: true
141
+ };
142
+ }
143
+ });
144
+
145
+ // Type safety enforced at compile time
146
+ const result: ProcessedUser = await processUserTask.trigger({
147
+ id: '123',
148
+ name: 'John Doe',
149
+ email: 'john@example.com'
150
+ });
151
+ ```
152
+
153
+ ### Generic Setup Values
154
+
155
+ Tasks can accept setup values through generics, perfect for configuration:
156
+
157
+ ```typescript
158
+ interface TaskConfig {
159
+ apiEndpoint: string;
160
+ retryCount: number;
161
+ timeout: number;
162
+ }
163
+
164
+ const configuredTask = new Task<TaskConfig>({
165
+ name: 'ConfiguredTask',
166
+ taskSetup: async (): Promise<TaskConfig> => ({
167
+ apiEndpoint: 'https://api.example.com',
168
+ retryCount: 3,
169
+ timeout: 5000
170
+ }),
171
+ taskFunction: async (data: any, setupValue: TaskConfig) => {
172
+ // setupValue is fully typed!
173
+ for (let i = 0; i < setupValue.retryCount; i++) {
174
+ try {
175
+ return await fetchWithTimeout(
176
+ setupValue.apiEndpoint,
177
+ setupValue.timeout
178
+ );
179
+ } catch (error) {
180
+ if (i === setupValue.retryCount - 1) throw error;
181
+ }
182
+ }
183
+ }
184
+ });
185
+ ```
186
+
187
+ ### Type-Safe Task Chains
188
+
189
+ Chain tasks with preserved type flow:
190
+
191
+ ```typescript
192
+ // Each task knows its input and output types
193
+ const fetchTask = new Task<void>({
194
+ name: 'FetchUsers',
195
+ taskFunction: async (): Promise<UserData[]> => {
196
+ return await api.getUsers();
197
+ }
198
+ });
199
+
200
+ const filterTask = new Task<void>({
201
+ name: 'FilterActive',
202
+ taskFunction: async (users: UserData[]): Promise<UserData[]> => {
203
+ return users.filter(user => user.isActive);
204
+ }
205
+ });
206
+
207
+ const mapTask = new Task<void>({
208
+ name: 'MapToProcessed',
209
+ taskFunction: async (users: UserData[]): Promise<ProcessedUser[]> => {
210
+ return users.map(transformUser);
211
+ }
212
+ });
213
+
214
+ // Type safety flows through the chain
215
+ const chain = new Taskchain({
216
+ name: 'UserPipeline',
217
+ taskArray: [fetchTask, filterTask, mapTask]
218
+ });
219
+
220
+ const finalResult: ProcessedUser[] = await chain.trigger();
221
+ ```
222
+
223
+ ## Progress Tracking & Metadata ๐Ÿ“Š ๐Ÿ†•
224
+
225
+ TaskBuffer now provides comprehensive progress tracking and metadata collection, perfect for building dashboards and monitoring systems.
226
+
227
+ ### Step-by-Step Progress
228
+
229
+ Define weighted steps for accurate progress calculation:
230
+
231
+ ```typescript
232
+ const migrationTask = new Task({
233
+ name: 'DatabaseMigration',
234
+ steps: [
235
+ { name: 'backup', description: 'Backing up database', percentage: 20 },
236
+ { name: 'schema', description: 'Updating schema', percentage: 30 },
237
+ { name: 'data', description: 'Migrating data', percentage: 40 },
238
+ { name: 'validate', description: 'Validating integrity', percentage: 10 }
239
+ ] as const,
240
+ taskFunction: async () => {
241
+ migrationTask.notifyStep('backup');
242
+ await backupDatabase();
243
+ console.log(`Progress: ${migrationTask.getProgress()}%`); // ~20%
244
+
245
+ migrationTask.notifyStep('schema');
246
+ await updateSchema();
247
+ console.log(`Progress: ${migrationTask.getProgress()}%`); // ~50%
248
+
249
+ migrationTask.notifyStep('data');
250
+ await migrateData();
251
+ console.log(`Progress: ${migrationTask.getProgress()}%`); // ~90%
252
+
253
+ migrationTask.notifyStep('validate');
254
+ await validateIntegrity();
255
+ console.log(`Progress: ${migrationTask.getProgress()}%`); // 100%
256
+ }
257
+ });
258
+
259
+ // Get detailed step information
260
+ const steps = migrationTask.getStepsMetadata();
261
+ steps.forEach(step => {
262
+ console.log(`${step.name}: ${step.status} (${step.percentage}%)`);
263
+ if (step.duration) {
264
+ console.log(` Duration: ${step.duration}ms`);
265
+ }
266
+ });
267
+ ```
268
+
269
+ ### Task Metadata Collection
270
+
271
+ Get comprehensive metadata about task execution:
272
+
273
+ ```typescript
274
+ const task = new Task({
275
+ name: 'DataProcessor',
276
+ buffered: true,
277
+ bufferMax: 5,
278
+ steps: [
279
+ { name: 'process', description: 'Processing', percentage: 100 }
280
+ ] as const,
281
+ taskFunction: async () => {
282
+ task.notifyStep('process');
283
+ await processData();
284
+ }
285
+ });
286
+
287
+ // Get complete task metadata
288
+ const metadata = task.getMetadata();
289
+ console.log({
290
+ name: metadata.name,
291
+ status: metadata.status, // 'idle' | 'running' | 'completed' | 'failed'
292
+ progress: metadata.currentProgress, // 0-100
293
+ currentStep: metadata.currentStep,
294
+ runCount: metadata.runCount,
295
+ lastRun: metadata.lastRun,
296
+ buffered: metadata.buffered,
297
+ bufferMax: metadata.bufferMax
298
+ });
299
+ ```
300
+
301
+ ### TaskManager Enhanced Metadata
302
+
303
+ The TaskManager now provides rich metadata for monitoring and dashboards:
304
+
305
+ ```typescript
306
+ const manager = new TaskManager();
307
+
308
+ // Add tasks with step tracking
309
+ manager.addAndScheduleTask(backupTask, '0 2 * * *'); // 2 AM daily
310
+ manager.addAndScheduleTask(cleanupTask, '0 */6 * * *'); // Every 6 hours
311
+
312
+ // Get metadata for all tasks
313
+ const allTasksMetadata = manager.getAllTasksMetadata();
314
+ allTasksMetadata.forEach(task => {
315
+ console.log(`Task: ${task.name}`);
316
+ console.log(` Status: ${task.status}`);
317
+ console.log(` Progress: ${task.currentProgress}%`);
318
+ console.log(` Run count: ${task.runCount}`);
319
+ console.log(` Schedule: ${task.cronSchedule}`);
320
+ });
321
+
322
+ // Get scheduled tasks with next run times
323
+ const scheduledTasks = manager.getScheduledTasks();
324
+ scheduledTasks.forEach(task => {
325
+ console.log(`${task.name}: Next run at ${task.nextRun}`);
326
+ if (task.steps) {
327
+ console.log(` Steps: ${task.steps.length}`);
328
+ }
329
+ });
330
+
331
+ // Get upcoming executions
332
+ const nextRuns = manager.getNextScheduledRuns(10);
333
+ console.log('Next 10 scheduled executions:', nextRuns);
334
+ ```
335
+
336
+ ### Execute and Track Tasks
337
+
338
+ Execute tasks with full lifecycle tracking and automatic cleanup:
339
+
340
+ ```typescript
341
+ const manager = new TaskManager();
342
+
343
+ const analyticsTask = new Task({
344
+ name: 'Analytics',
345
+ steps: [
346
+ { name: 'collect', description: 'Collecting metrics', percentage: 30 },
347
+ { name: 'analyze', description: 'Analyzing data', percentage: 50 },
348
+ { name: 'report', description: 'Generating report', percentage: 20 }
349
+ ] as const,
350
+ taskFunction: async () => {
351
+ analyticsTask.notifyStep('collect');
352
+ const metrics = await collectMetrics();
353
+
354
+ analyticsTask.notifyStep('analyze');
355
+ const analysis = await analyzeData(metrics);
356
+
357
+ analyticsTask.notifyStep('report');
358
+ return await generateReport(analysis);
359
+ }
360
+ });
361
+
362
+ // Execute with automatic cleanup and metadata collection
363
+ const report = await manager.addExecuteRemoveTask(analyticsTask, {
364
+ trackProgress: true
365
+ });
366
+
367
+ console.log('Execution Report:', {
368
+ taskName: report.taskName,
369
+ duration: report.duration,
370
+ stepsCompleted: report.stepsCompleted,
371
+ finalProgress: report.progress,
372
+ result: report.result
373
+ });
374
+ ```
375
+
376
+ ### Frontend Integration Example
377
+
378
+ Perfect for building real-time progress UIs:
379
+
380
+ ```typescript
381
+ // WebSocket server for real-time updates
382
+ io.on('connection', (socket) => {
383
+ socket.on('startTask', async (taskId) => {
384
+ const task = new Task({
385
+ name: taskId,
386
+ steps: [
387
+ { name: 'start', description: 'Starting...', percentage: 10 },
388
+ { name: 'process', description: 'Processing...', percentage: 70 },
389
+ { name: 'finish', description: 'Finishing...', percentage: 20 }
390
+ ] as const,
391
+ taskFunction: async () => {
392
+ task.notifyStep('start');
393
+ socket.emit('progress', {
394
+ step: 'start',
395
+ progress: task.getProgress(),
396
+ metadata: task.getStepsMetadata()
397
+ });
398
+
399
+ task.notifyStep('process');
400
+ socket.emit('progress', {
401
+ step: 'process',
402
+ progress: task.getProgress(),
403
+ metadata: task.getStepsMetadata()
404
+ });
405
+
406
+ task.notifyStep('finish');
407
+ socket.emit('progress', {
408
+ step: 'finish',
409
+ progress: task.getProgress(),
410
+ metadata: task.getStepsMetadata()
411
+ });
412
+ }
413
+ });
414
+
415
+ await task.trigger();
416
+ socket.emit('complete', task.getMetadata());
417
+ });
418
+ });
419
+ ```
420
+
421
+ ## Buffer Behavior Deep Dive ๐ŸŒŠ
422
+
423
+ The buffer system in TaskBuffer provides intelligent control over concurrent executions, preventing system overload while maximizing throughput.
424
+
425
+ ### How Buffering Works
426
+
427
+ When a task is buffered, TaskBuffer manages a queue of executions:
428
+
429
+ ```typescript
430
+ const bufferedTask = new Task({
431
+ name: 'BufferedOperation',
432
+ taskFunction: async (data) => {
433
+ console.log(`Processing: ${data}`);
434
+ await simulateWork();
435
+ return `Processed: ${data}`;
84
436
  },
85
437
  buffered: true,
86
- bufferMax: 3, // Maximum 3 concurrent executions
87
- execDelay: 1000, // Wait 1 second between executions
438
+ bufferMax: 3 // Maximum 3 concurrent executions
88
439
  });
89
440
 
90
- // These will be automatically throttled
441
+ // Trigger 10 executions rapidly
91
442
  for (let i = 0; i < 10; i++) {
92
- apiTask.trigger(`/api/data/${i}`);
443
+ bufferedTask.trigger(`Item ${i}`);
444
+ }
445
+
446
+ // What happens:
447
+ // 1. First 3 tasks start immediately
448
+ // 2. Items 4-10 are queued
449
+ // 3. As each task completes, next queued item starts
450
+ // 4. Never more than 3 tasks running simultaneously
451
+ ```
452
+
453
+ ### Buffer Truncation Behavior
454
+
455
+ When buffer limit is reached, new calls are intelligently managed:
456
+
457
+ ```typescript
458
+ const truncatingTask = new Task({
459
+ name: 'TruncatingBuffer',
460
+ taskFunction: async (data) => {
461
+ await processData(data);
462
+ },
463
+ buffered: true,
464
+ bufferMax: 5 // Maximum 5 in buffer
465
+ });
466
+
467
+ // Rapid fire 100 calls
468
+ for (let i = 0; i < 100; i++) {
469
+ truncatingTask.trigger(`Data ${i}`);
470
+ }
471
+
472
+ // Buffer behavior:
473
+ // - First 5 calls: Added to buffer and start processing
474
+ // - Calls 6-100: Each overwrites the 5th buffer slot
475
+ // - Result: Only processes items 0,1,2,3, and 99 (last one)
476
+ // - This prevents memory overflow in high-frequency scenarios
477
+ ```
478
+
479
+ ### Advanced Buffer Strategies
480
+
481
+ #### 1. **Sliding Window Buffer**
482
+ Perfect for real-time data processing where only recent items matter:
483
+
484
+ ```typescript
485
+ const slidingWindowTask = new Task({
486
+ name: 'SlidingWindow',
487
+ taskFunction: async (data) => {
488
+ return await analyzeRecentData(data);
489
+ },
490
+ buffered: true,
491
+ bufferMax: 10, // Keep last 10 items
492
+ execDelay: 100 // Process every 100ms
493
+ });
494
+
495
+ // In a real-time stream scenario
496
+ dataStream.on('data', (chunk) => {
497
+ slidingWindowTask.trigger(chunk);
498
+ // Older items automatically dropped when buffer full
499
+ });
500
+ ```
501
+
502
+ #### 2. **Throttled Buffer**
503
+ Combine buffering with execution delays for rate limiting:
504
+
505
+ ```typescript
506
+ const apiRateLimiter = new Task({
507
+ name: 'RateLimitedAPI',
508
+ taskFunction: async (request) => {
509
+ return await api.call(request);
510
+ },
511
+ buffered: true,
512
+ bufferMax: 10, // Max 10 queued requests
513
+ execDelay: 1000 // 1 second between executions
514
+ });
515
+
516
+ // Requests are queued and executed at 1/second
517
+ // Prevents API rate limit violations
518
+ ```
519
+
520
+ #### 3. **Priority Buffer** (Custom Implementation)
521
+ Implement priority queuing with buffer management:
522
+
523
+ ```typescript
524
+ class PriorityBufferedTask extends Task {
525
+ private priorityQueue: Array<{data: any, priority: number}> = [];
526
+
527
+ constructor(options) {
528
+ super({
529
+ ...options,
530
+ taskFunction: async (item) => {
531
+ // Process based on priority
532
+ return await this.processByPriority(item);
533
+ }
534
+ });
535
+ }
536
+
537
+ triggerWithPriority(data: any, priority: number) {
538
+ if (this.priorityQueue.length >= this.bufferMax) {
539
+ // Remove lowest priority item if buffer full
540
+ this.priorityQueue.sort((a, b) => b.priority - a.priority);
541
+ this.priorityQueue.pop();
542
+ }
543
+ this.priorityQueue.push({data, priority});
544
+ this.priorityQueue.sort((a, b) => b.priority - a.priority);
545
+ return this.trigger(this.priorityQueue.shift());
546
+ }
93
547
  }
94
548
  ```
95
549
 
550
+ ### Buffer Monitoring
551
+
552
+ Track buffer utilization and performance:
553
+
554
+ ```typescript
555
+ const monitoredTask = new Task({
556
+ name: 'MonitoredBuffer',
557
+ taskFunction: async (data) => {
558
+ const startTime = Date.now();
559
+ const result = await processData(data);
560
+ console.log(`Processing time: ${Date.now() - startTime}ms`);
561
+ console.log(`Buffer utilization: ${monitoredTask.bufferRunner.bufferCounter}/${monitoredTask.bufferMax}`);
562
+ return result;
563
+ },
564
+ buffered: true,
565
+ bufferMax: 20
566
+ });
567
+
568
+ // Monitor buffer saturation
569
+ setInterval(() => {
570
+ const utilization = (monitoredTask.bufferRunner.bufferCounter / monitoredTask.bufferMax) * 100;
571
+ if (utilization > 80) {
572
+ console.warn(`Buffer near capacity: ${utilization.toFixed(1)}%`);
573
+ }
574
+ }, 1000);
575
+ ```
576
+
577
+ ### Buffer Best Practices
578
+
579
+ 1. **Choose appropriate buffer sizes**:
580
+ - I/O operations: 5-10 concurrent
581
+ - CPU-intensive: Number of cores
582
+ - API calls: Based on rate limits
583
+
584
+ 2. **Handle buffer overflow gracefully**:
585
+ ```typescript
586
+ const task = new Task({
587
+ taskFunction: async (data) => {
588
+ try {
589
+ return await process(data);
590
+ } catch (error) {
591
+ if (error.code === 'BUFFER_OVERFLOW') {
592
+ // Implement backoff strategy
593
+ await delay(1000);
594
+ return task.trigger(data);
595
+ }
596
+ throw error;
597
+ }
598
+ },
599
+ buffered: true,
600
+ bufferMax: 10
601
+ });
602
+ ```
603
+
604
+ 3. **Monitor and adjust dynamically**:
605
+ ```typescript
606
+ // Adjust buffer size based on system load
607
+ const adaptiveTask = new Task({
608
+ name: 'AdaptiveBuffer',
609
+ taskFunction: async (data) => {
610
+ const cpuLoad = await getSystemLoad();
611
+ if (cpuLoad > 0.8) {
612
+ adaptiveTask.bufferMax = Math.max(2, adaptiveTask.bufferMax - 1);
613
+ } else if (cpuLoad < 0.5) {
614
+ adaptiveTask.bufferMax = Math.min(20, adaptiveTask.bufferMax + 1);
615
+ }
616
+ return await process(data);
617
+ },
618
+ buffered: true,
619
+ bufferMax: 10
620
+ });
621
+ ```
622
+
623
+ ## Common Patterns ๐ŸŽจ
624
+
96
625
  ### Task Chains - Sequential Workflows
97
626
 
98
627
  Build complex workflows where each step depends on the previous:
@@ -172,8 +701,17 @@ import { Task, TaskManager } from '@push.rocks/taskbuffer';
172
701
 
173
702
  const backupTask = new Task({
174
703
  name: 'DatabaseBackup',
704
+ steps: [
705
+ { name: 'dump', description: 'Creating dump', percentage: 70 },
706
+ { name: 'upload', description: 'Uploading to S3', percentage: 30 }
707
+ ] as const,
175
708
  taskFunction: async () => {
709
+ backupTask.notifyStep('dump');
176
710
  await performBackup();
711
+
712
+ backupTask.notifyStep('upload');
713
+ await uploadToS3();
714
+
177
715
  console.log(`Backup completed at ${new Date().toISOString()}`);
178
716
  },
179
717
  });
@@ -182,11 +720,14 @@ const manager = new TaskManager();
182
720
 
183
721
  // Add and schedule tasks
184
722
  manager.addAndScheduleTask(backupTask, '0 0 * * *'); // Daily at midnight
185
- manager.addAndScheduleTask(healthCheck, '*/5 * * * *'); // Every 5 minutes
186
723
 
187
724
  // Start the scheduler
188
725
  manager.start();
189
726
 
727
+ // Monitor scheduled tasks
728
+ const scheduled = manager.getScheduledTasks();
729
+ console.log('Scheduled tasks:', scheduled);
730
+
190
731
  // Later... stop if needed
191
732
  manager.stop();
192
733
  ```
@@ -282,45 +823,6 @@ runner.registerTask(imageResizeTask);
282
823
  runner.start();
283
824
  ```
284
825
 
285
- ### Buffer Management Strategies
286
-
287
- Fine-tune concurrent execution behavior:
288
-
289
- ```typescript
290
- const task = new Task({
291
- name: 'ResourceIntensive',
292
- taskFunction: async () => {
293
- /* ... */
294
- },
295
- buffered: true,
296
- bufferMax: 5, // Max 5 concurrent
297
- execDelay: 100, // 100ms between starts
298
- timeout: 30000, // 30 second timeout
299
- });
300
- ```
301
-
302
- ### Cycle Detection and Prevention
303
-
304
- TaskBuffer automatically detects and prevents circular dependencies:
305
-
306
- ```typescript
307
- const taskA = new Task({
308
- name: 'TaskA',
309
- taskFunction: async () => {
310
- /* ... */
311
- },
312
- preTask: taskB, // This would create a cycle
313
- });
314
-
315
- const taskB = new Task({
316
- name: 'TaskB',
317
- taskFunction: async () => {
318
- /* ... */
319
- },
320
- preTask: taskA, // Circular dependency detected!
321
- });
322
- ```
323
-
324
826
  ### Dynamic Task Creation
325
827
 
326
828
  Create tasks on-the-fly based on runtime conditions:
@@ -331,8 +833,17 @@ const dynamicWorkflow = async (config: Config) => {
331
833
  (step) =>
332
834
  new Task({
333
835
  name: step.name,
836
+ steps: step.substeps?.map(s => ({
837
+ name: s.id,
838
+ description: s.label,
839
+ percentage: s.weight
840
+ })) as const,
334
841
  taskFunction: async (input) => {
335
- return await processStep(step, input);
842
+ for (const substep of step.substeps || []) {
843
+ task.notifyStep(substep.id);
844
+ await processStep(substep, input);
845
+ }
846
+ return input;
336
847
  },
337
848
  }),
338
849
  );
@@ -350,26 +861,46 @@ const dynamicWorkflow = async (config: Config) => {
350
861
 
351
862
  ### Task Options
352
863
 
353
- | Option | Type | Description |
354
- | -------------- | ---------- | ------------------------------ |
355
- | `name` | `string` | Unique identifier for the task |
356
- | `taskFunction` | `Function` | Async function to execute |
357
- | `buffered` | `boolean` | Enable buffer management |
358
- | `bufferMax` | `number` | Maximum concurrent executions |
359
- | `execDelay` | `number` | Delay between executions (ms) |
360
- | `timeout` | `number` | Task timeout (ms) |
361
- | `preTask` | `Task` | Task to run before |
362
- | `afterTask` | `Task` | Task to run after |
864
+ | Option | Type | Description |
865
+ | -------------- | ---------- | -------------------------------------- |
866
+ | `name` | `string` | Unique identifier for the task |
867
+ | `taskFunction` | `Function` | Async function to execute |
868
+ | `steps` | `Array` | Step definitions with name, description, percentage |
869
+ | `buffered` | `boolean` | Enable buffer management |
870
+ | `bufferMax` | `number` | Maximum concurrent executions |
871
+ | `execDelay` | `number` | Delay between executions (ms) |
872
+ | `timeout` | `number` | Task timeout (ms) |
873
+ | `preTask` | `Task` | Task to run before |
874
+ | `afterTask` | `Task` | Task to run after |
875
+
876
+ ### Task Methods
877
+
878
+ | Method | Description |
879
+ | ------------------------- | ---------------------------------------------- |
880
+ | `trigger(x?)` | Execute the task |
881
+ | `notifyStep(stepName)` | Mark a step as active (typed step names!) |
882
+ | `getProgress()` | Get current progress percentage (0-100) |
883
+ | `getStepsMetadata()` | Get all steps with their current status |
884
+ | `getMetadata()` | Get complete task metadata |
885
+ | `resetSteps()` | Reset all steps to pending state |
363
886
 
364
887
  ### TaskManager Methods
365
888
 
366
- | Method | Description |
367
- | ------------------------------- | ------------------------ |
368
- | `addTask(task, cronExpression)` | Add and schedule a task |
369
- | `removeTask(taskName)` | Remove a scheduled task |
370
- | `start()` | Start the scheduler |
371
- | `stop()` | Stop the scheduler |
372
- | `getStats()` | Get execution statistics |
889
+ | Method | Description |
890
+ | ----------------------------------- | -------------------------------------- |
891
+ | `addTask(task)` | Add a task to the manager |
892
+ | `addAndScheduleTask(task, cron)` | Add and schedule a task |
893
+ | `getTaskByName(name)` | Get a specific task by name |
894
+ | `getTaskMetadata(name)` | Get metadata for a specific task |
895
+ | `getAllTasksMetadata()` | Get metadata for all tasks |
896
+ | `getScheduledTasks()` | Get all scheduled tasks with info |
897
+ | `getNextScheduledRuns(limit)` | Get upcoming scheduled executions |
898
+ | `addExecuteRemoveTask(task, opts)` | Execute task with lifecycle tracking |
899
+ | `triggerTaskByName(name)` | Trigger a task by its name |
900
+ | `scheduleTaskByName(name, cron)` | Schedule a task using cron expression |
901
+ | `descheduleTaskByName(name)` | Remove task from schedule |
902
+ | `start()` | Start the scheduler |
903
+ | `stop()` | Stop the scheduler |
373
904
 
374
905
  ### Taskchain Methods
375
906
 
@@ -387,14 +918,20 @@ const dynamicWorkflow = async (config: Config) => {
387
918
  3. **Implement proper error handling**: Use try-catch in task functions
388
919
  4. **Monitor task execution**: Use the built-in stats and logging
389
920
  5. **Set appropriate timeouts**: Prevent hanging tasks from blocking your system
921
+ 6. **Use step tracking wisely**: Don't create too many granular steps - aim for meaningful progress points
390
922
 
391
923
  ## Error Handling ๐Ÿ›ก๏ธ
392
924
 
393
925
  ```typescript
394
926
  const robustTask = new Task({
395
927
  name: 'RobustOperation',
928
+ steps: [
929
+ { name: 'try', description: 'Attempting operation', percentage: 80 },
930
+ { name: 'retry', description: 'Retrying on failure', percentage: 20 }
931
+ ] as const,
396
932
  taskFunction: async (input) => {
397
933
  try {
934
+ robustTask.notifyStep('try');
398
935
  return await riskyOperation(input);
399
936
  } catch (error) {
400
937
  // Log error
@@ -402,6 +939,7 @@ const robustTask = new Task({
402
939
 
403
940
  // Optionally retry
404
941
  if (error.retryable) {
942
+ robustTask.notifyStep('retry');
405
943
  return await riskyOperation(input);
406
944
  }
407
945
 
@@ -415,12 +953,20 @@ const robustTask = new Task({
415
953
 
416
954
  ## Real-World Examples ๐ŸŒ
417
955
 
418
- ### API Rate Limiting
956
+ ### API Rate Limiting with Progress
419
957
 
420
958
  ```typescript
421
959
  const apiClient = new Task({
422
960
  name: 'RateLimitedAPI',
961
+ steps: [
962
+ { name: 'wait', description: 'Rate limit delay', percentage: 10 },
963
+ { name: 'call', description: 'API call', percentage: 90 }
964
+ ] as const,
423
965
  taskFunction: async (endpoint: string) => {
966
+ apiClient.notifyStep('wait');
967
+ await delay(100); // Rate limiting
968
+
969
+ apiClient.notifyStep('call');
424
970
  return await fetch(`https://api.example.com${endpoint}`);
425
971
  },
426
972
  buffered: true,
@@ -429,22 +975,43 @@ const apiClient = new Task({
429
975
  });
430
976
  ```
431
977
 
432
- ### Database Migration Pipeline
978
+ ### Database Migration Pipeline with Progress
433
979
 
434
980
  ```typescript
435
981
  const migrationChain = new Taskchain({
436
982
  name: 'DatabaseMigration',
437
983
  taskArray: [
438
- backupTask,
439
- schemaUpdateTask,
440
- dataTransformTask,
441
- validationTask,
442
- cleanupTask,
984
+ new Task({
985
+ name: 'Backup',
986
+ steps: [{ name: 'backup', description: 'Creating backup', percentage: 100 }] as const,
987
+ taskFunction: async () => {
988
+ backupTask.notifyStep('backup');
989
+ return await createBackup();
990
+ }
991
+ }),
992
+ new Task({
993
+ name: 'SchemaUpdate',
994
+ steps: [
995
+ { name: 'analyze', description: 'Analyzing changes', percentage: 30 },
996
+ { name: 'apply', description: 'Applying migrations', percentage: 70 }
997
+ ] as const,
998
+ taskFunction: async () => {
999
+ schemaTask.notifyStep('analyze');
1000
+ const changes = await analyzeSchema();
1001
+
1002
+ schemaTask.notifyStep('apply');
1003
+ return await applyMigrations(changes);
1004
+ }
1005
+ }),
1006
+ // ... more tasks
443
1007
  ],
444
1008
  });
1009
+
1010
+ // Execute with progress monitoring
1011
+ const result = await migrationChain.trigger();
445
1012
  ```
446
1013
 
447
- ### Microservice Health Monitoring
1014
+ ### Microservice Health Monitoring Dashboard
448
1015
 
449
1016
  ```typescript
450
1017
  const healthMonitor = new TaskManager();
@@ -452,36 +1019,89 @@ const healthMonitor = new TaskManager();
452
1019
  services.forEach((service) => {
453
1020
  const healthCheck = new Task({
454
1021
  name: `HealthCheck:${service.name}`,
1022
+ steps: [
1023
+ { name: 'ping', description: 'Pinging service', percentage: 30 },
1024
+ { name: 'check', description: 'Checking health', percentage: 50 },
1025
+ { name: 'report', description: 'Reporting status', percentage: 20 }
1026
+ ] as const,
455
1027
  taskFunction: async () => {
1028
+ healthCheck.notifyStep('ping');
1029
+ const responsive = await ping(service.url);
1030
+
1031
+ healthCheck.notifyStep('check');
456
1032
  const healthy = await checkHealth(service.url);
1033
+
1034
+ healthCheck.notifyStep('report');
457
1035
  if (!healthy) {
458
1036
  await alertOps(service);
459
1037
  }
1038
+
1039
+ return { service: service.name, healthy, timestamp: Date.now() };
460
1040
  },
461
1041
  });
462
1042
 
463
1043
  healthMonitor.addAndScheduleTask(healthCheck, '*/1 * * * *'); // Every minute
464
1044
  });
1045
+
1046
+ // Dashboard endpoint
1047
+ app.get('/api/health/dashboard', (req, res) => {
1048
+ const metadata = healthMonitor.getAllTasksMetadata();
1049
+ res.json({
1050
+ services: metadata.map(task => ({
1051
+ name: task.name.replace('HealthCheck:', ''),
1052
+ status: task.status,
1053
+ lastCheck: task.lastRun,
1054
+ nextCheck: healthMonitor.getScheduledTasks()
1055
+ .find(s => s.name === task.name)?.nextRun,
1056
+ progress: task.currentProgress,
1057
+ currentStep: task.currentStep
1058
+ }))
1059
+ });
1060
+ });
465
1061
  ```
466
1062
 
467
1063
  ## Testing ๐Ÿงช
468
1064
 
469
1065
  ```typescript
470
1066
  import { expect, tap } from '@git.zone/tstest';
471
- import { Task } from '@push.rocks/taskbuffer';
1067
+ import { Task, TaskStep } from '@push.rocks/taskbuffer';
1068
+
1069
+ tap.test('should track task progress through steps', async () => {
1070
+ const task = new Task({
1071
+ name: 'TestTask',
1072
+ steps: [
1073
+ { name: 'step1', description: 'First step', percentage: 50 },
1074
+ { name: 'step2', description: 'Second step', percentage: 50 }
1075
+ ] as const,
1076
+ taskFunction: async () => {
1077
+ task.notifyStep('step1');
1078
+ expect(task.getProgress()).toBeLessThanOrEqual(50);
1079
+
1080
+ task.notifyStep('step2');
1081
+ expect(task.getProgress()).toBeLessThanOrEqual(100);
1082
+ }
1083
+ });
1084
+
1085
+ await task.trigger();
1086
+ expect(task.getProgress()).toEqual(100);
1087
+ });
472
1088
 
473
- tap.test('should execute task successfully', async () => {
474
- const result = await myTask.trigger();
475
- expect(result).toEqual(expectedValue);
1089
+ tap.test('should collect execution metadata', async () => {
1090
+ const manager = new TaskManager();
1091
+ const task = new Task({
1092
+ name: 'MetadataTest',
1093
+ taskFunction: async () => 'result'
1094
+ });
1095
+
1096
+ const report = await manager.addExecuteRemoveTask(task);
1097
+ expect(report.taskName).toEqual('MetadataTest');
1098
+ expect(report.result).toEqual('result');
1099
+ expect(report.duration).toBeGreaterThan(0);
476
1100
  });
477
1101
 
478
1102
  tap.start();
479
1103
  ```
480
1104
 
481
- ## Contributing ๐Ÿค
482
-
483
- We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
484
-
485
1105
  ## Support ๐Ÿ’ฌ
486
1106
 
487
1107
  - ๐Ÿ“ง Email: [hello@task.vc](mailto:hello@task.vc)
@@ -490,7 +1110,7 @@ We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) f
490
1110
 
491
1111
  ## License and Legal Information
492
1112
 
493
- 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.
1113
+ 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.
494
1114
 
495
1115
  **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.
496
1116
 
@@ -505,4 +1125,4 @@ Registered at District court Bremen HRB 35230 HB, Germany
505
1125
 
506
1126
  For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
507
1127
 
508
- 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.
1128
+ 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.